using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(StatusEffectController))] public class UnitBase : MonoBehaviour { //移动速度 public float speed = 5f; //重力加速度 public float gravity = -20f; //活动范围 public float range = 500f; //兼容旧场景配置,目标搜索不再受此范围限制 public float detectRange = 200f; //攻击范围 public float attackRange = 5f; //模型自身前方相对逻辑前方的水平修正角度。 [Header("模型朝向修正")] public float modelFacingOffset = 0f; //是否保留动画根运动;需要攻击动作带动整体躯干时开启。 [Header("动画根运动")] public bool useRootMotion = false; //基础攻击力,实际伤害结算逻辑后续接入 [Min(0f)] public float attackPower = 10f; //完整攻击动作时长,单位为秒;设置为0时使用动画真实时长 [Min(0f)] public float attackDuration = 0f; public enum AttackType { //近战锁定目标,攻击事件触发时直接扣血。 MeleeDirect, //近战范围伤害,暂未实现。 MeleeArea, //远程锁定目标,攻击事件触发时生成飞行物。 RangedProjectile, //远程抛物线,不锁定目标,暂未实现。 RangedArc } [Header("攻击类型")] public AttackType attackType = AttackType.MeleeDirect; [Header("远程飞行物")] public GameObject projectilePrefab; public Transform projectileSpawnPoint; [Min(0f)] public float projectileSpeed = 20f; [Min(0f)] public float projectileLifetime = 5f; //每次普通攻击命中后增加的技能能量。 [Header("技能能量")] [Min(0f)] public float energyPerAttack = 20f; [Header("阵型分散")] [Min(0f)] public float separationRadius = 1.5f; [Min(0f)] public float separationWeight = 3f; [Min(0f)] public float targetSpreadBias = 6f; [Header("召唤师模式")] // 召唤师:不自动追击远处敌人,只在站定后攻击射程内的目标;移动由 SummonerClickMovement 控制。 public bool summonerMode = false; //召唤师模式下允许自动攻击的开关(移动时由操控脚本关闭,站定时开启)。 private bool summonerAttackEnabled = true; private static Collider[] s_separationBuffer = new Collider[30]; //角色状态 private State state = State.Alert; //攻击目标 public GameObject target; //当前目标点 private Vector3 targetPos; //初始位置坐标 private Vector3 startPos; // NavMesh 寻路更新间隔(优化性能) private float nextPathUpdateTime = 0f; private const float PathUpdateInterval = 0.5f; // 每 0.5 秒更新一次路径 //动画控制器 private Animator animator; // 角色控制器 private CharacterController controller; // NavMesh 寻路代理(如果有的话优先使用) private UnityEngine.AI.NavMeshAgent navAgent; // 自身的血条组件,同时负责显示技能能量条。 private WorldHealthBar worldHealthBar; // 通用状态效果组件。 private StatusEffectController statusEffectController; //垂直速度 private float verticalVelocity; // 攻击动画的有效经过时间;眩晕期间不增加。 private float attackElapsedTime; private bool isPlayingSkill; private float attackSpeedMultiplier = 1f; private UnitSkill unitSkill; private System.Action skillFinishedCallback; //状态枚举 public enum State { //警戒巡逻 Alert, //追踪状态 Track, //攻击状态 Attack, //技能状态 Skill } // Start is called before the first frame update public void InitializeFromPlayerData(float health, float attack, float moveSpeed) { attackPower = attack; speed = moveSpeed; if (worldHealthBar == null) { worldHealthBar = GetComponent(); } if (worldHealthBar != null) { worldHealthBar.maxHealth = health; worldHealthBar.currentHealth = health; } } void Start() { //初始化 animator = GetComponent(); controller = GetComponent(); navAgent = GetComponent(); worldHealthBar = GetComponent(); statusEffectController = EnsureStatusEffectController(); unitSkill = GetComponent(); // 如果有 NavMeshAgent,配置它并禁用 CharacterController if (navAgent != null) { navAgent.speed = speed; navAgent.angularSpeed = 120f; navAgent.acceleration = 8f; navAgent.stoppingDistance = attackRange * 0.95f; // 接近攻击范围边缘时停止 navAgent.autoBraking = true; // 禁用 CharacterController,避免冲突 if (controller != null) { controller.enabled = false; } } // 大多数角色由 CharacterController 位移;特殊攻击动作可保留动画根运动。 animator.applyRootMotion = useRootMotion; //记录初始坐标 startPos = transform.position; randomTargetPos(); } private StatusEffectController EnsureStatusEffectController() { if (statusEffectController == null) { statusEffectController = GetComponent(); } if (statusEffectController == null) { statusEffectController = gameObject.AddComponent(); } return statusEffectController; } // Update is called once per frame void Update() { if (statusEffectController != null && statusEffectController.IsStunned) { animator.SetInteger("speed", 0); applyGravity(); return; } //攻击状态下等待Animator报告攻击动画播放完成。 if (state == State.Attack) { // 攻击时直接禁用 NavMeshAgent,彻底停止移动 if (navAgent != null && navAgent.enabled) { navAgent.enabled = false; } animator.SetInteger("speed", 0); attackElapsedTime += Time.deltaTime; applyGravity(); CheckAttackAnimationFinished(); return; } if (isPlayingSkill) { animator.SetInteger("speed", 0); applyGravity(); CheckSkillAnimationFinished(); return; } //查找场景中距离自己最近的敌对目标 target = FindNearestOpponent(); if (target != null) { // 召唤师模式:不主动追击,站定且攻击开关开启时才攻击射程内目标。 if (summonerMode) { if (!IsWithinAttackReach(target) || !summonerAttackEnabled) { state = State.Alert; // 动画由 SummonerClickMovement 控制,这里不设置 applyGravity(); return; } attack(target); return; } state = State.Track; //目标在攻击范围外则朝目标移动,否则开始攻击 if (!IsWithinAttackReach(target)) { move(target.transform.position - transform.position); } else { attack(target); } return; } //场上没有敌人时进入待机,不再巡逻移动 state = State.Alert; // 召唤师模式下动画由 SummonerClickMovement 控制(移动时 speed=1,站定后 speed=0) if (!summonerMode) { animator.SetInteger("speed", 0); } applyGravity(); } public GameObject Target => target; public string OpponentTag => GetOpponentTag(); // 召唤师模式下控制"移动中不攻击":移动时 false,站定后 true。 public void SetSummonerAttackEnabled(bool enabled) { summonerAttackEnabled = enabled; if (!enabled && state == State.Track) { state = State.Alert; } } public float AttackSpeedMultiplier => attackSpeedMultiplier; public bool IsStunned => statusEffectController != null && statusEffectController.IsStunned; public void ApplyStun(float duration) { EnsureStatusEffectController().ApplyStun(duration); } public float ModifyIncomingDamage(float damage, GameObject attacker) { if (damage <= 0f) { return 0f; } if (unitSkill == null) { return damage; } return unitSkill.ModifyIncomingDamage(damage, attacker); } public bool ReceiveDamage(float damage, GameObject attacker = null) { WorldHealthBar healthBar = GetComponent(); if (healthBar == null) { return false; } healthBar.TakeDamage(ModifyIncomingDamage(damage, attacker), attacker); return true; } //根据自身Tag找到敌对Tag private string GetOpponentTag() { // 已弃用:改用阵营 ID 系统,不再依赖 Tag 判断敌我 // 保留此方法是为了兼容旧代码,但不应再被调用 Debug.LogWarning("GetOpponentTag() 已弃用,请使用阵营 ID 系统"); return null; } //查找距离自己最近的敌对目标(使用阵营 ID 判断) private GameObject FindNearestOpponent() { var myTeam = GetComponent(); if (myTeam == null) { Debug.LogWarning($"{gameObject.name} 缺少 UnitTeam 组件,无法查找敌人"); return null; } GameObject nearestOpponent = null; float nearestSqrDistance = float.PositiveInfinity; // 查找所有 UnitTeam 组件(不再依赖 Tag) UnitTeam[] allUnits = FindObjectsOfType(); foreach (var unit in allUnits) { // 跳过自己和友军 if (unit.gameObject == gameObject || !myTeam.IsEnemy(unit.gameObject)) { continue; } //排除自己所在层级中的对象 if (unit.transform.root == transform.root) { continue; } float sqrDistance = (unit.transform.position - transform.position).sqrMagnitude; //加入确定性分散偏移:让不同单位偏好不同目标,避免全部涌向同一个敌人 float bias = ((uint)(gameObject.GetInstanceID() * 7919) ^ (uint)(unit.gameObject.GetInstanceID())) % 10u / 10f * targetSpreadBias; float adjustedSqrDistance = sqrDistance + bias; if (adjustedSqrDistance < nearestSqrDistance) { nearestSqrDistance = adjustedSqrDistance; nearestOpponent = unit.gameObject; } } return nearestOpponent; } //判断是否进入可攻击距离 private bool IsWithinAttackReach(GameObject opponent) { if (opponent == null) { return false; } // NavMeshAgent 会保持一定距离,所以增加额外的攻击范围缓冲 // 原来的逻辑:selfRadius + opponentRadius + attackRange // 现在:直接使用 attackRange,并增加 1.5 米的缓冲,让单位在更远距离就能攻击 float reachDistance = attackRange + 1.5f; return Vector3.Distance(transform.position, opponent.transform.position) <= reachDistance; } private float GetHorizontalCollisionRadius(CharacterController characterController) { if (characterController == null) { return 0f; } Bounds bounds = characterController.bounds; return Mathf.Max(bounds.extents.x, bounds.extents.z); } //查找当前Animator中使用的攻击动画时长 private float GetAttackAnimationDuration() { if (animator == null || animator.runtimeAnimatorController == null) { return 0f; } var overrideController = animator.runtimeAnimatorController as AnimatorOverrideController; if (overrideController != null) { var overrides = new List>(); overrideController.GetOverrides(overrides); foreach (var pair in overrides) { if (IsAttackAnimation(pair.Key)) { return pair.Value != null ? pair.Value.length : pair.Key.length; } } } foreach (var clip in animator.runtimeAnimatorController.animationClips) { if (IsAttackAnimation(clip)) { return clip.length; } } return 0f; } // 查找当前 Animator 中使用的技能动画时长。 private float GetSkillAnimationDuration() { if (animator == null || animator.runtimeAnimatorController == null) { return 0f; } var overrideController = animator.runtimeAnimatorController as AnimatorOverrideController; if (overrideController != null) { var overrides = new List>(); overrideController.GetOverrides(overrides); foreach (var pair in overrides) { if (IsSkillAnimation(pair.Key)) { return pair.Value != null ? pair.Value.length : pair.Key.length; } } } foreach (var clip in animator.runtimeAnimatorController.animationClips) { if (IsSkillAnimation(clip)) { return clip.length; } } return 0f; } // 根据动画名称判断是否为技能动画。 private bool IsSkillAnimation(AnimationClip clip) { if (clip == null) { return false; } string clipName = clip.name.ToLowerInvariant(); return clipName.Contains("skill") || clipName.Contains("技能"); } //根据动画名称判断是否为攻击动画 private bool IsAttackAnimation(AnimationClip clip) { if (clip == null) { return false; } string clipName = clip.name.ToLowerInvariant(); return clipName.Contains("attack") || clipName.Contains("攻击") || clipName.Contains("挥剑"); } //设置攻击动画播放速度 private void SetAttackAnimationSpeed() { float animationDuration = GetAttackAnimationDuration(); if (animationDuration <= 0f || attackDuration <= 0f) { animator.speed = attackSpeedMultiplier; return; } animator.speed = animationDuration / attackDuration * attackSpeedMultiplier; } //攻击 private void attack(GameObject friend) { target = friend; // 攻击时直接禁用 NavMeshAgent(彻底停止移动) if (navAgent != null && navAgent.enabled) { navAgent.enabled = false; } FaceTarget(friend); if (TryReleaseSkill()) { return; } //根据目标攻击时长加速或减速完整攻击动画 SetAttackAnimationSpeed(); animator.SetInteger("speed", 0); animator.SetTrigger("attack"); state = State.Attack; attackElapsedTime = 0f; } private void FaceTarget(GameObject opponent) { FaceTarget(opponent, modelFacingOffset); } private void FaceTarget(GameObject opponent, float facingOffset) { if (opponent == null) { return; } Vector3 direction = opponent.transform.position - transform.position; direction.y = 0f; if (direction.sqrMagnitude <= 0.0001f) { return; } ApplyFacingRotation(direction, facingOffset); } public void FaceCurrentTarget() { FaceTarget(target); } public void FaceCurrentTarget(float facingOffset) { FaceTarget(target, facingOffset); } private bool TryReleaseSkill() { if (worldHealthBar == null || !worldHealthBar.IsEnergyFull || unitSkill == null || target == null || !target.activeInHierarchy) { return false; } worldHealthBar.ResetEnergy(); state = State.Alert; animator.SetInteger("speed", 0); unitSkill.ReleaseSkill(); return true; } private void ApplyFacingRotation(Vector3 direction, float facingOffset) { direction.y = 0f; if (direction.sqrMagnitude <= 0.0001f) { return; } Quaternion targetRotation = Quaternion.LookRotation(direction, Vector3.up); transform.rotation = Quaternion.Euler( 0f, targetRotation.eulerAngles.y + facingOffset, 0f); } //由攻击动画中最合适的帧通过 Animation Event 调用。 public void OnAttackEffect() { if (IsStunned || state != State.Attack || target == null) { return; } switch (attackType) { case AttackType.MeleeDirect: ApplyDirectDamage(target); break; case AttackType.RangedProjectile: FireProjectile(target); break; case AttackType.MeleeArea: case AttackType.RangedArc: break; } } private void ApplyDirectDamage(GameObject opponent) { UnitBase targetUnit = opponent.GetComponentInParent(); if (targetUnit != null && targetUnit.ReceiveDamage(attackPower, gameObject)) { RegisterAttackHit(); return; } BuildingHealth targetBuilding = opponent.GetComponentInParent(); if (targetBuilding != null && targetBuilding.ReceiveDamage(attackPower, gameObject)) { RegisterAttackHit(); return; } // 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase) WorldHealthBar targetHealthBar = opponent.GetComponentInParent(); if (targetHealthBar != null) { targetHealthBar.TakeDamage(attackPower, gameObject); RegisterAttackHit(); } } public void ApplySkillHit(float damageMultiplier, bool registerEnergy) { if (IsStunned || target == null) { return; } float damage = attackPower * Mathf.Max(0f, damageMultiplier); UnitBase targetUnit = target.GetComponentInParent(); bool hit = targetUnit != null && targetUnit.ReceiveDamage(damage, gameObject); if (!hit) { BuildingHealth targetBuilding = target.GetComponentInParent(); hit = targetBuilding != null && targetBuilding.ReceiveDamage(damage, gameObject); } if (!hit) { return; } if (registerEnergy) { RegisterAttackHit(); } } public void ApplyAreaSkillDamage(float radius, float damage, bool registerEnergy) { if (worldHealthBar == null || damage <= 0f) { return; } UnitTeam myTeam = GetComponent(); if (myTeam == null) { return; } float clampedRadius = Mathf.Max(0f, radius); float radiusSquared = clampedRadius * clampedRadius; // 查找所有带 UnitTeam 的对象(包括单位和召唤师) UnitTeam[] allTeams = FindObjectsOfType(); HashSet hitHealthBars = new HashSet(); foreach (UnitTeam targetTeam in allTeams) { GameObject opponent = targetTeam.gameObject; if (opponent == null || opponent == gameObject || opponent.transform.root == transform.root) { continue; } // 检查是否是敌人 if (!myTeam.IsEnemy(opponent)) { continue; } Vector3 offset = opponent.transform.position - transform.position; offset.y = 0f; if (offset.sqrMagnitude > radiusSquared) { continue; } UnitBase targetUnit = opponent.GetComponentInParent(); BuildingHealth targetBuilding = opponent.GetComponentInParent(); WorldHealthBar healthBar = null; if (targetUnit != null) { healthBar = targetUnit.GetComponent(); } else if (targetBuilding != null) { healthBar = targetBuilding.GetComponent(); } else { // 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase) healthBar = opponent.GetComponentInParent(); } if (healthBar == null || !hitHealthBars.Add(healthBar)) { continue; } if (targetUnit != null) { targetUnit.ReceiveDamage(damage, gameObject); } else if (targetBuilding != null) { targetBuilding.ReceiveDamage(damage, gameObject); } else if (healthBar != null) { // 召唤师等特殊单位 healthBar.TakeDamage(damage, gameObject); } } if (registerEnergy) { RegisterAttackHit(); } } public void ApplyAreaStun(float radius, float duration) { if (duration <= 0f) { return; } UnitTeam myTeam = GetComponent(); if (myTeam == null) { return; } float clampedRadius = Mathf.Max(0f, radius); float radiusSquared = clampedRadius * clampedRadius; // 查找所有带 UnitTeam 的对象(包括单位和召唤师) UnitTeam[] allTeams = FindObjectsOfType(); HashSet hitUnits = new HashSet(); foreach (UnitTeam targetTeam in allTeams) { GameObject opponent = targetTeam.gameObject; if (opponent == null || opponent == gameObject || opponent.transform.root == transform.root) { continue; } // 检查是否是敌人 if (!myTeam.IsEnemy(opponent)) { continue; } Vector3 offset = opponent.transform.position - transform.position; offset.y = 0f; if (offset.sqrMagnitude > radiusSquared) { continue; } UnitBase unit = opponent.GetComponentInParent(); if (unit != null && hitUnits.Add(unit)) { unit.ApplyStun(duration); continue; } // 召唤师等特殊单位(有 StatusEffectController 但没有 UnitBase) StatusEffectController statusEffect = opponent.GetComponentInParent(); if (statusEffect != null) { statusEffect.ApplyStun(duration); } } } public void RegisterAttackHit() { if (IsStunned) { return; } AddAttackEnergy(); } private void FireProjectile(GameObject opponent) { if (projectilePrefab == null || projectileSpawnPoint == null) { return; } GameObject projectileObject = Instantiate( projectilePrefab, projectileSpawnPoint.position, projectileSpawnPoint.rotation); Projectile projectile = projectileObject.GetComponent(); if (projectile != null) { projectile.Initialize(opponent, attackPower, gameObject, projectileSpeed, projectileLifetime); } } public void FireSkillProjectile(GameObject opponent) { FireSkillProjectile(opponent, 1f); } public void FireSkillProjectile(GameObject opponent, float damageMultiplier) { if (opponent == null || projectilePrefab == null || projectileSpawnPoint == null) { return; } GameObject projectileObject = Instantiate( projectilePrefab, projectileSpawnPoint.position, projectileSpawnPoint.rotation); Projectile projectile = projectileObject.GetComponent(); if (projectile != null) { // 技能箭不再次积攒能量,避免技能触发递归。 projectile.Initialize(opponent, attackPower * Mathf.Max(0f, damageMultiplier), gameObject, projectileSpeed, projectileLifetime, false); } } // 普通攻击命中后只积攒能量,大招在下一次普攻开始前释放。 private void AddAttackEnergy() { if (worldHealthBar == null || energyPerAttack <= 0f) { return; } worldHealthBar.AddEnergy(energyPerAttack); } private float GetSkillAnimationSpeed(float targetDuration) { float animationDuration = GetSkillAnimationDuration(); if (animationDuration <= 0f || targetDuration <= 0f) { return 1f; } return animationDuration / targetDuration; } public void PlaySkillAnimation(System.Action onFinished, float targetDuration = 0f) { if (isPlayingSkill) { return; } skillFinishedCallback = onFinished; isPlayingSkill = true; state = State.Skill; animator.speed = GetSkillAnimationSpeed(targetDuration); animator.SetInteger("speed", 0); animator.SetTrigger("skill"); } public void MultiplyAttackSpeed(float multiplier, float limit) { attackSpeedMultiplier = Mathf.Min( attackSpeedMultiplier * multiplier, limit); animator.speed = attackSpeedMultiplier; } private void CheckSkillAnimationFinished() { AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0); if (!stateInfo.IsName("skill") || stateInfo.normalizedTime < 1f) { return; } isPlayingSkill = false; state = State.Alert; animator.speed = attackSpeedMultiplier; skillFinishedCallback?.Invoke(); skillFinishedCallback = null; } // 根据当前攻击动画的播放进度自动结束攻击,不依赖 Animation Event。 private void CheckAttackAnimationFinished() { float animationDuration = attackDuration > 0f ? attackDuration : GetAttackAnimationDuration(); if (animationDuration > 0f && attackElapsedTime >= animationDuration) { FinishAttack(); return; } if (!IsPlayingAttackAnimation()) { return; } AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0); if (stateInfo.normalizedTime >= 1f) { FinishAttack(); } } private bool IsPlayingAttackAnimation() { AnimatorClipInfo[] clipInfos = animator.GetCurrentAnimatorClipInfo(0); foreach (AnimatorClipInfo clipInfo in clipInfos) { if (IsAttackAnimation(clipInfo.clip)) { return true; } } return false; } private void FinishAttack() { //攻击动画结束后恢复当前攻速倍率。 animator.speed = attackSpeedMultiplier; //攻击动画结束后,目标仍在攻击范围内则立即进行下一次攻击。 target = FindNearestOpponent(); if (target != null && IsWithinAttackReach(target)) { // 攻击结束后可能已切换到新的近距离目标,先更新朝向再开始下一次攻击。 FaceTarget(target); attack(target); return; } //目标离开攻击范围,回到待机逻辑。 state = State.Alert; animator.SetInteger("speed", 0); applyGravity(); } //角色移动,持续到目标点 void move(Vector3 movement) { movement.y = 0; // 如果有 NavMeshAgent,使用它来移动 if (navAgent != null) { // 重新启用 NavMeshAgent(攻击后可能被禁用) if (!navAgent.enabled) { navAgent.enabled = true; } if (navAgent.isOnNavMesh) { // 确保没有被停止 if (navAgent.isStopped) { navAgent.isStopped = false; } // 限制寻路更新频率(性能优化) if (Time.time >= nextPathUpdateTime) { // 设置目标位置(相对于当前位置) Vector3 targetPosition = transform.position + movement.normalized * 10f; navAgent.SetDestination(targetPosition); nextPathUpdateTime = Time.time + PathUpdateInterval; } // 根据速度设置动画 if (navAgent.velocity.magnitude > 0.1f) { animator.SetInteger("speed", 1); } else { animator.SetInteger("speed", 0); } return; } } // 否则使用 CharacterController(旧方式) //友军分离力:与过近的同阵营单位互相推开,避免扎堆 Vector3 separation = ComputeSeparation(); if (separation.sqrMagnitude > 0.0001f) { movement += separation * separationWeight; movement.y = 0; } if (movement == Vector3.zero) { animator.SetInteger("speed", 0); applyGravity(); return; } //改变动画控制器的参数 animator.SetInteger("speed", 1); //向目标点移动,同时应用垂直重力 Vector3 velocity = movement.normalized * speed; velocity.y = getVerticalVelocity(); controller.Move(velocity * Time.deltaTime); //移动时模型正面朝向移动方向;模型朝向修正只用于攻击动作。 Quaternion movementRotation = Quaternion.LookRotation(movement, Vector3.up); transform.rotation = Quaternion.Euler(0f, movementRotation.eulerAngles.y, 0f); } //计算与附近同阵营单位的分离向量;越近推力越大。 private Vector3 ComputeSeparation() { if (separationRadius <= 0f) { return Vector3.zero; } int hitCount = Physics.OverlapSphereNonAlloc(transform.position, separationRadius, s_separationBuffer); Vector3 separation = Vector3.zero; for (int i = 0; i < hitCount; i++) { Collider other = s_separationBuffer[i]; if (other == null || other.gameObject == gameObject) { continue; } //只推开同阵营单位,敌人间的碰撞由CharacterController自然阻挡 if (!other.CompareTag(gameObject.tag)) { continue; } Vector3 diff = transform.position - other.transform.position; diff.y = 0f; float distance = diff.magnitude; if (distance > 0.01f && distance < separationRadius) { separation += diff / distance * (separationRadius - distance); } } return separation; } private float getVerticalVelocity() { if (controller.isGrounded && verticalVelocity < 0) { verticalVelocity = -2f; } else { verticalVelocity += gravity * Time.deltaTime; } return verticalVelocity; } private void applyGravity() { // 只在 CharacterController 启用时应用重力 if (controller != null && controller.enabled) { controller.Move(Vector3.up * getVerticalVelocity() * Time.deltaTime); } } // 在 Scene 视图中标记箭的发射位置和方向,便于调整空物体。 private void OnDrawGizmos() { if (projectileSpawnPoint == null) { return; } Vector3 position = projectileSpawnPoint.position; Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(position, 0.08f); Gizmos.DrawLine(position, position + projectileSpawnPoint.forward * 0.4f); Gizmos.color = Color.green; Gizmos.DrawLine(position, position + projectileSpawnPoint.up * 0.15f); } //随机目标点 private void randomTargetPos() { //以startPos为中心,在水平圆形范围内随机一个点 Vector2 randomOffset = Random.insideUnitCircle * range; targetPos = startPos + new Vector3(randomOffset.x, 0, randomOffset.y); } }