Creating food for NPCs

Creating food for NPCs

Part 8 – From idea to game: A beginner's guide to creating games with Unity!

Apr 22, 2023Β·

13 min read

Welcome to part 8 of my series on Unity development (And already my 10th article on Hashnode πŸŽ‰)! In the last article, I showed you how to create random movement behaviour for the fish in my game. In this article, I will show you how to create fish food.

Preparations

First, I merged the "Create fish" branch into the main branch after checkin all my changes. What this means, is that all changes on that branch are now also in main. This is what I showed earlier, back in part 5.

Then, I created a new branch called "Create food". When creating a new branch in PlasticSCM it automatically switches your workspace to that branch, so that means I am ready to build cool shit πŸ”₯!

Materials

For every minute spent in organizing, an hour is earned.

Wise words from Benjamin Franklin indeed. So I applied it to my project as well. I was going to play with some materials to make the fish stand out from the food. To do so, I simply wanted to use materials for now. And sure, it would work perfectly if I would just store those in my Assets folder, but what if I need to add more later? Your folders will get messy quickly, so make sure you stay organised.

I added a new folder in the Assets folder called "Materials". Simple as that.

Next, I created two new materials (Right click > Create > Material). The first one I named "Fish01", as I am expecting to add more in the future. I gave it an orangish colour and upped the smoothness to give it more shine.

Screenshot of the inspector tab in Unity showing the color and upped smoothness.

Then, I dragged it onto the Fish prefab, which is now orange.

Screenshot of the unity game tab, where the material is applied to the fish gameobject, now turned orange.

The second material I created is for the food, so I named it, drum roll please, "Food01". I made it yellowish and removed any smoothness it had.

Screenshot of the inspector tab in Unity showing the yellowish color and no smoothness on the material.

Prefabs

Not only materials get their folder during this article. Also, prefabs will. I created a folder in Assets called "Prefabs", which gets two child folders, "NPCs" and "Items".

Before we dive into the folders and the organisational side of things, let me take a moment to quickly explain what prefabs are. As always, from the docs:

Unity’s Prefab system allows you to create, configure, and store a GameObject complete with all its components, property values, and child GameObjects as a reusable Asset. The Prefab Asset acts as a template from which you can create new Prefab instances in the Scene .

In other words, a prefab is a template or a blueprint for creating GameObjects more easily. I promise I will get into the nitty-gritty of prefabs later, but that is all you need for now.

Now, before I dragged the Fish GameObject from the Hierarchy tab to the NPCs folder, I made sure its coordinates are set to 0,0,0, and that I added a Tag to the GameObject named "Fish".

Before you can apply custom tags, you need to create them first. Simply select the GameObject you want to tag and click "Add tag" in the dropdown labelled "Tag" below its name.

Screenshot of the unity inspector tab, where a dropdown on 'tag' is shown, with the cursor hovering over "Add tag".

There, click the plus icon and give your tag a name and click "Save". Now, back at the GameObject you want to tag, your custom tags will appear.

Screenshot of the unity editor where the popup to enter new tag name is shown.

The reason I added these tags will become clear later in the article, but for those who can not wait, it has to do with CompareTag.

Stacking layers

While I was doing this setup-type work, I also decided to create custom layers to keep my GameObjects in. I will admit, I expected to make use of them during this article, but there was no need (yet). So this is optional, but I am sure I will use this later down the line.

Screenshot of the unity editor where the Fish tag and NPC layer are applied to the Fish GameObject.

Adding them works the same way as Tags, and you will find the dropdown to the left of the Tag dropdown. But while adding them is similar, their use case is not. Layers are used to define the collision matrix between GameObjects, while tags are used to identify and categorise GameObjects for other purposes such as scripting or setting up triggers. And while you can create as many custom tags as you would like, you are limited to 32 layers.

You can use layers to include or exclude collision of certain layers, as well as showing and hide certain layers in the editor, so it is easier on the eyes.

I created two layers, "NPCs" and "Items". I applied the former to the Fish GameObject and the Fish Prefab.

Creating the Food GameObject

With all the preparations out of the way, I started working on the meat and purpose of this article: Creating food. I created a new GameObject, a 3D sphere this time. So in the Hierarchy tab, I right-clicked > 3D Object > Sphere and renamed it to "Food".

Next, I dragged the material "Food01", which I created earlier, onto the newly created GameObject, turning it yellowish. And since I was working on appearance, I also scaled it down to 0.2, 0.2, 0.2, so the size is better suited for food, rather than a pufferfish.

