Unity: цикл анимации в ожидании перехода состояния - PullRequest
0 голосов
/ 15 июня 2019

Ранее я писал этот вопрос в игре Dev 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, которое обрабатывает переходы между последовательными атаками, вводит указанное целое число в аниматоре и затем изменяет мое состояние на атаку. После этого я создал цикл while, который продолжается до конца установленного временного интервала, в течение которого игрок сможет нажать то же самое кнопку атаки, чтобы связать комбо. Если этого не происходит, состояние и целочисленный параметр сбрасываются.

Проблема, с которой я сталкиваюсь, заключается в том, что, хотя игрок может фактически выполнять комбо со второй атакой, в течение всего интервала, в течение которого активна первая анимация, он продолжает цикл. Кроме того, я заметил, что вторая анимация не достигает конца, похоже, она останавливается, как только закончится заданный мною интервал.

Обновление: Это скриншот окна моего аниматора: enter image description here

Переходы любого состояния -> 1stAttack и 1stAttack -> 2ndAttack обрабатываются одним и тем же целочисленным параметром, comboSequence, который обычно устанавливается равным 0, 1 для 1stAttack и 2 для второго. Я заметил, что переход любого состояния -> 1stAttack запускается несколько раз, когда я нажимаю кнопку удара, в соответствии с проблемой зацикливания, с которой я сталкиваюсь.

Я пробовал пару вещей, например, используя нормальные функции вместо сопрограммы, но таким образом я не понимаю, почему существуют проблемы с состояниями перечислений, также я думаю, что в долгосрочной перспективе этот подход будет более модульным и настраиваемым. Я чувствую, что упускаю что-то тривиальное, но я не понимаю что, и это было какое-то время, поэтому любая помощь будет высоко ценится!

...