
아래 링크로 접속 및 확인 부탁 드립니다.
https://youtube.com/shorts/UocXlYL7Y08?si=yI8fvgybkNM3nrSF
Interface로 접근하게 하여 Framework 화 시키는 것이 좋을 것 같음.
Idle; 몬스터가 가만히 있거나 주변을 정찰하는 상태Chasing; 플레이어를 추적하는 상태AttackDelay; 플레이어에게 공격 전 준비 상태Attack; 플레이어에게 공격을 하는 상태Dead; 사망 상태Damaged; 플레이어에게 공격을 받는 상태State-Independent Event 필요 이유

Blackboard 데이터 생성 관리 및 Script Component 내 함수 실행에만 책임이 있음.
State Machine 내 xNode에서 제공되는 Graph 클래스가 Pure C# 로 이루어진 Node class 를 모두 지님.
추후 Node 추가 용이.
Node 간 통신
// EnemyNodeIdle.cs
using XNode;
public class NodeEnemyIdle : EnemyBaseNode
{
[Input] public EnemyStateConnection entry;
[Output] public EnemyStateConnection exitToFollowingPlayer;
[Output] public EnemyStateConnection exitToDead;
public override string Execute(EnemyBlackboard blackboard)
{
blackboard.IsIdle = false;
string transitionName;
transitionName = ToFollowingToPlayer(blackboard);
if (transitionName != null) return transitionName; // "exitToFollowingPlayer"
return null;
}
}
Input / output 으로 정의된 이름을 활용. (주로 port라는 용어 활용)Return String 값이 각 port의 이름과 같으면 상태가 전이됨.// EnemyStatMachine.cs 의 일부.
string portName = (_currentNode as EnemyBaseNode)?.Execute(_blackboard);
if (portName != null) _currentNode = _currentNode.GetOutputPort(portName).Connection.node; Agent; Blackboard
INeedBlackboard Interface 를 통해 필요한 Class 에 Blackboard 객체 전달. (Dependency Injection 후술)Action으로 연결된 Monobehaviour 클래스가 실제 Enemy 의 Action 을 담당하게됨 추후 관련 Action 연결 용이.
// EnemyBlackboard.cs 의 일부.
private bool _isIdle;
public bool IsIdle
{
get { return _isIdle; }
set
{
_isIdle = value;
if (value) OnIdle?.Invoke();
}
}
public Action OnIdle;
// EnemyAgent.cs 의 일부.
_blackboard.OnIdle += OnPatrol;
Attack; 공격 Action 용 클래스
Data Dispatcher 후술)
BlackBoard 주입을 통한 데이터 공유.
주로 객체 내 데이터 공유 역할 담당
Enemy 및 New Weapon 관련 Code 에서 활용 중.
// EnemyBlackboard.cs 의 일부.
public class EnemyBlackboard
{
public readonly EnemyData origin; // Scriptable Object 의 Data Class
public int currentHp;
public EnemyBlackboard(EnemyData origin, EnemyAgent agent)
{
this.origin = origin;
this.agent = agent;
}
}
데이터는 해당 객체의 중심 역할을 하는 Script 에서 Blackboard 에 대한 생성 및 파괴를 담당.
그 외 Script 에서는 해당 Blackboard 를 전달 받아 활용
// EnemyStateMachine.cs 의 일부.
if (_blackboard == null)
_blackboard = new EnemyBlackboard(_originData, _agent);
_blackboard.Init();
_agent.SetBlackBoard(_blackboard);
// EnemyAgent.cs 의 일부
_damagedScript.SetBlackboard(blackboard);
// EnemyDamaged.cs 의 일부.
public class EnemyDamaged : MonoBehaviour, IDamageable, INeedEnemyBlackboard
{
private EnemyBlackboard blackboard;
public void SetBlackboard(EnemyBlackboard blackboard)
{
this.blackboard = blackboard;
}
public void TakeDamage(DamageType type, int damage)
{
blackboard.currentHp -= damage;
}
}

