Grenade Visuals

Hello, I took a few minutes to implement AddExplosionForce rather than using the random deaths of the Ragdolls.

First I created a new class (above the GrenadeProjectile class) to hold the explosion info from the grenade:

public class ExplosionDetails
{
    public float BlastForce { get; set; }
    public Vector3 Origin { get; set; }
    public float Radius { get; set; }
    public float UpwardModifier { get; set; }
}

Then I added it in to the damage:

var explosionDetails = new ExplosionDetails
{
    BlastForce = explosionForce,
    Origin = _targetPosition,
    Radius = explosionRadius,
    UpwardModifier = explosionUpwardsModifier
};
targetUnit.Damage(damage, explosionDetails);

Then I passed it through the unit Damage method:

public void Damage(int damageAmount, ExplosionDetails explosionDetails)
{
     _healthSystem.Damage(damageAmount, explosionDetails);
}

Then in the HealthSystem I added ExplosionDetails to the OnDeath event and passed it through Damage and into Die:

public event EventHandler<ExplosionDetails> OnDeath;


public void Damage(int damageAmount, ExplosionDetails explosionDetails)
{
    health -= damageAmount;
    if (health < 0)
    {
        health = 0;
    }

    OnDamaged?.Invoke(this, EventArgs.Empty);

    if (health == 0)
    {
        Die(explosionDetails);
    }
}

private void Die(ExplosionDetails explosionDetails)
{
    OnDeath?.Invoke(this, explosionDetails);
}

Then I changed the RagdollSpawner to apply the Explosion Force:

private void HealthSystem_OnDeath(object sender, ExplosionDetails e)
{
    var ragdoll = Instantiate(ragdollPrefab, transform.position, transform.rotation);
    if (e != null)
    {
        Rigidbody[] rigidbodies = ragdoll.GetComponentsInChildren<Rigidbody>();
        foreach (Rigidbody rigidbody in rigidbodies)
        {
            rigidbody.AddExplosionForce(e.BlastForce, e.Origin, e.Radius, e.UpwardModifier);
        }
    }
}

And then finally in the ShootAction when the Damage method is called, I passed null for the ExplosionDetails:

private void Shoot()
{
    var args = new OnShootEventArgs
    {
        targetUnit = _targetUnit,
        shootingUnit = _unit
    };
    OnShoot?.Invoke(this, args);
    OnAnyShoot?.Invoke(this, args);
    _targetUnit.Damage(40, null);
}

I also serlialized all of the fields for the ExplosionDetails (other than origin, of course since that comes from the blast location) in the GrenadeProjectile class so one could easily change the values. My favorite ended up being:
BlastForce = 400,
Radius = 50,
UpwardModifier = 5

I don’t entirely understand why the radius had to be so dramatically different from the radius for damage, but oh well, it looks great and I guess that’s all that matters!

I hope this was helpful to someone and thanks for the course, Code Monkey!!!

4 Likes

That’s a nice addition, good job!

1 Like

I did a similar thing, but with a slight difference. I passed the damage info in for shooting as well, and when applying a force considered the target of the force to be a point 1.75f up the target, which is the same height the bullet impacts as well. So the grenade (origin on the floor) pushes the victims up a bit as well as away because that’s the direction of the impact, and the regular shooting just pushes the target backwards rather than lifting them incongruously off their feet.

I see there’s a sword attack coming up soon, and I’m expecting that doing the same thing but having the damage come in from a higher point instead will also result in a satisfying and differentiated ragdoll…

(I suppose that were I to expand upon this game basis in the future with different sized units it would be wise to move these constants for over-the-shoulder height and shoot height and hit height and so on into serialised fields so they can be set up more easily for different types of enemy, of course…)

public class UnitRagdollSpawner : MonoBehaviour
{
    [SerializeField] private Transform _ragdollPrefab;
    [SerializeField] private Transform _originalRootBone; 

    private HealthSystem _healthSystem;

    private Unit _shootingUnit; /
    private Unit _unit; 

    private void Awake()
    {
        _healthSystem = GetComponent<HealthSystem>();       
        _unit= GetComponent<Unit>();
    }

    private void Start()
    {
        _healthSystem.OnDead += HealthSystem_OnDead; /

        ShootAction.OnAnyShoot += ShootAction_OnAnyShoot;
    }

    private void ShootAction_OnAnyShoot(object sender, ShootAction.OnShootEventArgs e)
    {
        if(e.targetUnit== _unit) // Если целью является этот юнит , то сохраним того кто по нам стрелял
        {
            _shootingUnit = e.shootingUnit;
        }        
    }
        

    private void HealthSystem_OnDead(object sender, System.EventArgs e)
    {
        Transform ragdollTransform = Instantiate(_ragdollPrefab, transform.position, transform.rotation); 

        UnitRagdoll unitRagdoll = ragdollTransform.GetComponent<UnitRagdoll>(); 
               
        unitRagdoll.Setup(_originalRootBone, _shootingUnit);
    }
}
public class UnitRagdoll : MonoBehaviour 
{
    [SerializeField] private Transform _ragdollRootBone;

 
    public void Setup(Transform originalRootBone, Unit shootingUnit) 
    {
        MatchAllChildrenTransform(originalRootBone, _ragdollRootBone);

        Vector3 explosionPosition; 

        BaseAction selectedAction = UnitActionSystem.Instance.GetSelectedAction(); 
        
        switch (selectedAction)
        {
            default:
            case GrenadeAction grenadeAction:
                
                Vector3 randomDir = new Vector3(Random.Range(-1f, 1f), 0, Random.Range(-1f, 1f)); 
                explosionPosition = transform.position + randomDir;
                break;

            case ShootAction shootAction:
                
                Vector3 directionKeeler = (shootingUnit.transform.position - transform.position).normalized; 
                explosionPosition = transform.position + directionKeeler;                
                break;
        }

        ApplyExplosionToRagdoll(_ragdollRootBone, 300f, explosionPosition, 10f);
        
    }

Privacy & Terms