Multi-Floors Grenade Visuals

While the fixes for ShootAction should also generally work for GrenadeAction, I found a lot of trouble getting the visuals to behave in GrenadeProjectile.

The solution I settled on was to have two AnimationCurves and select the appropriate one depending on if the GrenadeProjectile is travelling from a lower floor to a higher floor or vice versa. High-To-Low AnimationCurve begins at a height of 1 and ends at a height of 0 while the Low-To-High is the opposite; starting at 0 and ending at 1. This is very useful because it allows multiplying these start and end points by any value with predictable results, whereas a starting/ending point of 0.6, as was originally used, will drastically change the world position depending on what height the GrenadeProjectile is instantiated at.

    [SerializeField] AnimationCurve _arcYAnimationCurve;    // Default AnimationCurve, start value is 1 & end value is 0
    [SerializeField] AnimationCurve _arcYHighToLow;         // Start value is 1 & end value is 0
    [SerializeField] AnimationCurve _arcYLowToHigh;         // Start value is 0 & end value is 1

With the two new AnimationCurves set in the inspector, I then have the following values to calculate the right positions:

    Vector3 _targetPosition;
    float _totalDistance;
    Vector3 _positionXZ;
    float _floorDifferential;   // The difference in world position height between the starting GridPosition floor and the target GridPosition floor
    float _heightDifferential;  // The difference in world position height between this projectile's transform.position.y and the target GridPosition floor
    float _targetFloorHeight;   // The target GridPosition's world position height
    float _originFloorHeight;   // The starting GridPosition's world position height
    float _reachedTargetDistance = 0.2f;
    bool _targetPositionIsHigher = false;

Setup() is configured like this:

public void Setup(GridPosition targetGridPosition, GridPosition originGridPosition, Action onGrenadeBehaviourComplete)
    {
        _positionXZ = transform.position;

        _floorDifferential = Mathf.Abs(targetGridPosition.floor * LevelGrid.FLOOR_HEIGHT);
        _heightDifferential = Mathf.Abs(transform.position.y - (targetGridPosition.floor * LevelGrid.FLOOR_HEIGHT));


        _targetFloorHeight = targetGridPosition.floor * LevelGrid.FLOOR_HEIGHT;
        _originFloorHeight = originGridPosition.floor * LevelGrid.FLOOR_HEIGHT;


        if(targetGridPosition.floor > originGridPosition.floor)
        {
            _arcYAnimationCurve = _arcYLowToHigh;
            _targetPositionIsHigher = true;
        }
        if(targetGridPosition.floor < originGridPosition.floor)
        {
            _arcYAnimationCurve = _arcYHighToLow;
            _targetPositionIsHigher = false;
        }

        this._onGrenadeBehaviourComplete = onGrenadeBehaviourComplete;
        _targetPosition = LevelGrid.Instance.GetWorldPosition(targetGridPosition);

        
        _totalDistance = Vector3.Distance(_positionXZ, _targetPosition);
    }

To keep Update compact, I split the code there into two methods depending on the _targetPositionIsHigher bool:

    void Update()
    {
        if(_targetPositionIsHigher)
        {
            LowToHighArcMovement();
        }
        else
        {
            HighToLowArcMovement();
        }

        if (Vector3.Distance(_positionXZ, _targetPosition) < _reachedTargetDistance)
        {
            Explode();
        }
    }

    void LowToHighArcMovement()
    {
        Vector3 moveDirection = (_targetPosition - _positionXZ).normalized;

        _positionXZ += _moveSpeed * Time.deltaTime * moveDirection;

        float distance = Vector3.Distance(_positionXZ, _targetPosition);
        float distanceNormalized = 1 - distance / _totalDistance;

        float positionY;

        positionY = (_arcYLowToHigh.Evaluate(distanceNormalized) * _floorDifferential) + (_heightDifferential + _originFloorHeight);

        transform.position = new Vector3(_positionXZ.x, positionY, _positionXZ.z);
    }

    void HighToLowArcMovement()
    {
        Vector3 moveDirection = (_targetPosition - _positionXZ).normalized;

        _positionXZ += _moveSpeed * Time.deltaTime * moveDirection;

        float distance = Vector3.Distance(_positionXZ, _targetPosition);
        float distanceNormalized = 1 - distance / _totalDistance;

        float positionY;

        positionY = (_arcYAnimationCurve.Evaluate(distanceNormalized) * _heightDifferential) + _targetFloorHeight;

        transform.position = new Vector3(_positionXZ.x, positionY, _positionXZ.z);
    }

Hopefully it’s clear in the methods that they use different values to multiply by and add to their respective AnimationCurves to get the same desired result: The GrenadeProjectile’s height is set to its starting transform.position.y and its ending point is the target GridPosition’s transform.position.y
This should work the same no matter what the differences are in world height and GridPosition.floor

Privacy & Terms