Bullet deals damage on hit

The ShootAction likely should not get to decide whether a hit has landed and damage applied. It’s just a trigger behavior and should literally be “fire and forget”. In order to keep the ShootAction separated from the damage logic, I removed the targetUnit.Damage(40) call and instead have the Bullet call Damage() on the Unit when it hits (calling it Hit would probably be more accurate).

If we were to use colliders for this attack system then when the Bullet and Unit collide the two objects would interact in some way. I’d think the authority for what happens is ideally worked out between those two objects. Since we don’t use colliders and a “hit” is determined by a Bullet position check, I moved more of that logic into the BulletProjectile.

In the UnitAnimator we were already finding the position of the target unit and use that position check for the bullet trajectory. Since the bullet needs to know where it is on each Update frame to eventually decide “I hit something”, allowing it to fully control that trajectory seemed more appropriate. Although in this game the target unit doesn’t move positions while a projectile is in flight, the projectile knowing about the Unit on each Update frame seems potentially useful (heat seeking projectiles… you can’t run from me!). Also the bullet represents more than just visuals in our game world (if the bullet were invisible with no Trail it would still represent an object interaction), so it should have additional data and behavior affixed.

As such, I felt the bullet keeping track of its own damage value and type would be ideal. For example derivative classes could be created for regular rounds, incendiary rounds, armor piercing, etc. Ultimately the course already has things coded such that the bullet detects when it hits the target Unit (a grid position) I’ve just made it now tell the Unit “I hit you, here is my information” e.g. Damage(bulletType, bulletDamage) . What is passed into that Damage call could vary based on your implementation needs, it could even be the entire bullet object. In this case I scoped down the data to type and damage amount (string for the bullet type is bad, just a placeholder for now).

Instead of being told “you received 40 damage”, this implementation allows the Unit to decide how it reacts to Damage calls. Perhaps it has a shield activated that reduces damage, maybe resistances to this damage type, a temporary buff that leads to a dodge attempt, etc. At the end of the day, the Unit isn’t just told it received damage, but rather it was hit by “a thing” that might deal damage.

Thoughts?

UnitAnimator.cs

private void ShootAction_OnShoot(object sender, ShootAction.OnShootEventArgs e)
    {
        animator.SetTrigger(Shoot);
        
        // Store the target unit for when the bullet is instantiated.
        targetUnit = e.targetUnit;
    }

    // Called by the animation event so that the bullet is created at the correct time
    public void InstantiateBullet()
    {
        Transform bulletProjectileTransform = Instantiate(bulletProjectilePrefab, shootPointTransform.position, Quaternion.identity);
        BulletProjectile bulletProjectile = bulletProjectileTransform.GetComponent<BulletProjectile>();
        bulletProjectile.Setup(targetUnit);
    }

BulletProjectile.cs

public class BulletProjectile : MonoBehaviour
{
    const string bulletType = "bullet";
    
    [SerializeField] private TrailRenderer trailRenderer;
    [SerializeField] private Transform bulletHitVfxPrefab;
    [SerializeField] private int bulletDamage = 40;
   
    Vector3 targetPosition;
    private Unit targetUnit;
    
    public void Setup(Unit targetUnit)
    {
        this.targetUnit = targetUnit;
        targetPosition = targetUnit.GetWorldPosition();
        
        // Add some visual variety by moving towards random position on the upper half of target Unit
        BoxCollider boxCollider = targetUnit.GetComponent<BoxCollider>();
        float halfHeight = boxCollider.size.y / 2f;
        targetPosition.y += halfHeight + Random.Range(0f, halfHeight);
    }

    private void Update()
    {
        float moveSpeed = 200f;
        var toTarget = targetPosition - transform.position;
        var dist = toTarget.magnitude;

        if (dist > 0)
        {
            var move = toTarget.normalized * (moveSpeed * Time.deltaTime);
            if (move.magnitude > dist)
            {
                move = toTarget;
            }
            transform.position += move;
        }
        else
        {
            transform.position = targetPosition;
            trailRenderer.transform.parent = null;
            
            targetUnit.Damage(bulletType, bulletDamage);
            
            Destroy(gameObject);
            Instantiate(bulletHitVfxPrefab, targetPosition, Quaternion.identity);
        }
    }
}
3 Likes

Absolutely correct.

If you introduce different types of ammo, and also some different types of damage resistances, then the bullets would have to be aware of what damage type they deal and how much of it. Modifiers from weapon stats are something that could be applied in the bullet’s Setup(), so you could also have some weapon/bullet matching logic, where different weapons may have some shared “ammo class” but various sub-types of ammo are better or worse for a certain weapon.

Eventually it should be the health system’s responsibility to take in all information about what hit some unit and what resistances the target unit might have, and from that calculate the final damage value. Any type of buff or other effect could just be spawned into the health system, too.

If you wanted to, the health system would also take care of time based effects (as much as the concept of “time” exists in a strictly turn-based world, so any turn woud be a tick of the “clock”).
You want a poison effect or other damage-over time? Easily done, just subscribe to OnTurnChanged and apply each of those effects.

1 Like

Privacy & Terms