Ранее я писал этот вопрос в игре 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](https://i.stack.imgur.com/7ux7q.png)
Переходы любого состояния -> 1stAttack и 1stAttack -> 2ndAttack обрабатываются одним и тем же целочисленным параметром, comboSequence
, который обычно устанавливается равным 0, 1 для 1stAttack и 2 для второго. Я заметил, что переход любого состояния -> 1stAttack запускается несколько раз, когда я нажимаю кнопку удара, в соответствии с проблемой зацикливания, с которой я сталкиваюсь.
Я пробовал пару вещей, например, используя нормальные функции вместо сопрограммы, но таким образом я не понимаю, почему существуют проблемы с состояниями перечислений, также я думаю, что в долгосрочной перспективе этот подход будет более модульным и настраиваемым. Я чувствую, что упускаю что-то тривиальное, но я не понимаю что, и это было какое-то время, поэтому любая помощь будет высоко ценится!