일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- mutex
- 안드로이드스튜디오
- list
- semaphore
- Unity
- 유니티슈팅게임
- ARface
- SpinLock
- 게임개발
- 바이너리세마포
- 유니티
- 뮤텍스
- map
- Vector
- 포톤
- unityAR
- photon
- unorderedset
- registerForActivityResult
- C++
- dependencyResilutionManagement
- 세마포
- StartActivityForResult
- Java
- 지크슈
- NotFoundException: String resource ID #0x0
- 광유다
- 스핀락
- 동기화
- unorderedmap
- Today
- Total
와와
캐릭터 움직임 구현1: 유한상태머신(FSM) 본문
캐릭터 조작을 구현하려면 FSM 공부가 필수인가봅니다.
공부해보겠습니다
1. 왜 필요할까?
내가 현재 작업하고 있는 부분은 플랫포머 2D 캐릭터 조작이다.
좌/우 이동, 점프, 사다리 오르내리기만 구현하면 될거라 생각하고 아주 얕잡아봤었다!
이 코드는 초반에 내가 작성한 코드......
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveController : MonoBehaviour
{
private Movement movement;
private float x;
private void Awake()
{
movement = GetComponent<Movement>();
}
private void Update()
{
x = Input.GetAxisRaw("Horizontal");
// jump
if (Input.GetKeyDown(KeyCode.Space))
{
movement.Jump();
}
// long jump
if (Input.GetKey(KeyCode.Space))
{
movement.LongJump(true);
}
else if (Input.GetKeyUp(KeyCode.Space))
{
movement.LongJump(false);
}
}
private void FixedUpdate()
{
//move
movement.Move(x);
}
}
첨엔 이런식으로 입력에 따라 움직임 함수를 호출하고 movement 클래스에 각 움직임을 구현했었다.
문제는 사다리타기 였는데
사다리 코드 자체를 아주 복잡하게 구현해놓은 상태에서 사다리타다가 점프, 점프하다가 사다리까지 구현하려다 보니 내 머리가 터져나갔고, 코드 양도 홍수처럼 불어났었다...
이 모습을 보다못한 팀원들이 이 상태에서 애니메이션까지 들어가면 많이 힘들어질 것 같다며 FSM를 추천해줬다. 링크까지 달아줌,, 얼마나 답답했을까~!
2. 유한 상태 기계(finite-state machine, FSM)
유한 상태 기계의 요점
- 가질 수 있는 '상태'가 한정된다. ( 서있기, 사다리, 걷기, 점프 상태 )
- 한 번에 '한 가지' 상태만 될 수 있다. ( 서있기&사다리 동시에 있을 수 없음 )
- '입력'이나 '이벤트'가 기계에 전달된다. ( A,D,W,S,Space 키 )
- 각 상태에는 입력에 따라 다음 상태로 바뀌는 '전이'가 있다.

