577 lines
17 KiB
C#
577 lines
17 KiB
C#
using System.Collections.Generic;
|
||
using BehaviorDesigner.Runtime;
|
||
using BehaviorDesigner.Runtime.Tasks;
|
||
using DragonLi.Core;
|
||
using DragonLi.Frame;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
using UnityEngine.AI;
|
||
|
||
|
||
public class CheckEnemyState : Conditional
|
||
{
|
||
public SharedInt curEnemyState;
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
|
||
if (transform.GetComponent<Enemy>().enemyState == (EnemyState)curEnemyState.Value)
|
||
{
|
||
return TaskStatus.Success;
|
||
}
|
||
return TaskStatus.Failure;
|
||
}
|
||
}
|
||
public class SetEnemyState : Action
|
||
{
|
||
public SharedInt curEnemyState;
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
var enemy= transform.GetComponent<Enemy>();
|
||
if (enemy != null)
|
||
{
|
||
enemy.enemyState = (EnemyState)curEnemyState.Value;
|
||
return TaskStatus.Success;
|
||
}
|
||
return base.OnUpdate();
|
||
}
|
||
}
|
||
|
||
public class MoveToPlayerNearby3 : Action
|
||
{
|
||
public SharedGameObject self;
|
||
public SharedFloat minRange = new SharedFloat { Value = 3f };
|
||
public SharedFloat maxRange = new SharedFloat { Value = 10f };
|
||
public SharedFloat avoidRadius = new SharedFloat { Value = 2f };
|
||
public SharedLayerMask enemyLayer;
|
||
public int maxAttempts = 15;
|
||
|
||
private NavMeshAgent agent;
|
||
private Vector3 targetPosition;
|
||
|
||
public override void OnStart()
|
||
{
|
||
base.OnStart();
|
||
agent = GetComponent<NavMeshAgent>();
|
||
|
||
// 随机优先级,降低 agent 之间争路
|
||
agent.avoidancePriority = Random.Range(0, 100);
|
||
agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
|
||
agent.autoBraking = false;
|
||
|
||
Transform player = GameManager.Ins.player.transform;
|
||
targetPosition = FindValidPosition(player);
|
||
if (agent.isActiveAndEnabled)
|
||
agent.SetDestination(targetPosition);
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance)
|
||
return TaskStatus.Success;
|
||
return TaskStatus.Running;
|
||
}
|
||
|
||
private Vector3 FindValidPosition(Transform player)
|
||
{
|
||
// 从 Manager 或其他地方拿到所有敌人的引用列表
|
||
List<Enemy> allEnemies = GameManager.Ins.curEnemyList;
|
||
|
||
Vector3 bestPos = Vector3.zero;
|
||
float bestScore = -1f;
|
||
|
||
for (int i = 0; i < maxAttempts; i++)
|
||
{
|
||
// 1. 随机取点
|
||
Vector3 dir = Random.insideUnitSphere;
|
||
dir.y = 0;
|
||
dir.Normalize();
|
||
float dist = Random.Range(minRange.Value, maxRange.Value);
|
||
Vector3 raw = player.position + dir * dist;
|
||
|
||
// 2. NavMesh 采样
|
||
if (!NavMesh.SamplePosition(raw, out NavMeshHit hit, 2f, NavMesh.AllAreas))
|
||
continue;
|
||
var cand = hit.position;
|
||
|
||
// 3. 与玩家最小距离硬性筛掉
|
||
if (Vector3.Distance(cand, player.position) < minRange.Value)
|
||
continue;
|
||
|
||
// 4. 计算到其它每个敌人的最小距离
|
||
float minDistToOthers = float.MaxValue;
|
||
foreach (var go in allEnemies)
|
||
{
|
||
if (go == null || go == self.Value) continue;
|
||
float d = Vector3.Distance(cand, go.transform.position);
|
||
minDistToOthers = Mathf.Min(minDistToOthers, d);
|
||
if (minDistToOthers < avoidRadius.Value)
|
||
break; // 太近了,提前跳出
|
||
}
|
||
|
||
//5.如果通过了 avoidRadius,就直接返回
|
||
if (minDistToOthers >= avoidRadius.Value)
|
||
return cand;
|
||
|
||
// 6. 否则记录最佳分数点
|
||
if (minDistToOthers > bestScore)
|
||
{
|
||
bestScore = minDistToOthers;
|
||
bestPos = cand;
|
||
}
|
||
}
|
||
|
||
// 如果所有尝试都没严格过,也选“最远离群”的那个
|
||
if (bestScore > 0f)
|
||
return bestPos;
|
||
|
||
// 兜底:玩家前方一个安全距离
|
||
Vector3 fb = player.position + player.forward * minRange.Value;
|
||
if (NavMesh.SamplePosition(fb, out NavMeshHit fh, 2f, NavMesh.AllAreas))
|
||
return fh.position;
|
||
|
||
// 最后随机返回一个点
|
||
return player.position + Random.insideUnitSphere * (minRange.Value + 0.5f);
|
||
}
|
||
|
||
// 调试画出可行区域
|
||
void OnDrawGizmosSelected()
|
||
{
|
||
if (self == null || self.Value != gameObject) return;
|
||
Gizmos.color = Color.cyan;
|
||
Gizmos.DrawWireSphere(targetPosition, 0.2f);
|
||
}
|
||
}
|
||
|
||
public class MoveToPlayerFront : Action
|
||
{
|
||
public SharedFloat minDistance = new SharedFloat { Value = 3f };
|
||
public SharedFloat maxDistance = new SharedFloat { Value = 5f };
|
||
public SharedFloat minAngle = new SharedFloat { Value = 30f };
|
||
public SharedFloat maxAngle = new SharedFloat { Value = 40f };
|
||
// 添加静态字典记录每个怪物的移动状态
|
||
private static Dictionary<GameObject, bool> hasMoved = new Dictionary<GameObject, bool>();
|
||
|
||
private NavMeshAgent agent;
|
||
private bool hasReachedPosition;
|
||
private Vector3 finalPosition; // 存储最终位置
|
||
|
||
// 添加静态字典记录每个敌人的移动状态
|
||
private static Dictionary<int, bool> hasMovedDict = new Dictionary<int, bool>();
|
||
private int instanceID;
|
||
|
||
|
||
public override void OnAwake()
|
||
{
|
||
instanceID = gameObject.GetInstanceID();
|
||
}
|
||
|
||
public override void OnStart()
|
||
{
|
||
// 检查是否已经移动过
|
||
if (hasMovedDict.ContainsKey(instanceID) && hasMovedDict[instanceID])
|
||
{
|
||
hasReachedPosition = true;
|
||
return;
|
||
}
|
||
|
||
agent = GetComponent<NavMeshAgent>();
|
||
agent.autoBraking = true;
|
||
agent.stoppingDistance = 0.1f;
|
||
|
||
// 计算玩家正前方扇形区域内的随机位置
|
||
finalPosition = CalculateFrontPosition();
|
||
|
||
if (agent.isActiveAndEnabled)
|
||
{
|
||
agent.SetDestination(finalPosition);
|
||
hasReachedPosition = false;
|
||
}
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
// 如果已经移动过,直接返回成功
|
||
if (hasMovedDict.ContainsKey(instanceID) && hasMovedDict[instanceID])
|
||
{
|
||
return TaskStatus.Success;
|
||
}
|
||
|
||
if (hasReachedPosition)
|
||
{
|
||
//标记为已移动
|
||
hasMovedDict[instanceID] = true;
|
||
return TaskStatus.Success;
|
||
}
|
||
|
||
// 检查是否到达目标位置
|
||
if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance)
|
||
{
|
||
// 完全停止导航系统
|
||
agent.isStopped = true;
|
||
agent.ResetPath();
|
||
agent.enabled = false; // 禁用导航组件
|
||
|
||
// 固定位置
|
||
transform.position = finalPosition;
|
||
|
||
// 添加物理约束(如果有刚体)
|
||
Rigidbody rb = GetComponent<Rigidbody>();
|
||
if (rb != null)
|
||
{
|
||
rb.isKinematic = true;
|
||
rb.velocity = Vector3.zero;
|
||
rb.angularVelocity = Vector3.zero;
|
||
}
|
||
|
||
// 标记为已移动
|
||
hasMoved[gameObject] = true;
|
||
hasReachedPosition = true;
|
||
return TaskStatus.Success;
|
||
}
|
||
|
||
return TaskStatus.Running;
|
||
}
|
||
|
||
private Vector3 CalculateFrontPosition()
|
||
{
|
||
Transform player = GameManager.Ins.player.transform;
|
||
Vector3 playerPos = player.position;
|
||
Vector3 playerForward = player.forward;
|
||
|
||
// 随机角度 (30-40度之间,随机左右侧)
|
||
float randomAngle = Random.Range(minAngle.Value, maxAngle.Value) *
|
||
(Random.value > 0.5f ? 1f : -1f);
|
||
|
||
// 随机距离
|
||
float randomDistance = Random.Range(minDistance.Value, maxDistance.Value);
|
||
|
||
// 计算方向向量 (玩家前方旋转随机角度)
|
||
Quaternion rotation = Quaternion.Euler(0f, randomAngle, 0f);
|
||
Vector3 direction = rotation * playerForward;
|
||
|
||
// 计算目标位置
|
||
Vector3 targetPos = playerPos + direction * randomDistance;
|
||
|
||
// 确保位置在NavMesh上
|
||
if (NavMesh.SamplePosition(targetPos, out NavMeshHit hit, 5f, NavMesh.AllAreas)) // 增加采样范围
|
||
{
|
||
// 确保不会出现在半空中
|
||
if (Mathf.Abs(hit.position.y - playerPos.y) > 2f)
|
||
{
|
||
// 如果高度差太大,调整到玩家高度
|
||
return new Vector3(hit.position.x, playerPos.y, hit.position.z);
|
||
}
|
||
return hit.position;
|
||
}
|
||
|
||
// 如果采样失败,使用玩家位置(确保不会出现在半空中)
|
||
return playerPos + Vector3.up * 0.1f; // 稍微高于地面
|
||
}
|
||
|
||
// 当敌人被销毁时清理记录
|
||
public override void OnEnd()
|
||
{
|
||
if (hasMovedDict.ContainsKey(instanceID))
|
||
{
|
||
hasMovedDict.Remove(instanceID);
|
||
}
|
||
}
|
||
}
|
||
|
||
public class SetAttackState : Action
|
||
{
|
||
public SharedBool attackState;
|
||
|
||
// 添加静态字典记录每个敌人的攻击状态
|
||
private static Dictionary<int, bool> attackStateSet = new Dictionary<int, bool>();
|
||
private int instanceID;
|
||
|
||
public override void OnAwake()
|
||
{
|
||
instanceID = gameObject.GetInstanceID();
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
// 如果已经设置过攻击状态,直接返回成功
|
||
if (attackStateSet.ContainsKey(instanceID) && attackStateSet[instanceID])
|
||
{
|
||
return TaskStatus.Success;
|
||
}
|
||
|
||
Enemy1 enemy = GetComponent<Enemy1>();
|
||
if (enemy != null)
|
||
{
|
||
enemy.SetAttackState(attackState.Value);
|
||
|
||
// 标记为已设置
|
||
attackStateSet[instanceID] = true;
|
||
return TaskStatus.Success;
|
||
}
|
||
|
||
return TaskStatus.Failure;
|
||
}
|
||
|
||
// 当敌人被销毁时清理记录
|
||
public override void OnEnd()
|
||
{
|
||
if (attackStateSet.ContainsKey(instanceID))
|
||
{
|
||
attackStateSet.Remove(instanceID);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测boss是否到阶段血量
|
||
/// </summary>
|
||
public class CheckEnemyHp : Conditional
|
||
{
|
||
public SharedFloat hpIndex;
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
var curHpIndex = transform.GetComponent<Enemy>().health/transform.GetComponent<Enemy>().maxHealth;
|
||
if (curHpIndex <= hpIndex.Value)
|
||
{
|
||
return TaskStatus.Success;
|
||
}
|
||
return TaskStatus.Failure;
|
||
}
|
||
}
|
||
|
||
public class UserSkill : Action
|
||
{
|
||
public Enemy EnemyObj;
|
||
public SharedFloat hpIndex;
|
||
public override void OnStart()
|
||
{
|
||
base.OnStart();
|
||
EnemyObj = transform.GetComponent<Enemy>();
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if (EnemyObj == null)
|
||
return TaskStatus.Failure;
|
||
var curHpIndex = EnemyObj.health/EnemyObj.maxHealth;
|
||
if (curHpIndex <= hpIndex.Value)
|
||
{
|
||
EnemyObj.userSillIng = false;
|
||
}
|
||
var isUserIng = EnemyObj.userSillIng;
|
||
if (isUserIng) return TaskStatus.Running;
|
||
return TaskStatus.Success;
|
||
}
|
||
}
|
||
|
||
public class BossIsUserSkill : Conditional
|
||
{
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
Enemy curEnemy = GetComponent<Enemy>();
|
||
if(curEnemy.userSillIng)
|
||
return TaskStatus.Success;
|
||
return TaskStatus.Failure;
|
||
}
|
||
}
|
||
|
||
public class BossIsAttackState : Conditional
|
||
{
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
Enemy curEnemy = GetComponent<Enemy>();
|
||
if(curEnemy.isAttack)
|
||
return TaskStatus.Success;
|
||
return TaskStatus.Failure;
|
||
}
|
||
}
|
||
|
||
public class BossIsQteAttack : Conditional
|
||
{
|
||
public SharedBool isQteAttack;
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if(isQteAttack.Value)
|
||
return TaskStatus.Success;
|
||
return TaskStatus.Failure;
|
||
}
|
||
}
|
||
|
||
|
||
public class WaitAllPetDie : Action
|
||
{
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if (GameManager.Ins.IsAllBossPetDie())
|
||
{
|
||
Enemy curEnemy = GetComponent<Enemy>();
|
||
if (curEnemy)
|
||
{
|
||
curEnemy.isShield = false;
|
||
curEnemy.shieldObj.SetActive(false);
|
||
}
|
||
return TaskStatus.Success;
|
||
}
|
||
return TaskStatus.Running;
|
||
}
|
||
}
|
||
|
||
public class IsBossGunDie : Conditional
|
||
{
|
||
public SharedString GunIdStr;
|
||
|
||
private List<int> gunIdList;
|
||
public override void OnStart()
|
||
{
|
||
base.OnStart();
|
||
gunIdList=new List<int>();
|
||
string[] str=GunIdStr.Value.Split(',');
|
||
foreach (var item in str)
|
||
{
|
||
gunIdList.Add(int.Parse(item));
|
||
}
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
Enemy curEnemy = GetComponent<Enemy>();
|
||
foreach (var id in gunIdList)
|
||
{
|
||
if (curEnemy.components[id] != null && !curEnemy.components[id].isDead)
|
||
{
|
||
return TaskStatus.Failure;
|
||
}
|
||
}
|
||
|
||
return TaskStatus.Success;
|
||
}
|
||
}
|
||
|
||
public class BossQteAttack : Action
|
||
{
|
||
private Enemy _curEnemy;
|
||
|
||
public float QteTime;
|
||
public SharedBool IsQteAttacked;
|
||
public SharedBool IsStopQteAttacked;
|
||
private float _currentQteTime;
|
||
public override void OnStart()
|
||
{
|
||
base.OnStart();
|
||
//QteTime = 5;
|
||
_curEnemy = GetComponent<Enemy>();
|
||
if (_curEnemy)
|
||
{
|
||
IsQteAttacked.Value = false;
|
||
_curEnemy.StartQteAttack();
|
||
}
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
_currentQteTime += Time.deltaTime;
|
||
if (_curEnemy.components[^1] != null && _curEnemy.components[^1].isDead)
|
||
{
|
||
IsStopQteAttacked.Value =true;
|
||
_curEnemy.StopQteAttack();
|
||
return TaskStatus.Success;
|
||
}
|
||
if (_currentQteTime >= QteTime)
|
||
{
|
||
IsStopQteAttacked.Value =false;
|
||
_curEnemy.ThreeAttackMode();
|
||
return TaskStatus.Success;
|
||
}
|
||
return TaskStatus.Running;
|
||
}
|
||
}
|
||
|
||
public class CooldownTick : Action
|
||
{
|
||
public SharedFloat cooldownTimer;
|
||
public SharedBool skillReady;
|
||
|
||
public SharedFloat hp;
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
Enemy curEnemy = GetComponent<Enemy>();
|
||
var curHpIndex = curEnemy.health/curEnemy.maxHealth;
|
||
if (curHpIndex <= hp.Value)
|
||
{
|
||
skillReady.Value = false;
|
||
cooldownTimer.Value = 15;
|
||
return TaskStatus.Success;
|
||
}
|
||
if (cooldownTimer.Value > 0f) {
|
||
cooldownTimer.Value -= Time.deltaTime;
|
||
if (cooldownTimer.Value <= 0f) {
|
||
cooldownTimer.Value = 0f;
|
||
skillReady.Value = true;
|
||
return TaskStatus.Success;
|
||
}
|
||
}
|
||
return TaskStatus.Running;
|
||
}
|
||
}
|
||
|
||
public class ReleaseSkill : Action
|
||
{
|
||
public SharedFloat cooldownTimer;
|
||
public SharedBool skillReady;
|
||
public float skillCooldown = 15f;
|
||
|
||
public override void OnStart()
|
||
{
|
||
base.OnStart();
|
||
// 进入冷却
|
||
cooldownTimer.Value = skillCooldown;
|
||
skillReady.Value = false;
|
||
}
|
||
}
|
||
|
||
public class NormalAttack : Action
|
||
{
|
||
private Enemy1 enemy;
|
||
|
||
public override void OnAwake()
|
||
{
|
||
enemy = GetComponent<Enemy1>();
|
||
}
|
||
|
||
public override void OnStart()
|
||
{
|
||
//// 你的普攻逻辑
|
||
//Debug.Log("执行普攻");
|
||
//Enemy curEnemy = GetComponent<Enemy>();
|
||
//curEnemy.Attack();
|
||
if(enemy != null && !enemy.isAttack)
|
||
{
|
||
enemy.Attack();
|
||
}
|
||
}
|
||
}
|
||
|
||
public class IsSkillReady : Conditional
|
||
{
|
||
public SharedBool skillReady;
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
return skillReady.Value ? TaskStatus.Success : TaskStatus.Failure;
|
||
}
|
||
}
|
||
|
||
public class ConditionalStopQte : Conditional
|
||
{
|
||
public SharedBool IsStopQteAttack;
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if(!IsStopQteAttack.Value)
|
||
return TaskStatus.Success;
|
||
return TaskStatus.Failure;
|
||
}
|
||
}
|