Next, I had to make sure it can collide with the fish, as that is essential in the logic where the Fish consumes the food later on. So I added a Rigidbody component to the Food and set the drag to 5. I changed the drag so it looks more like the food is floating down like fish food, rather than sinking like a rock. Perhaps later I can add a more natural going-down movement, but this will work for the time being.

To finalise creating the food I added the tag "Food" to the GameObject, and added it to the layer "Items". Both of which I created earlier.

FoodBehaviour.cs

Now that I had a GameObject that represents food, it was time to make it act like food. So in the Assets/scripts folder, I created a new file, FoodBehaviour.cs and dragged it onto the food GameObject:

using UnityEngine;

/// <summary>
/// The FoodBehaviour script is used to handle collision events between food and fish in a Unity game.
/// </summary>
public class FoodBehaviour : MonoBehaviour
{
    // Misc
    [SerializeField] private float foodValue = 10.0f;

    /// <summary>
    /// This method is called when a collision event occurs between the food object and another collider in the scene.
    /// </summary>
    /// <param name="collision">The Collision object that contains information about the collision event.</param>
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Fish"))
        {
            // TODO: Reduce fish hunger
            Destroy(gameObject);
        }
    }

    /// <summary>
    /// Gets the value of the food.
    /// </summary>
    /// <returns>The value of the food as a float.</returns>
    public float GetFoodValue()
    {
        return foodValue;
    }
}

The script is pretty small and straightforward for now. I created one field which holds the "value" of the food. I made it a private field but with a public method GetFoodValue which returns the value. You might be asking, why not just make the field public instead? But there are a couple of reasons to go with a private field instead. First of all, there is encapsulation. By making the field private, we encapsulate the internal state of the object and prevent direct access to it from outside the class. Then there is also the "least privilege principle" which is an information security concept which maintains that a user or entity should only have access to the specific data, resources and applications needed to complete a required task.

Then there is OnCollisionEnter, which, according to the docs is called when this collider/Rigidbody has begun touching another Rigidbody/collider. In the method, I made sure to check if the collision is with a GameObject with the "Fish" tag, by calling CompareTag on the GameObject of the collision.

For now, I simply decided to Destroy the food GameObject, and added a to-do for myself. Eventually, I want the fish to collide with the food. On consumption, it should reduce the fish its hunger, and destroy the food in the process. However, at the moment, the fish does not have hunger or a way to reduce it, so I leave a to-do, to come back to it later.

Naming conventions + small rework

At this stage, I was not content with the names of the scripts. I come from a Ruby on Rails background, which is all about convention over configuration. But looking at my script names, it was becoming messy already so I decided to do some research.

This is what I found went with in the end:

  • _Controller is used for scripts that control GameObjects via input or feedback loops.

  • _Manager is used for coordinating multiple aspects of a GameObject.

  • _Behaviour is used to indicate that the script is responsible for defining the behaviour or functionality of a specific GameObject or component.

With that cleared up, I renamed my scripts:

FishMovement.cs turned into FishMovementBehaviour.cs, CameraBorderCollider.cs became CameraBorderManager.cs and finally FoodBehaviour.cs changed to FoodCollisionBehaviour.cs. And besides renaming the files, I also changes the class names in the file, as they always have to be equal to the file name.

As I mentioned in previous articles, I am a sucker for organisation and clarity. So perhaps I will revisit this later and change it up, but this will work, for now.

FishHungerController.cs

At this point I have Fish and I have Food. The food is destroyed in collision with the Fish, and when that occurs, something needs to happen on the side of the Fish too. I created a new script, FishHungerController.cs:

using UnityEngine;

/// <summary>
/// Controls the hunger level of a fish object.
/// </summary>
public class FishHungerController : MonoBehaviour
{
    // Hunger
    private float _fishHunger = 0.0f;
    [SerializeField] private float _hungerIncrement = 1.0f;

    /// <summary>
    /// The amount by which the fish's hunger increases every time <see cref="TickFishHunger"/> is called.
    /// </summary>
    private void Start()
    {
        InvokeRepeating(nameof(TickFishHunger), 0.5f, 0.5f);
    }

    /// <summary>
    /// Increases the fish's hunger level by <see cref="_hungerIncrement"/>.
    /// </summary>
    private void TickFishHunger()
    {
        UpdateFishHunger(_hungerIncrement);
    }

    /// <summary>
    /// Returns the current hunger level of the fish.
    /// </summary>
    /// <returns>The current hunger level of the fish.</returns>
    public float GetFishHunger()
    {
        return _fishHunger;
    }

    /// <summary>
    /// Updates the fish's hunger level by the specified amount.
    /// </summary>
    /// <param name="amount">The amount by which to update the fish's hunger level.</param>
    public void UpdateFishHunger(float amount)
    {
        _fishHunger += amount;
    }
}

