[Unity] (2024_09_25) 2D 플랫 포머 게임 개발일지 FSM Part 2

2024. 9. 26. 09:11·Unity
목차
  1. Script : PlayerIdleState
  2. Script : PlayerRunState
  3. Script : PlayerAttackState
  4. Script : PlayerJumpState
  5. Script : PlayerJumpAttackState
  6. Script : PlayerFallState
  7. AnimationSetting
  8. Script Setting

깃헙 링크(에셋 저작권 이슈 때문에 Script만 따로 빼옴) : https://github.com/ljs1206/NKProject-Script-

 

part2 에선 실제 State 스크립트와 실제 구현과 세팅들을 구현한 부분을 정리해두었다.

 

내가 사용한 State들은

Idle (구현 함)  , 
Run (구현 함)  , 
Fall (구현 함)  ,
Attack (구현 함) ,
Dash (구현 안함) ,
Hit (구현 안함) ,
Ground (구현 함)  ,
Jump (구현 함)  ,

JumpAttack (구현 함)

이다.

따라서 9개의 스크립트를 구현하였다(구현 안한거도 있긴함).

 

Script : PlayerIdleState

리플랙션 때문에 Player + 이름 + State로 꼭 지켜서 스크립트를 만들어야 한다. 꼭이다!

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerIdleState : PlayerState
{
    public PlayerIdleState(Player player, PlayerStateMachine stateMachine, string boolName) : base(player, stateMachine, boolName)
    {
    }

    public override void Enter()
    {
        base.Enter();
        _player.InputReder.OnAttackEvent += HandleAttackEvent;
        _player.InputReder.OnJumpEvent += HandleJumpEvent;
    }

    private void HandleAttackEvent()
    {
        if(_player.PlayerMovement.IsGround){
            _stateMachine.ChangeState(PlayerStateEnum.Attack);
        }
    }

    private void HandleJumpEvent()
    {
        if(_player.PlayerMovement.IsGround){
            _stateMachine.ChangeState(PlayerStateEnum.Jump);
        }
    }

    public override void UpdateState()
    {
        if (_player.InputReder.Movement.x != 0)
        {
            _player.StateMachine.ChangeState(PlayerStateEnum.Run);
        }
    }

    public override void Exit()
    {
        base.Exit();
        _player.InputReder.OnAttackEvent -= HandleAttackEvent;
        _player.InputReder.OnJumpEvent -= HandleJumpEvent;
    }
}

 

PlayerState 상속받고

생성자는 꼭 있어야 안그러면 초기화가 안됨 그리고 [Enter, Update, Exit]가 필요하니 모두 구현하여 준다.

public override void Enter()
{
    base.Enter();
    _player.InputReder.OnAttackEvent += HandleAttackEvent;
    _player.InputReder.OnJumpEvent += HandleJumpEvent;
}

public override void UpdateState()
{
    if (_player.InputReder.Movement.x != 0)
    {
        _player.StateMachine.ChangeState(PlayerStateEnum.Run);
    }
}

public override void Exit()
{
    base.Exit();
    _player.InputReder.OnAttackEvent -= HandleAttackEvent;
    _player.InputReder.OnJumpEvent -= HandleJumpEvent;
}

Enter에서는 Action 두개를 구독하여주는데 Idle에서는 공격과 점프 두개의 State를 오갈 수 있기 때문에 InputReader에 키입력 Event 두개를 전부 받아와 구독하여준다. 

Update에서는 만일 움직임이 있었다면 (2D이나까 Movement.x의 값이 1 or -1이라면 (Conditional)) RunState로 변환 (transition)시켜준다.

Exit에서는 받아온 Event을 구독 취소해준다.

 

private void HandleAttackEvent()
{
    if(_player.PlayerMovement.IsGround){
        _stateMachine.ChangeState(PlayerStateEnum.Attack);
    }
}

private void HandleJumpEvent()
{
    if(_player.PlayerMovement.IsGround){
        _stateMachine.ChangeState(PlayerStateEnum.Jump);
    }
}

구독해준 함수들이다.

Attack함수에서는 땅위에 붙어있다면 AttackState으로 변경시켜준다.

Jump함수에서는 땅위에 붙어 있다면 JumpState로 변경시켜준다.

 

Script : PlayerRunState

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerRunState : PlayerState
{
    public PlayerRunState(Player player, PlayerStateMachine stateMachine, string boolName) : base(player, stateMachine, boolName)
    {
    }

    public override void Enter()
    {
        base.Enter();
        _player.InputReder.OnJumpEvent += HandleJumpEvent;
        _player.InputReder.OnAttackEvent += HandleAttackEvent;
    }

    private void HandleAttackEvent()
    {
        if(_player.PlayerMovement.IsGround){
            _stateMachine.ChangeState(PlayerStateEnum.Attack);
        }
    }

    private void HandleJumpEvent()
    {
        if(_player.PlayerMovement.IsGround){
            _stateMachine.ChangeState(PlayerStateEnum.Jump);
        }
    }

    public override void UpdateState()
    {
        base.UpdateState();
        if(_player.InputReder.Movement.x == 0){
            _stateMachine.ChangeState(PlayerStateEnum.Idle);
        }
        else{
            _player.PlayerMovement.Move();
        }
    }

    public override void Exit()
    {
        base.Exit();
        _player.InputReder.OnJumpEvent -= HandleJumpEvent;
        _player.InputReder.OnAttackEvent -= HandleAttackEvent;
    }
}

