根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
``` docs(changelog): 更新项目变更日志 - 添加新的版本记录 - 修正已知问题描述 - 更新功能特性说明 ``` 注意:由于您提供的code differences部分为空,以上是一个示例模板。实际使用时请根据具体的代码变更内容来填写相应的type、scope、subject和body信息。
This commit is contained in:
1 parent
69f33d8637
commit
951ba49c79
26 files changed
+942
-455
No files matched your search
@@ -0,0 +1,141 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// AI 召唤师控制器:自动召唤单位攻击玩家
|
||||
/// </summary>
|
||||
public class AISummoner : MonoBehaviour
|
||||
{
|
||||
[Header("AI 配置")]
|
||||
[Tooltip("召唤间隔(秒)")]
|
||||
[Min(1f)]
|
||||
public float summonInterval = 3f;
|
||||
|
||||
[Tooltip("随机偏移范围(秒),避免太规律")]
|
||||
[Min(0f)]
|
||||
public float summonRandomOffset = 1f;
|
||||
|
||||
[Tooltip("AI 难度:召唤CD缩放(越小越快)")]
|
||||
[Range(0.5f, 2f)]
|
||||
public float difficultyScale = 1f;
|
||||
|
||||
[Header("能量管理")]
|
||||
[Tooltip("AI 初始能量")]
|
||||
[Min(0)]
|
||||
public int startingMana = 150;
|
||||
|
||||
[Tooltip("AI 是否使用无限能量")]
|
||||
public bool unlimitedMana = false;
|
||||
|
||||
private MainCharacterCombat combat;
|
||||
private ManaSystem manaSystem;
|
||||
private float summonCooldown;
|
||||
private bool isActive;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
combat = GetComponent<MainCharacterCombat>();
|
||||
if (combat == null)
|
||||
{
|
||||
Debug.LogError($"[AISummoner] {name} 缺少 MainCharacterCombat 组件!");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
manaSystem = FindObjectOfType<ManaSystem>();
|
||||
if (manaSystem == null)
|
||||
{
|
||||
Debug.LogWarning($"[AISummoner] 场景中没有 ManaSystem,AI 将无法检查能量");
|
||||
}
|
||||
|
||||
// 初始延迟,避免开局立刻召唤
|
||||
summonCooldown = summonInterval * difficultyScale;
|
||||
isActive = true;
|
||||
|
||||
Debug.Log($"[AISummoner] {name} AI 初始化完成,召唤间隔: {summonInterval}s");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isActive) return;
|
||||
|
||||
// AI 被眩晕时停止召唤
|
||||
if (combat.IsStunned) return;
|
||||
|
||||
summonCooldown -= Time.deltaTime;
|
||||
if (summonCooldown <= 0f)
|
||||
{
|
||||
TrySummon();
|
||||
ResetCooldown();
|
||||
}
|
||||
}
|
||||
|
||||
private void TrySummon()
|
||||
{
|
||||
// 随机选择一个可召唤的角色槽位(1-5)
|
||||
var characters = combat.summonableCharacters;
|
||||
if (characters == null || characters.Length == 0)
|
||||
{
|
||||
Debug.LogWarning($"[AISummoner] {name} 没有可召唤的角色");
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤出有效的角色数据
|
||||
int[] validSlots = new int[characters.Length];
|
||||
int validCount = 0;
|
||||
|
||||
for (int i = 0; i < characters.Length; i++)
|
||||
{
|
||||
if (characters[i] != null && characters[i].unitPrefab != null)
|
||||
{
|
||||
// 检查能量
|
||||
if (unlimitedMana || manaSystem == null || manaSystem.CurrentManaInt >= characters[i].manaCost)
|
||||
{
|
||||
validSlots[validCount++] = i + 1; // slot 是 1-based
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount == 0)
|
||||
{
|
||||
// 没有可召唤的角色(能量不足或没有有效角色)
|
||||
return;
|
||||
}
|
||||
|
||||
// 随机选择一个有效槽位
|
||||
int randomIndex = Random.Range(0, validCount);
|
||||
int selectedSlot = validSlots[randomIndex];
|
||||
|
||||
// 执行召唤(使用 MainCharacterCombat.RequestSummon)
|
||||
bool success = combat.RequestSummon(selectedSlot);
|
||||
|
||||
if (success)
|
||||
{
|
||||
var selectedCharacter = characters[selectedSlot - 1];
|
||||
Debug.Log($"[AISummoner] {name} 召唤了 {selectedCharacter.characterName} (槽位: {selectedSlot})");
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetCooldown()
|
||||
{
|
||||
float randomOffset = Random.Range(-summonRandomOffset, summonRandomOffset);
|
||||
summonCooldown = summonInterval * difficultyScale + randomOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用/禁用 AI
|
||||
/// </summary>
|
||||
public void SetActive(bool active)
|
||||
{
|
||||
isActive = active;
|
||||
Debug.Log($"[AISummoner] {name} AI {(active ? "启用" : "禁用")}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整难度(实时修改召唤速度)
|
||||
/// </summary>
|
||||
public void SetDifficulty(float scale)
|
||||
{
|
||||
difficultyScale = Mathf.Clamp(scale, 0.5f, 2f);
|
||||
Debug.Log($"[AISummoner] {name} 难度调整为 {difficultyScale}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CCke4S2lUy3nU786T7yvHZYNAkerlhq9/n+qIWEgtRjk/6EPe8RNOPo=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -88,6 +88,19 @@ public class Barracks : MonoBehaviour
|
||||
unit.name = characterData.characterName;
|
||||
unit.tag = unitTag;
|
||||
|
||||
// 设置阵营 ID(根据建筑的阵营)
|
||||
var buildingTeam = GetComponent<UnitTeam>();
|
||||
var unitTeam = unit.GetComponent<UnitTeam>();
|
||||
if (buildingTeam != null && unitTeam != null)
|
||||
{
|
||||
unitTeam.teamId = buildingTeam.teamId;
|
||||
}
|
||||
else if (unitTeam != null)
|
||||
{
|
||||
// 兜底:根据 tag 判断阵营
|
||||
unitTeam.teamId = unitTag == "Friend" ? 0 : 1;
|
||||
}
|
||||
|
||||
// 如果单位有UnitBase组件,使用1级属性初始化
|
||||
var unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
|
||||
@@ -235,6 +235,13 @@ public class BattleSetupManager : MonoBehaviour
|
||||
unit.name = "Friend_" + data.characterName;
|
||||
unit.tag = "Friend";
|
||||
|
||||
// 设置阵营 ID(玩家单位为 team 0)
|
||||
var unitTeam = unit.GetComponent<UnitTeam>();
|
||||
if (unitTeam != null)
|
||||
{
|
||||
unitTeam.teamId = 0;
|
||||
}
|
||||
|
||||
var unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏管理器:管理全局游戏状态,包括本地玩家的阵营 ID。
|
||||
/// PVE:LocalPlayerTeamId = 0(固定)
|
||||
/// PVP:服务器分配后设置 LocalPlayerTeamId(比如 0 或 1)
|
||||
/// </summary>
|
||||
public class GameManager : MonoBehaviour
|
||||
{
|
||||
public static GameManager Instance { get; private set; }
|
||||
|
||||
[Header("本地玩家阵营")]
|
||||
[Tooltip("PVE: 固定为0; PVP: 服务器分配")]
|
||||
public int LocalPlayerTeamId = 0;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 单例模式
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置本地玩家阵营 ID(PVP 时由服务器调用)
|
||||
/// </summary>
|
||||
public void SetLocalPlayerTeam(int teamId)
|
||||
{
|
||||
LocalPlayerTeamId = teamId;
|
||||
Debug.Log($"本地玩家阵营设置为: {teamId}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CXkdt3ulUS3TPoI5TieIx0QPMa6EXoIKUewqD1fNwBgwo43HfDPsYzY=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(StatusEffectController))]
|
||||
public class MainCharacterCombat : MonoBehaviour
|
||||
{
|
||||
[Header("自动攻击")]
|
||||
@@ -42,6 +43,7 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
[Min(0f)]
|
||||
public float projectileLifetime = 5f;
|
||||
|
||||
private StatusEffectController statusEffectController;
|
||||
private Animator animator;
|
||||
private MainCharacterDebugMovement movement;
|
||||
private GameObject target;
|
||||
@@ -56,10 +58,18 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
private bool summonSpawned;
|
||||
private bool summoning;
|
||||
|
||||
public bool IsStunned => statusEffectController != null && statusEffectController.IsStunned;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
movement = GetComponent<MainCharacterDebugMovement>();
|
||||
statusEffectController = GetComponent<StatusEffectController>();
|
||||
|
||||
if (statusEffectController == null)
|
||||
{
|
||||
statusEffectController = gameObject.AddComponent<StatusEffectController>();
|
||||
}
|
||||
|
||||
if (animator != null)
|
||||
{
|
||||
@@ -69,6 +79,16 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 眩晕时停止所有行动
|
||||
if (IsStunned)
|
||||
{
|
||||
if (animator != null)
|
||||
{
|
||||
animator.SetInteger("speed", 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (attackCooldown > 0f)
|
||||
{
|
||||
attackCooldown -= Time.deltaTime;
|
||||
@@ -230,11 +250,12 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// 立即停止移动
|
||||
// 立即停止移动,并禁用移动脚本(防止干扰动画)
|
||||
SummonerClickMovement clickMovement = GetComponent<SummonerClickMovement>();
|
||||
if (clickMovement != null)
|
||||
{
|
||||
clickMovement.ClearTarget();
|
||||
clickMovement.enabled = false; // 禁用移动脚本,避免它设置 speed 参数
|
||||
}
|
||||
|
||||
pendingSummonPrefab = prefab;
|
||||
@@ -244,9 +265,17 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
summonSpawned = false;
|
||||
summoning = true;
|
||||
animator.speed = 1f;
|
||||
|
||||
// 强制重置动画状态,避免状态过渡冲突
|
||||
animator.SetInteger("speed", 0);
|
||||
animator.ResetTrigger("skill");
|
||||
animator.ResetTrigger("attack");
|
||||
|
||||
// 直接强制播放 skill 动画,绕过状态机的所有过渡条件
|
||||
// Play() 会立即切换,不管当前状态是什么
|
||||
animator.Play("skill", 0, 0f);
|
||||
|
||||
Debug.Log("强制播放 skill 动画");
|
||||
}
|
||||
|
||||
public void OnSkillHit1()
|
||||
@@ -272,7 +301,22 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
pendingSummonPrefab,
|
||||
spawnPosition,
|
||||
Quaternion.LookRotation(direction, Vector3.up));
|
||||
summoned.tag = "Friend";
|
||||
|
||||
// 召唤的单位继承召唤师的阵营(不再硬编码为 Friend)
|
||||
UnitTeam summonerTeam = GetComponent<UnitTeam>();
|
||||
UnitTeam summonedTeam = summoned.GetComponent<UnitTeam>();
|
||||
|
||||
if (summonerTeam != null && summonedTeam != null)
|
||||
{
|
||||
summonedTeam.teamId = summonerTeam.teamId;
|
||||
// 根据阵营设置 tag(向后兼容旧系统)
|
||||
summoned.tag = summonerTeam.teamId == 0 ? "Friend" : "Enemy";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 降级:没有 UnitTeam 时保持原逻辑
|
||||
summoned.tag = "Friend";
|
||||
}
|
||||
|
||||
UnitBase unitBase = summoned.GetComponent<UnitBase>();
|
||||
if (unitBase != null && pendingSummonCharacterData != null && pendingSummonPlayerData != null)
|
||||
@@ -404,16 +448,25 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
private void CheckSummonFinished()
|
||||
{
|
||||
AnimatorStateInfo state = animator.GetCurrentAnimatorStateInfo(0);
|
||||
|
||||
// 正常完成:skill 动画播放完毕
|
||||
if (state.IsName("skill") && state.normalizedTime >= 1f)
|
||||
{
|
||||
FinishSummon();
|
||||
return;
|
||||
}
|
||||
|
||||
// skill 状态意外被其他动画打断时,才使用超时兜底,避免永久锁定召唤。
|
||||
// 超时检测:添加调试信息
|
||||
if (summonElapsed >= summonAnimationTimeout)
|
||||
{
|
||||
Debug.LogWarning("主角召唤动画超时,已解除召唤状态");
|
||||
// 输出当前动画状态,帮助诊断问题
|
||||
string currentState = "unknown";
|
||||
if (state.IsName("skill")) currentState = "skill";
|
||||
else if (state.IsName("idle")) currentState = "idle";
|
||||
else if (state.IsName("run")) currentState = "run";
|
||||
else if (state.IsName("attack")) currentState = "attack";
|
||||
|
||||
Debug.LogWarning($"主角召唤动画超时,已解除召唤状态。当前动画状态: {currentState}, normalizedTime: {state.normalizedTime:F2}");
|
||||
FinishSummon();
|
||||
}
|
||||
}
|
||||
@@ -427,6 +480,15 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
animator.speed = 1f;
|
||||
animator.SetInteger("speed", 0);
|
||||
animator.Play("idle", 0, 0f);
|
||||
|
||||
// 重新启用移动脚本
|
||||
SummonerClickMovement clickMovement = GetComponent<SummonerClickMovement>();
|
||||
if (clickMovement != null)
|
||||
{
|
||||
clickMovement.enabled = true;
|
||||
}
|
||||
|
||||
Debug.Log("召唤完成,恢复移动脚本");
|
||||
}
|
||||
|
||||
private void CheckAttackFinished()
|
||||
@@ -535,18 +597,38 @@ public class MainCharacterCombat : MonoBehaviour
|
||||
private GameObject FindNearestEnemy()
|
||||
{
|
||||
// 索敌范围使用较小的 detectRange,主角不主动远离基地寻找敌人
|
||||
UnitTeam myTeam = GetComponent<UnitTeam>();
|
||||
if (myTeam == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject nearest = null;
|
||||
float nearestDistance = detectRange * detectRange;
|
||||
|
||||
foreach (GameObject enemy in GameObject.FindGameObjectsWithTag("Enemy"))
|
||||
// 查找所有带 UnitTeam 的对象(包括单位和召唤师)
|
||||
UnitTeam[] allTeams = FindObjectsOfType<UnitTeam>();
|
||||
|
||||
foreach (UnitTeam targetTeam in allTeams)
|
||||
{
|
||||
if (enemy == null || enemy.transform.root == transform.root)
|
||||
GameObject enemy = targetTeam.gameObject;
|
||||
|
||||
if (enemy == null || enemy == gameObject || enemy.transform.root == transform.root)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否是敌人(使用阵营 ID)
|
||||
if (!myTeam.IsEnemy(enemy))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否有 UnitBase 或 WorldHealthBar(召唤师)
|
||||
UnitBase enemyUnit = enemy.GetComponentInParent<UnitBase>();
|
||||
if (enemyUnit == null)
|
||||
WorldHealthBar enemyHealth = enemy.GetComponentInParent<WorldHealthBar>();
|
||||
|
||||
if (enemyUnit == null && enemyHealth == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -116,20 +116,29 @@ public class Projectile : MonoBehaviour
|
||||
|
||||
UnitBase targetUnit = hitObject.GetComponentInParent<UnitBase>();
|
||||
BuildingHealth targetBuilding = hitObject.GetComponentInParent<BuildingHealth>();
|
||||
if (targetUnit == null && targetBuilding == null)
|
||||
WorldHealthBar targetHealthBar = hitObject.GetComponentInParent<WorldHealthBar>();
|
||||
|
||||
if (targetUnit == null && targetBuilding == null && targetHealthBar == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hasHit = true;
|
||||
|
||||
if (targetUnit != null)
|
||||
{
|
||||
targetUnit.ReceiveDamage(damage, owner);
|
||||
}
|
||||
else
|
||||
else if (targetBuilding != null)
|
||||
{
|
||||
targetBuilding.ReceiveDamage(damage, owner);
|
||||
}
|
||||
else if (targetHealthBar != null)
|
||||
{
|
||||
// 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase)
|
||||
targetHealthBar.TakeDamage(damage, owner);
|
||||
}
|
||||
|
||||
if (registerEnergyOnHit && owner != null)
|
||||
{
|
||||
UnitBase unit = owner.GetComponent<UnitBase>();
|
||||
|
||||
+89
-35
@@ -342,48 +342,50 @@ public class UnitBase : MonoBehaviour
|
||||
//根据自身Tag找到敌对Tag
|
||||
private string GetOpponentTag()
|
||||
{
|
||||
if (CompareTag("Friend"))
|
||||
{
|
||||
return "Enemy";
|
||||
}
|
||||
|
||||
if (CompareTag("Enemy"))
|
||||
{
|
||||
return "Friend";
|
||||
}
|
||||
|
||||
// 已弃用:改用阵营 ID 系统,不再依赖 Tag 判断敌我
|
||||
// 保留此方法是为了兼容旧代码,但不应再被调用
|
||||
Debug.LogWarning("GetOpponentTag() 已弃用,请使用阵营 ID 系统");
|
||||
return null;
|
||||
}
|
||||
|
||||
//查找距离自己最近的敌对目标
|
||||
//查找距离自己最近的敌对目标(使用阵营 ID 判断)
|
||||
private GameObject FindNearestOpponent()
|
||||
{
|
||||
string opponentTag = GetOpponentTag();
|
||||
if (string.IsNullOrEmpty(opponentTag))
|
||||
var myTeam = GetComponent<UnitTeam>();
|
||||
if (myTeam == null)
|
||||
{
|
||||
Debug.LogWarning($"{gameObject.name} 缺少 UnitTeam 组件,无法查找敌人");
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject nearestOpponent = null;
|
||||
float nearestSqrDistance = float.PositiveInfinity;
|
||||
GameObject[] opponents = GameObject.FindGameObjectsWithTag(opponentTag);
|
||||
|
||||
foreach (var opponent in opponents)
|
||||
// 查找所有 UnitTeam 组件(不再依赖 Tag)
|
||||
UnitTeam[] allUnits = FindObjectsOfType<UnitTeam>();
|
||||
|
||||
foreach (var unit in allUnits)
|
||||
{
|
||||
//排除自己以及自己所在层级中的对象
|
||||
if (opponent == gameObject || opponent.transform.root == transform.root)
|
||||
// 跳过自己和友军
|
||||
if (unit.gameObject == gameObject || !myTeam.IsEnemy(unit.gameObject))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float sqrDistance = (opponent.transform.position - transform.position).sqrMagnitude;
|
||||
//排除自己所在层级中的对象
|
||||
if (unit.transform.root == transform.root)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float sqrDistance = (unit.transform.position - transform.position).sqrMagnitude;
|
||||
//加入确定性分散偏移:让不同单位偏好不同目标,避免全部涌向同一个敌人
|
||||
float bias = ((uint)(gameObject.GetInstanceID() * 7919) ^ (uint)(opponent.GetInstanceID())) % 10u / 10f * targetSpreadBias;
|
||||
float bias = ((uint)(gameObject.GetInstanceID() * 7919) ^ (uint)(unit.gameObject.GetInstanceID())) % 10u / 10f * targetSpreadBias;
|
||||
float adjustedSqrDistance = sqrDistance + bias;
|
||||
if (adjustedSqrDistance < nearestSqrDistance)
|
||||
{
|
||||
nearestSqrDistance = adjustedSqrDistance;
|
||||
nearestOpponent = opponent;
|
||||
nearestOpponent = unit.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,6 +653,15 @@ public class UnitBase : MonoBehaviour
|
||||
if (targetBuilding != null && targetBuilding.ReceiveDamage(attackPower, gameObject))
|
||||
{
|
||||
RegisterAttackHit();
|
||||
return;
|
||||
}
|
||||
|
||||
// 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase)
|
||||
WorldHealthBar targetHealthBar = opponent.GetComponentInParent<WorldHealthBar>();
|
||||
if (targetHealthBar != null)
|
||||
{
|
||||
targetHealthBar.TakeDamage(attackPower, gameObject);
|
||||
RegisterAttackHit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -689,23 +700,33 @@ public class UnitBase : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
string opponentTag = GetOpponentTag();
|
||||
if (string.IsNullOrEmpty(opponentTag))
|
||||
UnitTeam myTeam = GetComponent<UnitTeam>();
|
||||
if (myTeam == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float clampedRadius = Mathf.Max(0f, radius);
|
||||
float radiusSquared = clampedRadius * clampedRadius;
|
||||
GameObject[] opponents = GameObject.FindGameObjectsWithTag(opponentTag);
|
||||
|
||||
// 查找所有带 UnitTeam 的对象(包括单位和召唤师)
|
||||
UnitTeam[] allTeams = FindObjectsOfType<UnitTeam>();
|
||||
HashSet<WorldHealthBar> hitHealthBars = new HashSet<WorldHealthBar>();
|
||||
|
||||
foreach (GameObject opponent in opponents)
|
||||
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;
|
||||
@@ -716,9 +737,21 @@ public class UnitBase : MonoBehaviour
|
||||
|
||||
UnitBase targetUnit = opponent.GetComponentInParent<UnitBase>();
|
||||
BuildingHealth targetBuilding = opponent.GetComponentInParent<BuildingHealth>();
|
||||
WorldHealthBar healthBar = targetUnit != null
|
||||
? targetUnit.GetComponent<WorldHealthBar>()
|
||||
: targetBuilding != null ? targetBuilding.GetComponent<WorldHealthBar>() : null;
|
||||
WorldHealthBar healthBar = null;
|
||||
|
||||
if (targetUnit != null)
|
||||
{
|
||||
healthBar = targetUnit.GetComponent<WorldHealthBar>();
|
||||
}
|
||||
else if (targetBuilding != null)
|
||||
{
|
||||
healthBar = targetBuilding.GetComponent<WorldHealthBar>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase)
|
||||
healthBar = opponent.GetComponentInParent<WorldHealthBar>();
|
||||
}
|
||||
|
||||
if (healthBar == null || !hitHealthBars.Add(healthBar))
|
||||
{
|
||||
@@ -729,10 +762,15 @@ public class UnitBase : MonoBehaviour
|
||||
{
|
||||
targetUnit.ReceiveDamage(damage, gameObject);
|
||||
}
|
||||
else
|
||||
else if (targetBuilding != null)
|
||||
{
|
||||
targetBuilding.ReceiveDamage(damage, gameObject);
|
||||
}
|
||||
else if (healthBar != null)
|
||||
{
|
||||
// 召唤师等特殊单位
|
||||
healthBar.TakeDamage(damage, gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (registerEnergy)
|
||||
@@ -748,23 +786,33 @@ public class UnitBase : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
string opponentTag = GetOpponentTag();
|
||||
if (string.IsNullOrEmpty(opponentTag))
|
||||
UnitTeam myTeam = GetComponent<UnitTeam>();
|
||||
if (myTeam == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float clampedRadius = Mathf.Max(0f, radius);
|
||||
float radiusSquared = clampedRadius * clampedRadius;
|
||||
GameObject[] opponents = GameObject.FindGameObjectsWithTag(opponentTag);
|
||||
|
||||
// 查找所有带 UnitTeam 的对象(包括单位和召唤师)
|
||||
UnitTeam[] allTeams = FindObjectsOfType<UnitTeam>();
|
||||
HashSet<UnitBase> hitUnits = new HashSet<UnitBase>();
|
||||
|
||||
foreach (GameObject opponent in opponents)
|
||||
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;
|
||||
@@ -774,12 +822,18 @@ public class UnitBase : MonoBehaviour
|
||||
}
|
||||
|
||||
UnitBase unit = opponent.GetComponentInParent<UnitBase>();
|
||||
if (unit == null || !hitUnits.Add(unit))
|
||||
if (unit != null && hitUnits.Add(unit))
|
||||
{
|
||||
unit.ApplyStun(duration);
|
||||
continue;
|
||||
}
|
||||
|
||||
unit.ApplyStun(duration);
|
||||
|
||||
// 召唤师等特殊单位(有 StatusEffectController 但没有 UnitBase)
|
||||
StatusEffectController statusEffect = opponent.GetComponentInParent<StatusEffectController>();
|
||||
if (statusEffect != null)
|
||||
{
|
||||
statusEffect.ApplyStun(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 单位阵营标记:用阵营 ID 替代 Unity Tag 判断敌我关系。
|
||||
/// 支持 PVP:客户端只需设置 GameManager.LocalPlayerTeamId,所有单位的敌我判断自动正确。
|
||||
/// </summary>
|
||||
public class UnitTeam : MonoBehaviour
|
||||
{
|
||||
[Header("阵营 ID")]
|
||||
[Tooltip("0 = 玩家1阵营,1 = 玩家2阵营,2+ = 其他阵营")]
|
||||
public int teamId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 判断目标是否为敌人(不同阵营)
|
||||
/// </summary>
|
||||
public bool IsEnemy(GameObject other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
var otherTeam = other.GetComponent<UnitTeam>();
|
||||
if (otherTeam == null) return false;
|
||||
|
||||
return otherTeam.teamId != this.teamId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断目标是否为友军(相同阵营)
|
||||
/// </summary>
|
||||
public bool IsFriendly(GameObject other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
var otherTeam = other.GetComponent<UnitTeam>();
|
||||
if (otherTeam == null) return false;
|
||||
|
||||
return otherTeam.teamId == this.teamId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否属于本地玩家阵营
|
||||
/// </summary>
|
||||
public bool IsLocalPlayerTeam()
|
||||
{
|
||||
if (GameManager.Instance == null) return teamId == 0;
|
||||
return teamId == GameManager.Instance.LocalPlayerTeamId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: WnxJsSr/VX1DeBY80HGM2j/amZWAkx8FQCM7bFTomhdecHzVdHd5eOw=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -484,9 +484,16 @@ public class WorldHealthBar : MonoBehaviour
|
||||
|
||||
private Color GetHealthFillColor()
|
||||
{
|
||||
return CompareTag("Enemy")
|
||||
? new Color(0.88f, 0.12f, 0.12f, 1f)
|
||||
: new Color(0.1f, 0.8f, 0.18f, 1f);
|
||||
// 使用阵营 ID 判断敌我(支持 PVP)
|
||||
var unitTeam = GetComponent<UnitTeam>();
|
||||
if (unitTeam != null && unitTeam.IsLocalPlayerTeam())
|
||||
{
|
||||
return new Color(0.1f, 0.8f, 0.18f, 1f); // 绿色(我方)
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Color(0.88f, 0.12f, 0.12f, 1f); // 红色(敌方)
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetEnergyFillColor()
|
||||
|
||||
Reference in new issue
Block a user