몬스터의 패턴을 정의하는 작업을 진행하는 작업을 (또) 진행했다.
현재까지 구현한 목록으론
1. 몬스터 대기
2. 몬스터 이동
3. 몬스터 피격
몬스터의 행동에 있어서 FSM을 쓸지 BT를 쓸지 고민을 했다. 둘다 장단이 있지만 AI 스크립팅에 익숙해지고 싶은 것과 복잡한 패턴을 구현하기 좋은 BT를 택하기로 했다.
BT를 구현하는데 있어서 다른 몬스터에 적용하기 쉽도록 기존과 다른 방안이 필요할거라는 생각이 들었다.
특정 패턴을 컴포넌트화라던지 해서 다른 적을 구현할 때 해당 컴포넌트(혹은 더 낮은 개념인 클래스나 기타 등등)로 붙여서 조립하여 완성하는 느낌을 들도록 말이다...
현재 지금은 한 클래스에서 각 노드를 상세히 구현하도록 되어 있다. 추후에 이 것을 모듈화하는 작업이 필요할지도 모르겠다. 혹은 BTRunner를 적 마다 새로이 구현하는 것도 생각해 봐야겠다.
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.XR;
public class EnemyBT : MonoBehaviour, IDamagable
{
private Animator animator;
private StatusComponent status;
private BTRunner btRunner;
public bool bWalk = false;
public bool bAttack = false;
public bool bChase = false;
private void Awake()
{
animator = GetComponent<Animator>();
status = GetComponent<StatusComponent>();
btRunner = new BTRunner(SettingBT());
}
private void Update()
{
btRunner?.Operate();
}
#region BT Seting
BTNode SettingBT() => new SelectorNode
(
new List<BTNode>
{
new ActionNode(CheckDead),
new SelectorNode
(
new List<BTNode>
{
new ActionNode(DoWaiting),
new ActionNode(DoPatrol),
}
)
}
);
#endregion
#region Idle
[SerializeField]
private float waitTime = 3.0f;
public float currentWaitTime;
private BTNode.NodeState DoWaiting()
{
if (bWalk == true)
return BTNode.NodeState.FAILURE;
if(currentWaitTime > 0)
{
currentWaitTime -= Time.deltaTime;
bWalk = false;
animator.SetBool("IsWalking", bWalk);
return BTNode.NodeState.RUNNING;
}
currentWaitTime = waitTime;
return BTNode.NodeState.FAILURE;
}
#endregion
#region Patrol
[SerializeField]
private float patrolDistance; // 탐지 거리
private float minDistance = 3.0f; // 최소 탐지 거리
private Vector3 destination; // 도착지점
private bool bArrive = true;
public bool Arrive
{
get
{
return bArrive;
}
}
// 일정 범위 랜덤 위치 구하기
private Vector3 GetRandomPosByAround()
{
float x = Random.Range(-patrolDistance, patrolDistance) * minDistance;
float z = Random.Range(-patrolDistance, patrolDistance) * minDistance;
// 지정한 곳이 이동할 수 있는 구역인지 검사
Ray ray = new Ray();
ray.origin = transform.position + new Vector3(x, 1.5f, z);
ray.direction = Vector3.down;
RaycastHit hit;
if (Physics.Raycast(ray, out hit,
Mathf.Infinity, LayerMask.GetMask("Ground")))
{
return new Vector3(x, 0, z);
}
return Vector3.zero;
}
private void MoveToDest(Vector3 destination)
{
Vector3 direction = destination - transform.position;
direction = direction.normalized * status.Speed;
// 움직이게함
transform.Translate(direction * Time.deltaTime, Space.World);
// 지정한 방향으로 돌린다.
Quaternion targetRatation = Quaternion.LookRotation(direction, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRatation, 3.0f);
animator.SetBool("IsWalking", bWalk);
}
private BTNode.NodeState DoPatrol()
{
if (Vector3.Distance(transform.position, destination) <= 0.001f)
bArrive = true;
if (bArrive == true)
{
destination = transform.position + GetRandomPosByAround();
Debug.DrawLine(transform.position, this.destination,
Color.red, 3.0f);
bArrive = false;
bWalk = false;
animator.SetBool("IsWalking", bWalk);
return BTNode.NodeState.SUCCESS;
}
bWalk = true;
MoveToDest(destination);
return BTNode.NodeState.RUNNING;
}
#endregion
#region Detect
#endregion
#region Attack
#endregion
#region
private BTNode.NodeState CheckDead()
{
if (status.Dead == true)
return BTNode.NodeState.SUCCESS;
return BTNode.NodeState.FAILURE;
}
#endregion
public void Damage(GameObject attacker, Sword causer, float power)
{
status.Damage(power);
if (status.Dead == false)
{
animator.SetTrigger("Damaged");
return;
}
animator.SetTrigger("Dead");
Collider collider = GetComponent<Collider>();
collider.enabled = false;
Destroy(this, 5.0f);
}
}
'개발 > Unity' 카테고리의 다른 글
0509 Monster Pattern (0) | 2024.05.09 |
---|---|
Monster Pattern 0508 작업 (0) | 2024.05.08 |
[Unity] 벡터의 내적과 외적 (0) | 2024.04.30 |
프리팹(Prefab) (0) | 2024.04.27 |
레거시 / 휴머노이드 애니메이션 차이 (0) | 2024.04.26 |