내가 구현하고자 하는 모습을 먼저 그려보았다
3. 열거형(enum)을 이용한 FSM
상태를 표시하는 플러그 변수( isJumping, isClimbing,,,)들이 많고, 하나만 참일 때가 많다면 열겨형을 사용하자!
그렇다면 enum이 뭘까?
: 열거형 상수(constant)를 표현하기 위한 것
: 상수 숫자들을 단어들로 표현할 수 있음
enum문은 클래스 안이나 네임스페이스 내에서만 선언될 수 있다.
https://www.csharpstudy.com/CSharp/CSharp-enum.aspx
내 캐릭터의 4가지 상태를 PlayerState에 정리했다.
public enum PlayerState{
Idle, //0
Run, //1
Jump, //2
Climb //3
}
그리고 뒤에 나올 PlayerController 클래스에 각 상태에 대한 동작을 처리할 메서드(Handle~ )들을 구현하였다.
또한, ChangeState 메서드를 사용하여 상태를 변경할 수 있도록 하였음
public enum PlayerState
{
Idle,
Run,
Jump,
Climb
}
public class PlayerController : MonoBehaviour
{
private PlayerState currentState;
private Vector2 climbDirection;
private float climbSpeed = 5f;
private bool isGrounded;
private void Start()
{
currentState = PlayerState.Idle;
}
private void Update()
{
switch (currentState)
{
case PlayerState.Idle:
HandleIdleState();
break;
case PlayerState.Run:
HandleRunState();
break;
case PlayerState.Jump:
HandleJumpState();
break;
case PlayerState.Climb:
HandleClimbState();
break;
}
}
private void HandleIdleState()
{
// Idle 상태에서의 동작 구현
}
private void HandleRunState()
{
// Run 상태에서의 동작 구현
}
private void HandleJumpState()
{
// Jump 상태에서의 동작 구현
}
private void HandleClimbState()
{
// Climb 상태에서의 동작 구현
transform.Translate(climbDirection * climbSpeed * Time.deltaTime);
}
private void ChangeState(PlayerState newState)
{
currentState = newState;
}
//사다리 감지
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Ladder"))
{
climbDirection = Vector2.up;
ChangeState(PlayerState.Climb);
}
}
//사다리 탈출 감지
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Ladder"))
{
climbDirection = Vector2.zero;
ChangeState(PlayerState.Idle);
}
}
//isGrounded 검사
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
isGrounded = true;
}
}
}
자세한 구현은 더보기. ( 이 글에선 안쓸예정... )
틀만 잡아놨는데 한달 밀린 방청소 끝낸 기분...!
여기서 더 나아가 Jump->Climb, Climb->Jump 의 상태전이도 생각해보았다.
HandleClimbState 메서드에서 Space 키를 누르면 Jump 상태로 변경하고
OnTriggerStay2D 메서드를 통해 Jump 상태에서 사다리에 닿아있을 때, Climb 키를 누르면 캐릭터가 사다리를 타도록 구현함
< PlayerController.cs >
public enum PlayerState
{
Idle,
Run,
Jump,
Climb
}
public class PlayerController : MonoBehaviour
{
private PlayerState currentState;
private Vector2 climbDirection;
private float climbSpeed = 5f;
private float jumpForce = 5f;
private bool isGrounded;
private void Start()
{
currentState = PlayerState.Idle;
}
private void Update()
{
switch (currentState)
{
case PlayerState.Idle:
HandleIdleState();
break;
case PlayerState.Run:
HandleRunState();
break;
case PlayerState.Jump:
HandleJumpState();
break;
case PlayerState.Climb:
HandleClimbState();
break;
}
}
private void HandleIdleState()
{
// Idle 상태에서의 동작 구현
}
private void HandleRunState()
{
// Run 상태에서의 동작 구현
}
private void HandleJumpState()
{
// Jump 상태에서의 동작 구현
if (isGrounded)
{
ChangeState(PlayerState.Idle);
}
}
private void HandleClimbState()
{
// Climb 상태에서의 동작 구현
transform.Translate(climbDirection * climbSpeed * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.Space))
{
ChangeState(PlayerState.Jump);
GetComponent<Rigidbody2D>().velocity = Vector2.up * jumpForce;
isGrounded = false;
}
}
private void ChangeState(PlayerState newState)
{
currentState = newState;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Ladder"))
{
climbDirection = Vector2.up;
ChangeState(PlayerState.Climb);
}
}
private void OnTriggerStay2D(Collider2D other)
{
if (other.CompareTag("Ladder") && Input.GetKeyDown(KeyCode.UpArrow))
{
climbDirection = Vector2.up;
ChangeState(PlayerState.Climb);
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Ladder"))
{
climbDirection = Vector2.zero;
ChangeState(PlayerState.Idle);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
isGrounded = true;
}
}
}
상태 패턴도 공부해야겠다.
'개발 > Unity 3D' 카테고리의 다른 글
캐릭터 움직임 구현2: 상태 패턴 (0) | 2023.06.20 |
---|---|
[유니티] 유니버셜 랜더 파이프라인(URP) / 포스트 프로세싱 (1) | 2023.01.21 |
[ Unity 슈팅게임 ] 5. 움직이는 배경, 메뉴 씬, 실행 화면 (0) | 2022.08.20 |
[ Unity 슈팅게임 ] 4. 게임 오버, 스코어 기록, 랭킹 (0) | 2022.08.20 |
[ Unity 슈팅게임 ] 3. 마우스 클릭으로 총알 발사 (0) | 2022.08.20 |