RunState는 Idle과 동일한데 다른 점은 Update이다.

Update에서는 움직이지 않았다면 (Movement.x의 값이 0이라면) IdleState로 변환하고 아니라면 움직인다. (PlayerMovement의 Move함수 실행)

Script : PlayerAttackState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAttackState : PlayerState
{
    public PlayerAttackState(Player player, PlayerStateMachine stateMachine, string boolName) : base(player, stateMachine, boolName)
    {
    }

    public override void Enter()
    {
        _player.PlayerMovement.CanMove = false;
        _player.PlayerMovement.StopImmediately();
        base.Enter();
    }

    public override void UpdateState()
    {
        if(_endTriggerCalled)
        {
            _stateMachine.ChangeState(PlayerStateEnum.Idle);
        }
    }

    public override void Exit()
    {
        _player.PlayerMovement.CanMove = true;
        base.Exit();
    }

    public override void AnimationFinishTrigger()
    {
        _endTriggerCalled = true;
    }
}

 

Attack은 AnimationFinishTrigger가 필요해서 Enter, Update, Exit와 함께 구현한다.

Enter에서는 Attack 도중에는 움직일 수가 없어야 하니 PlayerMovement의 CanMove를 false로 하고 StopImmediately를 실행시켜 Velcity를 0으로 한다.

Update에서는 애니메이션이 종료했다면 (endTriggerCalled == true) IdleState로 변환시켜준다.

Exit 에서는 Attkack이 종료했으니 다시 움직일 수 있게 CanMove를 true로 만들어준다.

AnimationFinishTrigger 에서는 endTriggerCalled를 true로 만들어준다.

Script : PlayerJumpState

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerJumpState : PlayerState
{
    public PlayerJumpState(Player player, PlayerStateMachine stateMachine, string boolName) : base(player, stateMachine, boolName)
    {
    }

    public override void UpdateState()
    {
        if(!_player.PlayerMovement.IsGround && _player.PlayerMovement.Rg2d.velocity.y <= 0){
            _stateMachine.ChangeState(PlayerStateEnum.Fall);
        }
        if(_player.PlayerMovement.Rg2d.velocity.y == 0){
            _stateMachine.ChangeState(PlayerStateEnum.Idle);
        }
    }

    public override void Enter()
    {
        base.Enter();
        _player.Jump();
        _player.InputReder.OnAttackEvent += HandleAttackEvent;
    }

    public override void Exit()
    {
        base.Exit();
        _player.InputReder.OnAttackEvent -= HandleAttackEvent;
    }

    private void HandleAttackEvent()
    {
        _stateMachine.ChangeState(PlayerStateEnum.JumpAttack);
    }
}

JumpState이다.

Enter 에서는 Player Jump 함수를 통해 점프를 해주고 AttackEvent를 들고 와 구독하고

Update 에서는 땅에 닿아있지 않고 지금 올라가고 있지 않다면 (isGround == false && velocity.y <= 0) 땅에 닿았다면 ( velocity.y == 0)

Exit 에서는 구독을 취소한다.

AttackEvent 에서는 Jump상태에서 공격을 했으니 JumpAttackState로 변환 시켜준다.

 

Script : PlayerJumpAttackState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerJumpAttackState : PlayerState
{
    public PlayerJumpAttackState(Player player, PlayerStateMachine stateMachine, string boolName) : base(player, stateMachine, boolName)
    {
    }

    public override void UpdateState()
    {
        base.UpdateState();
        if(_player.PlayerMovement.IsGround){
            _stateMachine.ChangeState(PlayerStateEnum.Idle);
        }

        if(_endTriggerCalled){
            if(_player.PlayerMovement.Rg2d.velocity.y > 0)
                _stateMachine.ChangeState(PlayerStateEnum.Jump);
            else if(_player.PlayerMovement.Rg2d.velocity.y < 0)
                _stateMachine.ChangeState(PlayerStateEnum.Fall);
        }
    }

    public override void AnimationFinishTrigger()
    {
        _endTriggerCalled = true;
    }
}

Enter와 Exit에서는 딱히 할게 없어서 구현하지 않았다. 하지만 AttackState의 종류중 하나이기 때문에 AFT(줄임말)함수는 사용한다. 

