Files
FutureMen/Assets/Scripts/Actions.cs
2025-07-23 13:48:49 +08:00

667 lines
19 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.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 = 3f; // 最小距离
public SharedFloat maxDistance = 8f; // 最大距离
public SharedFloat minAngle = 30f; // 最小角度(度)
public SharedFloat maxAngle = 40f; // 最大角度(度)
public SharedFloat attackRange = 10f; // 攻击范围
public SharedFloat attackInterval = 1f; // 攻击间隔(秒)
//分散参数
public SharedFloat minSeparation = 2.5f;//最小间距
public int maxPlacementAttempts = 15;//最大尝试次数
private NavMeshAgent agent;
private Transform playerTransform;
private bool hasReachedDestination;
private float lastAttackTime;
private Enemy1 enemyComponent;
private Vector3 targetPosition;
//静态列表跟踪所有小兵位置
private static List<Vector3> occupiedPositions = new List<Vector3>();
public override void OnStart()
{
agent = GetComponent<NavMeshAgent>();
playerTransform = GameManager.Ins.player.transform;
enemyComponent = GetComponent<Enemy1>();
agent.stoppingDistance = 0.1f;
agent.autoBraking = true;
// 计算分散位置
targetPosition = CalculateFrontPosition();
agent.SetDestination(targetPosition);
hasReachedDestination = false;
//添加到已占用位置列表
occupiedPositions.Add(targetPosition);
//初始化攻击计时器
lastAttackTime = Time.time;
}
public override void OnEnd()
{
//从占用列表中移除
occupiedPositions.Remove(targetPosition);
}
public override TaskStatus OnUpdate()
{
// 1. 如果还没到达目标位置,继续移动
if (!hasReachedDestination)
{
if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance)
{
// 到达后停止移动并保持原位
agent.isStopped = true;
hasReachedDestination = true;
}
else
{
return TaskStatus.Running;
}
}
// 2. 持续面向玩家
FacePlayer();
// 3. 在攻击范围内且冷却结束,执行攻击
if (CanAttackPlayer())
{
AttackPlayer();
}
// 4. 保持在原位执行攻击
return TaskStatus.Running;
}
private Vector3 CalculateFrontPosition()
{
int attempts = 0;
Vector3 candidatePosition;
bool positionValid;
do
{
// 1. 生成候选位置
candidatePosition = CalculateRandomFrontPosition();
// 2. 检查位置是否有效
positionValid = IsPositionValid(candidatePosition);
// 3. 增加尝试计数
attempts++;
} while (!positionValid && attempts < maxPlacementAttempts);
return candidatePosition;
}
private Vector3 CalculateRandomFrontPosition()
{
//获取玩家前方向
Vector3 playerForward = playerTransform.forward;
playerForward.y = 0;
playerForward.Normalize();
//随机角度30-40°之间随机左右
float randomAngle = Random.Range(minAngle.Value, maxAngle.Value);
randomAngle *= (Random.value > 0.5f) ? 1 : -1;//随机左右方向
//随机距离
float randomDistance = Random.Range(minDistance.Value, maxDistance.Value);
//计算方向向量
Quaternion rotation = Quaternion.Euler(0, randomAngle, 0);
Vector3 direction = rotation * playerForward;
//计算目标位置
Vector3 targetPosition = playerTransform.position + direction * randomDistance;
//NavMesh采样
if(NavMesh.SamplePosition(targetPosition,out NavMeshHit hit, 2f, NavMesh.AllAreas))
{
return hit.position;
}
return playerTransform.position + playerForward * minDistance.Value;
}
private bool IsPositionValid(Vector3 position)
{
//检查是否与其他小怪距离太近
foreach(Vector3 occupiedPos in occupiedPositions)
{
if (Vector3.Distance(position, occupiedPos) < minSeparation.Value)
{
return false;
}
}
//检查与玩家的距离
float distanceToPlayer = Vector3.Distance(position, playerTransform.position);
if (distanceToPlayer < minDistance.Value || distanceToPlayer > maxDistance.Value)
{
return false;
}
//检查NavMesh是否可以行走
if(!NavMesh.SamplePosition(position,out NavMeshHit hit, 0.5f, NavMesh.AllAreas))
{
return false;
}
return true;
}
private void FacePlayer()
{
//平滑转向玩家
Vector3 direction = (playerTransform.position - transform.position).normalized;
direction.y = 0;
if (direction != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(
transform.rotation,
targetRotation,
Time.deltaTime * 5f
);
}
}
private bool CanAttackPlayer()
{
// 1. 检查距离
float distance = Vector3.Distance(transform.position, playerTransform.position);
if (distance > attackRange.Value) return false;
// 2. 检查攻击冷却
if (Time.time - lastAttackTime < attackInterval.Value) return false;
//// 3. 检查视线(可选)
//RaycastHit hit;
//if (Physics.Raycast(transform.position, playerTransform.position - transform.position, out hit, attackRange.Value))
//{
// return hit.transform == playerTransform;
//}
return true; ;
}
private void AttackPlayer()
{
if (enemyComponent != null)
{
enemyComponent.Attack();
lastAttackTime = Time.time;
}
}
// 调试可视化
private void OnDrawGizmosSelected()
{
if (!Application.isPlaying || playerTransform == null) return;
Gizmos.color = Color.cyan;
Gizmos.DrawSphere(agent.destination, 0.5f);
// 绘制扇形区域
DrawAngleSector(minAngle.Value, maxAngle.Value, minDistance.Value, maxDistance.Value);
}
private void DrawAngleSector(float minDeg, float maxDeg, float minDist, float maxDist)
{
Gizmos.color = new Color(1, 0.5f, 0, 0.3f);
// 绘制左右扇形
DrawSingleSector(minDeg, maxDeg, minDist, maxDist);
DrawSingleSector(-minDeg, -maxDeg, minDist, maxDist);
}
private void DrawSingleSector(float startDeg, float endDeg, float minDist, float maxDist)
{
Vector3 playerPos = playerTransform.position;
Vector3 baseDir = playerTransform.forward;
int segments = 10;
float angleStep = (endDeg - startDeg) / segments;
// 绘制外弧
for (int i = 0; i <= segments; i++)
{
float angle = startDeg + i * angleStep;
Quaternion rot = Quaternion.Euler(0, angle, 0);
Vector3 dir = rot * baseDir;
Vector3 point = playerPos + dir * maxDist;
if (i > 0)
{
float prevAngle = startDeg + (i - 1) * angleStep;
Quaternion prevRot = Quaternion.Euler(0, prevAngle, 0);
Vector3 prevDir = prevRot * baseDir;
Vector3 prevPoint = playerPos + prevDir * maxDist;
Gizmos.DrawLine(prevPoint, point);
}
}
// 绘制内弧
for (int i = 0; i <= segments; i++)
{
float angle = startDeg + i * angleStep;
Quaternion rot = Quaternion.Euler(0, angle, 0);
Vector3 dir = rot * baseDir;
Vector3 point = playerPos + dir * minDist;
if (i > 0)
{
float prevAngle = startDeg + (i - 1) * angleStep;
Quaternion prevRot = Quaternion.Euler(0, prevAngle, 0);
Vector3 prevDir = prevRot * baseDir;
Vector3 prevPoint = playerPos + prevDir * minDist;
Gizmos.DrawLine(prevPoint, point);
}
}
// 绘制连接线
Vector3 startMin = playerPos + Quaternion.Euler(0, startDeg, 0) * baseDir * minDist;
Vector3 startMax = playerPos + Quaternion.Euler(0, startDeg, 0) * baseDir * maxDist;
Vector3 endMin = playerPos + Quaternion.Euler(0, endDeg, 0) * baseDir * minDist;
Vector3 endMax = playerPos + Quaternion.Euler(0, endDeg, 0) * baseDir * maxDist;
Gizmos.DrawLine(startMin, startMax);
Gizmos.DrawLine(endMin, endMax);
}
}
/// <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
{
public override void OnStart()
{
// 你的普攻逻辑
Debug.Log("执行普攻");
Enemy curEnemy = GetComponent<Enemy>();
curEnemy.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;
}
}