Damage Events

Introduction

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.

ReactToDamageDealt Event

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.

private void CustomInitialize()
{
    this.ReactToDamageReceived += HandleDamageReceived;
}

private async void HandleDamageReceived(decimal arg1, IDamageArea arg2)
{
    var colorBefore = AxisAlignedRectangleInstance.Color;
    AxisAlignedRectangleInstance.Color = Color.White;
    await TimeManager.DelaySeconds(.1);
    AxisAlignedRectangleInstance.Color = colorBefore;
}

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

ModifyDamageDealt Event

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:

bool isFireBullet;
public bool IsFireElement
{
    get => isFireBullet;
    set
    {
        isFireBullet = value;
        if(value)
        {
            this.CircleInstance.Color = Color.Red;
        }
    }
}

Next, add the following code to Enemy.cs:

bool isIceEnemy;
public bool IsIceEnemy
{
    get => isIceEnemy;
    set
    {
        isIceEnemy = value;
        if(isIceEnemy) 
        {
            AxisAlignedRectangleInstance.Color = Color.Blue;
        }
    }
}

Now we'll modify Player.cs to create fire bullets when pressing left Alt key:

private void CustomActivity()
{
    if(InputManager.Keyboard.KeyPushed(Microsoft.Xna.Framework.Input.Keys.Space))
    {
        var bullet = Factories.BulletFactory.CreateNew(this.Position);
        bullet.YVelocity = 200;
    }
    if (InputManager.Keyboard.KeyPushed(Microsoft.Xna.Framework.Input.Keys.LeftAlt))
    {
        var bullet = Factories.BulletFactory.CreateNew(this.Position);
        bullet.IsFireElement = true;
        bullet.YVelocity = 200;
    }
}

Next, we'll modify the code that creates Enemies in GameScreen.cs to create ice enemies on a right-click:

void CustomActivity(bool firstTimeCalled)
{
    var cursor = GuiManager.Cursor;

    if(cursor.PrimaryPush)
    {
        var cursorPosition = cursor.WorldPosition;
        Factories.EnemyFactory.CreateNew(cursorPosition.X, cursorPosition.Y);
    }
    if (cursor.SecondaryPush)
    {
        var cursorPosition = cursor.WorldPosition;
        var enemy = Factories.EnemyFactory.CreateNew(cursorPosition.X, cursorPosition.Y);
        enemy.IsIceEnemy = true;
    }
}

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:

private void CustomInitialize()
{
    this.ReactToDamageReceived += HandleDamageReceived;
    this.ModifyDamageReceived += HandleModifyDamageReceived;
}

private decimal HandleModifyDamageReceived(decimal damage, IDamageArea arg2)
{
    var asBullet = arg2 as Bullet;

    if(asBullet != null && asBullet.IsFireElement && this.IsIceEnemy)
    {
        // for ice vs fire, deal double damage:
        damage *= 2;
    }

    return damage;
}

Now ice enemies die after 5 hits instead of 10 since each bullet will do an effective 20 points of damage.

Additional Events

This tutorial discusses two events:

  • IDamageable.ReactToDamageReceived

  • IDamageable.ModifyDamageReceived