working on humbies ai
This commit is contained in:
@@ -6,6 +6,7 @@ using RebootKit.Engine.Extensions;
|
||||
using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Network;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using RebootReality.jelycho.Beacons;
|
||||
using RebootReality.jelycho.Player;
|
||||
using TriInspector;
|
||||
using Unity.Mathematics;
|
||||
@@ -61,33 +62,65 @@ namespace RebootReality.jelycho.Enemies {
|
||||
}
|
||||
}
|
||||
|
||||
public class ZombieBlackboard {
|
||||
public ZombieActor Self;
|
||||
class ZombiePickVictim : IStrategy {
|
||||
public BehaviourNode.Status Process(Actor target, float dt) {
|
||||
if (target is not ZombieActor zombie) {
|
||||
return BehaviourNode.Status.Failure;
|
||||
}
|
||||
|
||||
Debug.Log("Picking victim");
|
||||
|
||||
Actor victim = zombie.FindNewVictim();
|
||||
if (victim == null) {
|
||||
return BehaviourNode.Status.Failure;
|
||||
}
|
||||
|
||||
return BehaviourNode.Status.Success;
|
||||
}
|
||||
}
|
||||
|
||||
class ZombieGoToPlayer : IStrategy {
|
||||
public BehaviourNode.Status Process(Actor target, float dt) {
|
||||
if (target is not ZombieActor zombie) {
|
||||
return BehaviourNode.Status.Failure;
|
||||
}
|
||||
|
||||
Debug.Log("Goto player");
|
||||
|
||||
if (!zombie.HasTravelDestination) {
|
||||
float3 victimPos = zombie.Victim.transform.position;
|
||||
float dstToVictimSq = math.distancesq(victimPos, zombie.transform.position);
|
||||
|
||||
if (dstToVictimSq < 1.0f) {
|
||||
return BehaviourNode.Status.Success;
|
||||
}
|
||||
|
||||
zombie.GoTo(victimPos);
|
||||
return BehaviourNode.Status.Running;
|
||||
}
|
||||
|
||||
return BehaviourNode.Status.Running;
|
||||
}
|
||||
}
|
||||
|
||||
[DeclareBoxGroup("Body parts")]
|
||||
[DeclareBoxGroup("Animations")]
|
||||
public class ZombieActor : Actor, IKillable {
|
||||
static readonly Logger s_Logger = new Logger(nameof(ZombieActor));
|
||||
|
||||
static readonly int s_MovementSpeedHash = Animator.StringToHash("MovementSpeed");
|
||||
|
||||
enum AIState {
|
||||
Idle,
|
||||
Dead,
|
||||
AttackBase,
|
||||
AttackCharacter,
|
||||
PanicEscape,
|
||||
Berserk
|
||||
}
|
||||
|
||||
[SerializeField] AnimancerComponent m_Animancer;
|
||||
|
||||
[SerializeField] NavMeshAgent m_NavAgent;
|
||||
public NavMeshAgent NavAgent {
|
||||
get {
|
||||
return m_NavAgent;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] Collider m_RootCollider;
|
||||
[SerializeField] Rigidbody[] m_RagdollRigidbodies;
|
||||
|
||||
[SerializeField] float m_MaxAttackDistance = 1.0f;
|
||||
[SerializeField] float m_MaxAttackDistance = 2.0f;
|
||||
[SerializeField] float m_LoseInterestMinDistance = 10.0f;
|
||||
[SerializeField] ulong m_BaseDamage = 10;
|
||||
[SerializeField] float m_AttackDelay = 1.0f;
|
||||
@@ -97,31 +130,50 @@ namespace RebootReality.jelycho.Enemies {
|
||||
[SerializeField, Group("Body parts")] ZombieBodyPart m_RightArm;
|
||||
[SerializeField, Group("Body parts")] ZombieBodyPart m_LeftLeg;
|
||||
[SerializeField, Group("Body parts")] ZombieBodyPart m_RightLeg;
|
||||
|
||||
AIState m_State = AIState.Idle;
|
||||
|
||||
PlayerActor m_PlayerTarget;
|
||||
float m_NextAttackTimer;
|
||||
|
||||
public UnityEvent died = new UnityEvent();
|
||||
[SerializeField, Group("Animations")] AnimationClip[] m_AttackClips;
|
||||
|
||||
BehaviourTree m_BehaviourTree;
|
||||
|
||||
public enum MindState {
|
||||
Normal,
|
||||
RunAway,
|
||||
Berserk
|
||||
}
|
||||
public bool IsRagdoll { get; private set; } = false;
|
||||
public MindState Mind { get; private set; } = MindState.Normal;
|
||||
public Actor Victim { get; private set; }
|
||||
|
||||
public bool HasTravelDestination { get; private set; }
|
||||
public float3 TravelDestination { get; private set; }
|
||||
|
||||
public UnityEvent died = new UnityEvent();
|
||||
|
||||
//
|
||||
// @MARK: Unity callbacks
|
||||
//
|
||||
void Awake() {
|
||||
SetRagdollLocal(false);
|
||||
SetRagdollLocal(IsRagdoll);
|
||||
|
||||
m_BehaviourTree = new BehaviourTree("Zombie Behaviour");
|
||||
|
||||
var rootSelector = new Selector("Root");
|
||||
m_BehaviourTree.AddChild(rootSelector);
|
||||
|
||||
var attackPlayerSequence = new Sequence("Attack Player",
|
||||
() => m_PlayerTarget != null);
|
||||
rootSelector.AddChild(attackPlayerSequence);
|
||||
|
||||
rootSelector.AddChild(CreateNormalSequence());
|
||||
}
|
||||
|
||||
BehaviourNode CreateNormalSequence() {
|
||||
var normalSequence = new Sequence("Normal", () => Mind == MindState.Normal);
|
||||
normalSequence.AddChild(new Leaf("Pick Victim", new ZombiePickVictim()));
|
||||
|
||||
var attackPlayerSequence = new Sequence("Attack Player", IsVictimPlayer);
|
||||
normalSequence.AddChild(attackPlayerSequence);
|
||||
attackPlayerSequence.AddChild(new Leaf("Go to Player", new ZombieGoToPlayer()));
|
||||
|
||||
var attackMotherSequence = new Sequence("Attack Mother", IsVictimMother);
|
||||
normalSequence.AddChild(attackMotherSequence);
|
||||
return normalSequence;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -146,107 +198,69 @@ namespace RebootReality.jelycho.Enemies {
|
||||
return;
|
||||
}
|
||||
|
||||
m_BehaviourTree.Process(deltaTime);
|
||||
return;
|
||||
if (HasTravelDestination) {
|
||||
float3 pos = transform.position;
|
||||
|
||||
switch (m_State) {
|
||||
case AIState.Idle: {
|
||||
ServerTickIdle(deltaTime);
|
||||
break;
|
||||
}
|
||||
|
||||
case AIState.AttackBase: {
|
||||
ServerTickAttackBase(deltaTime);
|
||||
break;
|
||||
}
|
||||
|
||||
case AIState.AttackCharacter: {
|
||||
ServerTickAttackCharacter(deltaTime);
|
||||
break;
|
||||
}
|
||||
|
||||
case AIState.PanicEscape: {
|
||||
break;
|
||||
if (math.distancesq(pos, TravelDestination) <= 1.0f) {
|
||||
HasTravelDestination = false;
|
||||
}
|
||||
}
|
||||
|
||||
case AIState.Berserk: {
|
||||
ServerTickBerserk(deltaTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_BehaviourTree.Process(this, deltaTime);
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Zombie
|
||||
//
|
||||
void ServerTickIdle(float dt) {
|
||||
public bool GoTo(float3 pos) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only server can call GoTo");
|
||||
return false;
|
||||
}
|
||||
|
||||
TravelDestination = pos;
|
||||
HasTravelDestination = true;
|
||||
return m_NavAgent.SetDestination(TravelDestination);
|
||||
}
|
||||
|
||||
public bool IsVictimPlayer() {
|
||||
return Victim is PlayerActor;
|
||||
}
|
||||
|
||||
public bool IsVictimMother() {
|
||||
return Victim is MotherActor;
|
||||
}
|
||||
|
||||
public Actor FindNewVictim() {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only server can call FindNewVictim");
|
||||
return null;
|
||||
}
|
||||
|
||||
Victim = null;
|
||||
|
||||
(PlayerActor playerActor, float distSqToPlayer) = FindClosestPlayerActor(transform.position);
|
||||
if (playerActor == null || distSqToPlayer >= m_LoseInterestMinDistance * m_LoseInterestMinDistance) {
|
||||
return;
|
||||
if (playerActor != null && distSqToPlayer < m_LoseInterestMinDistance * m_LoseInterestMinDistance) {
|
||||
Victim = playerActor;
|
||||
return Victim;
|
||||
}
|
||||
|
||||
m_State = AIState.AttackCharacter;
|
||||
m_PlayerTarget = playerActor;
|
||||
if (RR.World.Context is WorldContext ctx) {
|
||||
Victim = ctx.BaseManager.Mother;
|
||||
return Victim;
|
||||
}
|
||||
|
||||
s_Logger.Info($"Found player actor to attack: {m_PlayerTarget}");
|
||||
m_NavAgent.SetDestination(m_PlayerTarget.transform.position);
|
||||
m_NavAgent.isStopped = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
void ServerTickAttackCharacter(float dt) {
|
||||
if (m_PlayerTarget == null || !m_PlayerTarget.IsAlive()) {
|
||||
SetIdleState();
|
||||
return;
|
||||
}
|
||||
|
||||
float3 playerPos = m_PlayerTarget.transform.position;
|
||||
float3 zombiePos = transform.position;
|
||||
|
||||
float distToPlayerSq = math.distancesq(playerPos, zombiePos);
|
||||
if (distToPlayerSq >= m_LoseInterestMinDistance * m_LoseInterestMinDistance) {
|
||||
SetIdleState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (distToPlayerSq <= m_MaxAttackDistance * m_MaxAttackDistance) {
|
||||
m_NextAttackTimer -= dt;
|
||||
if (m_NextAttackTimer <= 0.0f) {
|
||||
// m_Animator.CrossFade("Attack_0", 0.0f, 0);
|
||||
m_NextAttackTimer = m_AttackDelay;
|
||||
}
|
||||
|
||||
if (!m_NavAgent.isStopped) {
|
||||
m_NavAgent.isStopped = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float distFromDstToTargetSq = math.distancesq(playerPos, m_NavAgent.destination);
|
||||
if (distFromDstToTargetSq > 1.0f) {
|
||||
m_NavAgent.isStopped = false;
|
||||
m_NavAgent.SetDestination(m_PlayerTarget.transform.position);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerTickAttackBase(float dt) {
|
||||
|
||||
}
|
||||
|
||||
void ServerTickBerserk(float dt) {
|
||||
|
||||
}
|
||||
|
||||
void SetIdleState() {
|
||||
m_PlayerTarget = null;
|
||||
m_State = AIState.Idle;
|
||||
public void PerformAttack() {
|
||||
m_Animancer.Play(m_AttackClips.Random());
|
||||
}
|
||||
|
||||
void Die() {
|
||||
s_Logger.Info("Die");
|
||||
EnableRagdoll();
|
||||
m_NavAgent.enabled = false;
|
||||
|
||||
m_State = AIState.Dead;
|
||||
died.Invoke();
|
||||
}
|
||||
|
||||
@@ -281,7 +295,7 @@ namespace RebootReality.jelycho.Enemies {
|
||||
}
|
||||
|
||||
protected override void OnActorEventClient(ActorEvent actorEvent) {
|
||||
ZombieActorEvents zombieEvent = (ZombieActorEvents) actorEvent.EventID;
|
||||
var zombieEvent = (ZombieActorEvents) actorEvent.EventID;
|
||||
|
||||
switch (zombieEvent) {
|
||||
case ZombieActorEvents.EnableRagdoll: {
|
||||
|
||||
Reference in New Issue
Block a user