2022. 2. 7. 03:40ㆍProjects/Amnyang
점프에 대한 요구사항
제자리 점프일 경우,좌우 이동 불가- 이동 중 점프할 경우, 포물선 궤도를 그리며 점프
점프 중엔반대 방향으로 돌아보는 모션 불가- 점프 후 착지 시 약간의 경직이 있음
저번 글에서 두 가지는 구현을 했던 걸로 기억한다. (글을 적는 시점에 생각이 안 났다,,,)
이제 제자리 점프 후에 착지 시, 약간의 경직을 줘서 바로 달리지 못하도록 구현해야 한다. 이동 중 점프는 제자리 점프와는 모션이 다르므로 각각 구분해서 조작이 되도록 적용해줘야 한다.
1. 이동하면서 점프하는 애니메이션 적용하기
이동 중 점프 모션은 딱히 만들 게 없다. 그냥 적당한 간격의 프레임을 두고 팔, 다리, 목 등을 살짝 움직이는 효과만 주면 됐다.
믿기지 않겠지만, 정말로 이게 다다. 이제 스크립트에서, 제자리 점프(Sargent Jump)와 이동 중 점프(Move Jump)를 구분해서 잘 동작시켜주기만 하면 끝나는 부분이다.
2. 애니메이터 설정하기
서브 스테이트 머신(Sub-StateMachine)으로 "Jump" 스테이트 머신을 만들어서, 내부에 애니메이션들을 정리했다.
폴더로 정리해서 계층구조를 만든다고 생각해도 이해하기 좋을 듯 하다. 확실히 훨씬 깔끔해졌다.
애니메이터 매개변수(Parameters)는 다음과 같이 세 개를 추가해줬다.
MoveJump(Bool)은 원래 이름이 "Jump"였는데, "SargentJump"와 헷갈릴 것 같아서 이름을 다시 지어줬다.
해당 매개변수는 이동 중 점프에 대한 트랜지션 조건으로 사용했다.
Landing(Trigger)는 이전 글에서 설명했으니 생략. SargentJump(Bool) 매개변수를 추가하여 제자리 점프에 대한 트랜지션 조건으로 사용했다.
3. 스크립트 작성하기
Jump() 함수를 다음과 같이 수정해줬다.
private const float JUMP_CHARGING_DELAY = 0.55f;
private void Jump()
{
isJumping = true;
if (!hasControl)
{
animator.SetBool("SargentJump", true);
Invoke("_Jump", JUMP_CHARGING_DELAY);
}
else
{
animator.SetBool("MoveJump", true);
_Jump();
}
}
private void _Jump()
{
_rigidBody.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
}
그리고 바닥에 충돌했을 때 점프가 다시 가능하도록 구현해놨던 OnCollision2D() 함수를 다음과 같이 수정해줬다.
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.layer == LayerMask.NameToLayer("Jumpable_Floor") && isJumping)
{
if (animator.GetBool("SargentJump"))
{
animator.SetTrigger("Landing");
animator.SetBool("SargentJump", false);
StartCoroutine(LandingDelay());
return;
}
animator.SetBool("MoveJump", false);
isJumping = false;
}
}
IEnumerator LandingDelay()
{
yield return landingDelay;
isJumping = false;
}
이렇게 적고 보니, 정말 별 거 없는 것인데도 상당히 오래 걸려 버렸다. (물론 술 마신 탓도 있다...ㅎㅎ)
제자리 점프와 이동 점프를 구분해놨으면서 변수를 하나로 제어하려 하니, 이상한 버그가 발생하던 것이었다.
대표적인 예로, 제자리 점프 중 방향키 입력이 있으면 Landing 트리거가 실행되지 않아 착지 모션이 나오지 않던 버그가 있었다. Rigidbody 2D의 velocity의 y값을 조건으로 주기도 했는데, 잘 해결이 안 돼서 그냥 변수 하나 더 추가했다.
착지 딜레이를 StateMachineBehaviour를 사용해서 착지 애니메이션이 끝났을 때 줄까도 생각했다. 하지만, StateMachineBehaviour는 씬 오브젝트가 아니고 에셋이었다. StateMachineBehaviour에서 씬 오브젝트를 참조하려면 Find() 함수를 써야 하는데, 성능이 좋지 않다고 알고 있기에 이 방법은 보류했다.
(나중에 다시 보는데, 그냥 게임 시작할 때 처음에 캐싱(caching)해놓고 쓰면 괜찮았을 것 같은데,,)
또한, 이 방법은 트랜지션 도중 다른 트랜지션에 의해 Interruption이 일어나게 되면, 호출 기회가 사라지는 경우도 있기에 이 부분도 보류하는데 한 몫했다. (레거시에서는 그랬지만 메카님에서는 그럴 일이 거의 없다고 한다.)
게임 개발엔 참 다양한 방법들이 존재하는 것 같다. 정답은 없으니 이 방법 저 방법 보고 해보며 경험해보자.
4. 구현 완료
✏️전체 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SujiMoveController : MonoBehaviour
{
public float walkSpeed;
public float jumpPower;
public Transform sujiTransform;
private Animator animator;
private Rigidbody2D _rigidBody;
private float walkDirection;
private float initScaleX;
private const float JUMP_CHARGING_DELAY = 0.55f;
private WaitForSeconds landingDelay = new WaitForSeconds(0.5f);
private bool hasControl;
private bool isRunning;
private bool isJumping;
void Start()
{
_rigidBody = GetComponent<Rigidbody2D>();
animator = GetComponentInChildren<Animator>();
initScaleX = sujiTransform.localScale.x;
}
void Update()
{
walkDirection = Input.GetAxisRaw("Horizontal");
hasControl = !Mathf.Approximately(walkDirection, 0f);
isRunning = Input.GetButton("Run");
if(Input.GetButtonDown("Jump") && !isJumping)
{
Jump();
}
}
void FixedUpdate()
{
TurnOtherSide();
Move();
}
private void Jump()
{
isJumping = true;
if (!hasControl)
{
animator.SetBool("SargentJump", true);
Invoke("_Jump", JUMP_CHARGING_DELAY);
}
else
{
animator.SetBool("MoveJump", true);
_Jump();
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.layer == LayerMask.NameToLayer("Jumpable_Floor")
&& isJumping)
{
if (animator.GetBool("SargentJump"))
{
animator.SetTrigger("Landing");
animator.SetBool("SargentJump", false);
StartCoroutine(LandingDelay());
return;
}
animator.SetBool("MoveJump", false);
isJumping = false;
}
}
private void _Jump()
{
_rigidBody.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
}
IEnumerator LandingDelay()
{
yield return landingDelay;
isJumping = false;
}
private void Move()
{
/* 0f : Idle, 0.5f : Walk, 1f : Run */
if (isJumping)
return;
if (hasControl && isRunning)
{
animator.SetFloat("Move", 1f);
_rigidBody.velocity = new Vector2(walkDirection * walkSpeed * 2f,
_rigidBody.velocity.y);
return;
}
float walkValue = (hasControl && !isRunning) ? 0.5f : 0f;
animator.SetFloat("Move", walkValue);
_rigidBody.velocity = new Vector2(walkDirection * walkSpeed,
_rigidBody.velocity.y);
}
private void TurnOtherSide()
{
if (!hasControl || isJumping)
return;
var scaleX = sujiTransform.localScale.x;
if (Mathf.Approximately(walkDirection * scaleX, initScaleX))
return;
var scaleY = sujiTransform.localScale.y;
var scaleZ = sujiTransform.localScale.z;
sujiTransform.localScale = new Vector3(-scaleX, scaleY, scaleZ);
}
}
'Projects > Amnyang' 카테고리의 다른 글
[압량(Amnyang)] #10. 2D 게임 상호작용 시스템(숨기) 만들기 + 수많은 버그 수정 (0) | 2022.02.14 |
---|---|
[압량(Amnyang)] #9. 2D 게임 배경 간단히 배치 및 카메라 추적 이동 구현하기 (0) | 2022.02.13 |
[압량(Amnyang)] #7. 2D 주인공 제자리 점프(Sargent Jump), 착지(Landing) 애니메이션 추가하기 (0) | 2022.02.06 |
[압량(Amnyang)] #6. 2D 주인공 캐릭터 달리기(Run) 애니메이션 추가하기 (0) | 2022.02.05 |
[압량(Amnyang)] #5. 2D에서 Y축 회전을 통한 문 열리고 닫는 효과 구현하기 (0) | 2022.01.22 |