using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using DG.Tweening; public class Wolf : TargetAnimal { //动画播放器 AnimationPlayer ap; //寻路代理 NavMeshAgent agent; void Awake() { ap = GetComponent(); agent = GetComponent(); } void Start() { initAniListener(); this.agent.avoidancePriority = avoidancePriority; } static int _avoidancePriority = 0; static int avoidancePriority { get { if (_avoidancePriority < 50) { _avoidancePriority++; } else { _avoidancePriority = 1; } return _avoidancePriority; } } void Update() { if (HasCloseToDestination()) { OnReachDestination(); } UpdateAutoStrategy(); UpdateAction(); UpdateOutline(); } //可选皮肤材质 [SerializeField] Material[] materials; [SerializeField] Material[] outlineMaterials; public void ChangeColorByType(int type) { if (skinnedMeshRenderer == null) skinnedMeshRenderer = GetComponentInChildren(); baseMaterial = materials[type - 1]; outlineMaterial = outlineMaterials[type - 1]; skinnedMeshRenderer.material = baseMaterial; } Material baseMaterial; Material outlineMaterial; SkinnedMeshRenderer skinnedMeshRenderer = null; bool hasOutLine = false; void UpdateOutline() { Camera camera = Camera.main; if (!camera) return; float distance = Vector3.Distance(this.transform.position, camera.transform.position); if (distance > 15) { if (!hasOutLine) { hasOutLine = true; skinnedMeshRenderer.material = outlineMaterial; } } else { if (hasOutLine) { hasOutLine = false; skinnedMeshRenderer.material = baseMaterial; } } } 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") { 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 (state.dead) return; arrow.onDoNextShoot += delegate() { Destroy(this.gameObject); }; state.ResetActionState(); state.dead = true; this.agent.enabled = false; onDie?.Invoke(this); AudioMgr.ins.PlayAnimalEffect("wolf_die", AudioMgr.GetAudioSource(this.gameObject)); AudioMgr.ins.PlayCheer(true); } void Hurt() { if (!state.attacking) { if (Random.value < 0.2f) { CancelLockTarget(); state.ResetActionState(); state.lockingTarget = true; needAmbush = false; } else { //未锁定目标阶段是被击退,因为可能会再次满足z路径条件,因此需要重置Z路径记录 if (!state.lockingTarget) ResetZPathRecord(); CancelLockTarget(1); RunAwayFromHunter(); } } AudioMgr.ins.PlayAnimalEffect("wolf_injured", AudioMgr.GetAudioSource(this.gameObject)); } //启动寻路 void SetDestination(Vector3 pos) { state.ResetActionState(); state.moving = true; this.agent.destination = pos; } //寻路结束 void OnReachDestination() { if (!state.moving) return; state.ResetActionState(); } //是否已经接近目的地(寻路完成判断) bool HasCloseToDestination() { if (!state.moving) return true; if (Vector3.Distance(this.agent.nextPosition, this.agent.destination) < 0.25f) { return true; } if (state.movingTime > 0.1f && this.agent.velocity.magnitude < 0.05f) { return true; } return false; } int lastAutoType; float willStayTime; bool willAttack = false; bool hasRunAround = false; int needRunAroundCount = 0; bool hasRunToHunter = false; //取消锁定状态 //cancelType==1时,表示触发逃跑 void CancelLockTarget(int cancelType = 0) { if (!state.lockingTarget) return; state.lockingTarget = false; if (cancelType == 1) state.lockingTarget = true; RandomWillStayTime(); willAttack = false; hasRunAround = false; if (cancelType == 1) hasRunAround = true; needRunAroundCount = 0; if (cancelType == 1) needRunAroundCount = 2; hasRunToHunter = false; if (cancelType == 1) hasRunToHunter = true; } //帧更新逻辑-自动策略 void UpdateAutoStrategy() { if (state.dead) return; if (state.lockingTarget) { // if (state.attacking) { // this.transform.LookAt(this.hunterPosition); // } if (!hasRunToHunter) { hasRunToHunter = true; RunToAttackHunter(true); willAttack = true; return; } if (!state.moving && !state.attacking) { if (willAttack) { willAttack = false; Attack(); needRunAroundCount = 3; return; } if (needRunAroundCount > 0) { needRunAroundCount--; RunAroundHunter(!hasRunAround); hasRunAround = true; return; } if (hasRunAround) { hasRunAround = false; RunToAttackHunter(false); } willAttack = true; return; } return; } //以下为未锁定目标时的自动策略 if (state.staying) { if (state.stayingTime > willStayTime) { MoveSlowlyInZPath(); } return; } if (state.moving) { lastAutoType = 2; } if (!state.staying && !state.moving) { if (lastAutoType == 1 || lastAutoType == 0) { MoveSlowlyInZPath(); } else { if (!canCreateZPath && zPathPoints.Count == 0) { MoveSlowlyInZPath();//内含判断,状态将会转化为锁定目标 } else { RandomWillStayTime(); Stay(true); } } } } void RandomWillStayTime() { this.willStayTime = Random.value * 4 + 2; // this.willStayTime = 0; } void LookAtHunter() { this.transform.LookAt(hunterPosition); } bool needAmbush = true; //攻击 void Attack() { state.ResetActionState(); state.attacking = true; curAnimIndex = -1; /*注意:这样可避免狼两次使用同一攻击动作而第二次无法播放的情况 */ int hurtValue = 2; if (canAttackID == "A") { int avoidancePriority = this.agent.avoidancePriority; float baseoffset = this.agent.baseOffset; Vector3 hunterPos = hunterPosition; Vector3 jumpPoint = default; //起跳点 Vector3 landPoint = default; //落地点 Vector3 displace = default; //位移 Sequence seq = DOTween.Sequence(); //伏击动作 + 转头 Quaternion ambushQuaStart = transform.rotation; Vector3 rotateDir = hunterPos - transform.position; rotateDir.y = 0; Quaternion ambushQuaEnd = Quaternion.FromToRotation(Vector3.forward, rotateDir); float needRotateAngle = Quaternion.Angle(ambushQuaStart, ambushQuaEnd); this.agent.avoidancePriority = 0; if (needRotateAngle > 20) { state.ambushRotating = true; seq.Append(DOTween.To(() => 0f, value => { transform.rotation = Quaternion.Lerp(ambushQuaStart, ambushQuaEnd, value); }, 1f, needRotateAngle / 180f * 0.5f)); } else { ambushQuaEnd = ambushQuaStart; } seq.AppendCallback(delegate() { state.ambushRotating = false; if (needAmbush) state.ambushing = true; }); if (needAmbush) { seq.Append(DOTween.To(() => 0f, value => { this.transform.rotation = ambushQuaEnd; }, 1f, 2f)); } seq.AppendCallback(delegate() { if (state.dead) { seq.Kill(); return; } needAmbush = true; state.ambushing = false; state.attackA = true; #region //起点和终点计算 jumpPoint = this.transform.position; hunterPos.y = jumpPoint.y; Vector3 deltaPointer = jumpPoint - hunterPos; landPoint = hunterPos + deltaPointer.normalized * 1f; displace = landPoint - jumpPoint; #endregion }); //跳跃 seq.Append(DOTween.To(() => 0f, value => { this.transform.position = jumpPoint + displace * value; LookAtHunter(); if (value < 0.5) { this.agent.baseOffset = baseoffset + value; } else { this.agent.baseOffset = baseoffset + (1 - value); if (!state.dead && state.attackA) { state.attackA = false; playAniJumpDown(); } } }, 1f, 0.8f)); seq.AppendCallback(delegate() { this.agent.avoidancePriority = avoidancePriority; this.transform.position = landPoint; LookAtHunter(); if (!state.dead) onAttack?.Invoke(this, hurtValue); }); seq.Append(DOTween.To(() => 0f, value => { this.transform.position = landPoint; LookAtHunter(); }, 1f, 0.3f)); seq.AppendCallback(delegate() { if (!state.dead) stopAniJumpDown(); //停止动画,则动画自带的位移也停止变化 this.transform.position = landPoint; LookAtHunter(); }); seq.Append(DOTween.To(() => 0f, value => { this.transform.position = landPoint; LookAtHunter(); }, 1f, 0.1f)); //通过dotween是它不被动画的位移影响; seq.AppendCallback(delegate() { state.ResetActionState(); }); } else if (canAttackID == "B") { state.attackB = true; hurtValue = 3; onAttack?.Invoke(this, hurtValue); } } //逃跑远离猎人 void RunAwayFromHunter() { Vector3 backVec = GetPointerHunterToMe(); SetDestination(transform.position + backVec.normalized * 6); state.moveQuickly = true; this.agent.speed = 5f; } //跑到能攻击玩家的坐标点 string canAttackID = null; void RunToAttackHunter(bool quickly) { float needDistance; Vector3 displace = GetPointerHunterToMe().normalized; // if (Random.value < 0.33f) { // needDistance = 2; // canAttackID = "B"; // } else { needDistance = 8.5f; canAttackID = "A"; // } Vector3 newPos = animalsBaseT.position + displace * needDistance; SetDestination(newPos); state.moveSlowly = !quickly; state.moveQuickly = quickly; agent.speed = quickly ? 5f : 0.8f; } //在敌人身边奔跑徘徊 void RunAroundHunter(bool quickly) { float baseDistance = 10; float baseDistanceMoveRange = 10; Vector3 standardPointer = animalsBaseT.forward; Vector3 pointerWithLen = standardPointer.normalized * (baseDistance + Random.value * baseDistanceMoveRange); pointerWithLen = Quaternion.AngleAxis(-40f + Random.value * 80f, Vector3.up) * pointerWithLen; Vector3 newPos = animalsBaseT.position + pointerWithLen; SetDestination(newPos); state.moveSlowly = !quickly; state.moveQuickly = quickly; agent.speed = quickly ? 5f : 0.8f; } //Z字型路径缓慢移动 Queue zPathPoints = new Queue(); bool canCreateZPath = true; void ResetZPathRecord() { zPathPoints.Clear(); 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 = 10; //如果下一个构建的坐标在猎手身后或者距离猎手低于指定距离 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(bool correct = false) { if (state.moving || correct) { this.agent.destination = this.agent.nextPosition; } state.ResetActionState(); state.staying = true; } //动画播放 int curAnimIndex = 0; void playAniStay() { ap.play(curAnimIndex = 7, WrapMode.Loop); } bool isAniStay() { return curAnimIndex == 7; } 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 playAniAmbush() { ap.play(curAnimIndex = 3, WrapMode.Loop); } bool isAniAmbush() { return curAnimIndex == 3; } void playAniAttakA() { ap.play(curAnimIndex = 1, WrapMode.Once); } bool isAniAttakA() { return curAnimIndex == 1; } void playAniJumpDown() { ap.play(curAnimIndex = 6, WrapMode.Once); } void stopAniJumpDown() { ap.StopAnimation(6); } void playAniAttakB() { ap.play(curAnimIndex = 0, WrapMode.Once); } bool isAniAttakB() { return curAnimIndex == 0; } void playAniDie() { ap.play(curAnimIndex = 2, WrapMode.Once); } bool isAniDie() { return curAnimIndex == 2; } void initAniListener() { this.ap.completeCallback = delegate(AnimationPlayerCompleteResult res) { if (res.index == 0) { this.state.ResetActionState(); } }; } //帧更新逻辑-通过状态更新动作动画 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.ambushRotating && !isAniMoveQuickly()) playAniMoveQuickly(); if (state.ambushing && !isAniAmbush()) playAniAmbush(); if (state.attackA && !isAniAttakA()) playAniAttakA(); if (state.attackB && !isAniAttakB()) playAniAttakB(); } else if (state.dead) { if (!isAniDie()) playAniDie(); } } //状态 [System.Serializable] public class State { //数值区 public int hp = 1; //动作区 public bool staying = false; public bool moving = false; public bool moveSlowly = false; //慢走 public bool moveQuickly = false; //跑动 public bool attacking = false; public bool ambushRotating = false; //扑击前的转身 public bool ambushing = 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.ambushRotating = false; this.ambushing = 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 onDie; public System.Action onAttack; }