객체와 객체간 데이터 통신이 필요한 경우 사용.
플레이어 위치 정보 갱신, Object Pool 과의 통신, 플레이어 및 무기 데이터 업그레이드 등 많은 곳에 사용됨.
Generic 타입으로 선언됨. struct로 메시지를 정의 후 전달하는 것을 권장. PostManager라는 클래스 명이 탄생함.// PostManager.cs 의 일부
public class PostManager : Singleton<PostManager>
{
// Post Channel
public void Post<T>(PostMessageKey key, T data) { ... }
public void Subscribe<T>(PostMessageKey key, Action<T> callback) { ... }
public void Unsubscribe<T>(PostMessageKey key, Action<T> callback) { ... }
// Request Channel
public TRes Request<TReq, TRes>(PostMessageKey key, TReq data) { ... }
public void Subscribe<TReq, TRes>(PostMessageKey key, Func<TReq, TRes> callback) { ... }
public void Unsubscribe<TReq, TRes>(PostMessageKey key, Func<TReq, TRes> callback) { ... }
}
Generic 관련 설계 이슈Object Type 으로 선언하려 하였음.upcasting 가능.struct 의 경우 Boxing 문제 발생 -> 지나치게 많아지므로 object Type 폐기.Dictionary 선언 시 Generic 사용Dictionanry 자체가 Generic을 통한 설계가 되어 있어 선언시 Generic으로 선언하는 것이 불가능Dictionary<string, Action<int>> dict01 = new();
Dictionary<string, Action<T>> dict02 = new();
// ---------- 실행 결과
/*
Build with surface heuristics started at 21:09:41
Use build tool: C:\Program Files\dotnet\sdk\10.0.104\MSBuild.dll
CONSOLE: msbuild 버전 18.0.11+80d3e14f5(.NET용)
CONSOLE: 빌드 시작: 2026-03-26 오후 9:09:41
CONSOLE: 1 노드의 "C:\Users\mybeang\AppData\Local\Temp\Jatowup.proj" 프로젝트(기본 대상)입니다.
CONSOLE: ControllerTarget:
CONSOLE: Run controller from C:\Program Files\JetBrains\Rider\r2r\2025.3.0R\BCF63A397BE5DD45D53EE49FB08CD49\JetBrains.Platform.MsBuildTask.v17.dll
0>------- Started building project: Sandbox
-- skip --
0>Program.cs(37,35): Error CS0246 : 'T' 형식 또는 네임스페이스 이름을 찾을 수 없습니다. using 지시문 또는 어셈블리 참조가 있는지 확인하세요.
0>------- Finished building project: Sandbox. Succeeded: False. Errors: 1. Warnings: 0
Build completed in 00:00:02.636
*/public interface IPostMessages { }
class PostMessages<T> : IPostMessages
{
public Action<T> actions;
}
// 선언시 -------------
Dictionary<string, Action<int>> dict01 = new();
// 문제 없음.
Dictionary<string, Action<IPostMessages>> dict02 = new();C# Action 을 기반으로 1:N 의 통신이 가능하도록 구성.
Action 기반이기 때문에 반환 값 없음 (void type)
단방향 통신.
예시)
// PlayerController.cs 의 일부
PostManager.Instance.Post(PostMessageKey.PlayerPosition, transform.position);
// BossMovement.cs 의 일부
PostManager.Instance.Subscribe<Vector2>(PostMessageKey.PlayerPosition, UpdateMoveDirection);
C# Func 을 기반으로 1:1 의 통신이 가능하도록 구성.
Func 기반이기 때문에 반환 값 요구
단방향 통신.
예시)
// StatusUIController.cs 의 일부
float moveSpeed = PostManager.Instance.Request<bool, float>(PostMessageKey.PlayerStatusUIPlayer, true);
// PlayerState.cs
PostManager.Instance.Subscribe<bool, float>(PostMessageKey.PlayerStatusUIPlayer, PostMoveSpeed);
public enum PostMessageKey
{
PlayerPosition, // Player 의 위치정보를 받기 위한 Position 데이터
EnemySpawned, // Enemy 사망 후 Despawn
// ... 왜 30 개 이상 채널에서 사용 됨.
}
dummy data를 보낼 필요가 있음.PostTrigger, RequestTrigger