Adding force - attack on mouse click

Hi,

I have been struggling with TryApplyForce in my implementation of combat that uses mouse click attacks rather than click and hold logic.

The issues is: when I set the forceTime to values below 0.5f its working correctly, however when that values is set to something like 0.8 then the force is added after the whole animation ends rather than at the specified time.

Spent hours and wasn’t able to find details.

Player Attacking State:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAttackingState : PlayerBaseState
{
    private Attack attack;

    private float previousFrameTime;

    private float normalizedTime;

    private bool comboFailed;

    private bool alreadyAppliedForce;

    public PlayerAttackingState(
        PlayerStateMachine stateMachine,
        int AttackIndex
    ) :
        base(stateMachine)
    {
        attack = stateMachine.Attacks[AttackIndex];
    }

    public override void Enter()
    {
        Attack();
        stateMachine.InputReader.AttackEvent += TryComboAttack;
    }

    private void Attack()
    {
        stateMachine
            .Animator
            .CrossFadeInFixedTime(attack.AnimationName,
            attack.TransitionDuration);
    }

    public override void Tick(float deltaTime)
    {
        Move (deltaTime);
        FaceTarget();
        normalizedTime = GetNormalizedTime();
        if (normalizedTime < 1f)
        {
            if (normalizedTime >= attack.ForceTime)
            {
                TryApplyForce();
            }
        }
        else
        {
            SetPreviousState();
        }
    }

    private void TryComboAttack()
    {
        if (comboFailed) return;
        if (attack.ComboStateIndex == -1)
        {
            return;
        }
        if (normalizedTime < attack.ComboAttackTime)
        {
            comboFailed = true;
            return;
        }
        if (
            normalizedTime > attack.ComboAttackTime &&
            normalizedTime < attack.ComboAttackTime + attack.ComboWindow
        )
        {
            stateMachine
                .SwitchState(new PlayerAttackingState(stateMachine,
                    attack.ComboStateIndex));
        }
        else
        {
            comboFailed = true;
        }
    }

    private void TryApplyForce()
    {
        if (alreadyAppliedForce)
        {
            return;
        }
        alreadyAppliedForce = true;
        stateMachine
            .ForceReceiver
            .AddForce(stateMachine.transform.forward * attack.Force);
    }

    private void SetPreviousState()
    {
        if (stateMachine.Targeter.CurrentTarget != null)
        {
            stateMachine.SwitchState(new PlayerTargetingState(stateMachine));
        }
        else
        {
            stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
        }
    }

    public override void Exit()
    {
        stateMachine.InputReader.AttackEvent -= TryComboAttack;
    }

    private float GetNormalizedTime()
    {
        AnimatorStateInfo currentInfo =
            stateMachine.Animator.GetCurrentAnimatorStateInfo(0);
        AnimatorStateInfo nextInfo =
            stateMachine.Animator.GetNextAnimatorStateInfo(0);

        if (stateMachine.Animator.IsInTransition(0) && nextInfo.IsTag("Attack"))
        {
            return nextInfo.normalizedTime;
        }
        else if (
            !stateMachine.Animator.IsInTransition(0) &&
            currentInfo.IsTag("Attack")
        )
        {
            return currentInfo.normalizedTime;
        }
        else
        {
            return 0;
        }
    }
}

Force Receiver

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ForceReceiver : MonoBehaviour
{
    private float verticalVelocity;

    private Vector3 impact;

    private Vector3 dampingVelocity;

    [SerializeField]
    private CharacterController controller;

    [SerializeField]
    private float drag = 0.3f;

    public Vector3 Movement => impact + Vector3.up * verticalVelocity;

    private void Update()
    {
        if (verticalVelocity < 0 && controller.isGrounded)
        {
            verticalVelocity = Physics.gravity.y * Time.deltaTime;
        }
        else
        {
            verticalVelocity += Physics.gravity.y * Time.deltaTime;
        }

        impact =
            Vector3.SmoothDamp(impact, Vector3.zero, ref dampingVelocity, drag);
    }

    public void AddForce(Vector3 force)
    {
        impact += force;
    }
}

PlayerBaseStats

using UnityEngine;

public abstract class PlayerBaseState : State
{
    protected PlayerStateMachine stateMachine;

    public PlayerBaseState(PlayerStateMachine stateMachine)
    {
        this.stateMachine = stateMachine;
    }

    protected void Move(Vector3 motion, float deltaTime)
    {
        stateMachine
            .CharacterController
            .Move((motion + stateMachine.ForceReceiver.Movement) * deltaTime);
    }

    protected void Move(float deltaTime)
    {
        Move(Vector3.zero, deltaTime);
    }

    protected void FaceTarget()
    {
        if (stateMachine.Targeter.CurrentTarget == null) return;

        Vector3 lookPosition =
            stateMachine.Targeter.CurrentTarget.transform.position -
            stateMachine.transform.position;
        lookPosition.y = 0f;
        stateMachine.transform.rotation = Quaternion.LookRotation(lookPosition);
    }
}

Video:

Your Combo window on these attacks is from .5 to .7f (combo time .5, combo window .2)
Your Force time is .8…
If you make a combo, it will never reach the force time.
I would suggest making your Combo Time at just a moment after the force time, which will also serve as a visual indicator that this is the time to click to make the combo.

Hi Brian,

Thanks for your reply (again!, I really appreciate your work here).
I did change the values. My Animations have hit events so I have calculated when they should be played based on that now which is roughly 35% for the first two and then 40% in on the last one but it still doesnt feel right.

In the lecture it was said that the animation speed changes do no affect when we apply the force since we use normalized time.

Full speed:

When I’ve slowed down the animation to 0.1 we can see clearer on when the force is applied.

1st animation is applied correctly but the second one is starting immediately when animation starts (its super hard to combo when its so slow lol)

I can upload the project for you or give you a link to a github repo where I keep it if that helps

I got it figured out.

Basically what was happening the moment i called TryCombo attack on the first frame the animator was not in transition yet so was returning normalizedTime of the previous animation instead of the new one already.

I have added another check in the code to cover that case:

private float GetNormalizedTime()
    {
        AnimatorStateInfo currentInfo =
            stateMachine.Animator.GetCurrentAnimatorStateInfo(0);

        AnimatorStateInfo nextInfo =
            stateMachine.Animator.GetNextAnimatorStateInfo(0);

        if (
            stateMachine.Animator.IsInTransition(0) &&
            nextInfo.IsName(attack.AnimationName)
        )
        {
            return nextInfo.normalizedTime;
        }
       // THIS BELOW CHECK HAS BEEN ADDED TO CHECK IF WE'RE ALREADY IN NEW STATE
        else if (!currentInfo.IsName(attack.AnimationName))
        {
            return nextInfo.normalizedTime;
        }
        else if (
            !stateMachine.Animator.IsInTransition(0) &&
            currentInfo.IsTag("Attack")
        )
        {
            return currentInfo.normalizedTime;
        }
        else
        {
            return 0;
        }
    }

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms