Home Game Tech Books Unity Apprentice

14
Advanced Scriptable Objects Written by Ben MacKinnon

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

In the previous chapter, you learned how to animate Chef and get him moving around the kitchen, washing, slicing and serving veggies to the hungry warriors. So far, you’re only serving single-ingredient plates. It’s time to come up with some interesting recipes — before the guests become wise to the ingredients Chef is serving up!

But how can Chef prepare a meal without a recipe? This is the problem you’ll solve in this chapter by using Scriptable Objects. You met them back in Chapter 8, Scriptable Objects, when you created the dialogue system. In this chapter, you’ll look at a few more techniques that you can use scriptable objects for. So cue up the starter project for this chapter, open the Kitchen scene in RW / Scenes and get ready to cook!

Scriptable objects as data containers

The key property about scriptable objects is that they can be created as serialized objects and stored in your project folders. Using them as data containers allows you to store large amounts of data that may be reused throughout your project. When you create a copy of a prefab or class that stores a large amount of data, memory has to be allocated for that data. Create a whole load of these objects, and you’ve used a lot of memory.

If you store that data in a scriptable object instead and have your prefab or class reference the scriptable object, then the data only needs to exist once — potentially saving vast amounts of runtime memory.

Beyond the potential memory-saving superpowers, scriptable objects can also help you increase your workflow - you can save data changes to them while playing in the Editor - but they can also be used to decouple your code architecture. Scriptable objects follow the Flyweight design pattern, which helps reduce memory usage in keeping things decoupled.

For your Chef, you’ll use scriptable objects to first set up what you need to define a recipe. If you consider a game like you’re creating here, in the full version, there could be many different recipes designed for the game. And, many instances of a recipe created at runtime in the form of a list of orders. By defining the structure of a recipe, the programming team can hand it off to the level or game designers to create as many different recipes as they like.

Defining the recipe scriptable object

You’ll find the foundations of the recipe inside RW / Scripts / ScriptableObjects. Open the Recipe script inside your code editor.

public class Recipe : MonoBehaviour
public class Recipe : ScriptableObject
[CreateAssetMenu(fileName = "New Recipe", menuName = "Scriptable Objects/New Recipe", order = 51)]

public List<IngredientObject.IngredientType> ingredients; // 1
public GameObject prefab; // 2

public Sprite thumbnail; // 3
public int score; // 4

Scriptable Objects as events

The logic to handle scoring is inside the Plate script. Open it from Assets / RW / Scripts in your code editor, and navigate down to the last method: Serve.

public void Serve()
{
    // Check for a recipe
    if (Recipe != null)
    {
            
    }
    else
    {
        // Player served a non-recipe dish, award some points
        OrderBook.instance.Service();
    }
    // Return ingredients to the pool
    foreach (IngredientObject ingredient in 
        transform.GetComponentsInChildren<IngredientObject>(true))
    {
        IngredientPool.Instance.Add(ingredient);
    }

    OnPlateServed?.Invoke();
    Destroy(gameObject, 1f);
}

Scriptable event raiser

Inside the Assets / RW / Scripts / ScriptableObjects folder, create a new script called ServeEvent. Open it in your code editor.

[CreateAssetMenu(fileName = "New Serve Event", menuName = "Scriptable Objects/Serve Event", order = 52)]
public class ServeEvent : ScriptableObject
private List<ServeEventListener> listeners
    = new List<ServeEventListener>();
public void Raise()
{
    for(int i = listeners.Count -1; i>=0 ; i--)
    {
        listeners[i].OnEventRaised();
    }
}
public void RegisterListener(ServeEventListener listener)
{
    listeners.Add(listener);
}
public void UnregisterListener(ServeEventListener listener)
{
    listeners.Remove(listener);
}

Event listener

Now that you have the event class, it’s time to create the listener class. Create another script in the Assets / RW / Scripts / ScriptableObjects folder called ServeEventListener. You’re putting it in the same folder as the event, however this class is not going to be a scriptable object. Instead, it stays as a MonoBehaviour because you’ll attach it as a component to objects in the scene.

using UnityEngine.Events;
[SerializeField]
private ServeEvent serveEvent;
[SerializeField]
private UnityEvent response;
private void OnEnable()
{
    serveEvent.RegisterListener(this);
}

private void OnDisable()
{
    serveEvent.UnregisterListener(this);
}
public void OnEventRaised()
{
    response.Invoke();
}

public ServeEvent serveEvent;

Recipe.serveEvent?.Raise();

Expanding Chef’s repertoire

It may have felt like a lot of work to get the scriptable objects and event system set up, but you are about to see the power of having done so. Sure, Peas and Carrots is a fine dish to be serving, but Chef is too talented for just one signature dish! It’s time to mix things up by adding in another dish — Potatoes and Zucchini. (Or Courgette for your European friends.)

public enum IngredientType { Carrot, Pepper, Potato, Pea, Zucchini }

Challenge

Got one more in you? All the materials you need to create a third recipe are in the project. The final dish involves Peppers, Onion and Sausage. You’ve already learned everything you need to know to complete the steps for this final recipe, but the tasks you need to complete are:

Key points

  • Scriptable objects can be used as data containers — allowing you to reuse data throughout instances of objects — without assigning additional memory for the data.
  • Scriptable objects can also be used to represent events — allowing you to decouple your code and make a system that’s easy for a level designer to come in and expand your game without extra programming.
  • Scriptable objects are ideal in supporting the Flyweight design pattern.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

© 2022 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.