Implementing key objects in Unity by a newbie. Part 2.
Tags: unity3d, gamedev, csharp
Published: 2021-05-16 07:45:50

In the [previous part](https://dev.to/alesanchez/implementing-key-objects-in-unity-by-a-newbie-part-1-1hhi) we built the foundations for implementing the key objects.

In this post we are going to finish that task developing the actionable by key specific object and testing everything we developed until now.

In the first part the last step was the 3, so here we will start directly in the 4, for preserving the tags in the repo :)

## Step 4: Actionable by key

Let's implement the **key** (sorry for that) part of the post! Our ActionableByKey class!

As we saw in the diagram, the `ActionableByKey` object used a feature called events! Events in C# are a way to notify that something happened. We are going to use that to send a message to the universe when an item has been picked. Start by defining the event we'll need.

Create a new folder called "Events" inside the "Scripts" one and put inside a file called "ItemPick":

```c#
public delegate void NotifyItemPick(ActionableObject pickedItem);

public static class ItemPickEvents {
    public static event NotifyItemPick OnItemPick;

    public static void NotifyItemPick(ActionableObject pickedItem) {
        OnItemPick?.Invoke(pickedItem);
    }
}
```

First we define a [deletage](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/) called `NotifyItemPick` that will be called with an `ActionableObject` referencing the picked item.

The class itself will contain the `OnItemPick` event as static, so anyone can subscribe to it and a static method for notifying an item pick.

We have talked a lot about picking an item and objects that will be interacted when an item is picked, but haven't defined the item itself yet!

In order to represent our item, we need a `KeyItem` script that will define an object that has to be picked to activate another. Picking an object is some sort of interaction so... we can take advantage of our generic actionable object! Create a new folder called "Items" inside "Scripts" and, inside, create a "KeyItem" file:

```c#
public class KeyItem : ActionableObject
{
    protected override void InnerStart(){}

    protected override void InnerInteract() {
        ItemPickEvents.NotifyItemPick(this);
        Destroy(gameObject);
    }
}
```

As you can see, the inner start does nothing and the interaction just notify that itself has been picked, followed by the descruction of the picked item. Easy!

Now that we have the events as well as the key item, the next step is to create the actionable by key one. Create a file side by side with `StandardActionableObject` and call it `ActionableByKey`:

```c#
public class ActionableByKey : StandardActionableObject
{
    [Tooltip("This item will activate the actuator of this object when picked")]
    [SerializeField] KeyItem actionedBy;

    protected override void InnerStart()
    {
        base.InnerStart();
        SetIsInteractive(false);
    }

    private void OnEnable() {
        ItemPickEvents.OnItemPick += OnItemPick;
    }

    private void OnDisable() {
        ItemPickEvents.OnItemPick -= OnItemPick;
    }

    private void OnItemPick(ActionableObject pickedItem)
    {
        if(pickedItem is KeyItem && pickedItem.Equals(actionedBy)) {
            SetIsInteractive(true);
        }
    }
}
```

The first attribute is a reference to the key item that need to be picked to activate this object.

The `InnerStart` deactivates the interactions with this object.

We will subscribe to the `OnItemPick` event in the `OnEnable` and unsusbcribe in the `OnDisable`. The function bound to the event is called also `OnItemPick`.

In the `OnItemPick` method we check if the picked item is a KeyItem as well as the one that activates the interactions with the actionable object. If both conditions are met, activate the interactions :D

We are almost there! Just one more thing to code and we can jump back to the Unity editor!