Update 에서는 땅에 닿는 순간 공격을 취소해야하니 Idle State로 변환시켜주고 애니메이션이 끝났을때의 y Velocity가 > 0이라면 (Jump중) JumpState로 변환한다. y Velocity가 < 0이라면 (Fall중) FallState로 변환한다.

 

Script : PlayerFallState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerFallState : PlayerState
{
    public PlayerFallState(Player player, PlayerStateMachine stateMachine, string boolName) : base(player, stateMachine, boolName)
    {
    }

    public override void Enter()
    {
        base.Enter();
        _player.InputReder.OnAttackEvent += HandleAttackEvent;
    }

    public override void Exit()
    {
        base.Exit();
        _player.InputReder.OnAttackEvent -= HandleAttackEvent;
    }

    private void HandleAttackEvent()
    {
        _stateMachine.ChangeState(PlayerStateEnum.JumpAttack);
    }

    public override void UpdateState()
    {
        base.UpdateState();
        if(_player.PlayerMovement.IsGround){
            _stateMachine.ChangeState(PlayerStateEnum.Idle);
        }
    }
}

 

 

FallState는 JumpState와 마찬가지로 Jump 도중 공격 키를 입력하면 JumpAttackState로 넘어가게 구조를 짜두었고

Update 에서는 땅에 닿을 시 IdleState로 바꾸어준다.

 

GroundState(공중과 지상에서의 State 구분, 근데 딱히 필요없긴함)는 구현은 했지만 당장 쓰지않아서 보여주지는 않을거고 아직 Dash와 HitState는 구현하지 않았다.

 

아무튼 다음은 '실제로 어떤식으로 설정을 해야 작동하는가?' 이다.

 

AnimationSetting

전체적인 모습은 이러하다. 일단 선술 했듯 BoolParaMator의 이름이 Enum의 이름과 같다는 것을 알수 있다.

그리고 AnyState에서 모습 AnimationClip으로 이어져있는 모습 이런 형태로 하는 이유는 어떤 State애서든지 바로바로 다른 State의 Animation으로 이동을 해야하기 때문에 AnyState로 이어준 것이다.

Idle Setting값으로 예를 들면 Idle은 Idle이라는 BoolParamator가 true가 된다면 그 AnimationClip으로 이동하게 되게 만들었고 2D 기반으로 만드는 중이기 때문에 Transition Duration과 OffSet 딱히 필요없다 판단 되어 0으로 설정 해두었다. 그리고 BoolParamator 기반이기 때문에 자신 계속 불러와 Animation이 계속 처음부터 실행되는 것을 막고자 CanTransition To Self를 flase로 두었다. 다른 State들도 이런식으로 만들어주면 된다.

 

Script Setting

GameObject는 이렇게 만들어두었다

대충 이런식으로 설정했고

 

FootTrm을 만든 이유는 Player를 Trigger로 만들 것인데. Collider를 하나만 했다간 바닥에 뚫려 나락으로 갈게 뻔하기 때문에 만들어 두었다.

Physic2D Setting과 Collider 세팅한 그림들이다.

 

이렇게 만들었더니

잘 작동한다.

글이 좀 주저리 주저리 한 감이 있긴한데 다음부터는 좀 내용을 간결하게 줄여봐야겠다.

'Unity' 카테고리의 다른 글

[엔진 프로젝트]Enemy Walking Around with BT  (0) 2024.11.01
[Unity] (2024_10_15) 2D 플랫 포머 게임 개발일지(Pooling)  (2) 2024.10.17
[Unity] (2024_09_30) 2D 플랫 포머 게임 개발일지  (0) 2024.09.30
[Unity] (2024_09_25) 2D 플랫 포머 게임 개발일지 FSM Part 1  (3) 2024.09.26
[Unity] (2024_09_09) 2D 플랫 포머 게임 개발일지  (3) 2024.09.11
  1. Script : PlayerIdleState
  2. Script : PlayerRunState
  3. Script : PlayerAttackState
  4. Script : PlayerJumpState
  5. Script : PlayerJumpAttackState
  6. Script : PlayerFallState
  7. AnimationSetting
  8. Script Setting
'Unity' 카테고리의 다른 글
  • [Unity] (2024_10_15) 2D 플랫 포머 게임 개발일지(Pooling)
  • [Unity] (2024_09_30) 2D 플랫 포머 게임 개발일지
  • [Unity] (2024_09_25) 2D 플랫 포머 게임 개발일지 FSM Part 1
  • [Unity] (2024_09_09) 2D 플랫 포머 게임 개발일지
HK1206
HK1206
고3 게임 개발자의 개발 일지
  • HK1206
    GGM-LJS
    HK1206
  • 전체
    오늘
    어제
    • 분류 전체보기 (25)
      • Unity (16)
      • Shader (1)
      • Editor (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
HK1206
[Unity] (2024_09_25) 2D 플랫 포머 게임 개발일지 FSM Part 2

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.