Unity:在等待状态转换时进行动画循环

时间:2019-06-15 19:05:21

标签: c# unity3d state-machine coroutine

我以前曾在gameDev SE上发布过此问题,但没有运气,因此,我试图查看是否可以在这里找到帮助。

我的动画制作人遇到了一些麻烦。具体来说,我正在尝试建立一些代码来处理组合序列,并且为此,我使用了协程,这些协程利用了动画师中动画给出的状态机。这是我的脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityEngine.SceneManagement;

/*enum PlayerState is a list of states that can be taken by the player character. They will be used to implement a finite state machine-like
behavior with the actions it can take*/
public enum PlayerState {
    walk,
    attack,
    interact,
    dash,
    stagger
}

public class Player_Base : MonoBehaviour {


    //Basic parameters
    Rigidbody2D myRigidBody; //These are to call the components of the Player GameObject
    public static Transform playerPos;
    public PlayerState currentState;
    Animator myAnimator;
    public HealthManager myStatus;
    private bool isAlive = true;

    //Sorting layers parameters
    public CapsuleCollider2D myFeet;
    public static float playerVertPos; // Need this to sort other objects layers in another script.

    //Movement parameters
    [SerializeField] float moveSpeed = 3f; // use [serfiel] or public in order to have something that you can modify from Unity UI
    public Vector2 moveInput;
    private bool isMoving;//Implementing the state machine and the *blend trees*, you need only to define one bool for all animations of a kind (eg walking anims)

    //Combat parameters
    private int comboCounter = 0;
    private float comboTimer = 0;

    //dash parameters
    [SerializeField] float dashTimeMax = 1f;
    [SerializeField] float dashTime = 0;
    [SerializeField] float dashPush = 0.001f;
    [SerializeField] float dashSpeed = 10f;

    // Use this for initialization
    void Start()
    {
        currentState = PlayerState.walk;//Initial default state of the player
        myRigidBody = GetComponent<Rigidbody2D>(); /*the getcomp looks for the related component in the <> and uses it in the code*/
        myFeet = GetComponent<CapsuleCollider2D>();
        myAnimator = GetComponent<Animator>();
        myAnimator.SetFloat("MoveX", 0);//If i do not set a default values for these, if player attacks without moving first, all colliders will activate and will hit all around him
        myAnimator.SetFloat("MoveY", -1);
        myStatus = GameObject.FindObjectOfType<HealthManager>();
    }

    // Update is called once per frame
    void Update()
    {
        playerVertPos = myFeet.bounds.center.y;
        moveInput = Vector2.zero;/*getaxis and getaxisraw register the input of the axes and outputs +1 or -1 according to the axis direction*/
        moveInput.x = Input.GetAxisRaw("Horizontal");
        moveInput.y = Input.GetAxisRaw("Vertical");
        if (!isAlive)
        {
            return;
        }
        else {
            if (currentState == PlayerState.walk)//It will consider walking only when in that state, this means that if it is attacking for instance,
            //it needs to change its state. Good for compartimentalization of the actions (otherwise I could have changed the direction of the attacks)
            {
                if (moveInput != Vector2.zero)//This if statement is such that if there is no new input to update the movement with, the last (idle) animation 
                //will remain, so if you go right and stop, the player keeps facing right
                {
                    Move();
                    myAnimator.SetFloat("MoveX", moveInput.x);
                    myAnimator.SetFloat("MoveY", moveInput.y);
                    myAnimator.SetBool("isMoving", true);
                }
                else {
                    myAnimator.SetBool("isMoving", false);
                }
            }

            //Attack inputs
            if (Input.GetKeyDown(KeyCode.Mouse0) && currentState != PlayerState.attack)//second clause because i do not want to indefinitely attack every frame
            {
                StartCoroutine(FirstAttack());
            }

            if (Input.GetKeyDown(KeyCode.Space) && currentState != PlayerState.dash)
            {
                StartCoroutine(Dashing());
            }

            DeathCheck();//check if player is still alive

        }
    }

    public void Move()
    {
        moveInput.Normalize();
        myRigidBody.MovePosition(myRigidBody.position + moveInput * moveSpeed * Time.deltaTime);
        //If i want to work with the velocity vector: i have to use rb.velocity, not just taking the xplatinput times movespeed
    }

    public void MoveOnAnimation(int xMove, int yMove, float displacement)
    {
        moveInput.x = xMove;
        moveInput.y = yMove;
        moveInput.Normalize();
        myRigidBody.MovePosition(myRigidBody.position + moveInput * displacement * Time.deltaTime);
    }



