AI Behavior Tree (2)
이번에는 다음 부분을 구현할 것이다.
IsDetectEnemy
이 노드는 하는 일이 많다. 추후에 수정할 것이다.
Success
- redZone 에 Enemy 가 존재하는지 확인한다. 있으면 enemyObject 로 지정 및 SUCCESS -> 발포
- redZone 에 Enemy 가 없으면, yellowZone 에서 확인한다. 있으면 seekLevel 을 올린다. 80이 넘을 시 enemyObject 로 지정해 yellowZone 에 있을 때에도 SUCCESS -> 발포
Failure
- yellowZone 에 있고, seekLevel 이 80 미만일 시 FAILURE
- yellowZone 에도 없고, redZone 에도 없으면 FAILURE
public class IsDetectEnemy : Node
{
private EnemyRobotBT ebt;
private Timer detectLevelStayTimer;
public IsDetectEnemy(BehaviorTree bt) : base(bt)
{
ebt = (EnemyRobotBT)bt;
detectLevelStayTimer = new Timer(2f, () =>
{
ebt.ai.detectLevel.decTimer.active = true;
detectLevelStayTimer.active = false;
detectLevelStayTimer.Reset();
});
detectLevelStayTimer.active = false;
}
public override NodeState Evaluate()
{
if (!ebt.ai.isHit)
{
var redZoneEnemy = ebt.ai.visonSensor.redZoneObjectList.Find(o => o != null && !o.GetComponent<RobotStatusController>().isDeath && o.name == "Player");
ebt.ai.enemyObject = redZoneEnemy;
if (!redZoneEnemy)
{
var yellowZoneEnemy = ebt.ai.visonSensor.yellowZoneObjectList.Find(o =>
o != null && !o.GetComponent<RobotStatusController>().isDeath && o.name == "Player");
if (yellowZoneEnemy)
{
ebt.ai.detectLevel.incTimer.active = true;
ebt.ai.detectLevel.decTimer.active = false;
if (ebt.ai.detectLevel.currentLevel >= 80)
{
ebt.ai.enemyObject = yellowZoneEnemy;
}
}
else
{
if (ebt.ai.detectLevel.incTimer.active)
{
detectLevelStayTimer.active = true;
}
ebt.ai.detectLevel.incTimer.active = false;
}
}
else
{
ebt.ai.detectLevel.currentLevel = 100;
}
}
if (ebt.ai.enemyObject)
{
ebt.ai.seekLevel.currentLevel = 100;
ebt.ai.seekLevel.decTimer.active = false;
ebt.ai.lastEnemyPosition = ebt.ai.enemyObject.transform.position;
ebt.ai.seekPointReached = false;
ebt.ai.closestCoverPoint = null;
}
else
{
ebt.ai.seekLevel.decTimer.active = true;
}
detectLevelStayTimer.Update();
DebugExtension.DebugWireSphere(ebt.ai.lastEnemyPosition, Color.cyan, 0.5f);
return ebt.ai.enemyObject ? NodeState.SUCCESS : NodeState.FAILURE;
}
}
바깥이 YelloZone, 안쪽이 RedZone 이다.
YelloZone 에 오래 머물거나 RedZone 에 진입하면 발포한다.
TakeDistance, Aim
AI가 AI와 enemyObject 간의 거리를 일정 거리를 유지하려고 하게 만드는 코드이다.
public class TakeDistance : Node
{
private EnemyRobotBT ebt;
public TakeDistance(BehaviorTree bt) : base(bt)
{
ebt = (EnemyRobotBT)bt;
}
public override NodeState Evaluate()
{
Vector3 direction = ebt.ai.enemyObject.transform.position - ebt.ai.transform.position;
float distance = direction.magnitude;
direction.Normalize();
switch (distance)
{
case < 7.5f:
ebt.ai.StartMove(ebt.ai.transform.position - direction * 5);
break;
case > 10:
ebt.ai.StartMove(ebt.ai.transform.position + direction * 5);
break;
default:
ebt.ai.StopMove();
break;
}
return NodeState.SUCCESS;
}
}
public class Aim : Node
{
private EnemyRobotBT ebt;
public Aim(BehaviorTree bt) : base(bt)
{
ebt = (EnemyRobotBT)bt;
}
public override NodeState Evaluate()
{
ebt.ai.inputHandler.isAim = true;
ebt.ai.navAgent.speed = ebt.ai.inputHandler.maxSpeed / 2;
return NodeState.SUCCESS;
}
}
앞으로 돌격하면 뒤로 물러나고, 도망가면 쫒아온다.
NeedReload
gunController 에 직접 접근해서 남은 탄약을 확인하고,
재장전이 필요하면 SUCCESS, 필요하지 않으면 FAILURE 를 리턴한다.
public class NeedReload : Node
{
private EnemyRobotBT ebt;
public NeedReload(BehaviorTree bt) : base(bt)
{
ebt = (EnemyRobotBT)bt;
}
public override NodeState Evaluate()
{
bool needReload = ebt.ai.gunController.ammoSystem.magAmmo <= 0;
return needReload ? NodeState.SUCCESS : NodeState.FAILURE;
}
}
Reload
실제로 재장전한다.
public class Reload : Node
{
private EnemyRobotBT ebt;
public Reload(BehaviorTree bt) : base(bt)
{
ebt = (EnemyRobotBT)bt;
}
public override NodeState Evaluate()
{
if (!ebt.ai.GetComponent<Animator>().GetBool("isReload"))
{
ebt.ai.inputHandler.Reload();
}
return NodeState.RUNNING;
}
}
Fire
발포한다.
public class Fire : Node
{
private EnemyRobotBT ebt;
public Fire(BehaviorTree bt) : base(bt)
{
ebt = (EnemyRobotBT)bt;
}
public override NodeState Evaluate()
{
ebt.ai.inputHandler.isFire = true;
return NodeState.RUNNING;
}
}