GOAP에 대해 좀 더 학습하기 위해 방치형 게임에서 사용될만한 행동 중 피가 적을때 SafeZone으로 이동하는 행동과 주위 가까운 적을 추적 및 공격하는 행동을 구현해보았습니다.
public Dictionary<string, object> GetWorldState()
{
int enemies = GameObject.FindGameObjectsWithTag("Enemy").Length;
bool healthLow = health.CurrentHealth < 30;
return new Dictionary<string, object> {
{ "EnemyCount", enemies },
{ "HealthLow", healthLow }
};
}
public Dictionary<string, object> CreateGoalState()
{
var goal = new Dictionary<string, object>();
if (health.CurrentHealth < 30)
goal["AtSafeZone"] = true;
else
goal["EnemyCount"] = 0;
return goal;
}
- 안전지대와 적의대한 정보를 연결하고 목표의 형태를 정의해줍니다.
using UnityEngine;
public class AttackEnemyAction : GOAPAction
{
private bool attacked = false;
private GameObject targetEnemy;
public Transform Target => targetEnemy?.transform;
public AttackEnemyAction()
{
cost = 2f;
actionRange = 1.2f;
AddPrecondition("HealthLow", false);
// 플래너가 EnemyCount를 1만큼 줄여 계산하도록 설정
AddEffect("EnemyCount", -1);
}
// 실행 시에만 실제 Enemy를 찾아 타겟 지정
public override bool CheckProceduralPrecondition(GameObject agent)
{
var enemies = GameObject.FindGameObjectsWithTag("Enemy");
if (enemies.Length == 0) return false;
float minD = Mathf.Infinity;
foreach (var e in enemies)
{
float d = (e.transform.position - agent.transform.position).sqrMagnitude;
if (d < minD)
{
minD = d;
targetEnemy = e;
}
}
attacked = false; // 이전 실행 상태 초기화
return true;
}
public override bool RequiresInRange() => true;
public override bool Perform(GameObject agent)
{
if (!attacked &&
targetEnemy != null &&
Vector3.Distance(agent.transform.position, Target.position) <= actionRange)
{
Destroy(targetEnemy);
attacked = true;
}
return attacked;
}
public override bool IsDone() => attacked;
}
- 일단은 공격처리 없이 거리만 가까워지면 Destory하도록 구현하였습니다.
- AddPrecondition("HealthLow", false); -> 이 부분은 체력이 낮지 않은 경우에만 적 처치 액션을 고려하도록 하였습니다.
using UnityEngine;
public class FleeAction : GOAPAction
{
private bool fled = false;
private GameObject safeZone;
public Transform Target => safeZone != null ? safeZone.transform : null;
public FleeAction()
{
cost = 1f;
actionRange = 0.5f;
AddPrecondition("HealthLow", true);
AddEffect("AtSafeZone", true);
}
public override bool CheckProceduralPrecondition(GameObject agent)
{
safeZone = GameObject.FindWithTag("SafeZone");
if (safeZone == null) return false;
fled = false;
return true;
}
public override bool RequiresInRange() => true;
public override bool Perform(GameObject agent)
{
if (!fled && safeZone != null &&
Vector3.Distance(agent.transform.position, safeZone.transform.position) <= actionRange)
{
fled = true;
}
return fled;
}
public override bool IsDone() => fled;
}
- 체력이 낮은 경우에만 액션이 고려되도록 하였습니다.

NPC와 Player의 행동패턴으로 사용해보고 싶다는 생각이 드는 학습이였습니다.
특히 플레이어의 조작이 필요치 않는 또는 최소한의 동작만 허용되는 방치형 게임에서 사용하면 좋을 것 같다는 생각이 듭니다.