|
|
@@ -9,13 +9,6 @@ public class Wolf : TargetAnimal
|
|
|
AnimationPlayer ap;
|
|
|
//寻路代理
|
|
|
NavMeshAgent agent;
|
|
|
- //血量
|
|
|
- [System.NonSerialized] public int hp = 1;
|
|
|
- [SerializeField] Material[] materials;
|
|
|
-
|
|
|
- public void ChangeColorByType(int type) {
|
|
|
- GetComponentInChildren<SkinnedMeshRenderer>().material = materials[type - 1];
|
|
|
- }
|
|
|
|
|
|
void Awake()
|
|
|
{
|
|
|
@@ -25,254 +18,342 @@ public class Wolf : TargetAnimal
|
|
|
|
|
|
void Start()
|
|
|
{
|
|
|
- //初始动画
|
|
|
- playAniForIdle();
|
|
|
- //初始化自动策略
|
|
|
+ initAniListener();
|
|
|
InitAutoStrategy();
|
|
|
}
|
|
|
|
|
|
+
|
|
|
void Update()
|
|
|
{
|
|
|
- //寻路过程监测
|
|
|
if (HasCloseToDestination()) {
|
|
|
OnReachDestination();
|
|
|
- } else {
|
|
|
- OnMovingToDestination();
|
|
|
}
|
|
|
- UpdateAction();
|
|
|
- UpdateActionTime();
|
|
|
UpdateAutoStrategy();
|
|
|
+ UpdateAction();
|
|
|
}
|
|
|
|
|
|
- public void Disappear() {
|
|
|
- Destroy(this.gameObject);
|
|
|
+ //可选皮肤材质
|
|
|
+ [SerializeField] Material[] materials;
|
|
|
+ public void ChangeColorByType(int type) {
|
|
|
+ GetComponentInChildren<SkinnedMeshRenderer>().material = materials[type - 1];
|
|
|
}
|
|
|
|
|
|
public override void OnHit(Arrow arrow, Vector3 hitPoint, string partName)
|
|
|
{
|
|
|
arrow.Head().position = hitPoint + arrow.transform.forward * 0.1f;
|
|
|
arrow.Hit();
|
|
|
- if (partName == "Leg" || partName == "Tail") hp -= 2;
|
|
|
- else if (partName == "Body") hp -= 3;
|
|
|
- else if (partName == "Head") hp -= 100;
|
|
|
- if (hp <= 0) {
|
|
|
- Die(arrow);
|
|
|
- } else {
|
|
|
+ if (partName == "Leg" || partName == "Tail") {
|
|
|
+ state.hp -= 2;
|
|
|
+ }
|
|
|
+ else if (partName == "Body") {
|
|
|
+ state.hp -= 3;
|
|
|
+ }
|
|
|
+ else if (partName == "Head") {
|
|
|
+ state.hp -= 100;
|
|
|
+ }
|
|
|
+ if (state.hp > 0) {
|
|
|
Hurt();
|
|
|
+ } else {
|
|
|
+ Die(arrow);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void Die(Arrow arrow) {
|
|
|
- if (dead) return;
|
|
|
- arrow.onDoNextShoot += delegate() {
|
|
|
- Disappear();
|
|
|
- };
|
|
|
- dead = true;
|
|
|
+ if (state.dead) return;
|
|
|
+ arrow.onDoNextShoot += delegate() { Destroy(this.gameObject); };
|
|
|
+ state.ResetActionState();
|
|
|
+ state.dead = true;
|
|
|
this.agent.enabled = false;
|
|
|
- PuaseAutoStrategy();
|
|
|
onDie?.Invoke(this);
|
|
|
AudioMgr.ins.PlayAnimalEffect("wolf_die", AudioMgr.GetAudioSource(this.gameObject));
|
|
|
}
|
|
|
|
|
|
void Hurt() {
|
|
|
- addHurtFlag();
|
|
|
- PuaseAutoStrategy();
|
|
|
- RunAwayFromHunter();
|
|
|
+ state.lockingTarget = true;
|
|
|
+ state.ResetActionState();
|
|
|
AudioMgr.ins.PlayAnimalEffect("wolf_injured", AudioMgr.GetAudioSource(this.gameObject));
|
|
|
}
|
|
|
-
|
|
|
- JC.CS.CountLocker hurtFlag = new JC.CS.CountLocker();
|
|
|
- void addHurtFlag() {
|
|
|
- hurtFlag.Lock();
|
|
|
- Invoke("clearOneHurtFlag", 5);
|
|
|
- }
|
|
|
- void clearOneHurtFlag() {
|
|
|
- hurtFlag.Unlock();
|
|
|
- if (hurtFlag.IsReleased()) {
|
|
|
- onHurtFlagTimeout();
|
|
|
- }
|
|
|
- }
|
|
|
- void onHurtFlagTimeout() {
|
|
|
- if (!dead) ResumeAutoStrategy();
|
|
|
- }
|
|
|
-
|
|
|
- //是否已经死亡
|
|
|
- bool dead = false;
|
|
|
- //是否处于移动状态
|
|
|
- bool _moving = false;
|
|
|
- bool moving {
|
|
|
- get {
|
|
|
- return _moving;
|
|
|
- }
|
|
|
- set {
|
|
|
- if (_moving && !value) {
|
|
|
- movingTime = 0;
|
|
|
-
|
|
|
- }
|
|
|
- if (!_moving && value) stayingTime = 0;
|
|
|
- _moving = value;
|
|
|
- }
|
|
|
- }
|
|
|
- //实时记录“单次移动的持续时间”
|
|
|
- float movingTime = 0;
|
|
|
- //实时停留时间
|
|
|
- float stayingTime = 0;
|
|
|
+
|
|
|
//启动寻路
|
|
|
void SetDestination(Vector3 pos) {
|
|
|
- moving = true;
|
|
|
+ state.ResetActionState();
|
|
|
+ state.moving = true;
|
|
|
this.agent.destination = pos;
|
|
|
}
|
|
|
- //寻路过程
|
|
|
- void OnMovingToDestination() {
|
|
|
- if (!moving) return;
|
|
|
- }
|
|
|
//寻路结束
|
|
|
void OnReachDestination() {
|
|
|
- if (!moving) return;
|
|
|
- moving = false;
|
|
|
+ if (!state.moving) return;
|
|
|
+ state.ResetActionState();
|
|
|
}
|
|
|
//是否已经接近目的地(寻路完成判断)
|
|
|
bool HasCloseToDestination() {
|
|
|
- if (!moving) return true;
|
|
|
- if (movingTime > 0.1) {
|
|
|
- if (this.agent.velocity.magnitude < 0.05f) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
- //更新动作
|
|
|
- void UpdateAction() {
|
|
|
- if (dead) {
|
|
|
- if (!isAniDie()) playAniDie();
|
|
|
- } else if (moving) {
|
|
|
- if (!isAniRun()) playAniRun();
|
|
|
- } else {
|
|
|
- if (!IsAniForIdle()) {
|
|
|
- playAniForIdle();
|
|
|
- }
|
|
|
+ if (!state.moving) return true;
|
|
|
+ if (Vector3.Distance(this.agent.nextPosition, this.agent.destination) < 0.25f) {
|
|
|
+ return true;
|
|
|
}
|
|
|
- }
|
|
|
- //记录动作时间
|
|
|
- void UpdateActionTime() {
|
|
|
- if (moving) {
|
|
|
- movingTime += Time.deltaTime;
|
|
|
- } else {
|
|
|
- stayingTime += Time.deltaTime;
|
|
|
+ if (state.movingTime > 0.1f && this.agent.velocity.magnitude < 0.05f) {
|
|
|
+ return true;
|
|
|
}
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
float willStayTime;
|
|
|
float willMoveTime;
|
|
|
bool autoMoving = false;
|
|
|
float autoMovingTime;
|
|
|
- bool autoStrategy = true;
|
|
|
- //自动策略的更新逻辑
|
|
|
+ bool willAttack = false;
|
|
|
+ bool hasRunAround = false;
|
|
|
+ bool hasRunToHunter = false;
|
|
|
+ //帧更新逻辑-自动策略
|
|
|
void UpdateAutoStrategy() {
|
|
|
- if (!autoStrategy) return;
|
|
|
- if (stayingTime > willStayTime) {
|
|
|
- autoMoving = true;
|
|
|
- RandomMove();
|
|
|
+ if (state.dead) return;
|
|
|
+ if (state.lockingTarget) {
|
|
|
+ if (state.attacking) {
|
|
|
+ this.transform.LookAt(this.hunterPosition);
|
|
|
+ }
|
|
|
+ if (!hasRunToHunter) {
|
|
|
+ hasRunToHunter = true;
|
|
|
+ RunToAttackHunter();
|
|
|
+ willAttack = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!state.moving && !state.attacking) {
|
|
|
+ if (willAttack) {
|
|
|
+ willAttack = false;
|
|
|
+ Attack();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (Random.value > 0.5f) {
|
|
|
+ RunAroundHunter();
|
|
|
+ hasRunAround = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (hasRunAround) {
|
|
|
+ hasRunAround = false;
|
|
|
+ RunToAttackHunter();
|
|
|
+ }
|
|
|
+ willAttack = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (state.stayingTime > willStayTime) {
|
|
|
+ //退出stay
|
|
|
RandomWillStayTime();
|
|
|
+ //进入move
|
|
|
+ autoMoving = true;
|
|
|
+ MoveSlowlyInZPath();
|
|
|
}
|
|
|
if (autoMoving) {
|
|
|
- if (moving) {
|
|
|
+ if (state.moving) {
|
|
|
autoMovingTime += Time.deltaTime;
|
|
|
} else if (autoMovingTime < willMoveTime) {
|
|
|
- RandomMove();
|
|
|
- } else if (autoMovingTime >= willMoveTime) {
|
|
|
- Stay();
|
|
|
+ MoveSlowlyInZPath();
|
|
|
+ }
|
|
|
+ if (autoMovingTime >= willMoveTime) {
|
|
|
+ //退出move
|
|
|
RandomWillMoveTime();
|
|
|
autoMoving = false;
|
|
|
autoMovingTime = 0;
|
|
|
+ //进入stay
|
|
|
+ Stay();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
void InitAutoStrategy() {
|
|
|
- autoStrategy = true;
|
|
|
- this.willStayTime = 1 + Random.value * 4;
|
|
|
+ this.willStayTime = 1f + Random.value * 2;
|
|
|
RandomWillMoveTime();
|
|
|
}
|
|
|
- void PuaseAutoStrategy() {
|
|
|
- this.autoStrategy = false;
|
|
|
- }
|
|
|
- void ResumeAutoStrategy() {
|
|
|
- this.autoStrategy = true;
|
|
|
- RandomWillStayTime();
|
|
|
- RandomWillMoveTime();
|
|
|
- this.autoMoving = false;
|
|
|
- this.autoMovingTime = 0;
|
|
|
- }
|
|
|
void RandomWillStayTime() {
|
|
|
- this.willStayTime = Random.value * 3 + 5;
|
|
|
+ this.willStayTime = Random.value * 2 + 3;
|
|
|
}
|
|
|
void RandomWillMoveTime() {
|
|
|
- this.willMoveTime = Random.value * 2 + 3;
|
|
|
- }
|
|
|
-
|
|
|
- //逃跑远离猎人
|
|
|
- void RunAwayFromHunter()
|
|
|
- {
|
|
|
- Vector3 backVec = GetPointerHunterToMe();
|
|
|
- SetDestination(transform.position + backVec.normalized * 5);
|
|
|
- playAniRun();
|
|
|
+ this.willMoveTime = Random.value * 3 + 5;
|
|
|
}
|
|
|
|
|
|
- [System.NonSerialized] public float baseDistance = 10;
|
|
|
- [System.NonSerialized] public float baseDistanceMoveRange = 3;
|
|
|
- //随机移动
|
|
|
- void RandomMove() {
|
|
|
- Vector3 standardPointer = basePointer;
|
|
|
+ //攻击
|
|
|
+ void Attack() {
|
|
|
+ state.ResetActionState();
|
|
|
+ state.attacking = true;
|
|
|
+ if (Random.value < 0.5) state.attackA = true;
|
|
|
+ else state.attackB = true;
|
|
|
+ }
|
|
|
+ //跑到能攻击玩家的坐标点
|
|
|
+ void RunToAttackHunter() {
|
|
|
+ Vector3 standardPointer = animalsBaseT.forward;
|
|
|
+ Vector3 newPos = animalsBaseT.position + standardPointer * 1.5f;
|
|
|
+ SetDestination(newPos);
|
|
|
+ state.moveQuickly = true;
|
|
|
+ agent.speed = 5f;
|
|
|
+ }
|
|
|
+ //在敌人身边奔跑徘徊
|
|
|
+ void RunAroundHunter() {
|
|
|
+ float baseDistance = 2;
|
|
|
+ float baseDistanceMoveRange = 5;
|
|
|
+ Vector3 standardPointer = animalsBaseT.forward;
|
|
|
Vector3 pointerWithLen = standardPointer.normalized * (baseDistance + Random.value * baseDistanceMoveRange);
|
|
|
- pointerWithLen = Quaternion.AngleAxis(-30f + Random.value * 60f, Vector3.up) * pointerWithLen;
|
|
|
- Vector3 newPos = basePosition + pointerWithLen;
|
|
|
- // GameObject.Find("BBAA").transform.position = newPos;
|
|
|
- // GameObject.Find("BBBA").transform.LookAt(GameObject.Find("BBAA").transform);
|
|
|
+ pointerWithLen = Quaternion.AngleAxis(-80f + Random.value * 160f, Vector3.up) * pointerWithLen;
|
|
|
+ Vector3 newPos = animalsBaseT.position + pointerWithLen;
|
|
|
SetDestination(newPos);
|
|
|
+ state.moveQuickly = true;
|
|
|
+ agent.speed = 5f;
|
|
|
+ }
|
|
|
+ //Z字型路径缓慢移动
|
|
|
+ Queue<Vector3> zPathPoints = new Queue<Vector3>();
|
|
|
+ bool canCreateZPath = true;
|
|
|
+ void MoveSlowlyInZPath() {
|
|
|
+ if (zPathPoints.Count > 0) {
|
|
|
+ SetDestination(zPathPoints.Dequeue());
|
|
|
+ state.moveSlowly = true;
|
|
|
+ agent.speed = 0.8f;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!canCreateZPath) {
|
|
|
+ state.lockingTarget = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //构建Z字型路径
|
|
|
+ zPathPoints.Clear();
|
|
|
+ Vector3 standardPos = animalsBaseT.forward;
|
|
|
+ Vector3 hunterPos = hunterPosition;
|
|
|
+ Vector3 pointerToMe = GetPointerHunterToMe().normalized;
|
|
|
+ Vector3 pointer1 = Quaternion.AngleAxis(120, Vector3.up) * pointerToMe;
|
|
|
+ Vector3 pointer2 = Quaternion.AngleAxis(-120, Vector3.up) * pointerToMe;
|
|
|
+ Vector3 myPos = this.transform.position;
|
|
|
+ int[] pointerSeq = {1, 2, 1};
|
|
|
+ bool isReverse = Random.value < 0.5;
|
|
|
+ foreach (int seqID in pointerSeq)
|
|
|
+ {
|
|
|
+ Vector3 lastMyPos = myPos;
|
|
|
+ if (seqID == 1) myPos = myPos + (isReverse ? pointer2 : pointer1) * 5;
|
|
|
+ if (seqID == 2) myPos = myPos + (isReverse ? pointer1 : pointer2) * 10;
|
|
|
+ hunterPos.y = myPos.y;
|
|
|
+ Vector3 hunterToMe = myPos - hunterPos;
|
|
|
+ float angle = Vector3.Angle(hunterToMe, standardPos);
|
|
|
+ float minDistance = 5;
|
|
|
+ //如果下一个构建的坐标在猎手身后或者距离猎手低于指定距离
|
|
|
+ if (angle > 90 || Vector3.Distance(hunterPos, myPos) < minDistance) {
|
|
|
+ Vector3 hunterToMe2 = lastMyPos - hunterPos;
|
|
|
+ if (hunterToMe2.magnitude > minDistance + 2) {
|
|
|
+ Vector3 displace = (hunterToMe2).normalized * minDistance;
|
|
|
+ myPos = hunterPos + displace;
|
|
|
+ zPathPoints.Enqueue(myPos);
|
|
|
+ }
|
|
|
+ canCreateZPath = false;
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ zPathPoints.Enqueue(myPos);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
//停留
|
|
|
void Stay() {
|
|
|
- if (moving) {
|
|
|
+ if (state.moving) {
|
|
|
this.agent.destination = this.agent.nextPosition;
|
|
|
- OnReachDestination();
|
|
|
}
|
|
|
+ state.ResetActionState();
|
|
|
+ state.staying = true;
|
|
|
}
|
|
|
|
|
|
- //当前动画索引
|
|
|
+ //动画播放
|
|
|
int curAnimIndex = 0;
|
|
|
+ void playAniStay() {
|
|
|
+ ap.play(curAnimIndex = 3, WrapMode.Loop);
|
|
|
+ }
|
|
|
+ bool isAniStay() {
|
|
|
+ return curAnimIndex == 3;
|
|
|
+ }
|
|
|
+ void playAniMoveSlowly() {
|
|
|
+ ap.play(curAnimIndex = 5, WrapMode.Loop);
|
|
|
+ }
|
|
|
+ bool isAniMoveSlowly() {
|
|
|
+ return curAnimIndex == 5;
|
|
|
+ }
|
|
|
+ void playAniMoveQuickly() {
|
|
|
+ ap.play(curAnimIndex = 4, WrapMode.Loop);
|
|
|
+ }
|
|
|
+ bool isAniMoveQuickly() {
|
|
|
+ return curAnimIndex == 4;
|
|
|
+ }
|
|
|
+ void playAniAttakA() {
|
|
|
+ ap.play(curAnimIndex = 1, WrapMode.Once);
|
|
|
+ }
|
|
|
+ bool isAniAttakA() {
|
|
|
+ return curAnimIndex == 1;
|
|
|
+ }
|
|
|
+ void playAniAttakB() {
|
|
|
+ ap.play(curAnimIndex = 0, WrapMode.Once);
|
|
|
+ }
|
|
|
+ bool isAniAttakB() {
|
|
|
+ return curAnimIndex == 0;
|
|
|
+ }
|
|
|
void playAniDie() {
|
|
|
- ap.speed = 1.0f;
|
|
|
ap.play(curAnimIndex = 2, WrapMode.Once);
|
|
|
}
|
|
|
bool isAniDie() {
|
|
|
return curAnimIndex == 2;
|
|
|
}
|
|
|
- void playAniRun() {
|
|
|
- if (Random.value < 0.4 || hurtFlag.IsLocked()) {
|
|
|
- curAnimIndex = 4;
|
|
|
- ap.speed = 1.3f;
|
|
|
- this.agent.speed = 5;
|
|
|
- } else {
|
|
|
- curAnimIndex = 5;
|
|
|
- ap.speed = 1.0f;
|
|
|
- this.agent.speed = 0.5f;
|
|
|
- }
|
|
|
- ap.play(curAnimIndex, WrapMode.Loop);
|
|
|
- }
|
|
|
- bool isAniRun() {
|
|
|
- return curAnimIndex == 4 || curAnimIndex == 5;
|
|
|
+ void initAniListener() {
|
|
|
+ this.ap.completeCallback = delegate(AnimationPlayerCompleteResult res) {
|
|
|
+ if (res.index == 1 || res.index == 0) {
|
|
|
+ this.state.ResetActionState();
|
|
|
+ }
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
- bool IsAniForIdle() {
|
|
|
- return curAnimIndex == 3;
|
|
|
+ //帧更新逻辑-通过状态更新动作动画
|
|
|
+ void UpdateAction() {
|
|
|
+ if (state.staying) {
|
|
|
+ state.stayingTime += Time.deltaTime;
|
|
|
+ if (!isAniStay()) playAniStay();
|
|
|
+ }
|
|
|
+ else if (state.moving) {
|
|
|
+ state.movingTime += Time.deltaTime;
|
|
|
+ if (state.moveSlowly && !isAniMoveSlowly()) playAniMoveSlowly();
|
|
|
+ if (state.moveQuickly && !isAniMoveQuickly()) playAniMoveQuickly();
|
|
|
+ }
|
|
|
+ else if (state.attacking) {
|
|
|
+ if (state.attackA && !isAniAttakA()) playAniAttakA();
|
|
|
+ if (state.attackB && !isAniAttakB()) playAniAttakB();
|
|
|
+ }
|
|
|
+ else if (state.dead) {
|
|
|
+ if (!isAniDie()) playAniDie();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- void playAniForIdle() {
|
|
|
- ap.speed = 1.0f;
|
|
|
- curAnimIndex = 3;
|
|
|
- ap.play(curAnimIndex, WrapMode.Loop);
|
|
|
+ //状态
|
|
|
+ [System.Serializable]
|
|
|
+ public class State {
|
|
|
+ //数值区
|
|
|
+ public int hp = 1;
|
|
|
+ //动作区
|
|
|
+ public bool staying = true;
|
|
|
+ public bool moving = false;
|
|
|
+ public bool moveSlowly = false; //慢走
|
|
|
+ public bool moveQuickly = false; //跑动
|
|
|
+ public bool attacking = false;
|
|
|
+ public bool attackA = false; //扑击
|
|
|
+ public bool attackB = false; //撕咬
|
|
|
+ public bool dead = false;
|
|
|
+ public float stayingTime = 0;
|
|
|
+ public float movingTime = 0;
|
|
|
+ //特定区
|
|
|
+ public bool lockingTarget = false; //锁定目标
|
|
|
+ //重置动作区状态
|
|
|
+ public void ResetActionState() {
|
|
|
+ this.staying = false;
|
|
|
+ this.moving = false;
|
|
|
+ this.moveSlowly = false;
|
|
|
+ this.moveQuickly = false;
|
|
|
+ this.attacking = false;
|
|
|
+ this.attackA = false;
|
|
|
+ this.attackB = false;
|
|
|
+ this.dead = false;
|
|
|
+ this.stayingTime = 0;
|
|
|
+ this.movingTime = 0;
|
|
|
+ }
|
|
|
}
|
|
|
+ [SerializeField] public State state = new State();
|
|
|
|
|
|
//委托
|
|
|
public System.Action<Wolf> onDie;
|