This script is pretty straightforward, looking at the previous scripts I created so far. Perhaps with one exception, so let me run you through what I did.

First I created two fields. _fishHunger and _hungerIncrement. The former will hold the current hunger value of the Fish, and the latter holds the value by which the hunger will increase over time. I serialized the second one, so I can adjust it via the Unity editor in runtime to find the right values.

Then, at the bottom, I created two public methods. One will simply return the current hunger of the fish, and the other one is to update the hunger of the fish. This second method is what I will call from the FoodCollisionBehaviour.cs, when I revisit it to fix the to-do I left there.

But then there is TickFishHunger, which in and of itself is not that special. It simply leverages UpdateFishHunger to change fish hunger. However, the way it is called is something I have not shown you yet.

In the Start method, I use InvokeRepeating to call TickFishHunger with nameof. What? Those are a lot of methods I have not utilised before. As always, you can click the links to find the documentation in case you want to read up on the methods more.

InvokeRepeating invokes the method methodName in time seconds, then repeatedly every repeatRate seconds.

So to clarify what I did here. In the Start method, I told the script to run TickFishHunger every 0.5f time, starting 0.5f time from now. This means that _fishHunger will increase by _hungerIncrement every 0.5f. Amazing!

But wait, what about nameof? To be fair, I could have written InvokeRepeating("TickFishHunger", 0.5f, 0.5f); and it would also have worked. However, as Visual Studio suggested to me, it is better to use nameof instead. The reason why is that string literals are susceptible to mistakes. Perhaps you made a typo, or perhaps the method name changed due to a rework down the line. In those situations, when using a string literal to call the method name, you will only find out during runtime (When executing the code). By using nameof the IDE (Visual Studio in this case) can evaluate whether the method exists and can notify you if this will throw you errors during runtime.

So, TLDR, both options will work, but using nameof is safer as it will inform you about errors before runtime.

FoodCollisionBehaviour.cs

Now that I had a very basic fish hunger system in place, it was time to go back to the to-do I left earlier. Please note that the class name and filename have changed, and also I changed the _foodValue to -10.0f (Negative 10), from positive. This is because it should decrease the hunger of the fish.

using UnityEngine;

/// <summary>
/// The FoodBehaviour script is used to handle collision events between food and fish in a Unity game.
/// </summary>
public class FoodCollisionBehaviour : MonoBehaviour
{
    // Misc
    [SerializeField] private float _foodValue = -10.0f;

    /// <summary>
    /// This method is called when a collision event occurs between the food object and another collider in the scene.
    /// </summary>
    /// <param name="collision">The Collision object that contains information about the collision event.</param>
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Fish"))
        {
            // Check if the colliding GameObject has a FishHungerController component
            if (collision.gameObject.TryGetComponent(out FishHungerController fishHungerController))
            {
                fishHungerController.UpdateFishHunger(_foodValue);
            }

            Destroy(gameObject);
        }
    }

    /// <summary>
    /// Gets the value of the food.
    /// </summary>
    /// <returns>The value of the food as a float.</returns>
    public float GetFoodValue()
    {
        return _foodValue;
    }
}

So what did I do to replace my to-do? Simple, I called the public method UpdateFishHunger with the _foodValue on the fishHungerController. But of course, I needed to be able to access it.

I leveraged TryGetComponent on the GameObject to get and set fishHungerController via the out keyword. The simplest way I can describe this, is with this method, you (As the name implies) try and get a component. If that succeeds, you want to store the reference to that component. So basically what this method does, is checks if the component exists on the GameObject you call it on, and when it does, it sets the reference to that component to a variable (out FishHungerController fishHungerController).

I do all of this in the if statement. Combining this all together, if the statement succeeds, there is a component of that type on the GameObject, and a reference to it will be set to fishHungerController, which allows me to call UpdateFishHunger on it.

I placed the food above the Fish, froze the Fish in place and pressed play:

Screen recording of the food falling onto the fish in the Unity editor, resulting in the food being destroyed.

To finish it up, I reset the coordinates of the food to 0,0,0 and dragged the Food GameObject from the Hierarchy tab to the Assets/Prefabs/Items folder, to turn it into a prefab.

Checkin your changes, move your workspace back to the main branch and merge the Create food branch.

Screen recording of PlasticSCM where the create food branch is merged into main.

Congratulations πŸŽ‰

Voila! If you are following along, you have successfully created a food GameObject! If you have any feedback (On the code, the writing or anything else), I would love to know!

Did you find this article valuable?

Support BFranse by becoming a sponsor. Any amount is appreciated!

Β