Files
Zombie/Assets/_Zombie/Scripts/Enemys/Enemy.cs
2025-08-27 13:37:20 +08:00

653 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Collections.Generic;
using DragonLi.Core;
using Mirror;
using Pathfinding;
using Pathfinding.RVO;
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
using Animancer; // Animancer 支持
using DarkTonic.MasterAudio;
using DragonLi.Frame;
public enum EnemyState
{
Borning = 0,
Charge = 1,
Search = 2,
Run = 3,
Atk = 4,
Die = 99,
}
/// <summary>
/// 将原来的射手 Enemy 改造成丧尸近战 Enemy继承 Agent
/// - 使用 Animancer 播放动画(优先);若无 Animancer 则回落到 AnimatorComponent
/// - HostisServer为权威伤害与死亡在服务器上处理并通过 ClientRpc 通知客户端播放视觉动画
/// - 支持 IAstarAI / AIPath / RVOController 的移动接口SetDestination 等),以供 BehaviorDesigner 调用
/// </summary>
public class Enemy : Agent
{
// A* / RVO
private IAstarAI _ai;
public IAstarAI ai
{
get
{
if (_ai == null) _ai = GetComponent<IAstarAI>();
return _ai;
}
}
private AIPath _aiPath;
public AIPath aiPath
{
get
{
if (_aiPath == null) _aiPath = GetComponent<AIPath>();
return _aiPath;
}
}
private BehaviorTree _behaviorTree;
public BehaviorTree behaviorTree
{
get
{
if (_behaviorTree == null) _behaviorTree = GetComponent<BehaviorTree>();
return _behaviorTree;
}
}
private RVOController _rvoController;
public RVOController rvoController
{
get
{
if (_rvoController == null) _rvoController = GetComponent<RVOController>();
return _rvoController;
}
}
private Collider _collider;
public Collider selfCollider
{
get
{
if (_collider == null) _collider = GetComponent<Collider>();
return _collider;
}
}
public Transform weakness;
[Header("Animancer (优先) / Animator (回落)")]
public AnimancerComponent AnimancerComponent; // 请在 Inspector 赋值或让脚本自动获取
// AnimatorComponent 在 Agent 中已有AnimatorComponent 字段),保留为回落
[Header("动画片段 - 请在 Inspector 填充")]
public AnimationClip introClip; // 开场(非循环)
public AnimationClip walkClip; // 行走Loop
public AnimationClip attackClip; // 攻击
public AnimationClip hitLeftClip; // 受击 左
public AnimationClip hitRightClip; // 受击 右
public AnimationClip deathClip; // 死亡
[Header("死亡音效")]
[SoundGroup]
public string dieSound;
public GameObject explosion_prefab;
[Header("编号")]
[SyncVar]
public int id = 0;
[Header("怪物类型")]
[SyncVar]
public EnemyType type;
[Header("当前状态")]
public EnemyState state;
[Header("等级")]
[SyncVar]
public int lvl = 0;
[Header("速度")]
[SyncVar]
public float speed = 0;
[Header("攻击力")]
[SyncVar]
public float atk = 0;
[Header("特殊攻击1攻击力")]
[SyncVar]
public float SpecialAtk1 = 0;
[Header("特殊攻击2攻击力")]
[SyncVar]
public float SpecialAtk2 = 0;
[Header("目标引用 (通常由 BT 设置)")]
public GameObject target = null;
// 行为状态(本地/服务器)
private bool updateAnimatorSpeedValue = false;
private bool updateAnimatorSpeedFValue = false;
private bool updateAnimatorSpeedRValue = false;
// 动画播放缓存Animancer 状态)
private AnimancerState walkState;
// AI / 行为标志server 上驱动)
private bool introPlayed = false;
private bool isAttacking = false;
private bool isDead = false;
// RVO 速度参数(可在 Inspector 调整)
[Header("RVO 参数 (若使用 RVOController)")]
public float desiredSpeed = 1.2f;
public float maxSpeed = 1.5f;
void Awake()
{
// 自动获取 AnimancerComponent如果没有手动赋值
if (AnimancerComponent == null)
AnimancerComponent = GetComponent<AnimancerComponent>();
InitAnimator();
}
void InitAnimator()
{
// 检测 Animator 参数,以便按需设置 speed / speedf / speedr
if (AnimatorComponent == null) return;
AnimatorControllerParameter[] parameters = AnimatorComponent.parameters;
foreach (AnimatorControllerParameter val in parameters)
{
if (val.type == AnimatorControllerParameterType.Float)
{
switch (val.name)
{
case "speed":
updateAnimatorSpeedValue = true;
break;
case "speedr":
updateAnimatorSpeedRValue = true;
break;
case "speedf":
updateAnimatorSpeedFValue = true;
break;
}
}
}
}
[Server]
public virtual void OnSpawn(int id, EnemyType type, int lvl)
{
base.OnSpawn();
this.id = id;
this.type = type;
state = EnemyState.Borning;
this.lvl = lvl;
EnemyInfo enemyInfo = GameManager.Ins.EnemyInfos[type];
speed = enemyInfo.Speed;
atk = enemyInfo.Atk;
health = enemyInfo.Hp;
originHealth = enemyInfo.Hp;
aiPath.enabled = true;
aiPath.maxSpeed = speed;
GameManager.Ins.CreateEnemyUI(this);
// 初始设置server
if (isServer)
{
state = EnemyState.Borning;
if (aiPath != null) aiPath.enabled = true;
if (rvoController != null) rvoController.enabled = true;
if (selfCollider != null) selfCollider.enabled = true;
if (behaviorTree != null) behaviorTree.enabled = true; // 仅 HostServer上运行 BT
// 初始血量已在 Agent.OnSpawn 中设置为 originHealth / health
}
}
private void Update()
{
OnUpdate();
}
public override void OnUpdate()
{
// 只有 Server/Host 驱动移动rvo / ai其它客户端只做视觉播放由 Rpc 通知)
if (isServer && !isDead)
{
// 该方法在 Agent.Update 中由 Server 调用(以前你的实现)
// 这里我们保持相同更新:更新动画参数 & 旋转等(只在 Server 上操作 AI/移动)
if (aiPath != null && aiPath.enabled)
{
UpdateAnimatorValues(aiPath.velocity);
}
else if (RigidbodyComponent)
{
UpdateAnimatorValues(RigidbodyComponent.velocity);
}
UpdateRotation();
}
}
protected void UpdateAnimatorValues(Vector3 vel)
{
// if (AnimatorComponent != null)
// {
// if (updateAnimatorSpeedValue)
// {
// AnimatorComponent.SetFloat("speed", vel.magnitude);
// }
// Vector3 val;
// if (updateAnimatorSpeedFValue)
// {
// val = Vector3.Project(vel, transform.forward);
// float num = vel.magnitude > 0 ? val.magnitude / (vel.magnitude * (Vector3.Angle(vel, transform.forward) > 90f ? -1f : 1f)) : 0f;
// AnimatorComponent.SetFloat("speedf", num);
// }
// if (updateAnimatorSpeedRValue)
// {
// val = Vector3.Project(vel, transform.right);
// float num2 = vel.magnitude > 0 ? val.magnitude / (vel.magnitude * (Vector3.Angle(vel, transform.right) > 90f ? -1f : 1f)) : 0f;
// AnimatorComponent.SetFloat("speedr", num2);
// }
// }
// 如果使用 Animancer 且你希望通过脚本控制 walkClip 的播放,则在 UpdateLocalVisuals 中处理(下文)
}
public virtual void UpdateRotation()
{
if (target != null)
{
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(target.transform.position - transform.position), Time.deltaTime * 10f);
}
}
private void LateUpdate()
{
Vector3 pos = transform.position;
pos.y = 0f; // 或者设置为地面高度
transform.position = pos;
}
#region --- ---
// 在本地播放行走Animancer or Animator
public void PlayWalkLocal()
{
if (isDead) return;
if (AnimancerComponent != null && walkClip != null)
{
if (walkState == null)
{
Debug.LogError("移动");
walkState = AnimancerComponent.Play(walkClip);
}
else
{
walkState.Play();
}
}
else if (AnimatorComponent != null)
{
AnimatorComponent.CrossFade("Walk", 0.15f); // 假设存在名为 "Walk" 的状态,或使用 SetFloat 控制
}
}
// 在本地播放受击(根据 hitPoint 判断左右)
private void PlayHitLocal(Vector3 hitPoint)
{
if (isDead) return;
Vector3 local = transform.InverseTransformPoint(hitPoint);
AnimationClip clip = local.x < 0 ? hitLeftClip : hitRightClip;
if (clip == null) return;
if (AnimancerComponent != null)
{
var s = AnimancerComponent.Play(clip);
// 示例绑定本地结束回调(如果需要)
s.Events(this).OnEnd = () =>
{
// 受击动画结束时的本地处理(通常不需要通知服务器)
};
}
else if (AnimatorComponent != null)
{
AnimatorComponent.Play(clip.name); // 回落:直接按名字播放(确保 Animator 有对应 state
}
}
// 在本地播放死亡动画
private void PlayDeathLocal(Vector3 hitPoint)
{
if (isDead) return;
isDead = true;
// 停用碰撞与 BT本地表现
if (selfCollider != null) selfCollider.enabled = false;
if (behaviorTree != null) behaviorTree.enabled = false;
if (aiPath != null) aiPath.enabled = false;
if (rvoController != null) rvoController.enabled = false;
if (AnimancerComponent != null && deathClip != null)
{
var s = AnimancerComponent.Play(deathClip);
s.Events(this).OnEnd = () =>
{
NetworkServer.Destroy(gameObject);
if(isServer)
GameManager.Ins.DeleteEnemy(id);
// client 本地死亡结束后的处理(通常由 server 决定是否销毁)
// 这里不直接销毁网络对象server 会在需要时 Destroy
};
// 播放死亡音效(客户端本地)
GameManager.Ins.PlaySound3DRPC(dieSound,transform,true);
}
else
{
if (AnimatorComponent != null)
{
AnimatorComponent.SetBool("dead", true);
GameManager.Ins.PlaySound3DRPC(dieSound,transform,true);
}
}
}
#endregion
#region --- Server authoritative: 广/ ---
/// <summary>
/// 当 Server 调用 ApplyDamage -> 会触发 OnReceiveDamage 在 Server 上执行
/// 我们在这里播放受击视觉(通过 Rpc 广播给客户端)
/// 返回的 float 仍然是实际要减少的生命值(可在这里应用韧性/抗性计算)
/// </summary>
public override float OnReceiveDamage(float value, object info, Transform _sender)
{
// info 可以携带 hitPoint如果是 Vector3 则按命中点处理;否则可按 _sender 确定方向
Vector3 hitPoint = transform.position;
if (info is Vector3)
{
hitPoint = (Vector3)info;
}
else if (_sender != null)
{
// 近似命中点为攻击者位置
hitPoint = _sender.position;
}
// Server 上播放受击(广播给所有客户端)
RpcPlayHit(hitPoint);
// 伤害计算:默认返回原始值;若你需要韧性(toughness)生效,可在这里调整
// 例如value = Mathf.Max(0f, value - toughness);
return value;
}
/// <summary>
/// Server -> Rpc: 通知所有客户端播放受击视觉
/// </summary>
[ClientRpc]
private void RpcPlayHit(Vector3 hitPoint)
{
PlayHitLocal(hitPoint);
}
/// <summary>
/// 覆盖 DieAgent.DieServer 上调用后广播死亡给客户端并做 server 清理
/// </summary>
public override void Die(object info, Transform _sender)
{
// 服务端处理(设置状态、移除 UI 等)
GameManager.Ins.DeleteEnemyUI(id);
state = EnemyState.Die;
// 禁用交互组件server 端)
if (selfCollider != null) selfCollider.enabled = false;
if (behaviorTree != null) behaviorTree.enabled = false;
if (aiPath != null) aiPath.enabled = false;
if (rvoController != null) rvoController.enabled = false;
// 在 server 上播放死亡并通知客户端
Vector3 hitPoint = transform.position;
if (info is Vector3) hitPoint = (Vector3)info;
RpcPlayDeath(hitPoint);
// 播放死亡音效(在客户端通过 RpcPlayDeath 会触发本地 PlaySound
// Server 端额外处理:生成爆炸/掉落/计分等
PlayServerDeathEffects();
// 最终 server 可决定是否销毁网络对象
if (/*你想直接销毁*/ false)
{
NetworkServer.Destroy(gameObject);
}
else
{
// 保持对象存在,直到 server 另外决定销毁
}
}
[ClientRpc]
private void RpcPlayDeath(Vector3 hitPoint)
{
PlayDeathLocal(hitPoint);
}
// Server 上死亡后的额外处理(掉落、爆炸、声音等)
[Server]
private void PlayServerDeathEffects()
{
// 生成爆炸示例(如果需要)
if (explosion_prefab != null)
{
GameObject explosion = Instantiate(explosion_prefab);
explosion.transform.position = transform.position;
NetworkServer.Spawn(explosion);
// 延迟销毁可能需要协程或任务系统
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
if (explosion != null) NetworkServer.Destroy(explosion);
}, 2f);
}
// 播放死亡音效通过 Rpc客户端会播放
RpcPlaySound(dieSound);
}
[ClientRpc]
private void RpcPlaySound(string sound)
{
if (!string.IsNullOrEmpty(sound))
MasterAudio.PlaySound3DAtVector3(sound, transform.position);
}
#endregion
#region --- Behavior Tree / Actions 使 ---
// BehaviorDesigner Action 可直接调用这些方法(与你现有 Actions 风格兼容)
public void PlayIntro()
{
if (introPlayed) return;
StartCoroutine(PlayIntroCoroutine());
}
private IEnumerator PlayIntroCoroutine()
{
if (introClip != null)
{
if (AnimancerComponent != null)
{
var s = AnimancerComponent.Play(introClip);
bool finished = false;
s.Events(this).OnEnd = () => finished = true;
yield return new WaitUntil(() => finished);
}
else if (AnimatorComponent != null)
{
AnimatorComponent.Play(introClip.name);
// 若要等待 Animator 的结束,需要使用 AnimatorStateInfo.length不可靠这里不等待
yield return null;
}
}
introPlayed = true;
}
/// <summary>
/// 设置目标点NavMesh / RVO 通用)
/// </summary>
public void SetDestination(Vector3 pos)
{
if (ai != null)
{
// IAstarAI 通常实现 destination via AIPath or Seeker; use ai.destination if available
// 若 ai 实现的是 IAstarAI则可以尝试 cast to RichAI / AIPath 处理
var rich = ai as IAstarAI;
try
{
ai.destination = pos;
}
catch { }
}
if (aiPath != null)
{
aiPath.maxSpeed = speed;
aiPath.enabled = true;
// AIPath/Seeker 会寻路到 target (assuming Call to seeker handled by BT)
}
if (rvoController != null)
{
rvoController.locked = false;
rvoController.SetTarget(pos, desiredSpeed, maxSpeed, pos);
}
}
public void SetDestinationToTarget(Transform t)
{
if (t == null) return;
SetDestination(t.position);
}
public void MoveForwardDistance(float distance)
{
Vector3 targetPos = transform.position + transform.forward * distance;
SetDestination(targetPos);
}
public void MoveLocalBackward(float distance)
{
Vector3 targetPos = transform.position + transform.TransformDirection(new Vector3(0, 0, -1)) * distance;
SetDestination(targetPos);
}
public void StartChase()
{
if (ai != null) ai.canMove = true;
if (aiPath != null) aiPath.enabled = true;
if (rvoController != null) rvoController.locked = false;
}
public void StopChase()
{
if (ai != null) ai.canMove = false;
if (aiPath != null) aiPath.enabled = false;
if (rvoController != null) { rvoController.locked = true; rvoController.ForceSetVelocity(Vector3.zero); }
}
/// <summary>
/// 发起一次攻击播放攻击动画Server 上触发伤害判定/事件)
/// - BehaviorTree 在 host 上执行时应调用 DoAttack(),并在动画的事件帧调用真正的伤害函数(或在 DoAttackCoroutine 中等待到事件再次调用)
/// </summary>
public virtual void DoAttack()
{
if (!isServer) return; // 攻击判定/伤害应由 Server 执行Host 权威)
StartCoroutine(DoAttackCoroutine());
}
private IEnumerator DoAttackCoroutine()
{
if (isDead) yield break;
isAttacking = true;
if (attackClip != null && AnimancerComponent != null)
{
var s = AnimancerComponent.Play(attackClip);
bool finished = false;
// 你可以在动画的关键帧添加事件并调用本脚本的 ServerDealDamage() —— 或者在这里等待结束再处理
s.Events(this).OnEnd = () => finished = true;
yield return new WaitUntil(() => finished);
}
else if (AnimatorComponent != null && attackClip != null)
{
AnimatorComponent.Play(attackClip.name);
yield return new WaitForSeconds(0.7f);
}
else
{
yield return new WaitForSeconds(0.7f);
}
isAttacking = false;
}
/// <summary>
/// Server 端在合适时机调用(例如动画事件帧)执行伤害判定
/// 你可以在 Animancer 的动画事件里调用这个方法(服务器上的 host 会执行)
/// </summary>
[Server]
public void ServerDealDamageToTarget(float damage)
{
if (!isServer) return;
if (target == null) return;
IDamagable dam = target.GetComponent<IDamagable>();
if (dam != null)
{
// info 可传命中点
Vector3 hitPoint = target.transform.position;
// 调用目标的 ApplyDamage目标可能是 Player服务器应执行
// 假设目标的 ApplyDamage 签名为 ApplyDamage(float, object, Transform)
var targetAgent = target.GetComponent<Agent>();
if (targetAgent != null)
{
targetAgent.ApplyDamage(damage, hitPoint, transform);
}
else
{
// 如果目标不是 Agent尝试用 IDamagable 接口
dam.ApplyDamage(damage, hitPoint, transform);
}
}
}
#endregion
#region Debug Gizmos
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, 1.5f); // 可替换为攻击范围的字段
if (target != null)
{
Gizmos.color = Color.yellow;
Gizmos.DrawLine(transform.position, target.transform.position);
}
}
#endregion
}