23 Mar 2020

Io Progress Report - Pre-Beta

23 Mar 2020

At a Glance

The first week in this sprint was spring break, and instead of getting new tasks we worked on finishing up things from the previous sprint. During this time, I worked on the EnemyDeath component which handles behavior when the player kills an enemy. My next task was an editor script to help level designers determine the relative “difficulty” of a level based on the number of certain enemies. This took longer than expected due to the campus shutdown and sudden move-out.

  • Wrote enemy death script
    • Runs when any enemy dies. Despawns it, shows a corpse, and plays a particle effect
  • Created enemy balance editor script
    • Uses a formula adapted from Alex Kisil’s research during DREAMWILLOW
    • Finds all enemies in the scene and calculates their difficulty score
      • Difficulty is defined by attributes like hitbox size, firing rate, etc.
    • Shows this information in an editor window to help level designers
  • Attended two meetings
    • One was in-person at the usual time, one was cancelled, and the other happened over Discord voice chat

Enemy Death Script

The EnemyDeath component is designed to define the basic behaviors that all enemies will carry out when the player kills them. It needs to despawn the enemy, show a corpse sprite, and play a particle effect. This was pretty straightforward, but I did spend some time taking feedback and modifying the design to allow for more flexibility. At the time I was writing this script, there were no finished enemy prefabs to use in testing. I had to use the basic LAB_EnemyPrototype which is a relic from the first days of the project. This meant my code made assumptions on the structure of all enemy prefabs based on just this one.

My plan was to have public fields for the particle effect and corpse prefabs, and have them spawn at the enemy’s location before destroying its GameObject. There was a problem though, because the actual enemy was a child of the root prefab element. This parent just acted as a container, while the child had all of the relevant components and scripts attached. If I put the death script on the child and destroyed it on death, it raised lots of errors because the AggroRange and other siblings had references to a deleted GameObject they were trying to access.

LAB_EnemyPrototype
├── LAB_Enemy (Patrol)
│   └── Visuals
├── EnemyAggroRange
├── Left Bound
└── Right Bound

The enemy object hierarchy, with the EnemyDeath component on a child of the root prefab element.

To fix this, I had the death component delete the parent of the object the script is attached to. I was already thinking ahead at this point and was worried that future prefabs may not fit this mold, so I added a conditional: if the enemy has a parent, delete that, otherwise, just delete the enemy. At this point, the script worked so I put in a pull request. I did get feedback that this wasn’t the best way of doing it. Even though I was trying to fit a few cases, it was possible that future enemies could be nested further which would break the code. It still wasn’t flexible.

LAB_EnemyPrototype
├── LAB_Enemy (Patrol)
│   └── Visuals
├── EnemyAggroRange
├── Left Bound
└── Right Bound

The EnemyDeath component is now on the root element.

I moved the component to the root element. The parent’s position doesn’t move with the enemy, so it needs a way to get the enemy’s position to know where to spawn the corpe and particle effect. My first solution was to iterate through the children and do a name search which I recognized as messy and bad. I decided to just add two additional public properties for the corpse and particle effect spawn locations. In the interest of making the code as flexible as possible for future enemies, I thought it made sense to be able to set the spawn locations independently anyway. If a designer wants to have the effect happen at somewhere that’s not exactly at the enemy’s center point, they should have that power. And if they do want it exactly at the enemy’s center, that’s the default since both fields are set to the child enemy in the prefab editor.

The finished EnemyDeath component in action: spawning a corpse and particle effect when the player kills it.

Finally, I did some experimenting with how other types of enemies could die. For this basic walking enemy, the basic corpse sprite with no collider is enough. The player can walk through it as if it’s in the background. However, I wanted to make sure this could work with flying enemies. Each enemy can use a unique corpse prefab, so I created one with a rigidbody and collider so it would fall to the ground on death. I gave it a tag that wouldn’t interact or block the player. I did this in a separate local branch since changes to the tags list and physics matrix would cause problems if it got merged. This was meant as more of a proof-of-concept so the team would know it works with flying enemies.

A flying enemy falls to the ground after being killed.

Enemy Balance Editor Script

My next task was making an editor window to help level designers gauge the rough difficulty of their levels. This is based on Alex Kisil’s work during DREAMWILLOW. He didn’t have time to implement it into the game, but came up with ideas on how to calculate an enemy’s “difficulty score.” This is based on properties like its speed, aggro radius, hitbox size, and firing rate. Does it fly? Does it have a shield? It also takes into account the subjective idea of “complexity” as defined by the designer. All of these are used to calculate a value for each enemy, and these can be summed to get a value for the entire level.

The enemy balancing equation, adapted from Alex Kisil's work on DREAMWILLOW.

The enemy balancing equation, adapted from Alex Kisil's research.

I’ve never written editor scripts before, so I had a lot of learning to do. I followed the basic Brackey’s tutorial for editor windows and used the code I wrote there as a jumping off point. After tinkering around for a while with the EditorWindow API (the fact that there are two classes, EditorGUI and EditorGUILayout can be confusing), I worked on just getting a list of the enemies in the scene. Then I could iterate through them and calculate their difficulty scores and display them in the GUI. I used Resources.FindObjectsOfTypeAll to get an array of all GameObjects in the scene. Next, I needed a way to isolate these to just the enemies.

A prototype Enemy Balance window just listing the enemies in the scene

A prototype Enemy Balance window just showing a list of all the enemies in the scene.

How can you determine which GameObjects are enemies? The only finished enemy prefab at this point was the Qrabz, so it was hard for me to know if there was any script that would be on all enemies which I could use to identify them. I investigated the idea of having a field in the editor window that would let designers fill in a list of prefabs which would be considered enemies by the script. Then they could also set the complexity factor right there in the same editor window. This ended up being a huge dead end. I needed the mapping to be persistent across scene loads and changes by other people which turned out to be more trouble than it was worth.

I decided the simpler solution was to make a new script that should be on all enemy prefabs. There are likely only going to be five enemies in the game, so this wouldn’t be too hard to enforce. This component, EnemyComplexity could contain a field where designers could enter a complexity value. This actually works better than the previous mapping idea, because specific enemies from the same prefab can have distinct comlexity values if the designers want to do so. For example, they can take the enemy’s location into account when setting this value. Otherwise, it’ll just use the default from the prefab.

The Enemy Complexity component.

The Enemy Complexity component lets designers set a generic value for all prefab instances but also adjust manually per-enemy when necessary.

Once the component could get the list of enemies, it was just a matter of getting the relevant data from the other components. All of these values are plugged into the formula to generate the scores. I made a test scene with a bunch of enemies, all with different relevant values to make sure the script was working properly. Right now, the projectile fields just use default values since there are no finished prefabs that use projectiles.

The Enemy Balance editor window with difficluty data.

The current Enemy Balance editor window with difficulty scores.

Looking Ahead

In the short term, I would like to advance the UI of this component to make it friendlier to use. I want each enemy’s name to be clickable and directly highlight the associated object in the scene view and hierarchy. I’ll also have to figure out how to get projectile properties to use in the calculation. Finally, I will be keeping track of the hours I put into the project. I’m sorry for not doing so this sprint.