06 Apr 2020
06 Apr 2020
The first draft of the EnemyBalance script I wrote last sprint did not take projectile properties into account. The issue was two-fold: the projectile firing script is unique for every enemy, and most projectile properties were located on the projectile prefab rather than the firing script. After the scope-reduction there are only going to be five enemies in the game, and only two of those are going to have projectiles. I figured it just made sense to just check for both.
Using the existing code as a template, I used
GetComponent<PrototypeDropProjectile> to get the projectile component. This returned null for all the non-projectile enemies, so I had to check if it was null and instead use a default value so it wouldn’t break the code or zero out the formula. While I was in the code, I added similar checks for every GetComponent. For required components, I did the same thing with default values. This tool is only important for certain people involved in level design, so I didn’t want it to cause fatal errors (stopping play) even if important components were missing from enemies. Instead, I manually threw warnings using
Debug.LogWarning and used default values so the formula would still work. If an enemy is missing something important like the health or damage script, it outputs a warning to the console without preventing the game from being played.
I was a little confused on how to get the projectile damage however. The projectile prefab itself has a component called
DamageOnCollision which has the damage as a serialized field. There’s a reference to this prefab in the
PrototypeDropProjectile component, but I thought I had to instantiate the prefab before I could check the value. This ended up being messy since every time
OnGUI ran, it was creating projectile objects in the scene and then instantly deleting them. I talked to my pod lead Nikhil and he told me I can just access it directly from the prefab. I just assumed you couldn’t use
GetComponent on a prefab without making an instance first, but you easily can.
// Need to get damage value of projectile from prefab GameObject projectilePrefab = dropProjectile.projectileType; DamageOnCollision projectileDmg = projectilePrefab.GetComponent<DamageOnCollision>(); if(projectileDmg) projectileDamageValue = projectileDmg.damage;
Finally, I ran into a merge conflict when I submitted a pull request. I modified the Qrabz enemy prefab to add the
EnemyComplexity component to them. I also modified some values when I was making my test scene. I didn’t mean to commit them, so I tried to fix it by merging the development branch into my own while I was still working on it. This was a few days before making the pull request. It probably just made things worse since there was still a merge conflict. I wanted to just use the version from the development branch to avoid breaking other people’s code, but this alone took longer than it should’ve since I was using git on commandline. After choosing the development version of the prefab, I then had to redo some work in my test scene, making sure to only make local changes to each object rather than modifying the prefab.
Last sprint, the EnemyBalance editor UI was very basic. It printed an enemy’s name, then its difficulty score on the next line. It worked in my small test scene, but in the actual first level of the game there were too many enemies and they couldn’t all fit. It was also just a jumble of text and was hard to read/use. I added a scroll bar and set a max-height. I also organized the data in a table. This is not directly supported by Unity so I had to manually draw the lines and set the widths of every element so it would work properly.
The other big addition is the ‘Select’ button for each enemy. I noticed that a lot of the object names in the scene were very similar. If a designer used the tool and wanted to tweak an enemy’s difficulty, it would be hard to find it in the scene. The Select button selects the given enemy so they can change the properties. It took some searching in the docs, but I found you can use
Selection.activeObject to change the currently selected object in the scene view, and you can use
SceneView.lastActiveSceneView.FrameSelected() to frame the object. This is what happens when you press
F in the editor; it zooms in on the selected object.
While working on the editor script, I got the idea for a heatmap of enemy difficulties. I wanted designers to be able to see all the enemies in the level and their associated difficulties in a visual way rather than just in a table. The idea was clear in my mind, but this was a time when I had no clue how to actually implement it. Here are some of the challenges I faced while trying to figure this out:
In my original idea, I saw this as a small window/rectangle inside the EnemyBalance editor window. It would be like a minimap with the heatmap overlayed. I found
Handles.DrawCamera which might have worked, but I also wanted the user to be able to zoom in and out of the map. That would be difficult with a camera rendering the game. I briefly considered making a custom view by inheriting from
SceneView but I couldn’t find any good examples and documentation seemed unhelpful. Next idea was gizmos.
Gizmos.DrawIcon looked promising, but resizing it was impossible. This forum post talked about how they could only be scaled using the slider. I wanted them to scale indpendently with the camera so this would not work. Also, gizmos are only meant to work on the selected objects. I would either need to do a messy workaround or possibly select all the enemies.
I decided to use
Editor.OnSceneGUI to manipulate GameObjects in the scene. These objects would be sprites that I could resize and recolor based on the difficulty and scene zoom level. Also, this function can be used directly from the editor window script by adapting code from here.
I wanted the sprites to scale based on how far the scene camera is zoomed in. You need to be able to see the sprites when the map is completely zoomed out, but they can’t be that big when you start zooming in to specific sections or rooms.
The first problem was the performance of this. The entire editor became unresponsive and unusable. The script iterates through all enemy location marker sprites in
Editor.OnSceneGUI and calculates what their scale should be based on their difficulty score and the zoom level as defined by
SceneView.currentDrawingSceneView.camera.orthographicSize. I added some debug statements and found that this function is run very often. If you do anything in the editor, it’s run hundreds of times. To fix this, I added a check. It only runs the expensive for-loop part if the zoom actually changed since the last time it was run. This fixed it, but if the problem persisted on lower end hardware, it could be resolved by having it run only if the zoom changes by a certain value (e.g. > 1.0). This number could be made bigger to increase performance but at the cost of the smoothness of scaling.
The formula for scaling took a lot of trial and error. Size had to be based primarily on the zoom level but also represent the enemy’s difficulty. Here’s what I ended up with:
float scaleValue = (zoom / 3.0f) * SqueezeFloatToRange(difficulty) + 2.0f; scaleValue = Mathf.Clamp(scaleValue, 0.0f, maxSize * ClampToMax(difficulty));
SqueezeFloatToRange() takes the difficulty and divides it by
(maxDifficulty, minDifficulty), squeezing it into the range
(0, 1). I added
2.0f to make the weakest enemies still have visible sprites as close ranges. Then, when the map is really far zoomed out, the value is clamped to prevent it from getting too big.
ClampToMax squeezes the difficulty into the range
(0.6, 1) which, multiplied with the set
maxSize, maxes them out at a reasonable size while still showing the differences in difficulty.
Part of the existing difficulty formula takes projectile speed into account. The dropper has a very fast vertical projectile: it’s set to 500. This is weighted way too hard by the balance calculation and makes it appear 500 times more difficult than a default enemy. Since the projectile part of the script is done specifically for the dropper, I modified it to divide the speed by 100 to bring it within the same order of magnitude.
I attended two meetings this sprint. Both lasted a little over an hour, but I spent the rest of the time working on the playtesting feedback form. This was new on 3/29, but I was more prepared for the 4/5 meeting. I recorded my playtest sessions and uploaded them to Youtube. I played the game first with keyboard and mouse, then again with controller.
On 3/29, my biggest piece of gameplay-related feedback was that I did not like the control scheme for the teleportation mechanic. You had to use the left control stick to move and also aim the teleport weapon, which was very clunky when you had to teleport in the opposite direction you were moving. I suggested moving it to the right stick. It actually was moved in this build and… it’s even worse. Now it’s hard to jump and use the teleport unless you use an uncomfortable claw grip. This is the first time I felt like I was the player who didn’t know what was good for them! Using the right stick to aim radially feels more natural, but it does NOT lend itself to the gameplay here.
I honestly wasn’t sure what else to suggest, since it just feels better with the keyboard and mouse and I’m not sure how the controller method can be improved from the original scheme. The mouse is almost perfect for aiming, but it slows down too when Io uses her slow-mo ability. I think this shows in the videos; I felt like I used the slow-mo way less with the mouse and keyboard because it was frustrating to use. I also commented on some level design things like blind jumps as well as some animation issues. Overall, it was cool to see the progress and finally get to see the enemies implemented in a build.
The EnemyBalance script is now done/on-hold. Next week I’ll be working on implementing player health/damage from enemies. I do wish I got to work on the EnemyBalance editor window earlier in the semester because it doesn’t seem like it’ll be used much at this point. Hopefully it can be used in future games.