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!

DEV Blog: Spirit Cleanser Game Mechanics

All the best games are easy to learn and difficult to master. They should reward the first quarter and the hundredth.

— Atari founder Nolan Bushnell, on the subject of video game design.

This particular aphorism is oft repeated, yet it’s still a statement I agree with. As a designer, one of my main goals is to create mechanics that are accessible to everyone, but those who truly understand their limits can push the ceiling as high it can go.

Let’s use Overwatch as an example. Overwatch is a team-based FPS shooter, with a character called Lucio. Lucio has the ability to ride walls, and speed boost his team. Not too difficult to understand. I thought I grasped the idea pretty well. But then you have professional streamers: they show complete mastery of these elements, using them to be untouchable murder machines riding rings around the enemy team.

Wall riding can be difficult to learn…
A great Lucio example
But you can use it to great effect.

That’s why in developing this prototype, my intention is to have game mechanics that can be combined for riveting gameplay. I’ve been calling it “Non fare malocchio”, since it’s inspired from the Italian folklore about the evil eye, but “Spirit Cleanser” is a bit more to the point. Essentially, in this game you can project your spirit out into the world to “possess” and “cleanse” evil spirits from enemies.

There are three core mechanics to the gameplay that can be combined: spirit projection/dashing, enemy cleansing, and spirit leaping. Here’s how they work, and how they complement each other:

Spirit Projection/Dashing

The player project their spirit and traverse the world with it, leaving their body behind. When projecting the spirit, it will dash out into the world at a fast speed. It can be projected in all directions.

Spirit Projection
The player can project their spirit.

Enemy Cleansing

If the spirit collides with an enemy while dashing, it will “possess” them, and be able to “cleanse” them. This is done through a series of button presses.

Enemy Cleansing
The player’s spirit can cleanse enemies.

Spirit Leaping

To reunite the body and spirit, the body leaps from its current position to the spirit. If the leap button is held down, extra momentum will be added to the body once the spirit is reconnected.

Spirit Leaping
The player’s body can leap back to the spirit.

Combining the spirit dashing, enemy cleansing, and leaping to spirit mechanics makes for a game experience that is easy to learn yet difficult to master.

The game mechanics combined
Combining these elements makes for complex and rewarding gameplay.

I’m hoping that these base mechanics are enough to carry my idea while I flesh out the prototype more. No doubt, they will need to be revisited, and then revisited again, but for now the building blocks of what I’m trying to achieve are in a playable scene: that ticks the box for me.

My next goal is to create some more enemies to really test the mechanics and create captivating game moments! These elements seem to be fun now, but they have to hold up to a multitude of different enemies and environments.

Until next time!