517 lines
14 KiB
C#
517 lines
14 KiB
C#
using System;
|
|
using BehaviorDesigner.Runtime;
|
|
using DarkTonic.MasterAudio;
|
|
using DragonLi.Core;
|
|
using DragonLi.Frame;
|
|
using Mirror;
|
|
using UnityEngine;
|
|
namespace Valheim
|
|
{
|
|
public enum EnemyState
|
|
{
|
|
/// <summary>
|
|
/// 地上
|
|
/// </summary>
|
|
GroundIdle = 0,
|
|
/// <summary>
|
|
/// 地上警戒
|
|
/// </summary>
|
|
GroundAlert = 2,
|
|
/// <summary>
|
|
/// 地下警戒
|
|
/// </summary>
|
|
UnderGroundAlert = 1,
|
|
/// <summary>
|
|
/// 幽灵
|
|
/// </summary>
|
|
Ghost = 3,
|
|
///// <summary>
|
|
/// 受击
|
|
/// </summary>
|
|
TakeDamag = 4,
|
|
|
|
/// <summary>
|
|
/// 返回老巢状态
|
|
/// </summary>
|
|
ReturnToNest=5,
|
|
}
|
|
public class Enemy : Agent
|
|
{
|
|
#region 属性
|
|
/// <summary>
|
|
/// 怪物唯一id
|
|
/// </summary>
|
|
public int id = 0;
|
|
|
|
/// <summary>
|
|
/// 所属区域
|
|
/// </summary>
|
|
#if UNITY_EDITOR
|
|
[DisplayOnly]
|
|
#endif
|
|
public int areaId = -1;
|
|
|
|
|
|
[SyncVar]
|
|
public float modelScale = 1.5f;
|
|
|
|
/// <summary>
|
|
/// 怪物状态
|
|
/// </summary>
|
|
#if UNITY_EDITOR
|
|
[DisplayOnly]
|
|
#endif
|
|
public EnemyState state = EnemyState.GroundAlert;
|
|
|
|
/// <summary>
|
|
/// 等级
|
|
/// </summary>
|
|
#if UNITY_EDITOR
|
|
[DisplayOnly]
|
|
#endif
|
|
[SyncVar]
|
|
public int lvl = 0;
|
|
|
|
/// <summary>
|
|
/// 速度
|
|
/// </summary>
|
|
[NonSerialized]
|
|
public float speed = 3;
|
|
|
|
/// <summary>
|
|
/// 攻击
|
|
/// </summary>
|
|
#if UNITY_EDITOR
|
|
[DisplayOnly]
|
|
#endif
|
|
public float atk = 5;
|
|
|
|
/// <summary>
|
|
/// 攻击速率
|
|
/// </summary>
|
|
[NonSerialized]
|
|
public float rate = 1;
|
|
|
|
/// <summary>
|
|
/// 攻击范围
|
|
/// </summary>
|
|
// #if UNITY_EDITOR
|
|
// [DisplayOnly]
|
|
// #endif
|
|
public float atkArea = 0.4f;
|
|
|
|
/// <summary>
|
|
/// 占地半径
|
|
/// </summary>
|
|
[NonSerialized]
|
|
public float radius = 0.6f;
|
|
|
|
|
|
/// <summary>
|
|
/// 警戒范围
|
|
/// </summary>
|
|
[NonSerialized]
|
|
public float alertArea = 3.0f;
|
|
public EnemyType type;
|
|
#endregion
|
|
|
|
public BehaviorTree behaviorTree;
|
|
|
|
public LayerMask hitMask;
|
|
|
|
public GameObject model;
|
|
|
|
public Material[] skin;
|
|
|
|
public float modelHeight;
|
|
|
|
public string enemyName;
|
|
|
|
public Transform uiPos;
|
|
|
|
public Transform startPos;
|
|
|
|
/// <summary>
|
|
/// 皮肤
|
|
/// </summary>
|
|
public SkinnedMeshRenderer skinnedMeshRenderer;
|
|
|
|
/// <summary>
|
|
/// 装饰品
|
|
/// </summary>
|
|
public GameObject[] Ornamental;
|
|
|
|
/// <summary>
|
|
/// 武器
|
|
/// </summary>
|
|
public GameObject[] Weapon;
|
|
|
|
public GameObject ui;
|
|
|
|
private AudioSource _audioSource = null;
|
|
|
|
public AudioSource audioSource
|
|
{
|
|
get
|
|
{
|
|
if (_audioSource == null)
|
|
{
|
|
_audioSource = GetComponentInChildren<AudioSource>();
|
|
}
|
|
return _audioSource;
|
|
}
|
|
}
|
|
|
|
[SoundGroup] public string attackSound;
|
|
|
|
[SoundGroup] public string idleSound;
|
|
|
|
[SoundGroup] public string runSound;
|
|
|
|
[SoundGroup] public string dieSound;
|
|
|
|
public override void Awake()
|
|
{
|
|
base.Awake();
|
|
}
|
|
|
|
public override void InitData()
|
|
{
|
|
base.InitData();
|
|
}
|
|
|
|
public void InitBree()
|
|
{
|
|
behaviorTree.RegisterEvent("UserSkill", UseSkill);
|
|
}
|
|
|
|
[Server]
|
|
public virtual void OnSpawn(EnemyData data, int areaId, int lvl, EnemyState state)
|
|
{
|
|
base.OnSpawn();
|
|
//this.type = stat;
|
|
//id = curId;
|
|
this.areaId = areaId;
|
|
this.lvl = lvl;
|
|
this.state = state;
|
|
atk = data.Atk;
|
|
health = data.Hp;
|
|
OriginHealth = data.Hp;
|
|
//radius = transform.localScale.x/2f;
|
|
//NavAgent.radius = radius;
|
|
campId = data.Camp;
|
|
modelHeight = uiPos.position.y;
|
|
//atkArea=transform.localScale.x;
|
|
enemyName = data.Name;
|
|
startPos=transform;
|
|
RpcSyncModel(modelScale);
|
|
RpcChangeSkin();
|
|
RpcShowOrnamental();
|
|
// if (state == EnemyState.UnderGroundAlert)
|
|
// {
|
|
// AnimatorComponent.SetInteger("State", (int)EnemyState.UnderGroundAlert); RpcHideModel();
|
|
// }
|
|
behaviorTree.enabled = true;
|
|
NavAgent.enabled = true;
|
|
behaviorTree.SetVariable("InitPos", (SharedVector3)transform.position);
|
|
behaviorTree.SetVariable("InitRotate", (SharedVector3)transform.eulerAngles);
|
|
behaviorTree.RegisterEvent("TrueDie", TrueDie);
|
|
behaviorTree.RegisterEvent("FoundEnemy", FoundEnemy);
|
|
GameManager.Ins.CreateEnemyUI(this);
|
|
|
|
skillList.Clear();
|
|
foreach (var skillId in data.SkillIds)
|
|
{
|
|
GameManager.Ins.SkillDataList.TryGetValue(skillId, out var skill);
|
|
if(skill==null)
|
|
continue;
|
|
if (skill.SkillInterval <= 1)
|
|
{
|
|
skillHpInterval=skill.SkillInterval;
|
|
}
|
|
else
|
|
{
|
|
waitSkillTime = skill.SkillInterval;
|
|
}
|
|
skillList.Add(skillId);
|
|
}
|
|
curWaitSkillTime = waitSkillTime;
|
|
InitBree();
|
|
}
|
|
|
|
[ClientRpc]
|
|
public void RpcSyncModel(float scale)
|
|
{
|
|
if (model != null)
|
|
{
|
|
model.transform.localScale = new Vector3(scale, scale, scale);
|
|
}
|
|
}
|
|
|
|
[ClientRpc]
|
|
public void RpcChangeSkin()
|
|
{
|
|
// int randomIndex = UnityEngine.Random.Range(0, skin.Length);
|
|
//
|
|
// if (skinnedMeshRenderer != null)
|
|
// {
|
|
// skinnedMeshRenderer.material = skin[randomIndex];
|
|
// }
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 随机展示一个装饰物品
|
|
/// </summary>
|
|
[ClientRpc]
|
|
public void RpcShowOrnamental()
|
|
{
|
|
if (Ornamental.Length > 0)
|
|
{
|
|
Ornamental[UnityEngine.Random.Range(0, Ornamental.Length)].SetActive(true);
|
|
}
|
|
if (Weapon.Length > 0)
|
|
{
|
|
Weapon[UnityEngine.Random.Range(0, Weapon.Length)].SetActive(true);
|
|
}
|
|
}
|
|
|
|
[ClientRpc]
|
|
public void RpcHideModel()
|
|
{
|
|
if (model != null)
|
|
{
|
|
model.SetActive(false);
|
|
}
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
if (isServer && IsAlive)
|
|
{
|
|
if (NavAgent != null & NavAgent.enabled)
|
|
{
|
|
UpdateAniamtorValues(NavAgent.velocity);
|
|
}
|
|
else if (RigidbodyComponent)
|
|
{
|
|
UpdateAniamtorValues(RigidbodyComponent.velocity);
|
|
}
|
|
OnUpdate();
|
|
|
|
if (!isUserSkill)
|
|
{
|
|
curWaitSkillTime-=Time.deltaTime;
|
|
if (curWaitSkillTime <= 0)
|
|
{
|
|
|
|
isUserSkill = true;
|
|
}
|
|
}
|
|
|
|
if (!isUserHpSkill&&userHpSkillIndex<=0)
|
|
{
|
|
if (skillHpInterval != 0 && health <= skillHpInterval)
|
|
isUserHpSkill = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Die(object info, Transform _sender)
|
|
{
|
|
|
|
GameManager.Ins.EnemyList.Remove(this);
|
|
NavAgent.enabled = false;
|
|
GameManager.Ins.CreateSpawnBallPoint(new Vector3(transform.position.x, 0.1F, transform.position.z));
|
|
GameManager.Ins.CheckAreaSettle(areaId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 攻击
|
|
/// </summary>
|
|
public virtual void AttackOnce()
|
|
{
|
|
if (isServer)
|
|
{
|
|
GameObject target = (GameObject)behaviorTree.GetVariable("Target").GetValue();
|
|
if (target == null) return;
|
|
Ray ray = new Ray(transform.position, transform.forward);
|
|
if (Physics.Raycast(ray, out RaycastHit hit, 3F, hitMask))
|
|
{
|
|
Vector3 vector3 = new Vector3(hit.point.x, 0.2F, hit.point.z);
|
|
GameManager.Ins.CreateHitEffect(vector3);
|
|
}
|
|
|
|
IDamagable damagable = target.GetComponent<IDamagable>();
|
|
if (damagable != null)
|
|
{
|
|
Debug.Log("宠物受伤:"+atk);
|
|
damagable.ApplyDamage(atk, false, transform);
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void FoundEnemy()
|
|
{
|
|
RpcShowModel();
|
|
}
|
|
|
|
[ClientRpc]
|
|
public void RpcShowModel()
|
|
{
|
|
if (model != null)
|
|
{
|
|
model.SetActive(true);
|
|
}
|
|
}
|
|
|
|
public void TrueDie()
|
|
{
|
|
NetworkServer.Destroy(transform.gameObject);
|
|
NetworkServer.Destroy(ui.gameObject);
|
|
}
|
|
|
|
[ClientRpc]
|
|
public void PlayAudioRpc(string path)
|
|
{
|
|
if (audioSource != null)
|
|
{
|
|
AudioClip clip = Resources.Load<AudioClip>(path);
|
|
if (clip != null)
|
|
{
|
|
audioSource.PlayOneShot(clip);
|
|
}
|
|
}
|
|
}
|
|
|
|
[ClientRpc]
|
|
public void PlayAudioVecRpc(string path, Transform target)
|
|
{
|
|
MasterAudio.PlaySound3DAtVector3(path, target.position);
|
|
}
|
|
|
|
[ClientRpc]
|
|
public void PlayAudioFollowTrans(string path, Transform target)
|
|
{
|
|
MasterAudio.PlaySound3DFollowTransform(path, target);
|
|
}
|
|
|
|
// todo
|
|
[Server]
|
|
public void Iced(float atk)
|
|
{
|
|
IDamagable damagable = transform.GetComponent<IDamagable>();
|
|
if (damagable != null)
|
|
{
|
|
damagable.ApplyDamage(atk, false, null);
|
|
}
|
|
}
|
|
|
|
|
|
public void SetRadiu(float value)
|
|
{
|
|
radius = value;
|
|
}
|
|
|
|
[SoundGroup] public string hitSound;
|
|
|
|
/// <summary>
|
|
/// 被玩家击中
|
|
/// </summary>
|
|
[Server]
|
|
public void KnockTakeDamage(float atk, Vector3 hitPoint)
|
|
{
|
|
//造成伤害
|
|
IDamagable damagable = transform.GetComponent<IDamagable>();
|
|
if (damagable != null)
|
|
{
|
|
damagable.ApplyDamage(atk, false, null);
|
|
}
|
|
//PlayAudioVecRpc(hitSound, transform);
|
|
//受击特效
|
|
Vector3 vector3 = new Vector3(hitPoint.x, hitPoint.y, hitPoint.z);
|
|
GameManager.Ins.CreateHitEffect(vector3);
|
|
//播放受击动画
|
|
}
|
|
|
|
|
|
|
|
public int LastState;
|
|
|
|
/// <summary>
|
|
/// 受击
|
|
/// </summary>
|
|
[Server]
|
|
public void PlayTakeDamageAnim()
|
|
{
|
|
RpcTakeDamage();
|
|
}
|
|
public void RpcTakeDamage()
|
|
{
|
|
AnimatorStateInfo stateInfo = AnimatorComponent.GetCurrentAnimatorStateInfo(0);
|
|
if (!stateInfo.IsName("TakeDamage"))
|
|
{
|
|
if (AnimatorComponent.GetInteger("State") != 5)
|
|
{
|
|
LastState = AnimatorComponent.GetInteger("State");
|
|
}
|
|
state = EnemyState.TakeDamag;
|
|
behaviorTree.SetVariable("isHit", (SharedBool)true);
|
|
}
|
|
}
|
|
|
|
private Coroutine currentSkillCoroutine;
|
|
public void UseSkill()
|
|
{
|
|
int skillId= (int)behaviorTree.GetVariable("skillId").GetValue();
|
|
var target= (GameObject)behaviorTree.GetVariable("Target").GetValue();
|
|
if(target==null)
|
|
return;
|
|
SkillLogicBase skillLogic = GameManager.Ins.skillManager.GetSkillLogic(skillId);
|
|
if (skillLogic == null)
|
|
{
|
|
Debug.LogWarning($"技能ID[{skillId}] 不存在或未在 SkillManager 中注册");
|
|
return;
|
|
}
|
|
// 开启协程执行技能
|
|
if (currentSkillCoroutine != null)
|
|
{
|
|
// 如果上一个技能还在释放,可根据需求中断或直接返回
|
|
StopCoroutine(currentSkillCoroutine);
|
|
}
|
|
|
|
AnimatorComponent.SetInteger("SkillId", skillId);
|
|
AnimatorComponent.SetBool("userSkill", true);
|
|
currentSkillCoroutine = StartCoroutine(skillLogic.ExecuteSkill(gameObject, target));
|
|
|
|
}
|
|
|
|
|
|
public void PlaySound(int soundType)
|
|
{
|
|
MasterAudio.StopAllSoundsOfTransform(transform);
|
|
string str = "";
|
|
switch (soundType)
|
|
{
|
|
case 0:
|
|
str = idleSound;
|
|
break;
|
|
case 1:
|
|
str = runSound;
|
|
break;
|
|
case 2:
|
|
str = attackSound;
|
|
break;
|
|
case 3:
|
|
str = dieSound;
|
|
break;
|
|
}
|
|
if(str!="")
|
|
MasterAudio.PlaySound3DAtVector3(str, transform.position);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|