FlatRedBall provides a standardized, powerful, and flexible damage dealing system which can reduce the amount of code needed to add damage dealing to your game. This functionality is built around the IDamageable
and IDamageArea
interfaces. The following tutorials provide a step-by-step guide on how to add damage to your game.
The previous tutorial shows how to create new Entities which implement the IDamageable and IDamageArea interfaces. In some cases this results in the automatic creation of collision relationships, and other times manual collision relationships must be created (if the two opposing entity types have the same Team Index). This tutorial explores the Team Index property and its affect on the generated damage dealing code. This tutorial assumes a project which contains:
A Player entity implementing IDamageable with Team Index 0
A Bullet entity implementing IDamageArea with Team Index 0
An Enemy entity implementing IDamageable with Team Index 1
A collision relationship EnemyVsBullet
To test damage dealing, we need instances of entities which collide with one-another. Our game automatically contains a Player instance (this is created by the wizard) so we will use this instance to create bullets. To do this, add the following code to Player.cs CustomActivity:
Now the Player creates bullets when the Space key is pressed. Note that if you have a gamepad connected, the player automatically uses that as its input device. The space bar still shoots bullets, but movement will be controlled by the gamepad.
Next we'll create Enemy instances to shoot. To do this, add the following code to the GameScreen.cs CustomActivity:
Now we can click on the screen with the mouse to add enemies, and we can shoot these enemies with bullets.
You may have noticed that your enemies are destroyed by a single shot. We haven't set up the details for how damage should be dealt, so the default behavior is for bullets to deal damage to enemies every frame. This results in enemy health being drained very quickly, resulting in the enemies dying.
You may have also noticed that the bullets continue to move upward after destroying the enemy. We can control this behavior through an option on the collision relationships.
If we want the bullets to be destroyed after hitting the enemy, we can check the Destroy Bullet on Damage option on the EnemyVsBullet collision relationship.
This option provides a convenient way to handle a common situation - destroying the damage area (Bullet) on collision. Note that we can control whether the bullet should always be destroyed, or only if damage is dealt. Later tutorials show how to adjust the amount of damage dealt.
Also, keep in mind that these options are here for convenience but you are not required to use them. You can always leave them unchecked and handle everything manually in a collision relationship event.
With this option checked, the bullets are destroyed on collision, and each enemy takes multiple hits to be destroyed.
The EnemyVsBullet collision relationship also has the Deal Damage in Generated Code option checked. If this is checked, the following occurs on an Enemy vs Bullet collision:
The team index of the bullet and enemy are compared to see if they differ.
If so, the enemy performs damage over time checks to make sure it can take damage from the bullet. At this point our game does not have bullets which perform damage over time, but we will cover this in a later tutorial.
Damage calculations are performed by raising events which can modify how much damage the enemy receives. Currently we are not handling these events, but we will cover this in a future tutorial.
Health is subtracted from the enemy.
The enemy is killed (Destroy is called) if the enemy has less than or equal to 0 health
We can see that this logic works by shooting an enemy enough times to kill it - by default this is 10 shots.
The default variables used to deal damage and kill the enemy are defined in the Entity and Bullet entities. All entities created with the IDamageable interface default to have 100 health.
All Entities created with the IDamageArea interface default to dealing 10 damage.
If we change these values, we can change how many shots it takes to kill an enemy. For example, if the enemy is changed to have 20 health, it only takes 2 shots to kill each enemy.
As mentioned before, the collision relationship EnemyVsBullet results in damage dealing code because the Enemy and Bullet have different team indexes. Our bullets have a default Team Index of 0, so they will not deal damage to the Player even if a PlayerVsBullet collision relationship exists. To show this, we'll create a collision relationship by dragging PlayerList onto BulletList (you may have already done this in an earlier tutorial).
Even with this collision relationship, Bullets which are fired by the Player do not deal damage to the player - the generated code checks team index and both have a team index of 0 so no damage is dealt to the player.
We may want enemies to be able to shoot bullets at the player. These bullets should have the Enemy Team Index (a value of 1) which can be assigned in code. To do this, add the following code to Enemy.cs CustomActivity:
Also, you should check the option to destroy the bullet when it hits the player, but only if damage is dealt.
Now each Enemy shoots a bullet every 2 seconds which travels downward and which shares the same Team Index as the Enemy. These bullets now collide with the Player, deal damage to the Player, and ultimately kill the Player once the Player's health has dropped to 0.
When developing a full game, keep the following in mind:
Values like MaxHealth, TeamIndex, and DamageToDeal can all be set in the FlatRedBall Editor at the entity level. These should be used for useful defaults, but they can all be assigned in code.
Variables to control logic such as how frequently enemies shoot and bullet speed should be controlled through variables in the FlatRedBall Editor. These values were hardcoded above to keep the tutorial short, but do not reflect how full games should be structured.
This tutorial shows how the built-in logic in collision relationships results in automatic damage dealing and killing (destroying) of entities which can take damage. It also covered how to assign the Team Index variable in code to control whether a pair of entities result in damage dealing logic being applied. The next tutorial shows how to assign events to modify how much damage is dealt and to react to damage dealt visually.
The previous tutorials showed how to deal damage to an enemy with a Bullet. The Bullet entity implemented only the IDamageArea interface (not the IDamageable), so it served purely as an entity which could deal damage but could not receive damage.
Games which implement melee damage can do so by adding additional shapes to the Player entity and by marking the Player as an IDamageArea (as well as IDamageable). This tutorial shows how to modify your project to allow the Player to deal damage to an Enemy using a melee attack collision.
Melee attacks increase the complexity of games because they result in entities that can both deal and receive damage. We need both the Player and Enemy to be able to deal damage.
First, mark the Player entity as both IDamageable and IDamageArea.
Repeat the same for the Enemy entity.
Next, we'll add a collision object to the Player for melee attacks. This can be any type of shape, but we'll use an AxisAlignedRectangle for this tutorial. Be sure to give the new shape a name that clearly explains its purpose, such as MeleeCollision.
Adjust the MeleeCollision so that it is positioned outside of the Player entity. You can use Live Edit to iterate quickly on this.
By default, this new shape is used in all relationships which use the Player's <Entire Object>, which is the default for all collisions. For example, the PlayerVsSolidCollision in GameScreen uses this option, so the newly-created MeleeCollision collides with the map's SolidCollision.
Typically, collision shapes which are added for a special purpose (such as only dealing damage to Enemies) should not be used for all collision relationships. We can fix this by setting the MeleeCollision's IncludeInICollidable to false.
Now the MeleeCollision will only be considered in collision relationships which explicitly select it in the Subcollision dropdown (which we'll do later in this tutorial).
Next, we will create collision relationships between players and enemies. Conceptually, we want the following behavior:
When the body of the enemy touches the body of the player, the player should take damage
When the sword of the player touches the body of the enemy, the enemy should take damage
Each entity type can deal damage to each other entity type, and the direction of damage dealing depends on which shapes have collided.
We need to have a collision relationship between players and enemies, so we can add this by drag+dropping the PlayerList onto the EnemyList in GameScreen.
Notice that the newly-created collision relationship has the option for dealing damage checked, and that both the player and enemy will receive damage.
This collision relationship should only deal damage to the player if the player collides with the enemy. This collision relationship should not deal any damage to the enemy.
We will handle the melee collision (Player dealing damage to Enemy) in a different collision relationship that has its Subcollision set to MeleeCollision. Since we want this collision relationship to deal damage one way, we need to handle dealing damage in code. First, uncheck the option to deal damage in the collision relationship, and click the button to add a new event.
By checking the Add Event button, the FRB editor adds an event in GameScreen.Event.cs which we can fill in with our collision logic. To deal damage from the enemy to the player, add the following code:
Now the player takes damage when colliding with the enemy.
Note that the player dies very quickly after touching the enemy. This happens because the player takes damage every frame (10 damage), so after about 10 frames (1/6th of a second) the player's health reaches 0.
This is typically not desirable in games, so we can use damage dealing frequency and invulnerability to solve this problem. Specifically, we have two variables which can be used to solve this problem:
Enemy SecondsBetweenDamage (how frequently the damage is dealt to the player)
Player InvulnerabilityTimeAfterDamage (how frequently the player can receive damage)
While these two may seem similar at first, which you use can have an impact on your game. Most older games (such as Super Mario World, Zelda: Link to the Past, Mega Man X, and Super Metroid) implemented invulnerability after being dealt damage. This means that if the player took damage from any damage source, the player would remain invulnerable for a period of time after the damage was dealt. This approach can give the player a moment to recover after taking damage, and can avoid situations where the player is "sandwiched" between multiple damage sources, resulting in health draining rapidly.
By contrast, some games (such as Mega Man X) do not have invulnerability time on regular (non-boss) enemies, allowing players to mash very quickly to deal large amounts of damage. Keep in mind that invulnerability periods also prevent the use of the damage system for gradual damage, such as poison which deals damage every frame.
For this tutorial we will implement invulnerability time for the Player and Enemy, but your game may ultimately require mixing these approaches. More complicated games may also implement their own damage suppression techniques without using the built-in invulnerability and attack frequency values.
To add invulnerability time to the player, select the Player and change the Invulnerability Time After Damage variable.
Now, the player can only receive damage one time every second. We can see this is the case because the player survives much longer (about 10 seconds) when overlapping the enemy.
Now that our Player can take damage, we can deal damage to enemies using a similar approach. At a high level the approach is:
Create a collision relationship for the Player vs Enemy, setting Subcollision to the Player's MeleeCollision
Remove the default (code generated) damage dealing logic on the collision relationship
Add an event
Implement damage dealing in the event
To create another collision relationship for player vs enemy:
Drag + drop PlayerList onto Enemy list in GameScreen again
Set the Player's Subcollision to MeleeCollision so only the MeleeCollision is considered
Uncheck the damage dealing check box
Click the Add Event button
Now we can modify the damage dealing code in GameScreen.Event.cs:
Notice this code is similar to the collision code used to deal damage to the Player.
You may also want to set the Enemy's Invulnerability Time After Damage to some non-zero value. Keep in mind that doing so will affect the invulnerability time of the enemy from all damage. If you worked through this tutorial by continuing work from previous tutorials, then this code will change the behavior of how enemies take damage.
Notice that the invulnerability time is lower for enemies than for players. You can tune this value to get the right feel for your game.
As implemented now, the player's melee collision deals damage continually to enemies so long as it continues to collide with the enemy when the enemy's invulnerability time expires. Most games with melee attacks require a button to be pushed to perform the attack. When the button is pushed, the attack is only active for a set amount of time, then the attack goes into cooldown.
We can implement this in code by keeping track of when attacks last occurred. Although this has no impact on collision, we can also change the visibility of the weapon. First we will define some variables in code in Player.cs:
Note that the variables AttackDamageDuration
and AttackCooldown
are defined in code. In a full game these variables should be defined in the FRB Editor, but we are defining them in code for the sake of brevity.
Next, we can perform attacks and modify the visibility of the melee shape in CustomActivity by adding the following code:
Please note the property DirectionFacing
and enum HorizontalDirection
will not be available if you are working in a custom, blank project created without using a platformer wizard. In that case you will need to write your own direction detection logic. As an example, assuming MovementActivity method is called in CustomActivity for every frame update and MovementInput has been instantiated in CustomInitialize in Player.cs, it could look like this:
Keep in mind the visibility logic exists only for the sake of visualizing when damage can be dealt - we cannot use visibility to control whether collision occurs because invisible shapes collide just like visible shapes. However, we can use the IsAttackActive
property in the collision event in GameScreen.Event.cs for dealing damage to the enemy, as shown in the following modified code:
By using the IsAttackActive property in the OnPlayerVsEnemyCollided, we could suppress the dealing of damage unless the player is actively attacking. This approach is effective in this simple case, but more complicated games may have multiple types of objects which can receive damage from a player. For example, the game may include destructible obstacles, or it may even support PvP. Therefore, we can use the ModifyDamageDealt event to prevent dealing damage against any type of object unless the attack is happening. We can do this by modifying the Player.cs file as shown in the following code:
Note that this method can be used to modify damage in other scenarios. For example, you may have different attacks (strong vs weak), or multiple attacks in a combo. You are free to define variables in your Player file to support attacks of any complexity. You can then process these variables in ModifyDamageDealt to vary the damage dealt to enemies.
To properly test this approach in our particular example, you might want to remove the IsAttackActive check from the OnPlayerMeleeCollisionVsEnemyCollided event handler to allow all the attacks through (remember the melee collisions are still happening whether the melee rectangle is visible or not) and add the following line to the beginning of HandleDamageReceived delegate handler in Enemy.cs (prevents false flashing when damage is supressed by the Player's damage modification in HandleModifyDamageDealt):
Previous tutorials have discussed the automatic damage dealing logic provided by collision relationships. Full games often need to modify how much damage is dealt depending on a variety of factors such as element weaknesses. Similarly, games may need to respond to damage dealt such as by providing a visual indication of an entity taking damage.
This tutorial shows how to assign events on IDamageable entities to modify damage dealt and to visually indicate that damage has been dealt.
Entities which implement the IDamageable interface are required to have an event named ReactToDamageDealt. This is automatically generated by the FlatRedBall Editor, so the event is available in custom code. This can be used to add custom code to handle an entity receiving damage.
Keep in mind this event occurs after damage checks have been made. It will only be raised if the IDamageable entity has a different team index than the argument IDamageArea, and before the IDamageable is killed (if enough damage is dealt to kill the IDamageable). For example, we can make the enemy flash when it takes damage. First, we'll change the enemy's AxisAlignedRectangleInstance color to red in the FlatRedBall Editor.
Next, we can handle the event in code. In this case we'll use an async
call to set the color to white temporarily, then set it back to red. Keep in mind that for a full project you may want additional logic to allow the enemy to receive damage multiple times without flashing the rectangle back. For the sake of simplicity, we'll ignore this and write simpler code which automatically sets the rectangle back to its original color after a short delay.
Now the Enemy flashes white when taking damage. Note that to test this you may want to disable the enemy shooting logic added in the previous tutorial. Also, you may want to adjust the health of the enemy to take more hits.
The purpose of ReactToDamageDealt is to react to damage visually, or to perform other game logic outside of typical IDamageable logic. Therefore, ReactToDamageDealt should not:
Kill the damage receiver unless there are special circumstances
Deal more damage to the damage receiver
Destroy the IDamageArea (damage dealer) unless there are special circumstances
The ModifyDamageDealt event can be used to perform logic that can change how much damage an entity receives. For example, an enemy may be weak to a particular type of bullet such as an elemental bullet. For this example, we will add an IsFireBullet property to the Bullet, and an IsIceEnemy property to the Enemy. Note that a real game may handle these variants through entity inheritance, but we will add properties through code to keep the tutorial shorter. Add the following code to Bullet.cs:
Next, add the following code to Enemy.cs:
Now we'll modify Player.cs to create fire bullets when pressing left Alt key:
Next, we'll modify the code that creates Enemies in GameScreen.cs to create ice enemies on a right-click:
Now we can create fire enemies and ice bullets by right clicking the mouse and pressing the left Alt key, respectively.
Now that we have two different types of enemies and two different types of bullets, we can write logic in the Enemy's ModifyDamageDealt event in response to the elements:
Now ice enemies die after 5 hits instead of 10 since each bullet will do an effective 20 points of damage.
This tutorial discusses two events:
IDamageable.ReactToDamageReceived
IDamageable.ModifyDamageReceived
The IDamageable and IDamageArea entities provide additional events which can be used to handle damage modification, reaction to taking damage, and death. This is the full list of events provided by each interface:
IDamageable.ModifyDamageReceived - Allows an IDamageable to modify how much damage it takes
IDamageable.ReactToDamageReceived - Allows an IDamageable to respond to taking damage
IDamageable.Died - Allows an IDamageable to respond to dying, such as by playing a sound effect or creating particles
IDamageArea.ModifyDamageDealt - Allows an IDamageArea to modify how much damage it deals
IDamageArea.ReactToDamageDealt - Allows an IDamageArea to respond to dealing damage
IDamageArea.KilledDamageable - Allows an IDamageArea to respond to killing an IDamageable
IDamageArea.RemovedByCollision - Allows an IDamageArea to respond to being removed by collision
This tutorial has used both of the terms *delegate* and *event*, primarily using event to indicate that these are methods which are invoked in response to something happening in the game such as the enemy taking damage. Before moving on to a new topic we should cover some of the technical details of how these delegates work.
Technically, these are delegates and not events. In other words, the IDamageable and IDamageArea interfaces do not define these using the event
keyword. Therefore, these can be invoked externally, allowing the DamageableExtensionMethods.TakeDamage
methods to call these methods as necessary.
If you are familiar with event syntax in C#, then you are probably used to the +=
operator when assigning a handler. This approach works for the delegates defined on the IDamageable and IDamageArea interfaces, but we should take a moment to mention a subtlety related to the ModifyDamageDealt
and ModifyDamageReceived
delegates. These delegates use the Func
type, which means they return the modified damage value. If you use the +=
operator for these two delegates, you may end up dealing or receiving an unexpected amount of damage. The reason for this is because only the last handler added to these two delegates decides how much damage to deal. To understand why this might happen, let's look at an example.
Consider a game with an Enemy entity which has multiple variants (using inheritance). You may have an IsInvulnerable
property on the Enemy, resulting in the following code for handling damage:
You may also decide to have additional logic for an enemy variant which can hide in its shell. If the enemy is in its shell, it would receive half damage. The implementation may look like this:
In this situation, the base CustomInitialize
is called first, then the derived CustomInitialize
. This means that whenever the enemy receives damage, the base HandleDamageReceived
is called first, then the derived HandleDamageReceived
. Unfortunately, when HandleDamageReceived
is called on the derived, the initialDamage
is not modified by the first call. In other words, the initialDamage
will never be affected by the check for IsInvulnerable
because each method returns its value independent of the other. In effect this results in the TurtleEnemy never respecting its IsInvulnerable variable.
Therefore, it's best to never += on any of the Func
delegates - ModifyDamageDealt
and ModifyDamageReceived.
Instead only the =
operator should be used for clarity. In this case, we can resolve the problem by creating a virtual method for handling damage which derived classes can override, as shown in the following code:
Now the derived variant can override the method as shown in the following code:
This tutorial discusses how to handle events to modify damage and display visual feedback to the player when damage is dealt. The next tutorial covers the removal of IDamageAreas and how to implement damage over time.
The removal of IDamageArea entities can sometimes be handled automatically by collision relationshps, but sometimes this removal requires custom code. This tutorial discusses how IDamageArea removal can be customized.
The previous tutorials set up a BulletVsEnemy collision relationship which resulted in the removal of the Bullet when it collides with the enemy. To review, the removal of the bullet required the following:
A collision relationship between BulletList and EnemyList exists in GameScreen
The Enemy and Bullet have different team indexes
The BulletVsEnemy collision relationship has the Destroy Bullet on Damage option checked
Collision relationships with IDamageArea lists always display the Destroy option, even if the collision relationship is not against an IDamageable. This allows IDamageAreas such as Bullets to destroy themselves when colliding with non-IDamageArea objects such as solid collision. For example, a collision relationship between BulletList and SolidCollision can be added to the GameScreen to automatically destroy bullets. This collision relationship will have the Destroy Bullet on Damage option checked by default.
Bullets are now destroyed automatically when they collide with the wall.
Although the automatic removal is handy, we may want to control the removal of bullets. For example, we can modify our game so the bullets do not immediately disappear when hitting an enemy, but instead deal continuous damage over time. First, we'll modify the BulletVsEnemy collision relationship so bullets are not removed automatically by unchecking the Destroy Bullet on Damage option.
Now bullets will continually deal damage to the Enemy, one time per frame. This results in enemies dying very quickly.
As mentioned above, since the Bullet instance does not get destroyed immediately, it continues to live and it deals damage every frame. In this case the game runs at 60 frames per second, dealing 10 damage each frame. Therefore, enemies die after overlapping a bullet after 10 frames (1/6 of a second). We can change this behavior by changing the Bullet's default Seconds Between Damage. For example, if we change the value to 0.5, then bullets deal damage once every half second.
Each enemy automatically keeps track of the last time it took damage, so it will take damage only 2 times per second. To see this clearly, we'll also change the bullet speed in Player.cs so bullets move slower (50 instead of 200 pixels per second).
In this case the removal of our bullets is ultimately handled by collision with SolidCollision. Level 1 (by default) is surrounded by solid collision, so the bullets will eventually collide with a wall and be removed. Of course, this may not always be the case, or your game may require removal of bullets after travelling a certain distance, or after being alive for a certain amount of time. In this case, you can still add logic to the Bullet entity to destroy bullets based on any condition you need. For example, bullets could be destroyed after a few seconds using the following code in Player.cs.
The usage of automatic removal through collision relationships does not prevent you from also adding custom code for entity removal. You can mix these two approaches to achieve the desired entity removal.
This tutorial covers the different ways to remove an IDamageArea entity using automatic removal or manual removal. It also covers how to deal damage over time to IDamageables.
Most of the work needed to perform damage dealing can be done through the FlatRedBall Editor and generated code. Of course, damage dealing can also be managed in code for more flexibility.
This tutorial shows how to set your entities up so they can deal damage.
Damage dealing is done through two interfaces: IDamageable and IDamageArea. As the names suggest, IDamageable entities can take damage and IDamageArea entities can deal damage. Projects which use the standard damage system should mark entities as IDamageable or IDamageArea in the FlatRedBall Editor. Keep in mind that in some cases entities (such as enemies) can be both IDamageable and IDamageAreas.
If you have created a platformer or top-down project using the new project Wizard, then your Player entity already implements the IDamageable interface. You can verify this by selecting the Player entity and checking the Properties tab.
If your player does not already implement IDamageable, you can change this property in the Properties tab to true. Once this value is set, the Player will be ready to be used in the damage system.
Any entity can implement either IDamageable or IDamageArea interfaces - or both. For example, to add a Bullet entity which implements IDamageArea:
Right-click on Entities
Select to add a new entity
Add appropriate collision (such as a Circle)
Check the option for IDamageArea
Keep the Team Index to 0 (Player Team) if the bullet is created by the Player. This can be set on a per-instance basis in code if your game has bullets that can damage enemies and players (such as a shoot-'em-up game).
Your Bullet entity is now marked as a IDamageArea.
The Team Index specified in the new Entity window defines the default team index. The default team index can be overridden in code, but specifying a team index enables the FlatRedBall Editor to generate collision relationships automatically. For example, consider a game which already has a Bullet defined which uses the Team Index of 0 as shown above. If a new Entity is created using Team Index 1, then collision relationships can automatically be created. To test this out, we can add a new entity named Enemy:
Right-click on Entities
Select to add a new entity
Add appropriate collision (such as an AxisAlignedRectangle)
Check the option for IDamageable so the enemy can take damage from the bullet. Also, enemies may deal damage to the player so the enemy could also be marked as IDamageArea in a full game. To keep things simple we'll only check IDamageable for now.
Change the Team Index to 1 (Enemy Team)
Check the Add Opposing Team Index Collision Relationships to GameScreen option.
Notice that the new Entity window provides a preview of the collision relationships that are added to the GameScreen after creating the entity. This lets you check for unintended collision relationship creation. If the list of collision relationships matches what your game needs you can use the Add Opposing Team Index Collision Relationships to GameScreen option. Alternatively, if you would like to create your own collision relationships, you can uncheck this option.
In this case the game now has an IDamageArea entity (Bullet) and IDamageable entity (Enemy) on different Team Indexes. As shown in the new entity window, when the Enemy is added, the FRB creates collision relationships between the Enemy and Bullet.
In the example above, the EnemyVsBullet collision relationship was created automatically because:
Add Opposing Team Index Collision Relationships to GameScreen was checked when creating Enemy
The Enemy has a Team Index (1) different than the already-created Bullet Team Index (0)
The Enemy is IDamageable and the Bullet is IDamageArea
It is possible to manually create collision relationships between IDamageable and IDamageArea lists. For example, the default Bullet Team Index matches the Player Team Index, but your game may allow Enemy instances to shoot bullets too.
In this case we can still create a collision relationship between the PlayerList and BulletList. If the PlayerList is drag+dropped on the BulletList, a collision relationship is created with the damage-related checkboxes checked.
This guide shows how to create entities which are IDamageable and IDamageAreas. Once these entities are created, collision relationships are either automatically created when the entity is created, or they can be added manually with a drag+drop operation. The next guide will show the automatic damage-dealing logic provided by collision relationships and the Team Index variable functionality.