[멋쟁이사자처럼 부트캠프TIL회고] 44일차 : 옵저버 패턴 +α
지금까지는 Input을 State에서 관리했었다.
예시)
public void InitState(IBlackboardBase blackboard)
{
Blackboard = blackboard as Blackboard_Default;
public void UpdateState(float deltaTime)
{
if (Blackboard.jumpInput.triggered && Blackboard.rigidbody.velocity.y == 0.0f)
{
Fsm.ChangeState<JumpState>();
return;
}
var value = Blackboard.moveInput.ReadValue<Vector2>();
if (0 >= value.sqrMagnitude)
{
Fsm.ChangeState<IdleState>();
return;
}
Blackboard.rigidbody.velocity = new Vector3(value.x * Blackboard.moveSpeed, Blackboard.rigidbody.velocity.y, value.y * Blackboard.moveSpeed);
}
이제는 인풋매니저를 따로 만들고 ,그것을 관찰하는 옵저버를 만들어 할 예정이다.
++ 생각보다 시간이 오래걸리는 중이다.

무브는 계속해서 들어가는 중인데 계속해서 안되어서
currentReceiveInput = currentState as IReceiveInput;
Debug.Log($"현재 currentReciveInput: {currentReceiveInput}");
ChangeState(typeof(IdleState));
currentREceiveInput이 왜 Null값인지 찾다가 , 초기 설정을 잘못해주었다는 것을 알게되었다.
Debug.Log($"현재 currentReciveInput: {currentReceiveInput}");
ChangeState(typeof(IdleState));
currentReceiveInput = currentState as IReceiveInput;
if (moveInput.sqrMagnitude < 0.01f)
{
FSM.ChangeState(typeof(IdleState));
}
public void OnReadValue(string action, Vector2 value)
{
if (action == "Move")
{
moveInput = value;
}
}
}
계속해서 moveInput을 넣어주고 있는데 Idlestate로 바뀌는 문제 발생
public void Run()
{
CBlackBoard blackboard = GetComponent<CBlackBoard>();
blackboard.InitBlackBoard();
// List<Cstate> states = this.CreateState(StaterType.Capsule); 이건 작동을 하는데
// List<Cstate> states = CStateFactory.CreateState(StaterType.Capsule); 이건 작동을 안하는 이유를 몰것음
// 연관되어서 List<Cstate> cstates = this.CreateStates( StaterType.Capsule); 이건 작동을 하는데
// List<Cstate> cstates = StateFactory.CreateStates(this.FSM ,StaterType.Capsule); 이건 작동을 왜 안할까
// List<Cstate> cstates = StateFactory.CreateStates(this.FSM ,StaterType.Capsule);
List<Cstate> cstates =StateFactory.CreateStates(this, StaterType.Capsule);
// Cstate[] cstates = this.GetComponents<Cstate>();
foreach (var cstate in cstates)
{
AddCState(cstate, blackboard);
}
ChangeState(typeof(IdleState));
currentReceiveInput = currentState as IReceiveInput;
}
public void AddCState(Cstate cstate, CBlackBoard blackboard)
{
cstate.FSM = this;
cstate.InitState(blackboard);
CStates.Add(cstate.GetType(),cstate);
}
public void ChangeState(Type stateType)
{
currentState?.Exit();
if (!CStates.TryGetValue(stateType, out currentState)) return;
currentReceiveInput = currentState as IReceiveInput;
currentState.Enter();
}

