GOAP (2)

JJW·2025년 5월 28일
0

Unity

목록 보기
32/34

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;
    }
  • 안전지대와 적의대한 정보를 연결하고 목표의 형태를 정의해줍니다.

Enemy 추적

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); -> 이 부분은 체력이 낮지 않은 경우에만 적 처치 액션을 고려하도록 하였습니다.

SafeZone 이동

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;
}
  • 체력이 낮은 경우에만 액션이 고려되도록 하였습니다.

테스트 영상

Github Code

Github


느낀 점

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

profile
Unity 게임 개발자를 준비하는 취업준비생입니다..

0개의 댓글