Notice
Recent Posts
Recent Comments
Link
반응형
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |
Tags
- removeAll
- 티스토리챌린지
- unity sparkmain(clone)
- readonly
- unity korea
- Simulation
- dropdown
- 유니티
- dfs
- raycast
- 크루스칼
- BFS
- 깊이탐색
- Unity
- 트리구조
- sparkmain(clone)
- 행동트리
- articulation body
- 드롭다운
- sparkmain(clone) 무한생성
- 유니티 sparkmain(clone)
- 최단거리 알고리즘
- 오블완
- C#
- 디지털트윈
- GetComponent
- 최소신장트리 mst
- 너비탐색
- navisworks api
- list clear
Archives
- Today
- Total
낑깡의 게임 프로그래밍 도전기
C# 상태패턴 본문
반응형
SMALL
상태에 대한 것을 나눌때 상태패턴 전략적으로 나누면 전략패턴
W로 상태를 전이 시켰다.. 하나의 현재상태를 가지고 여러가지 상태로 전환할 수 있는 도구를 상태 머신이라고 한다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Unity.VisualScripting.Dependencies.Sqlite.SQLite3;
using static UnityEngine.UI.GridLayoutGroup;
public enum STATE_TYPE
{
IDLE,
WALK,
RUN
}
public class State
{
public GameManager owner = null;
public State(GameManager owner)
{
this.owner = owner;
}
public virtual void Update()
{
}
}
public class IdleState : State
{
public IdleState(GameManager owner) : base(owner)
{
}
public override void Update()
{
Debug.Log("대기상태");
if (owner.MoveVec != Vector3.zero)
owner.curState = new WalkState(owner);
}
}
public class WalkState : State
{
public WalkState(GameManager owner) : base(owner)
{
}
public override void Update()
{
Debug.Log("걷기상태");
if (owner.MoveVec == Vector3.zero)
owner.curState = new IdleState(owner);
if (Input.GetKeyDown(KeyCode.LeftShift))
owner.curState = new RunState(owner);
}
}
public class RunState : State
{
public RunState(GameManager owner) : base(owner)
{
}
public override void Update()
{
Debug.Log("달리기상태");
if (owner.MoveVec == Vector3.zero)
owner.curState = new IdleState(owner);
if (Input.GetKeyUp(KeyCode.LeftShift))
owner.curState = new WalkState(owner);
}
}
public class GameManager : MonoBehaviour
{
//현재 여기까지는 상태 머신.
// STATE_TYPE curType = STATE_TYPE.IDLE;
public State curState;
Vector3 moveVec;
public Vector3 MoveVec
{
get { return moveVec; }
}
private void Start()
{
curState = new IdleState(this);
}
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
moveVec = new Vector3(x, 0, z).normalized;
curState.Update();
/*
switch(curType)
{
case STATE_TYPE.IDLE:
Debug.Log("대기상태");
if(moveVec != Vector3.zero)
curType = STATE_TYPE.WALK;
break;
case STATE_TYPE.WALK:
Debug.Log("걷기상태");
if (moveVec == Vector3.zero)
curType = STATE_TYPE.IDLE;
if (Input.GetKeyDown(KeyCode.LeftShift))
curType = STATE_TYPE.RUN;
break;
case STATE_TYPE.RUN:
Debug.Log("달리기상태");
if (moveVec == Vector3.zero)
curType = STATE_TYPE.IDLE;
if (Input.GetKeyUp(KeyCode.LeftShift))
curType = STATE_TYPE.WALK;
break;
}
*/
}
}
상태 패턴으로 바꾼것 아직은 아쉬운 상태패턴 얘들을 유연하게 관리해주는 매니저가 있으면 더 좋다
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Unity.VisualScripting.Dependencies.Sqlite.SQLite3;
public enum STATE_TYPE
{
IDLE,
WALK,
RUN
}
public class State//유징스페이스 잘못쓰면 스테이트라고하는 다른놈이 들어오므로 주의
{
public StateMachine sm = null; //이제는 머신이 관리할 것이라 바꿔즘
public virtual void Enter()
{
Debug.Log(GetType().Name + "상태진입");//내타입을 가지고 오는 것
}
public virtual void Update()
{
}
public virtual void Exit()
{
Debug.Log(GetType().Name + "상태빠져나감");
}
}
public class IdleState : State
{
public override void Update()
{
Debug.Log("대기상태");
if (sm.owner.MoveVec != Vector3.zero)
sm.SetState("Walk");
if(Input.GetKeyDown(KeyCode.V))
sm.SetState("Attack");
}
}
public class WalkState : State
{
public override void Update()
{
Debug.Log("걷기상태");
if (sm.owner.MoveVec == Vector3.zero)
sm.SetState("Idle");
if (Input.GetKeyDown(KeyCode.LeftShift))
sm.SetState("Run");
}
}
public class RunState : State
{
public override void Update()
{
Debug.Log("달리기상태");
if (sm.owner.MoveVec == Vector3.zero)
sm.SetState("Idle");
if (Input.GetKeyUp(KeyCode.LeftShift))
sm.SetState("Walk");
}
}
public class AttackState : State
{
public override void Enter()
{
base.Enter();
Debug.Log("공격!");
sm.SetState("Idle");
}
}
public class StateMachine//보통 상태패턴을 제어하는 애 이름을 관용적으로 이렇게 지음
{
//이제 소유주를 머신에서 체크한다
public Player owner = null;
public State curState;
public Dictionary<string, State> stateDic;
public StateMachine(Player owner)
{
this.owner = owner;
stateDic = new Dictionary<string, State>();
}
public void AddState(string stateName, State state)
{
//여러개를 담으려면 콜렉션을 써야함. 딕셔너리를 써보겠음
if (stateDic.ContainsKey(stateName))//ContainsKey 있는지 없는지 체크해보는거
return;//이프이프 문 방지 리턴되는거 먼저 만들어 놓고 실행
stateDic.Add(stateName, state);
state.sm = this;
}
public void SetState(string stateName)
{
if (stateDic.ContainsKey(stateName))//예외처리
{
if(curState != null)
{
curState.Exit();
}
curState = stateDic[stateName];
curState.Enter();
}
}
public void Update()
{
curState.Update();
}
}
public class Player : MonoBehaviour
{
//public State curState;//위로뺌
StateMachine sm;
Vector3 moveVec;
public Vector3 MoveVec
{
get { return moveVec; }
}
void Start()
{
sm = new StateMachine(this);
sm.AddState("Idle", new IdleState());
sm.AddState("Walk", new WalkState());
sm.AddState("Run", new RunState());
sm.AddState("Attack", new AttackState());
sm.SetState("Idle");
}
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
moveVec = new Vector3(x, 0, z).normalized;
sm.Update();
}
}
매니저를 이용해 상태패턴을 다시 구현한 것
이렇게 하면 나중에 예를 들어 플레이어를 추격하는 몬스터에게 다양한 상태를 부여할 수 있는데(추후 플레이어를 추격을 하지 않았을때 몬스터에게 다양한 상태를 주고 플때 등) 처음 만들때는 그 모든 상태를 고려하지 않아도 되는 등 확장성이 매우 높아진다.
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class GMState
{
public virtual void Update()
{
}
}
public class DefaultState : GMState
{
public override void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("게임 시작합니다");
GameManager.instance.curState = new LoadingState();
}
}
}
public class PauseState : GMState
{
public override void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Debug.Log("일시정지 해제");
GameManager.instance.curState = new PlayState();
}
}
}
public class PlayState : GMState
{
public override void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Debug.Log("일시정지");
GameManager.instance.curState = new PauseState();
}
}
}
public class LoadingState : GMState
{
float targetTime = 3f;
float curTime = 0;
public override void Update()
{
curTime += Time.deltaTime;
if(targetTime < curTime)
{
GameManager.instance.curState = new PlayState();
curTime = 0;
}
}
}
public class GameOverState : GMState
{
public override void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
Debug.Log("다시시작");
GameManager.instance.curState = new PlayState();
}
else if (Input.GetKeyDown(KeyCode.G))
{
Debug.Log("기본상태로");
GameManager.instance.curState = new DefaultState();
}
}
}
public class GameManager : MonoBehaviour
{
public static GameManager instance = null;
//게임매니저는 싱글톤이기 때문에 생성자가 필요없을 수 있다.
//일시정지, 플레이, 로딩중, 게임오버
public GMState curState;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else
Destroy(gameObject);
}
void Start()
{
curState = new DefaultState();
}
// Update is called once per frame
void Update()
{
curState.Update();
}
}
좀 더 이해하기 쉬운 게임 상태변화 예시
구조체는 null을 가르킬수없고 클래스는 null을 가르킬 수 있는 등의 차이 탓도 있다.
대충 상태머신 하나만들고 여기저기 다쓰고 싶을때 유용할 것 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public interface IStateMachine
{
object GetOwner();
void SetState(string stateName);
}
public class StateMachine<T> : IStateMachine where T : class
{
public T owner = null;
public State curState;
public Dictionary<string, State> stateDic;
public StateMachine(T owner)
{
this.owner = owner;
stateDic = new Dictionary<string, State>();
}
public void AddState(string stateName, State state)
{
if (stateDic.ContainsKey(stateName))
return;
stateDic.Add(stateName, state);
state.Init(this);
}
public object GetOwner()
{
return owner;
}
public void SetState(string stateName)
{
if (stateDic.ContainsKey(stateName))
{
if (curState != null)
{
curState.Exit();
}
curState = stateDic[stateName];
curState.Enter();
}
}
public void Update()
{
curState?.Update();
}
}
public class State
{
public IStateMachine sm = null;
public virtual void Init(IStateMachine sm)
{
this.sm = sm;
}
public virtual void Enter()
{
Debug.Log(GetType().Name + "상태 진입");
}
public virtual void Update()
{
}
public virtual void Exit()
{
Debug.Log(GetType().Name + "상태 빠져나옴");
}
}
public class GMState : State
{
public GameManager gm;
public event Action onEnter;
public override void Enter()
{
base.Enter();
if(onEnter != null) onEnter();
}
public override void Init(IStateMachine sm)
{
this.sm = sm;
gm = (GameManager) sm.GetOwner();
}
}
public class DefaultState : GMState
{
public override void Update()
{
base.Update();
if(Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("게임 시작합니다.");
sm.SetState("Loading");//= new LoadingState();
}
}
}
public class PauseState : GMState
{
public override void Update()
{
base.Update();
if (Input.GetKeyDown(KeyCode.Q))
{
Debug.Log("일시정지를 해제합니다.");
sm.SetState("Play");//= new PlayState();
}
}
}
public class PlayState : GMState
{
public override void Update()
{
base.Update();
if (Input.GetKeyDown(KeyCode.Q))
{
Debug.Log("일시정지합니다.");
sm.SetState("Pause");
}
}
}
public class LoadingState : GMState
{
float targetTime = 3f;
float curTime = 0;
public override void Update()
{
base.Update();
curTime += Time.deltaTime;
if(targetTime < curTime)
{
sm.SetState("Play");
curTime = 0;
}
Debug.Log("로딩중..." + curTime);
}
}
public class GameOverState : GMState
{
public override void Update()
{
}
}
public class GameManager : MonoBehaviour
{
public static GameManager instance = null;
public StateMachine<GameManager> sm;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else
Destroy(gameObject);
}
public GameObject obj;
void Start()
{
sm = new StateMachine<GameManager>(this);
PlayState ps = new PlayState();
ps.onEnter += () => { obj.SetActive(true); };
sm.AddState("Default", new DefaultState());
sm.AddState("Play", ps);
sm.AddState("Loading", new LoadingState());
sm.AddState("Pause", new PauseState());
sm.SetState("Default");
}
// Update is called once per frame
void Update()
{
sm.Update();
}
}
반응형
LIST