Vector2 Value값을 계속해서 찍어보니 알고보니 WalkState로 값이 들어가고 있지 않았다.
이유는 바뀔 때 currentReiveInput을 바꿔주지 않아서.
또 JumpState의 시작부분에
public void Enter()
{
BlackBoard.color = Color.red;
BlackBoard.textMesh.color = Color.red;
BlackBoard.textMesh.text = "Jump";
BlackBoard.rb.velocity = new Vector3(BlackBoard.rb.velocity.x, BlackBoard.JumpForce, BlackBoard.rb.velocity.z);
}
다음과 같이 넣어주었는데 Jumpstate로 진입하지 않는 문제 발생
public void OnTriggered(string action, bool triggered)
{
if (action == "Jump")
{
jumpInputTriggered = triggered;
}
}
알고보니 우리는 Inputmanager에서 계속해서 action을 Jump로 주고 있었느데
action을 jump와 같을 때 라고 조건을 달아서 발생하지 않았던 것.
지금까지 내가 '이것이 옵저버 패턴이다' 라고 확신하지 못했던 이유를 찾았다.
'상태'의 변화를 알려주는 것이 옵저버 패턴이라고 생각했는데 이 경우는 아니었다.
CapusuleController가 입력 값을 받고, subject가 된다. 그리고 각 상태들은 obsver로써 데이터를 관찰하고 그 데이터에 따라 행동하게 된다.(사실은 중재자인 stateMachine이 있지만 내일 없에는 작업을 할 것이다.)
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public enum StaterType
{
None,
Capsule,
Max
}
public static class StateFactory
{
public static List<Cstate> CreateStates(this CStateMachine stateMachine, StaterType staterType)
{
List<Cstate> states = new List<Cstate>();
switch (staterType)
{
case StaterType.Capsule:
{
states.Add(stateMachine.AddComponent<IdleState>());
states.Add(stateMachine.AddComponent<WalkState>());
states.Add(stateMachine.AddComponent<JumpState>());
}
break;
}
return states;
}
}
public class CStateMachine : MonoBehaviour
{
private Cstate currentState;
//불필요하게 중복된 코드
//public CStateMachine FSM;
//이거 왜쓴거임 ? 아직 진짜 모르겠음
private Dictionary<Type,Cstate> CStates = new Dictionary<Type, Cstate>();
//private CBlackBoard blackboard;
private IReceiveInput currentReceiveInput;
public void Run()
{
CBlackBoard blackboard = GetComponent<CBlackBoard>();
blackboard.InitBlackBoard();
List<Cstate> cstates =StateFactory.CreateStates(this, StaterType.Capsule);
// Cstate[] cstates = this.GetComponents<Cstate>();
foreach (var cstate in cstates)
{
AddCState(cstate, blackboard);
}
ChangeState(typeof(IdleState));
currentReceiveInput = currentState as IReceiveInput;
}
public void AddCState(Cstate cstate, CBlackBoard blackboard)
{
cstate.FSM = this;
cstate.InitState(blackboard);
CStates.Add(cstate.GetType(),cstate);
}
public void ChangeState(Type stateType)
{
currentState?.Exit();
if (!CStates.TryGetValue(stateType, out currentState)) return;
currentReceiveInput = currentState as IReceiveInput;
currentState.Enter();
}
// 이 친구는 실행할 객체에서 Update에서 호출해 줄 핢수
public void UpdateState()
{
//이 친구는 강제로 구현하게 한(인터페이스로) 각각의 상태 안에서 구현하게 할 함수
currentState?.UpdateState();
}
public void OnTriggered(string action, bool triggered)
{
currentReceiveInput?.OnTriggered(action, triggered);
}
public void OnReadValue(string action, Vector2 value)
{
currentReceiveInput?.OnReadValue(action, value);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class CapsuleController : Singleton<CapsuleController>
{
private Dictionary<string, GameObject> capsules = new Dictionary<string, GameObject>();
private GameObject currentCapsule;
IEnumerator Start()
{
yield return new WaitForSecondsRealtime(0.5f);
ChangeCapsule("Number_1");
}
public void AddObserver(string key, GameObject obj)
{
capsules.Add(key, obj);
Debug.Log(key);
}
private bool ChangeCapsule(string key)
{
if (capsules.TryGetValue(key, out var capsule))
{
currentCapsule = capsule;
return true;
}
return false;
}
public void OnTriggered(string key, bool triggered)
{
if(triggered && ChangeCapsule(key)) return;
if (!currentCapsule) return;
if (currentCapsule.TryGetComponent<CStateMachine>(out var stateMachine))
{
stateMachine.OnTriggered(key, triggered);
}
}
public void OnReadValue(string key, Vector2 value)
{
if (!currentCapsule) return;
if (currentCapsule.TryGetComponent<CStateMachine>(out var stateMachine))
{
stateMachine.OnReadValue(key, value);
}
}
}
using System.Runtime.Serialization;
using TMPro;
using UnityEngine;
public class IdleState : MonoBehaviour, Cstate, IReceiveInput
{
public CBlackBoard BlackBoard { get; set; }
public CStateMachine FSM { get; set; }
private bool jumpInputTriggered = false;
private Vector2 moveInput = Vector2.zero;
public void InitState(CBlackBoard blackBoard)
{
this.BlackBoard = blackBoard;
}
public void Enter()
{
// BlackBoard.meshRenderer.material.color = Color.white;
BlackBoard.color = Color.white;
BlackBoard.textMesh.color = Color.white;
BlackBoard.textMesh.text = "Idle";
}
public void UpdateState()
{
//무브인풋이 들어오면 상태를 바꿔줌, 이동 코드는 WalkeState에 있음
if (moveInput.sqrMagnitude > 0.0f)
{
FSM.ChangeState(typeof(WalkState));
}
if (jumpInputTriggered && BlackBoard.rb.velocity.y <=0.1f)
{
FSM.ChangeState(typeof(JumpState));
}
}
public void Exit()
{
jumpInputTriggered = false;
//moveInput = Vector2.zero;
}
public void OnTriggered(string action, bool triggered)
{
if (action == "Jump")
{
Debug.Log("Jump");
jumpInputTriggered = triggered;
}
}
public void OnReadValue(string action, Vector2 value)
{
if (action == "Move")
{
moveInput = value;
}
}
}
이 정도만 올리면 나중에 다시 공부했을 때 재구성 할 수 있을 듯 하다.

오늘도 GPT님과 대화해서 1승 ~
++ 인줄 알았으나


허허.... 그냥 중재자라는 것이 하나 더 생긴 것이겠지 하고 받아들이는 중이다.
일단 자. 곧 해뜨겠어...
++ AM 5 :22 수정
결국 observer가 데이터를 받아 오는 것은 맞으나, 스테이트 머신을 통하면 중재자 패턴으로 받아오는 것이고, 직접 통하면 옵저버 패턴이라고 생각한다.
왜냐하면, 옵저버 패턴에서 옵저버 라는 것은 알림을 받고 행동이나 기능을 하는 객체를 말하기 때문에 결국 행동은 각각의 state에서 이루어 옵저버를 state나 state를 갖고 있는 객체로 봐야하기 때문에 스테이트 머신은 옵저버라기보다 중재자 ! 라고 봐야 한다.
왜 stateMachine에 그대로 두지 않았나!
stateMachine은 이미 그 기능( 스테이트 등록, 스테이트 변화) 이 있기 때문이다.
내일 실력자 분들에게 여쭈어봐야지.
진짜 끝....
다음은 이해하려 악전고투한 그림

지금 보니 이상하네, 옵저버라는 것이 상태나 객체를 관찰해야 한다는 강박이 있었나 보다.
++ AM 6시 추가 :

진짜 이겼다 ㅋㅋ 아 꿀잠자겠다.