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);
}
}
}