Is this the right way to set up food items as scriptable objects?

I created some scriptable objects (food items) and I would like the player to be able to click on them and restore some hunger points. This is how I set it up but I’m not getting anything in runtime

[CreateAssetMenu]
public class FoodObject : ItemObject
{
    private PlayerStats hunger;
   
    [SerializeField]
    private string foodName;
    [SerializeField]
    private Sprite icon;
    [SerializeField]
    private float price;
    [SerializeField]
    private float restoreHunger;
  
    public void Awake()
    {
        type = ItemType.Food;
 public void Eat()
    {
        hunger.hunger += restoreHunger;
    }



    }

I then created a scriptable object food1 in the asset file, slide that into another script (food1) that is attached to the food item prefab

public class Food1 : MonoBehaviour
{
    public FoodObject food1;
    // Start is called before the first frame update

    private void Start()
    {
        Debug.Log(food1.FoodName);
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            food1.Eat();
        }
       
    }
}

What am I doing wrong here? Am I writing the Eat() function in the right place?

7139240--853775--a.jpg

You call it at the right place, but accidentally you have put it inside in the Awake method in the ScriptableObject.

Oops sorry that was a mistake when I was copying and pasting the code over in here. I had the Eat () outside of Awake in my original script.

[CreateAssetMenu]
public class FoodObject : ItemObject
{
    private PlayerStats hunger;
   
    [SerializeField]
    private string foodName;
    [SerializeField]
    private Sprite icon;
    [SerializeField]
    private float price;
    [SerializeField]
    private float restoreHunger;
  
    public void Awake()
    {
        type = ItemType.Food;
       
    }

    public void Eat()
    {
        //hunger.hunger += restoreHunger;
    }
}

And when I click anywhere in game view I get the following error
I have attached the script to a food object so Idk why I’m getting it

Scriptable objects don’t have an awake call. Anything you need to do to initialise a scriptable object before it’s accessed needs to be done with OnEnable(), which is when it enters the scope of a scene. This is shown in the documentation: Unity - Scripting API: ScriptableObject

Edit: The above is not true as detailed below.

The way your code is written, it will try to eat food whenever you hit the mouse button no matter what you’re clicking. If there’s no food in your FoodObject’s variable, it will throw the null error. You should generally ALWAYS check for null.

Null errors always mean whatever you’re accessing isn’t there, and you need to find why it doesn’t happen to be there.

@spiney199
I know I haven’t setup the raycast manager and stuffs yet, I just want to know if it even eats anything as I click- right now it’s not. The hunger value doesn’t change at all

private void Update()
    {
        if (food1)
        {
            if (Input.GetMouseButtonDown(0))
            {
                Debug.Log("click");
                food1.Eat();
            }
        }

Is this the right way to check for null?

That can work, and so can reversing your if statements. Checking for input first, then checking if there’s food.

As for the null error, have you put a food scriptable object into your FoodObject monobehaviour variable in the inspector?

using UnityEngine;

[CreateAssetMenu]
public class SO : ScriptableObject
{
    private void Awake()
    {
        Debug.Log("I am an Awake in a ScriptableObject. Howdy!");
    }
}

7139348--853784--screenshot1.png

They have. It’s just in the editor it isn’t called on hitting the play button. It is called when the asset gets initialized (it’s an awake call after all), which means when you create it, on project load, sometimes after domain reload. In the game it’s called right at the beginning before OnEnable.

Here is how you can get “Awake” function in Play mode too.

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

public abstract class ManagedObject : ScriptableObject
{
    protected abstract void OnAwake();
    protected abstract void OnEnd();

#if UNITY_EDITOR
    protected void Awake() => EditorApplication.playModeStateChanged += OnPlayStateChange;
    protected void OnDestroy() => EditorApplication.playModeStateChanged -= OnPlayStateChange;

    private void OnPlayStateChange(PlayModeStateChange state)
    {
        if (state == PlayModeStateChange.EnteredPlayMode) OnAwake();
        if (state == PlayModeStateChange.ExitingPlayMode) OnEnd();
    }
#else
    protected void Awake() => OnAwake();
    protected void OnDestroy() => OnEnd();
#endif
}

Yup, totally wrong I was there. Even linked to evidence of how wrong I was! Just a bit used to using OnEnable I think.

1 Like