With the Dragon and its Friends getting their swing on, it’s time for our Player to start taking hits. Let’s make a plan:
- Add events to the Zombie attack animations.
- Maybe add a Weapon the the rest of the animation set (attack/hit/death) to our Mechanic.
- Add a Health component to our Player and also a Death handler.
is was an error when an Enemy was killed. It came from disabling the Nav Mesh Agent component and not disabling the Attack Target component.
Attack Target was trying to call Set Destination on the Nav Mesh Agent without checking to see if it was active. The solution is to disable the Attack Target component during an Enemy’s Zero Health event.
Some of these event lists are getting long and maintaining them is difficult. A solution may be a Controller object that tracks all of these and disables the lot on request.
I dropped a Health component onto the Player and hooked up a couple of Text Mesh Pro components.
The yellow arrow is a reference to a Text Mesh object that displays the text “You’re Dead!”. It is inactive to start and we display the message on the Player’s Zero Health event.
The Damage Taken event triggers the HUD to update the Text Mesh object displaying the Player’s remaining health.
Damaging the Player
The Animator Controller on each Enemy (or any Game Object, really) can communicate with the other components on the Enemy using events set up within the Animation.
These events trigger at a specific moment in time while the animation is playing by calling methods on the other components of the Game Object.
For the Enemy, the Attack Target script has two public methods called Hit Event and Finished Event for this purpose. It’s super important that these methods have the form public void YourEventName().
In order to deal damage to the Player, I swiped the Damage Target script which was written for the Weapon objects and added it to the base Enemy prefab.
Doing so passes it on to the Dragon, Zombie and Mechanic variant prefabs. The base damage can be adjusted for each Enemy.
Death and Spawning
Let’s have a look at the changes I needed to make to the Player object in order to facilitate Player death and Respawning.
This is the Zero Health event from the Player’s Health component. The red box is a Text Mesh Pro GUI object that displays the text “You’re Dead!”. The yellow box is a Spawn component on the Player. The green box is an Image Fade component attached to an Image in the GUI. The purple box is a hack type fix that stops enemies from attacking a dead Player.
The Spawn component.
The Player’s Spawn component is tracks where the Player should spawn and describes how other components on the Player should be reset.
The Spawn Point is a reference to a Game Object in the scene that has a Spawn Point component on it.
A Full Health method was added to the Health component so that the health can be restored. The Target’s Alive flag is also reset. This later bit is part of the hack to stop Enemy attacks after death.
Finally, the GUI has a Text Mesh Pro object that display’s the player’s health, and this needs to be updated.
Note that the Spawn script uses a coroutine to delay the Player’s spawn. This timer is set as part of the Zero Health event.
A Spawn Point is a placed on a Game Object where you want any Target object to spawn.
The Position and Rotation of the Spawn Point are copied to any object using the spawn. There is a bug in this code which I’ll talk about in a moment.
The Image Fade is part of a black screen in the GUI that fades the screen in slowly when the Spawn Point is used.
Finally, a Particle System on a child object of the Spawn Point plays green swirls around the Player.
The bug is with the Position of the transform which is on the ground. The Player is, out of necessity, positioned half the height of the Capsule Collider off the ground. Simply copying the Spawn Point’s y position causes the Player to spawn in the ground. This is not ideal.
The Position hack you see here will only work for spawn points located on the ground. If we put one inside a building it wouldn’t work. The best solution might be adding a y-offset field to the Target for these kinds of calculations. Alternatively, we could use a SetPosition field and avoid accessing the Transform of the Target directly.
The first change prevents damage if health has dropped below 0. The other two provide for incremental healing and recovering full health. The first method will come in hand when I introduce pick-ups down the line. The second is an important part of respawning the Player.
Fade In / Fade Out
Fading in and out is accomplished by adding an Image to the GUI that is stretched to fit the screen space.
Timers are specified in the component to control how quickly the fades happen. I think these should probably be set by the objects requesting the fade though, so this is likely to be refactored in the future.
Bugs in Attack Target
So all this was a real pain with the Attack Target script. Essentially, it boils down to Attack Target no knowing if the Target is alive or dead. This caused a weird bug where the script lost track of the Target and never invoked the Out of Range event.
The first part of the solution is an Animation event positioned at the 1:00 mark for each of the enemy attack animations.
The script that invokes the Attack Finished event is located on the Attack Target component. It’s job is to disable the Attack animation.
It’s super important to understand that the Attack animation does not loop because of this code. There are other solutions (using a trigger parameter instead of a bool) but I was not satisfied with those outcomes and discarded them.
The second part of the solution was to add a field to the Target to track whether or not the Target is alive or dead. This is WAY over-engineered and needs to be refactored down the line. I would have though that isAlive set as a public field would have been enough, but I had an impossible time getting it to show up in the Events. The last two methods are there to improve readability when testing the field. I am least proud of this hack.
This is the modified Update method for Attack Target to show how the state of the Target object is checked before commencing the attack. At this point, this is my solution to keep coupling weak between the Attack Target component and the Damage Target component.
Finally, I have an additional range calculation in the Damage Target component so that an attack in progress can miss a target that has moved outside of the area of effect.
I waffle back and forth on whether an enemy should be able to abandon an attack or be forced to finish it. I just waffled to “be forced to finish it”.
In Attack Target, I eliminated the invocation of the Out of Range event in favor of the Attack Finished event which is should be triggered for all attacks as long as the Animation is set up properly.
In Follow Target, the following changes take into account the alive state of the Target to guard the Update function.