Files
Zombie/Assets/_Zombie/Scripts/Enemys/Enemy.cs

581 lines
18 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("死亡音效")]
[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;
// 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()
{
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*GameManager.Ins.players.Count;
originHealth = enemyInfo.Hp*GameManager.Ins.players.Count;
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.ReflectVectorXOZ() - transform.position), Time.deltaTime * 10f);
}
}
private void LateUpdate()
{
if (GameLocal.Ins.place == Place.Yunnan_Lincang_Linxiang_Hengji_Dixia)
{
Vector3 pos = transform.position;
if (Physics.Raycast(pos + Vector3.up * 5f, Vector3.down, out RaycastHit hit, 20f, LayerMask.GetMask("Dixia")))
{
pos.y = hit.point.y;
transform.position = pos;
}
}
else
{
Vector3 pos = transform.position;
pos.y = 0f; // 或者设置为地面高度
transform.position = pos;
}
}
#region --- ---
// 在本地播放行走Animancer or Animator
public void PlayWalkLocal()
{
if (isDead) return;
if (AnimatorComponent != null)
{
AnimatorComponent.CrossFade("Walk", 0.15f); // 假设存在名为 "Walk" 的状态,或使用 SetFloat 控制
}
}
// 在本地播放受击(根据 hitPoint 判断左右)
private void PlayHitLocal(Vector3 hitPoint)
{
if (isDead) return;
Vector3 local = transform.InverseTransformPoint(hitPoint);
string hitStr=local.x<0 ?"leftHit":"rightHit";
if (AnimatorComponent != null)
{
AnimatorComponent.SetTrigger(hitStr); // 回落:直接按名字播放(确保 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 (AnimatorComponent != null)
{
AnimatorComponent.SetBool("dead",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;
if(info is int i && i != -1)
GameManager.Ins.AddScore(i.ToString(), GameManager.Ins.EnemyInfos[type].Score);
RpcPlayDeath(hitPoint);
}
[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 风格兼容)
/// <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 权威)
AnimatorComponent.SetInteger("state",1);
ApplyAttackDamage();
}
public virtual void StopAttack()
{
if (!isServer) return; // 攻击判定/伤害应由 Server 执行Host 权威)
AnimatorComponent.SetInteger("state",0);
}
private IEnumerator DoAttackCoroutine()
{
if (isDead) yield break;
isAttacking = true;
isAttacking = false;
}
public Transform attackPoint; // 攻击检测点
private float attackRange = 3f; // 攻击半径
private float attackAngle = 90f; // 扇形角度
public LayerMask playerLayer; // 玩家所在的层
/// <summary>
/// 在动画结束时调用(通过动画事件)
/// </summary>
public void ApplyAttackDamage()
{
Collider[] hits = Physics.OverlapSphere(attackPoint.position, attackRange, playerLayer);
foreach (Collider hit in hits)
{
// 判断是否在前方45度范围内
Vector3 dirToTarget = (hit.transform.position - attackPoint.position).normalized;
float angle = Vector3.Angle(attackPoint.forward, dirToTarget);
if (angle >= attackAngle ) // 扇形角度一半
{
Player player = hit.GetComponent<Player>();
if (player != null)
{
player.ApplyDamage(atk,null,transform);
}
}
}
}
/// <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
}