DEV Blog: Spirit Cleanser Enemies

A four-day long weekend coupled with a state-wide lockdown has done wonders for my productivity. Positivity is always welcome during times like these, which is why I’m thankful this streak of solitude granted me to the time to revisit a prototype I had shelved.

There’s a bit of irony on why I had avoided it for so long: I was frustrated with how the player handled being hit by a large enemy. It was only a rectangle box, but when it attacked from above the player just squished between it and the floor, instead of recoiling away. Should I have just fixed it? Perhaps. Did I instead spend a significant amount of time learning a visual scripting plugin for Unity, in hopes it would prevent future issues like above? Perhaps.

After all that I realized I’d rather write code than drag nodes, and after only 30 minutes of concentrated debugging, this insignificant bug was squashed. I wasted quite a bit of time on my time saving scheme. Nevertheless, glass-half-full as I am, this means that my base enemy logic is complete. So, I thought I would share it with you. Now I have a nice little prefab, consistent with every enemy, that can be built on to ensure unique yet unified foes.

That is, you can possess all enemies, and you can cleanse all enemies. This process is how every enemy in the game will be defeated.

Enemy Possession and Cleansing

Enemy possession and enemy cleansing are two parts of one whole. Together, they form the foundation of the whole game.

When the player projects their spirit, they can “spirit dash”. If they collide with an enemy during this dash, they will possess them. The body remains as a vulnerable shell until the spirit returns.

Possessing and enemy will make the player’s spirit disappear into the enemy. From there, a series of button presses will appear above the enemy. If they are pressed in the correct order, the enemy is cleansed and the player’s spirit is ejected back into the world.

I’ve represented these two pieces of logic with flowcharts. Flowcharts might not be the most appropriate for this, but I’m trying to re-familiarize myself with them.

Logic Flow from Spirit projecting to Enemy possession
Logic flow for Enemy cleansing

Making the Enemy Prefab

Every enemy needs to be possessable and cleansable. It stands to reason a base prefab is a great way to quickly and easily set the enemy logic, before layering on defining enemy characteristics. Straight from the Unity manual:

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.

Nice.

This prefab is going to need two things: scripts to handle the logic, and a canvas to display the UI elements.

The Enemy Canvas

The first thing I did for the canvas was to create prefabs for the buttons that need to be pressed for cleansing. These consist of an Image component, and a script that defined the sprite to show, and the corresponding button to press. All the script does is set the image, and tell another script about the button to press. There are four prefabs, one for each direction on the keypad.

    
public class CleanseButton : MonoBehaviour 
{   
    public Sprite buttonImage;
    public string buttonToPress;

    void Start()
    {
        GetComponent<Image>().sprite = buttonImage;
    }
}
Cleanse Button game object values

I chose this approach for cleanse buttons to allow for easy button adding. If more difficult enemies demand more buttons on the keypad, then all I’ll need to do is add some more prefabs in this fashion. These prefabs will be passed as parameters to the enemy logic script, dictating possible buttons to press.

Our canvas is going to need a place to store these buttons, and to hold the timer. The hierarchy looks like so:

Enemy Canvas heirachy

You’ll notice the Grid is empty. The object has a grid layout group on it, and the buttons will be populated as children at run time. This is to ensure randomness. These objects on the canvas are also disabled to start: they are only visible during possession. The slider has a script on it to start timing when it’s enabled (it also has a variable for timer duration).

public class EnemyDamageTimer : MonoBehaviour
{
    public Slider slider;
    public float maxTime = 5f;
    private float timeRemaining;
    public EnemyDamage enemyDamage;

    private void OnEnable()
    {
        timeRemaining = maxTime;
    }

    void Update()
    {
        slider.value = CalculateSliderValues(); 

        if (timeRemaining <= 0)
        {
            enemyDamage.EjectFromEnemy();
        } else
        {
            timeRemaining -= Time.deltaTime;
        }
    }

    private float CalculateSliderValues()
    {
        return timeRemaining / maxTime;
    }
}

This script must be placed directly onto the slider object. This means the timer logic is visually separate from the enemy logic (you need to go into the prefab’s children to find it). It irks me slightly, but it works.

The Enemy Script

Now, the parent object of the canvas will hold the big script. This script dictates which buttons can appear, and how many. On start, the script will set the buttons (which will still be hidden), and wait for the enemy to be possessed. Once an enemy is possessed, the canvas will be enabled (which will also trigger the slider timer), and the script will check for button presses. Correct button presses remove that button from the grid. Once all the buttons are gone, then we kill the enemy and jump out of them. Else, the timer runs out and we jump out anyway.

Enemy Damage script editable variables

There are a few variables to tune here. I’ve listed them below with a short description for each one:

  • Mana On Cleanse: The energy returned to the player upon successfully cleansing the enemy
  • Enemy Grid: A reference to the game object holder the grid
  • Slider: A reference to the game object containing the slider
  • Number of Cleanse Buttons: The number of buttons that need to be pressed to cleanse the enemy
  • Possible Buttons To Press: An array of Cleanse Buttons that will make up the cleanse buttons
  • Punch X Max: Punch VFX on correct button press – maximum X offset
  • Punch Y Max: Punch VFX on correct button press – maximum Y offset
  • Punch Duration: Punch VFX on correct button press – length of effect
  • Possessed: A boolean that shows if the player is currently possessing the enemy

Most important are the possible buttons to press, and the number of cleanse buttons. These allow us to tune the enemy difficulty by increasing/decreasing which buttons to press, and how many times.

The Final Result

I can tell already this is going to make creating new enemies a lot easier!
Here’s a basic enemy with four button presses needed in two seconds.

Four Button Cleanse

And here’s another one with fifteen button presses needed in five seconds.

Fifteen Button Cleanse

Having this base so easily customizable means enemies can be tweaked to allow for specific gameplay action. What about small enemies that only need one button press? Line them in a row and let the player quickly obliterate them all, jumping from one to the other. Maybe an enemy with a short timer and long button presses? You’ll have to be smart and possess them multiple times to finish them off.

There are a lot of possibilities, and I’m excited to see what I come up with!

Possible Changes

Like every project ever revisited, this enemy logic is screaming to be refactored.

I’d prefer the grid and slider variables in the enemy script to be found via code instead of the drag and drop method. The variables are already set in the prefab, so it’s set in every prefab instance, but removing the clutter from the Unity inspector would be nice. There is a slight possibility it could cause issues (how would the script differentiate between multiple sliders/grids if an enemy archetype needed many?), but it is on the back of my mind.

The punch values being tweakable from the inspector seem unnecessary. For now it’s okay since we can tweak it easily if necessary, but if it should be consistent between ALL enemies, then privatizing the variables and making them constants could declutter that script in the inspector.

The last change that needs to happen is a major refactor of the script design. I think it would be cleaner if there was an IPossessable interface, which an abstract Enemy class that implements it. Then, each enemy archetype would inherit from the Enemy base class, and everything would be neat and tidy. I’m currently reading a book called “Clean Code: A Handbook of Agile Software Craftsmanship” – so I’m hoping that will motivate and guide me into tidying the enemy up even further.

Anyway, that’s it for now. Until next time!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s