    private IEnumerator FirstAttack() {

        //Start Attack
        comboCounter = 1;
        myAnimator.SetInteger("comboSequence", comboCounter);
        currentState = PlayerState.attack;



        yield return new WaitForSeconds(AttackTemplate.SetDuration(0.6f) - comboTimer);//Problem: if i reduce the time below the animation time of the second animation, the second animation won't go untile the end
        comboTimer = AttackTemplate.SetComboTimer(0.4f);


        //if combo not triggered:
        while (comboTimer >= 0)
        {

            Debug.Log(comboTimer);
            comboTimer -= Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.Mouse0))
            {
                Debug.Log("Chained");
                StopCoroutine(FirstAttack());
                StartCoroutine(SecondAttack());                
            }
            yield return null;
        }
        comboCounter = 0;
        myAnimator.SetInteger("comboSequence", comboCounter);
        currentState = PlayerState.walk;

    }

    private IEnumerator SecondAttack()
    {

        comboCounter = 2;
        myAnimator.SetInteger("comboSequence", comboCounter);
        currentState = PlayerState.attack;
        yield return null;

        //if combo not triggered:
        yield return new WaitForSeconds(AttackTemplate.SetDuration(0.9f));
        comboCounter = 0;
        myAnimator.SetInteger("comboSequence", comboCounter);

        currentState = PlayerState.walk;

    }

    private void Dash()
    {
        if (dashTime >= dashTimeMax)
        {
            dashTime = 0;
            myRigidBody.velocity = Vector2.zero;
            currentState = PlayerState.walk;
        }
        else
        {
            currentState = PlayerState.dash;
            dashTime += Time.deltaTime;
            moveInput.Normalize();
            Vector2 lastDirection = moveInput;
            myRigidBody.velocity = lastDirection * dashSpeed;
        }
    }

    private IEnumerator Dashing()
    {

        currentState = PlayerState.dash;
        for (float timeLapse = 0; timeLapse < dashTime; timeLapse = timeLapse + Time.fixedDeltaTime)
        {
            moveInput.Normalize();
            Vector2 lastDirection = moveInput;
            myRigidBody.velocity = lastDirection * dashSpeed;
        }
        yield return null;
        myRigidBody.velocity = Vector2.zero;
        currentState = PlayerState.walk;

    }

    private void DeathCheck() //if the player health reaches 0 it will run
    {
        if (HealthManager.health == 0) {
            isAlive = false; // this is checked in the update, when false disables player inputs
            myRigidBody.constraints = RigidbodyConstraints2D.FreezePosition; // if i don't lock its position, the last bounce with the enemy pushes the player towards inifinity
            myAnimator.SetTrigger("death");//triggers the death animation
            StartCoroutine(LoadNextScene());
        }
    }

    [SerializeField] float LevelLoadDelay = 5f;
    [SerializeField] float LevelSlowMo = 1f;
    IEnumerator LoadNextScene()
    {

        Time.timeScale = LevelSlowMo;
        yield return new WaitForSecondsRealtime(LevelLoadDelay);
        Time.timeScale = 1f;

        var CurrentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        SceneManager.LoadScene(CurrentSceneIndex + 1);
    }


}

我基本上要做的是使用枚举定义玩家状态,并在输入时,如果玩家尚未处于攻击状态,请执行攻击。一旦FirstAttack()被调用,它将首先更新一个整数comboCounter,该整数处理连续攻击之间的转换,在动画器中输入该整数,然后将我的状态更改为Attack。此后,我创建了一个while循环,该循环一直持续到确定的时间间隔结束为止,在这段时间间隔内,玩家可以按下 same 攻击按钮来连击连击。如果没有发生,则将重置state和integer参数。

我面临的问题是,尽管玩家实际上可以在第二次攻击中执行连击,但在第一个动画处于活动状态的所有时间间隔内,它仍在循环播放。此外,我注意到第二个动画没有到达结尾,似乎一旦我之前设置的间隔结束就停止了。

更新:这是我的动画师窗口的屏幕截图: enter image description here

转换任何状态-> 1stAttack和1stAttack-> 2ndAttack都由相同的整数参数comboSequence处理,该参数通常设置为0,1stAttack设置为1,第二个设置为2。我观察到,每当我按下点击按钮时,任何状态-> 1stAttack的转换都会多次触发,这与我所面临的循环问题一致。

我已经尝试了一些方法,例如使用常规函数代替协程,但是这样,我不明白为什么枚举状态存在问题,而且我认为从长远来看,这种方法将会更加模块化和可定制。我觉得自己缺少一些琐碎的东西,但是我不知道是什么,现在已经有一段时间了,所以任何帮助将不胜感激!

0 个答案:

没有答案