Synzza Star - Implementing Skills through Scriptable Objects and Coroutines

In my previous post, I laid out some aspects of Synzza Star's combat system design. Let's discuss how I've been using Unity to realize these designs.

Scriptable Objects

Before explaining how I have been using Scriptable Objects, I'd like to take a moment to explain what Scriptable Objects actually are. Normally on this blog I like to avoid this kind of exposition, but the term "Scriptable Object" is so uniquely nondescript and awful that I suspect many uninitiated readers will, like me, be confused from the onset what these things are and why they should care about them.

What are Scriptable Objects?

In short, a Scriptable Object instance can be thought of as a row in a spreadsheet, and the Scriptable Object class definition can be thought of as the column headers on that spreadsheet.

FYI: "Scrib" is an abbreviation I use in code to mean "ScriptableObject". You'll see it a lot.

Note: Excel is not needed for Scriptable Objects. This is just a visualization to help explain the concept.

This is, of course, a bit of an oversimplification, but hopefully this conveys the idea in a way that contrasts it from other Unity features such as Prefabs. It might be tempting to interpret the "Object" part of Scriptable Object as meaning "GameObject" and thereby make the connection to Prefabs, but this would be wrong (hence, why the term is so vague and bad). Prefabs are more like templates which can be used to dynamically instantiate a hierarchy of GameObjects at runtime, whereas Scriptable Objects can be used to represent more ethereal concepts such as the stats of an RPG skill or a character's set of dialogue.

Once a Scriptable Object has been given a class definition, instances of it can be created just like any other Unity asset. These assets will be referable to each other and to GameObjects through the Unity editor, making them much more convenient than storing this kind of data outside of Unity in something like an Excel document.


Extending Scriptable Objects

What makes Scriptable Objects especially powerful is that they, like any C# class, can have behavior as well as data, and can be extended through inheritance. This is exemplified through my implementation of skill effects in Synzza Star using Scriptable Objects.

The idea here is to use polymorphism to allow a skill to specify what its behavior is without needing any foreknowledge of what it is or what it needs. This is necessary because Synzza Star will have a wide variety of skill effects (such as, to name a few, blocking, attacking with a melee weapon, creating an area of effect, a string of dashes, etc.) that need a common interface to communicate with the game's systems.

The inheriting subclasses of `SkillEffectScrib` will then define the specific nature of their effect and what parameters they need. Scriptable Object instances of those classes can then be created in order to provide values to those arguments.


Coroutines

The final piece of functionality skills in Synzza Star need is a precise way to coordinate timing. As I've discussed previously, much of the tactical strategy in this game is found in the interplay between the durations of the various phases of an attack: wind-up, effect, and wind-down.


In Unity, this is achieved via coroutines, via the IEnumerator interface. (I won't be explaining these terms as I did for Scriptable Objects because these are more well-established concepts. If you need some additional background, understanding the C# yield keyword in relation to IEnumerators is a good starting point.)

To briefly explain what we're looking at here, `_runningSkill` is essentially a pending activation record of the skill's effect. When `CreateEffectCoroutine` is called, the skill's effect does not immediately start because the underlying coroutine has not been started yet; rather, the battler is put into a wind-up state, and only after the wind-up has been completed do we actually start the effect coroutine.

This is where the utility of polymorphism for skill effects should become apparent. The parent skill simply needs to invoke the effect's coroutine, and the downstream implementation will take over from there without need for further input. For example, this is what the code looks like for a simple "block" skill:

As can be seen, this is relatively straightforward, and not much is needed to make this skill work. To contrast this, here is the code for a skill which spawns a hitbox:

There's a lot more going on here, but it's all contained within this class definition, so the parent skill can remain blissfully ignorant of what goes into making its effect happen.

Seeing it in action

Continuous states (AKA "melee rules", a more to-the-point renaming I'll be using from now on) have also been implemented in a first-pass form; however, that will be better discussed in a future post after I have first implemented the pause-input system. For now, I'll leave you with a gif demonstrating an AI-controlled party member duking it out with enemies that can now attack with hitboxes. As always, take care, and stay tuned for future updates.

Comments

Popular posts from this blog

Synzza Star - Battle System - What's the goal?

Synzza Star - Experimentations with 2D and 3D graphics

Synzza Star - Designing Combat AI