Object class is a base class for all objects Unity can reference. From Object inherits another Unity's main class - GameObject - base class for all entities in Unity scenes. Besides GameObject we have another classes that derives directly from main Object class, for instance Component or ScriptableObject. Today we are going to talk about the last one.

What is the ScriptableObject?

Unlike the GameObject class, ScriptableObject can't be attached to any GameObject or Prefab. This kind of object is most useful for assets which are only used to store data. Scriptable Objects can be inspected and serialized like MonoBehaviour. They can also can be stored in .asset files. We can easily create instances of ScriptableObject-derived class by using the CreateAssetMenu attribute.

Example of basic ScriptableObject-derived class

Then if you want to create a new custom assset simply click right mouse button on Project view and select Create/Basic Scriptable Object. Your fresh Scriptable Object is ready to go.

Let's talk about advantages of using Scriptable Objects. They are built into Unity - you don't need any extra plugins to start using them. They can be saved as assets even during runtime (in Editor). In contrast to MonoBehaviour, Scriptable Objects can be referenced - that means you can reduce memory usage by avoiding copies of values. For example, 10 instances of a prefab which has 3MB of data = 30MB. If we use reference to ScriptableObject which holds 3MB data even if we have 100 instances total, data still equals to 3MB. What is more the performance is quite fast.

Obviously perfect solution doesn't exist so Scriptable Objects have disadvantages too. They require Editor Scripting so you can't edit or save it outside Unity. Because this is Unity code, you can't slightly optimize loading speed.

Examples of using Scriptable Objects

Scriptable Objects generally are used to:

  • store global game settings
  • create interchangeable components - you can store various settings like Easy, Difficult, Medium in separate assets and then select currently needed component.
  • create item database
  • create flexible player ability system.

Now I'm going to show you how to create simple ability system using Scriptable Object behaviour.

Ability system

Imagine that we are creating a MOBA game. We have bunch of heroes and every hero has three abilities. We decided to create each of ability in separate Scriptable Object asset. At first we will create base abstract Ability class:

public abstract class Ability : ScriptableObject {  
    public string abilityName = "New Ability";
    public Sprite abilityIcon;
    public AudioClip abilitySound;
    public float abilityCooldown = 1f;

    public abstract void InitializeAbility(GameObject obj);
    public abstract void TriggerAbility();
}

Okay, now we need to create at least one class which inherits from Ability class. Designer told us that currently we have 3 types of skills: projectile, instant cast on target and area of effect. Let's create projectile ability class:

[CreateAssetMenu(menuName = "Abilities/ProjectileAbility")]
public class ProjectileAbility : Ability {  
    public float projectileForce = 300;
    public Rigidbody projectilePrefab;

    private ProjectileBehaviour projectileBehaviour;

    public override void Initialize(GameObject obj) {
        projectileBehaviour = obj.GetComponent<ProjectileBehaviour>();
        projectileBehaviour.projectileForce = projectileForce;
        projectileBehaviour.projectilePrefab = projectilePrefab;
    }

    public override void TriggerAbility() {
        projectileBehaviour.Fire();
    }
}

So, projectile has some extra fields: force, prefab and projectileBehaviour. ProjectileBehaviour is separate MonoBehaviour class which is responsible for launching given projectile prefab with force described in Scriptable Object. Now in editor, we can create a projectile asset, for example fireball.

All right! We have our first ability. Next step is to create ability icon with cooldown visual counter. Create script named AbilityIcon:

[RequireComponent(typeof(AudioSource))]
public class AbilityIcon : MonoBehaviour {  
    public KeyCode keyTrigger;
    public Image abilityCooldownMask;
    public Text cooldownCounterText;

    [SerializeField] private Ability ability;
    [SerializeField] private GameObject abilityHolder;

    private Image abilityIconImage;
    private AudioSource abilityAudioSource;
    private float cooldownDuration;
    private float nextReadyTime;
    private float cooldownTimeLeft;

    private void Start() {
        Initialize(ability, abilityHolder);
    }

    public void Initialize(Ability selectedAbility, GameObject abilityHolder) {
        ability = selectedAbility;
        abilityIconImage = GetComponent<Image>();
        abilityAudioSource = GetComponent<AudioSource>();
        abilityIconImage.sprite = ability.aSprite;
        abilityCooldownMask.sprite = ability.aSprite;
        cooldownDuration = ability.aBaseCooldown;
        ability.Initialize(abilityHolder);
        AbilityReady();
    }

    private void AbilityReady() {
        ChangeCooldownVisibilityState(false);
    }

    private void Cooldown() {
        cooldownTimeLeft -= Time.deltaTime;
        cooldownCounterText.text = Mathf.Round(cooldownTimeLeft).ToString();
        abilityCooldownMask.fillAmount = (cooldownTimeLeft / cooldownDuration);
    }

    private void ButtonTriggered() {
        nextReadyTime = cooldownDuration + Time.time;
        cooldownTimeLeft = cooldownDuration;
        ChangeCooldownVisibilityState(true);
        abilityAudioSource.clip = ability.aSound;
        abilityAudioSource.Play();
        ability.TriggerAbility();
    }

    private void ChangeCooldownVisibilityState(bool state) {
        abilityCooldownMask.enabled = state;
        cooldownCounterText.enabled = state;
    }

    private void Update() {
        bool coolDownComplete = Time.time > nextReadyTime;
        if (coolDownComplete)
        {
            AbilityReady();
            if (Input.GetKeyUp(keyTrigger))
            {
                ButtonTriggered();
            }
        }
        else
        {
            Cooldown();
        }
    }
}

When skill is ready we can press key that we set in keyTrigger variable then script will trigger an ability and run the cooldown. During cooldown game shows animation with cooldown counter. The last thing that we need is to setup UI view for icon and attach script to it. The UI contains 3 elements:

  • image with AbilityIcon script
  • semi-transparent filled image for cooldown mask
  • a counter text

Let's attach needed references to script and set key trigger to Q button. Press Play and click Q button. That's it! Game spawned fireball from prefab, played it sound and after that cooldown counter has started. That way you can easily create next abilities to your game.

Conclusion

Scriptable Object behaviour is a very useful tool delivered by Unity. You can create local databases or settings with them. Using Scriptable Objects is very convinient for game designers. They don't have to search any setting or data in script but they can easily change it in asset stored in game project folder.

Of course Scriptable Objects have some limitations you should have on mind. They are only data containers and they can't be attached to any GameObject. What is important they require Editor Scripting so you can't edit or save them outside Editor as well. For data storage purposes you can always consider other possibilities like: XML, JSON, PlayerPrefs or external databases.

I hope it’s useful for you. If you have any comments or questions, please write them here!

Best regards,
University of Games Team



Show Comments