> Access the code for this step [here](https://github.com/alesanmed-blog-posts/unity-animate-doors/tree/step-4)

## Step 5: How to interact with objects

Ok, we have set everything up so that when we interact with an object it works like a charm but... How can we interact with an object?

That is what we are going to solve here. In general terms, the approach is to throw [Raycast](https://docs.unity3d.com/ScriptReference/Physics.Raycast.html) from the player eyes (more or less), a unit of distance and a half away and check if it collides with a collider belonging to an `IActionableObject` GameObject (that's why we set a collider up in our door). If we find an actionable object, we will store a reference to it and show the interaction text. When the key for interacting is pressed, we will call the `Interact` function of the stored actionable object.

We will create a file called `ObjectInteractuator` inside the `Scripts` folder:

```c#
public class ObjectInteractuator : MonoBehaviour
{
    [SerializeField] InteractionText interactText;

    [SerializeField] LayerMask interactionMask;

    GameActions actions;
    Camera mainCamera;

    IActionableObject currInteractiveObject;

    private void Awake() {
        actions = new GameActions();
        actions.Player.Interact.performed += OnInteractPerformed;
    }

    private void OnEnable() {
        actions.Player.Enable();
    }

    private void OnDisable() {
        actions.Player.Disable();
    }

    private void OnDestroy() {
        actions.Player.Interact.performed -= OnInteractPerformed;
    }

    private void Start() {
        mainCamera = Camera.main;
    }

    // Update is called once per frame
    void Update()
    {
        CheckInteractions();
    }

    private void CheckInteractions()
    {
        Ray cameraRay = mainCamera.ViewportPointToRay(Vector3.one / 2f);

        RaycastHit hit;
        IActionableObject objectHit;

        // We are looking towards an interactive object an in range
        if (Physics.Raycast(cameraRay, out hit, 1.5f, interactionMask, QueryTriggerInteraction.Collide) 
            && hit.transform.TryGetComponent<IActionableObject>(out objectHit) 
            && objectHit.IsInteracterActive()
        ) {
            interactText.SetInteractionText(objectHit.GetInteractionText());
            currInteractiveObject = objectHit;
            
            interactText.Enable();
        }
        else if (interactText.enabled)
        {
            interactText.Disable();
            currInteractiveObject = null;
        }
    }

    private void OnInteractPerformed(InputAction.CallbackContext obj)
    {
        currInteractiveObject?.Interact();
    }

}
```

The first attribute is a reference to the `InteractionText` of the scene (we will create it later). The second is the interaction mask the raycast has to check for performing a hit (you can create a custom layer if you want, but in this example we will use the `default` one).

The `GameActions` are needed to listen for event sent when the interaction key is pressed. Finally, we need the `Camera` to cast the ray from it.

The last attribute is to store a reference to the current interactive object (the one we are looking at right now, if any).

In the `Awake` we will bind the `OnInteractPerformed` function to the `Interact` action, and will unbind it in the `OnDestroy` function. The only purpose of that function is to call the `Interact` method of the interactive object we are looking at right now (checking first if there is any).

The `OnEnable` and `OnDisable` functions are used to enable and disable the game actions.

In the `Start` we are storing the reference to the main camera and in the `Update` we are going to check for interactions.

The `CheckInteractions` is where the magic happens, let's look into it step by step.

First we construct a Ray that starts at the middle point of the viewport (that's why we use `Vector.One / 2f`).

Then we create two variables to store the hit of the raycast as well as the actionable object.

Then we have an if with three conditions. The first one:

```c#
Physics.Raycast(cameraRay, out hit, 1.5f, interactionMask, QueryTriggerInteraction.Collide)
```

This casts the previously created ray 1.5 units of distance away, storing the possible hit in the `hit` variable, colliding only with `interactionMask` and reporting with trigger colliders thanks to the `QueryTriggerInteraction.Collide` attribute. If that condition is met (an object has been hit), then it goes for the second one:

```c#
hit.transform.TryGetComponent<IActionableObject>(out objectHit)
```

That one asks if the collided object has a component implementing the interface `IActionableObject`. If so, it stores the found component in the `objectHit` variable and goes for the last check:

```c#
objectHit.IsInteracterActive()
```

This one just asks if the interactions are enabled for the collided object.

If all of that is met, we set the interaction text to the one returned by the collided object, store a reference to the collided object and enable the interact text component.

If any of the conditions fail it means that we are not looking to an interactive object, so we disable the interaction text and clear the reference.

And with that, we have all set for interacting with objects!! Let's move again to the editor for the final touches.

> Access the code for this step [here](https://github.com/alesanmed-blog-posts/unity-animate-doors/tree/step-5)

## Step 6: Attaching all needed scripts

Now it's time to start adding scripts to all of the GameObjects in the scene.

As we said earlier, we must create the elements needed for showing the interaction text. We are going to use an overlay canvas with a text. That text will be white so that it makes more contrast with the background.

{% youtube g3DZKSW1VGU %}

After creating the canvas and switching the EventSystem to the new input system, we must attach the `InteractionText` script to the Text component in order to reference it in the `ObjectInteractuator` script.

{% youtube MtD6I9XBGME %}

To set up the interactor, we need to attach the `ObjectInteractuator` script to the FPSController. Then reference the text component we created in the previous step and set the interaction mask to **Default**.

The last step before we can begin to interact with our door is to make it a `StandardActionableObject` (for the moment).

Before that, we must fix a thing we forgot when creating the animation in Part 1. We need to make sure that the animation is not played on loop. For that, go to the animation file and uncheck the "Loop time" attribute. Then we just need to attach the `StandardActionableObject` script to it and fill the animation state name, which is "DoorOpen". 

{% youtube t2t63oJpfg0 %}

Now we are able to play around with our door opening and closing it. Our very last step is to make it actionable by a key!!

> Access the code developed in this step [here](https://github.com/alesanmed-blog-posts/unity-animate-doors/tree/step-6)

> **NOTE**: I changed the colliders of the doors so the player can walk across it. I created one trigger collider around the whole door for the raycast and one collider in the door itself to prevent the player from crossing it when closed.

## Step 7: Making the door actionable by ~~cube~~ key

The last thing we need to finish our heroic journey is making the door interactive only when a specific item is picked. In a real game, that item would be a key, in our amazing AAA game, it will be the most cubic cube you'll ever see.

The first step is to place our cube and make it a KeyItem. Since it will be picked up and destroyed, we don't need any reverse interaction text.

{% youtube hedKIVoRjiE %}

And the last thing... Replace the script attached to the door with an `ActionableByKey` script and set the reference to our key. That's all! The door won't be interactive until we pick up the key!

{% youtube c8AFsmk3Kqs %}

> Access the code of this step [here](https://github.com/alesanmed-blog-posts/unity-animate-doors/tree/step-7)

## Conclussions... or something like that

So... yes... it's finished, it's working, it's awesome... I'm not crying, you're crying.

Now seriously, I hope you enjoyed the journey reading and following the steps as much as I enjoyed writing the posts and recording the videos.

As in the first part, feel free to make any suggestions, improvements, doubts, requests or whatever you want to say!

See you in the next post!