根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ``` 注意:由于您提供的code differences部分为空,以上是一个示例模板。当您有具体的代码变更时,请提供详细的变更内容以便生成更准确的commit message。
This commit is contained in:
1 parent
8d58c2223c
commit
11f1af357d
191 files changed
+2315
-1539
No files matched your search
@@ -0,0 +1,137 @@
|
||||
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)
|
||||
{
|
||||
validSlots[validCount++] = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
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: DC8WtyqpBy35KStQpLkCge01+kowo40bznmAMX3C7ZNwPI0r5/h6PKA=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,305 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
/// <summary>
|
||||
/// 建筑攻击标志控制器(双阵营):每个阵营各自一个攻击标志,默认指向敌方主城堡。
|
||||
/// 本地玩家点击建筑切换自己阵营的标志;两方标志同时显示,指向同一建筑时并排错开。
|
||||
/// 单位寻敌:周围有敌人打敌人,否则直奔己方标志建筑;到达后是我方阵营(增援完成)→ 己方标志切回主城堡。
|
||||
/// </summary>
|
||||
public class AttackTargetController : MonoBehaviour
|
||||
{
|
||||
public static AttackTargetController Instance { get; private set; }
|
||||
|
||||
[Header("蓝方(teamId=0)攻击目标")]
|
||||
public GameObject blueTarget;
|
||||
|
||||
[Header("红方(teamId=1)攻击目标")]
|
||||
public GameObject redTarget;
|
||||
|
||||
[Header("标志视觉(头顶箭头)")]
|
||||
[Tooltip("标志 Prefab:拖入自定义图标替换默认箭头;留空时代码生成默认箭头")]
|
||||
[SerializeField] private GameObject markerPrefab;
|
||||
[Tooltip("标志相对建筑包围盒顶部的额外高度")]
|
||||
[SerializeField] private float markerHeightOffset = 0.8f;
|
||||
[Tooltip("双方标志指向同一建筑时的并排错开距离")]
|
||||
[SerializeField] private float markerSideOffset = 1.1f;
|
||||
[Tooltip("建筑子级下该名字的空物体 = 标志锚点(可选,编辑器可调位置)")]
|
||||
[SerializeField] private string anchorName = "标志锚点";
|
||||
|
||||
private Transform blueMarker;
|
||||
private Transform redMarker;
|
||||
private Renderer[] blueMarkerRenderers;
|
||||
private Renderer[] redMarkerRenderers;
|
||||
|
||||
private GameObject cachedBlueEnemyCastle; // teamId 0 的敌方主城堡(红色城堡)
|
||||
private GameObject cachedRedEnemyCastle; // teamId 1 的敌方主城堡(蓝色城堡)
|
||||
private float nextCastleRefreshTime;
|
||||
private const float CastleRefreshInterval = 0.5f;
|
||||
private Camera mainCamera;
|
||||
|
||||
private static readonly int TintColorId = Shader.PropertyToID("_TintColor");
|
||||
private static readonly int ColorId = Shader.PropertyToID("_Color");
|
||||
private static readonly Color BlueColor = new Color(0.2f, 0.55f, 1f, 0.95f);
|
||||
private static readonly Color RedColor = new Color(1f, 0.2f, 0.15f, 0.95f);
|
||||
private MaterialPropertyBlock propertyBlock;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
CreateMarkers();
|
||||
blueTarget = GetEnemyCastleOfTeam(0);
|
||||
redTarget = GetEnemyCastleOfTeam(1);
|
||||
Debug.Log($"攻击标志初始: 蓝方→{blueTarget?.name ?? "未找到"} 红方→{redTarget?.name ?? "未找到"}");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 主城堡引用失效时定期重查(场景加载时序容错)
|
||||
if ((cachedBlueEnemyCastle == null || cachedRedEnemyCastle == null) && Time.time >= nextCastleRefreshTime)
|
||||
{
|
||||
nextCastleRefreshTime = Time.time + CastleRefreshInterval;
|
||||
cachedBlueEnemyCastle = FindBuilding("红色城堡");
|
||||
cachedRedEnemyCastle = FindBuilding("蓝色城堡");
|
||||
if (blueTarget == null && cachedBlueEnemyCastle != null) blueTarget = cachedBlueEnemyCastle;
|
||||
if (redTarget == null && cachedRedEnemyCastle != null) redTarget = cachedRedEnemyCastle;
|
||||
}
|
||||
|
||||
HandleClick();
|
||||
}
|
||||
|
||||
// 点击建筑:只切换本地玩家阵营的攻击标志
|
||||
private void HandleClick()
|
||||
{
|
||||
if (mainCamera == null) mainCamera = Camera.main;
|
||||
if (mainCamera == null) return;
|
||||
|
||||
bool clicked = Input.GetMouseButtonDown(0)
|
||||
|| (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began);
|
||||
if (!clicked) return;
|
||||
|
||||
// 点击落在 UI 上不处理
|
||||
if (EventSystem.current != null)
|
||||
{
|
||||
if (Input.touchCount > 0)
|
||||
{
|
||||
if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId)) return;
|
||||
}
|
||||
else if (EventSystem.current.IsPointerOverGameObject()) return;
|
||||
}
|
||||
|
||||
Vector2 screenPos = Input.touchCount > 0 ? (Vector2)Input.GetTouch(0).position : Input.mousePosition;
|
||||
Ray ray = mainCamera.ScreenPointToRay(screenPos);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 500f))
|
||||
{
|
||||
Transform building = FindBuildingInParents(hit.collider.transform);
|
||||
if (building == null) return;
|
||||
|
||||
int localTeam = GameManager.Instance != null ? GameManager.Instance.LocalPlayerTeamId : 0;
|
||||
// 点自己方主城堡无效
|
||||
if (building.name == GetTeamCastleName(localTeam)) return;
|
||||
|
||||
if (localTeam == 0)
|
||||
{
|
||||
blueTarget = building.gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
redTarget = building.gameObject;
|
||||
}
|
||||
Debug.Log($"{(localTeam == 0 ? "蓝方" : "红方")}攻击标志 → {building.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>获取阵营当前的攻击目标(无效时兜底敌方主城堡)。单位出生时调用一次做使命快照。</summary>
|
||||
public GameObject GetTeamTarget(int teamId)
|
||||
{
|
||||
GameObject t = teamId == 0 ? blueTarget : redTarget;
|
||||
if (t != null && t.activeInHierarchy) return t;
|
||||
return GetEnemyCastleOfTeam(teamId);
|
||||
}
|
||||
|
||||
/// <summary>获取阵营的敌方主城堡(teamId 0→红色城堡,1→蓝色城堡),带缓存。</summary>
|
||||
public GameObject GetEnemyCastleOfTeam(int teamId)
|
||||
{
|
||||
if (teamId == 0)
|
||||
{
|
||||
if (cachedBlueEnemyCastle == null || !cachedBlueEnemyCastle.activeInHierarchy)
|
||||
{
|
||||
cachedBlueEnemyCastle = FindBuilding("红色城堡");
|
||||
}
|
||||
return cachedBlueEnemyCastle;
|
||||
}
|
||||
if (cachedRedEnemyCastle == null || !cachedRedEnemyCastle.activeInHierarchy)
|
||||
{
|
||||
cachedRedEnemyCastle = FindBuilding("蓝色城堡");
|
||||
}
|
||||
return cachedRedEnemyCastle;
|
||||
}
|
||||
|
||||
private static GameObject FindBuilding(string name)
|
||||
{
|
||||
var found = GameObject.Find(name);
|
||||
return found != null && found.CompareTag("Building") ? found : null;
|
||||
}
|
||||
|
||||
private static string GetTeamCastleName(int teamId)
|
||||
{
|
||||
return teamId == 0 ? "蓝色城堡" : "红色城堡";
|
||||
}
|
||||
|
||||
private static Transform FindBuildingInParents(Transform t)
|
||||
{
|
||||
while (t != null)
|
||||
{
|
||||
if (t.CompareTag("Building")) return t;
|
||||
t = t.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 双标志:位置/朝向/颜色(指向同一建筑时并排)
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (mainCamera == null) mainCamera = Camera.main;
|
||||
|
||||
bool sameBuilding = blueTarget != null && blueTarget == redTarget;
|
||||
float blueSide = sameBuilding ? markerSideOffset : 0f;
|
||||
float redSide = sameBuilding ? -markerSideOffset : 0f;
|
||||
|
||||
UpdateMarker(blueMarker, blueMarkerRenderers, blueTarget, BlueColor, blueSide);
|
||||
UpdateMarker(redMarker, redMarkerRenderers, redTarget, RedColor, redSide);
|
||||
}
|
||||
|
||||
private void UpdateMarker(Transform marker, Renderer[] renderers, GameObject target, Color color, float sideOffset)
|
||||
{
|
||||
if (marker == null) return;
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
marker.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
marker.gameObject.SetActive(true);
|
||||
|
||||
// 位置:锚点优先(可选),否则建筑包围盒顶部 + 高度偏移
|
||||
Vector3 pos;
|
||||
Transform anchor = target.transform.Find(anchorName);
|
||||
if (anchor != null)
|
||||
{
|
||||
pos = anchor.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Bounds bounds = CalculateTargetBounds(target);
|
||||
pos = new Vector3(target.transform.position.x, bounds.max.y + markerHeightOffset, target.transform.position.z);
|
||||
}
|
||||
|
||||
// 指向同一建筑时沿相机右方向并排错开
|
||||
if (sideOffset != 0f && mainCamera != null)
|
||||
{
|
||||
pos += mainCamera.transform.right * sideOffset;
|
||||
}
|
||||
marker.position = pos;
|
||||
|
||||
// billboard:箭头尖朝下指着建筑
|
||||
if (mainCamera != null)
|
||||
{
|
||||
Vector3 toCamera = mainCamera.transform.position - marker.position;
|
||||
marker.rotation = Quaternion.LookRotation(toCamera) * Quaternion.Euler(90f, 0f, 0f);
|
||||
}
|
||||
|
||||
// 颜色按标志所属阵营
|
||||
if (renderers == null) return;
|
||||
propertyBlock ??= new MaterialPropertyBlock();
|
||||
foreach (Renderer rend in renderers)
|
||||
{
|
||||
if (rend == null) continue;
|
||||
rend.GetPropertyBlock(propertyBlock);
|
||||
propertyBlock.SetColor(TintColorId, color);
|
||||
propertyBlock.SetColor(ColorId, color);
|
||||
rend.SetPropertyBlock(propertyBlock);
|
||||
}
|
||||
}
|
||||
|
||||
private static Bounds CalculateTargetBounds(GameObject target)
|
||||
{
|
||||
var renderers = target.GetComponentsInChildren<Renderer>();
|
||||
if (renderers.Length == 0) return new Bounds(target.transform.position, Vector3.one);
|
||||
Bounds b = renderers[0].bounds;
|
||||
for (int i = 1; i < renderers.Length; i++) b.Encapsulate(renderers[i].bounds);
|
||||
return b;
|
||||
}
|
||||
|
||||
private void CreateMarkers()
|
||||
{
|
||||
if (blueMarker != null && redMarker != null) return;
|
||||
|
||||
blueMarker = CreateMarker("蓝方攻击标志", BlueColor);
|
||||
blueMarkerRenderers = blueMarker.GetComponentsInChildren<Renderer>();
|
||||
redMarker = CreateMarker("红方攻击标志", RedColor);
|
||||
redMarkerRenderers = redMarker.GetComponentsInChildren<Renderer>();
|
||||
}
|
||||
|
||||
private Transform CreateMarker(string name, Color color)
|
||||
{
|
||||
GameObject markerGo;
|
||||
|
||||
// 优先用可替换的标志 Prefab(可在 Inspector 拖入自定义图标)
|
||||
if (markerPrefab != null)
|
||||
{
|
||||
markerGo = Instantiate(markerPrefab, transform);
|
||||
markerGo.name = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 兜底:代码生成默认倒锥箭头(尖朝下指着建筑)
|
||||
markerGo = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer));
|
||||
markerGo.transform.SetParent(transform, false);
|
||||
markerGo.GetComponent<MeshFilter>().sharedMesh = CreateArrowMesh();
|
||||
|
||||
var mat = new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
|
||||
mat.SetColor(TintColorId, color);
|
||||
markerGo.GetComponent<Renderer>().sharedMaterial = mat;
|
||||
}
|
||||
return markerGo.transform;
|
||||
}
|
||||
|
||||
// 4面锥体:尖朝下,高2,底半径0.7
|
||||
private static Mesh CreateArrowMesh()
|
||||
{
|
||||
var mesh = new Mesh();
|
||||
mesh.name = "AttackMarkerArrow";
|
||||
|
||||
Vector3 tip = new Vector3(0f, -1.2f, 0f);
|
||||
Vector3[] baseQuad = {
|
||||
new Vector3(-0.7f, 0.8f, -0.7f), new Vector3(0.7f, 0.8f, -0.7f),
|
||||
new Vector3(0.7f, 0.8f, 0.7f), new Vector3(-0.7f, 0.8f, 0.7f)
|
||||
};
|
||||
|
||||
var vertices = new System.Collections.Generic.List<Vector3>();
|
||||
var normals = new System.Collections.Generic.List<Vector3>();
|
||||
void AddFace(Vector3 a, Vector3 b, Vector3 c, Vector3 n)
|
||||
{
|
||||
vertices.AddRange(new[] { a, b, c });
|
||||
normals.AddRange(new[] { n, n, n });
|
||||
}
|
||||
|
||||
AddFace(baseQuad[0], baseQuad[1], tip, new Vector3(0f, 0f, -1f)); // 后
|
||||
AddFace(baseQuad[1], baseQuad[2], tip, new Vector3(1f, 0f, 0f)); // 右
|
||||
AddFace(baseQuad[2], baseQuad[3], tip, new Vector3(0f, 0f, 1f)); // 前
|
||||
AddFace(baseQuad[3], baseQuad[0], tip, new Vector3(-1f, 0f, 0f)); // 左
|
||||
|
||||
mesh.SetVertices(vertices);
|
||||
mesh.SetNormals(normals);
|
||||
mesh.triangles = new int[] { 0,1,2, 3,4,5, 6,7,8, 9,10,11 };
|
||||
mesh.RecalculateBounds();
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: DH0f5COpWy/XS+dNIQqjD+gef0E+5tOypqT0ghE/5oh3Kpon5JW5YkY=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 战斗管理器:监听双方召唤师死亡,触发胜负判定并显示结果 UI。
|
||||
/// </summary>
|
||||
public class BattleManager : MonoBehaviour
|
||||
{
|
||||
[Header("召唤师引用")]
|
||||
public GameObject playerSummoner;
|
||||
public GameObject enemySummoner;
|
||||
|
||||
[Header("胜负 UI")]
|
||||
public GameObject victoryPanel;
|
||||
public GameObject defeatPanel;
|
||||
|
||||
private WorldHealthBar playerHealthBar;
|
||||
private WorldHealthBar enemyHealthBar;
|
||||
private bool battleEnded = false;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 获取双方召唤师的血条组件
|
||||
if (playerSummoner != null)
|
||||
{
|
||||
playerHealthBar = playerSummoner.GetComponent<WorldHealthBar>();
|
||||
}
|
||||
|
||||
if (enemySummoner != null)
|
||||
{
|
||||
enemyHealthBar = enemySummoner.GetComponent<WorldHealthBar>();
|
||||
}
|
||||
|
||||
// 隐藏胜负 UI
|
||||
if (victoryPanel != null) victoryPanel.SetActive(false);
|
||||
if (defeatPanel != null) defeatPanel.SetActive(false);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (battleEnded) return;
|
||||
|
||||
// 检测己方召唤师死亡
|
||||
if (playerHealthBar != null && playerHealthBar.IsDead)
|
||||
{
|
||||
OnBattleEnd(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测敌方召唤师死亡
|
||||
if (enemyHealthBar != null && enemyHealthBar.IsDead)
|
||||
{
|
||||
OnBattleEnd(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBattleEnd(bool victory)
|
||||
{
|
||||
battleEnded = true;
|
||||
|
||||
// 暂停游戏
|
||||
Time.timeScale = 0f;
|
||||
|
||||
// 显示对应 UI
|
||||
if (victory)
|
||||
{
|
||||
Debug.Log("胜利!");
|
||||
if (victoryPanel != null)
|
||||
{
|
||||
victoryPanel.SetActive(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("失败!");
|
||||
if (defeatPanel != null)
|
||||
{
|
||||
defeatPanel.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重新开始按钮回调
|
||||
public void OnRestartButtonClicked()
|
||||
{
|
||||
Time.timeScale = 1f;
|
||||
UnityEngine.SceneManagement.SceneManager.LoadScene(
|
||||
UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
|
||||
}
|
||||
|
||||
// 退出按钮回调
|
||||
public void OnQuitButtonClicked()
|
||||
{
|
||||
Time.timeScale = 1f;
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CX4W5H+pAi6y7ekeQOC/HN3hMDdmdGBmOHSBBZ23k0yBSbBQ0lyufCA=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// 增益系统管理器(单例)
|
||||
/// 管理全局增益效果的应用
|
||||
/// </summary>
|
||||
public class BuffSystem : MonoBehaviour
|
||||
{
|
||||
private static BuffSystem instance;
|
||||
public static BuffSystem Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
var go = new GameObject("BuffSystem");
|
||||
instance = go.AddComponent<BuffSystem>();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (instance != null && instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
instance = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用群体治疗增益
|
||||
/// </summary>
|
||||
public void ApplyGroupHeal(float healAmount)
|
||||
{
|
||||
var allies = FindObjectsOfType<UnitBase>();
|
||||
int healedCount = 0;
|
||||
|
||||
foreach (var unit in allies)
|
||||
{
|
||||
var team = unit.GetComponent<UnitTeam>();
|
||||
if (team != null && team.teamId == 0) // 友方
|
||||
{
|
||||
var healthBar = unit.GetComponent<WorldHealthBar>();
|
||||
if (healthBar != null)
|
||||
{
|
||||
healthBar.Heal(healAmount);
|
||||
healedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"群体治疗:治疗了 {healedCount} 个友方单位,每个恢复 {healAmount} 生命");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用全局攻速增益
|
||||
/// </summary>
|
||||
public void ApplyGlobalAttackSpeedBuff(float speedMultiplier)
|
||||
{
|
||||
var allies = FindObjectsOfType<UnitBase>();
|
||||
int buffedCount = 0;
|
||||
|
||||
foreach (var unit in allies)
|
||||
{
|
||||
var team = unit.GetComponent<UnitTeam>();
|
||||
if (team != null && team.teamId == 0) // 友方
|
||||
{
|
||||
// 使用 UnitBase 的攻速倍增系统
|
||||
unit.MultiplyAttackSpeed(speedMultiplier, 3f); // 最高3倍攻速
|
||||
buffedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"全局攻速增益:{buffedCount} 个友方单位攻速提升 {(speedMultiplier - 1) * 100}%");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用全局攻击力增益
|
||||
/// </summary>
|
||||
public void ApplyGlobalAttackBuff(float attackMultiplier)
|
||||
{
|
||||
var allies = FindObjectsOfType<UnitBase>();
|
||||
int buffedCount = 0;
|
||||
|
||||
foreach (var unit in allies)
|
||||
{
|
||||
var team = unit.GetComponent<UnitTeam>();
|
||||
if (team != null && team.teamId == 0) // 友方
|
||||
{
|
||||
unit.attackPower *= attackMultiplier;
|
||||
buffedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"全局攻击增益:{buffedCount} 个友方单位攻击力提升 {(attackMultiplier - 1) * 100}%");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用全局生命增益
|
||||
/// </summary>
|
||||
public void ApplyGlobalHealthBuff(float healthMultiplier)
|
||||
{
|
||||
var allies = FindObjectsOfType<UnitBase>();
|
||||
int buffedCount = 0;
|
||||
|
||||
foreach (var unit in allies)
|
||||
{
|
||||
var team = unit.GetComponent<UnitTeam>();
|
||||
if (team != null && team.teamId == 0) // 友方
|
||||
{
|
||||
var healthBar = unit.GetComponent<WorldHealthBar>();
|
||||
if (healthBar != null)
|
||||
{
|
||||
healthBar.maxHealth *= healthMultiplier;
|
||||
healthBar.currentHealth *= healthMultiplier;
|
||||
buffedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"全局生命增益:{buffedCount} 个友方单位生命值提升 {(healthMultiplier - 1) * 100}%");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CS8f5CmqUnrbYBZA6cN6qw0k/aTgXi53/u8Zomku7cDHnoDDves9rxI=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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: CXsY5y6qB3m8jy9B8G9TGvKZDGiLWx+kljaHa6xdphe8IExxiF8gS90=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// 金币管理器,全局单例
|
||||
/// </summary>
|
||||
public class GoldManager : MonoBehaviour
|
||||
{
|
||||
public static GoldManager Instance { get; private set; }
|
||||
|
||||
[Header("金币设置")]
|
||||
[SerializeField] private int initialGold = 500;
|
||||
[SerializeField] private int currentGold = 500;
|
||||
|
||||
/// <summary>
|
||||
/// 当前金币数量(只读)
|
||||
/// </summary>
|
||||
public int CurrentGold => currentGold;
|
||||
|
||||
/// <summary>
|
||||
/// 金币变化事件(参数:新的金币数量)
|
||||
/// </summary>
|
||||
public event Action<int> OnGoldChanged;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 单例模式
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
currentGold = initialGold;
|
||||
OnGoldChanged?.Invoke(currentGold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加金币
|
||||
/// </summary>
|
||||
public void AddGold(int amount)
|
||||
{
|
||||
if (amount <= 0) return;
|
||||
|
||||
currentGold += amount;
|
||||
OnGoldChanged?.Invoke(currentGold);
|
||||
Debug.Log($"获得金币 +{amount},当前金币: {currentGold}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试消费金币,如果金币足够则扣除并返回 true,否则返回 false
|
||||
/// </summary>
|
||||
public bool TrySpendGold(int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
Debug.LogWarning("消费金币数量必须大于0");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentGold < amount)
|
||||
{
|
||||
Debug.LogWarning($"金币不足!需要 {amount},当前只有 {currentGold}");
|
||||
return false;
|
||||
}
|
||||
|
||||
currentGold -= amount;
|
||||
OnGoldChanged?.Invoke(currentGold);
|
||||
Debug.Log($"消费金币 -{amount},当前金币: {currentGold}");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有足够的金币
|
||||
/// </summary>
|
||||
public bool HasEnoughGold(int amount)
|
||||
{
|
||||
return currentGold >= amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: WyhL5CykAihbfEqP3wWb6UBmEruxa4+cSMr8jlsjQfxkQcT5+AouqHg=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class GoldSystem : MonoBehaviour
|
||||
{
|
||||
public static GoldSystem Instance { get; private set; }
|
||||
|
||||
[Header("金币配置")]
|
||||
[Min(0)] public int startingGold = 200;
|
||||
[Min(0)] public int maxGold = 9999;
|
||||
|
||||
[Header("UI(可选)")]
|
||||
public Text goldText;
|
||||
|
||||
public int CurrentGold { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
CurrentGold = startingGold;
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public bool TrySpend(int amount)
|
||||
{
|
||||
if (amount < 0 || CurrentGold < amount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrentGold -= amount;
|
||||
UpdateUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddGold(int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentGold = Mathf.Min(CurrentGold + amount, maxGold);
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
if (goldText != null)
|
||||
{
|
||||
goldText.text = CurrentGold.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: WX8YsHivBi942BoKU7cgLdeehhcrD10jK7kN5ksiyEdHUsqm9i72MxI=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,93 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class ManaSystem : MonoBehaviour
|
||||
{
|
||||
public static ManaSystem Instance { get; private set; }
|
||||
|
||||
[Header("能量配置")]
|
||||
[Min(1)]
|
||||
public int maxMana = 10;
|
||||
|
||||
[Min(0f)]
|
||||
public float manaRegenRate = 1f;
|
||||
|
||||
[Header("UI 引用")]
|
||||
public Image[] manaBeads;
|
||||
|
||||
private float currentMana;
|
||||
private float manaRegenTimer;
|
||||
|
||||
public float CurrentMana => currentMana;
|
||||
public int CurrentManaInt => Mathf.FloorToInt(currentMana);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
currentMana = maxMana;
|
||||
UpdateManaUI();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (currentMana < maxMana)
|
||||
{
|
||||
manaRegenTimer += Time.deltaTime;
|
||||
if (manaRegenTimer >= manaRegenRate)
|
||||
{
|
||||
manaRegenTimer = 0f;
|
||||
currentMana = Mathf.Min(currentMana + 1f, maxMana);
|
||||
UpdateManaUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryConsumeMana(int cost)
|
||||
{
|
||||
if (CurrentManaInt < cost)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
currentMana -= cost;
|
||||
if (currentMana < 0f)
|
||||
{
|
||||
currentMana = 0f;
|
||||
}
|
||||
|
||||
UpdateManaUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddMana(int amount)
|
||||
{
|
||||
currentMana = Mathf.Min(currentMana + amount, maxMana);
|
||||
UpdateManaUI();
|
||||
}
|
||||
|
||||
private void UpdateManaUI()
|
||||
{
|
||||
if (manaBeads == null || manaBeads.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int fullBeads = CurrentManaInt;
|
||||
for (int i = 0; i < manaBeads.Length; i++)
|
||||
{
|
||||
if (manaBeads[i] != null)
|
||||
{
|
||||
manaBeads[i].enabled = i < fullBeads;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: XnMc4S2sBy8qcU1oamvWm5En4Q1vl0fXBe/cr/TVqswIGCNCmFl3B8g=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,176 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
|
||||
[System.Serializable]
|
||||
public class PlayerOwnedCharacterJson
|
||||
{
|
||||
public string characterId;
|
||||
public int level = 1;
|
||||
public int starLevel = 0;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class SavedFormationData
|
||||
{
|
||||
public string[] characterIds = new string[0];
|
||||
public int[] cellIndices = new int[0];
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class PlayerSaveJson
|
||||
{
|
||||
public string playerId = "local_player";
|
||||
public PlayerOwnedCharacterJson[] unlockedCharacters = new PlayerOwnedCharacterJson[0];
|
||||
public string[] currentDeck = new string[0];
|
||||
public SavedFormationData savedFormation = new SavedFormationData();
|
||||
}
|
||||
|
||||
public class PlayerDataManager : MonoBehaviour
|
||||
{
|
||||
public static PlayerDataManager Instance { get; private set; }
|
||||
|
||||
[Header("本地玩家 JSON(正式版替换为服务器响应)")]
|
||||
public TextAsset localPlayerJson;
|
||||
|
||||
public List<PlayerCharacterData> ownedCharacters = new List<PlayerCharacterData>();
|
||||
public PlayerCharacterData[] currentDeck = new PlayerCharacterData[5];
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
InitializeFromJsonOrFallback();
|
||||
}
|
||||
|
||||
private void InitializeFromJsonOrFallback()
|
||||
{
|
||||
TextAsset json = localPlayerJson != null
|
||||
? localPlayerJson
|
||||
: Resources.Load<TextAsset>("PlayerData");
|
||||
|
||||
if (json != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var save = JsonUtility.FromJson<PlayerSaveJson>(json.text);
|
||||
if (save != null && save.unlockedCharacters != null)
|
||||
{
|
||||
ownedCharacters.Clear();
|
||||
foreach (var entry in save.unlockedCharacters)
|
||||
{
|
||||
if (entry == null || string.IsNullOrEmpty(entry.characterId)) continue;
|
||||
ownedCharacters.Add(new PlayerCharacterData(entry.characterId, Mathf.Max(1, entry.level), Mathf.Max(0, entry.starLevel)));
|
||||
}
|
||||
|
||||
currentDeck = new PlayerCharacterData[5];
|
||||
for (int i = 0; i < save.currentDeck.Length && i < currentDeck.Length; i++)
|
||||
currentDeck[i] = GetCharacterData(save.currentDeck[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError("玩家 JSON 解析失败:" + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
InitializeTempData();
|
||||
}
|
||||
|
||||
private void InitializeTempData()
|
||||
{
|
||||
ownedCharacters.Clear();
|
||||
ownedCharacters.Add(new PlayerCharacterData("human_soldier", 1, 0));
|
||||
ownedCharacters.Add(new PlayerCharacterData("archer", 1, 0));
|
||||
ownedCharacters.Add(new PlayerCharacterData("shield_guard", 1, 0));
|
||||
ownedCharacters.Add(new PlayerCharacterData("mage", 1, 0));
|
||||
ownedCharacters.Add(new PlayerCharacterData("elite_soldier", 1, 0));
|
||||
|
||||
currentDeck = new PlayerCharacterData[5];
|
||||
for (int i = 0; i < currentDeck.Length && i < ownedCharacters.Count; i++)
|
||||
currentDeck[i] = ownedCharacters[i];
|
||||
}
|
||||
|
||||
public PlayerCharacterData GetCharacterData(string characterId)
|
||||
{
|
||||
return ownedCharacters.Find(c => c.characterId == characterId);
|
||||
}
|
||||
|
||||
public PlayerCharacterData GetDeckCharacter(int slot)
|
||||
{
|
||||
if (slot < 0 || slot >= currentDeck.Length) return null;
|
||||
return currentDeck[slot];
|
||||
}
|
||||
|
||||
public void UpgradeCharacter(string characterId)
|
||||
{
|
||||
var character = GetCharacterData(characterId);
|
||||
if (character != null) character.level++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存阵型到 PlayerData.json
|
||||
/// </summary>
|
||||
public void SaveFormation(string[] characterIds, int[] cellIndices)
|
||||
{
|
||||
string path = Application.dataPath + "/Resources/PlayerData.json";
|
||||
|
||||
try
|
||||
{
|
||||
// 读取现有数据
|
||||
string json = File.ReadAllText(path);
|
||||
var saveData = JsonUtility.FromJson<PlayerSaveJson>(json);
|
||||
|
||||
// 更新阵型数据
|
||||
saveData.savedFormation = new SavedFormationData
|
||||
{
|
||||
characterIds = characterIds,
|
||||
cellIndices = cellIndices
|
||||
};
|
||||
|
||||
// 写回文件
|
||||
string updatedJson = JsonUtility.ToJson(saveData, true);
|
||||
File.WriteAllText(path, updatedJson);
|
||||
|
||||
Debug.Log($"<color=green>✅ 阵型已保存到 PlayerData.json</color>");
|
||||
Debug.Log($"保存了 {characterIds.Length} 个英雄的阵型数据");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError($"保存阵型失败: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 PlayerData.json 加载阵型
|
||||
/// </summary>
|
||||
public SavedFormationData LoadFormation()
|
||||
{
|
||||
TextAsset json = localPlayerJson != null
|
||||
? localPlayerJson
|
||||
: Resources.Load<TextAsset>("PlayerData");
|
||||
|
||||
if (json != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var save = JsonUtility.FromJson<PlayerSaveJson>(json.text);
|
||||
if (save != null && save.savedFormation != null)
|
||||
{
|
||||
return save.savedFormation;
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError("加载阵型失败:" + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return new SavedFormationData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: B3kety+vUirkqF/JCKtcAbO1EzdK3P+w/NCAM+fzZEfjZiKS0aAKu2k=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,132 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class StatusEffectController : MonoBehaviour
|
||||
{
|
||||
[Header("通用眩晕显示")]
|
||||
[SerializeField] private bool showStunIndicator = true;
|
||||
[SerializeField] private float indicatorHeight = 0.35f;
|
||||
[SerializeField] private GameObject stunIndicatorPrefab;
|
||||
|
||||
private Animator animator;
|
||||
private float stunnedRemaining;
|
||||
private float animatorSpeedBeforeStun = 1f;
|
||||
private GameObject stunIndicator;
|
||||
private Transform indicatorTransform;
|
||||
|
||||
public bool IsStunned => stunnedRemaining > 0f;
|
||||
public float StunnedRemaining => Mathf.Max(0f, stunnedRemaining);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
}
|
||||
|
||||
public void ApplyStun(float duration)
|
||||
{
|
||||
if (duration <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsStunned && animator != null)
|
||||
{
|
||||
animatorSpeedBeforeStun = animator.speed;
|
||||
}
|
||||
|
||||
stunnedRemaining = Mathf.Max(stunnedRemaining, duration);
|
||||
if (animator != null)
|
||||
{
|
||||
animator.speed = 0f;
|
||||
}
|
||||
|
||||
if (showStunIndicator)
|
||||
{
|
||||
EnsureIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsStunned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
stunnedRemaining -= Time.deltaTime;
|
||||
if (animator != null)
|
||||
{
|
||||
animator.speed = 0f;
|
||||
}
|
||||
|
||||
if (stunnedRemaining <= 0f)
|
||||
{
|
||||
stunnedRemaining = 0f;
|
||||
if (animator != null)
|
||||
{
|
||||
UnitBase unitBase = GetComponent<UnitBase>();
|
||||
animator.speed = unitBase != null
|
||||
? Mathf.Max(0.01f, unitBase.AttackSpeedMultiplier)
|
||||
: 1f;
|
||||
}
|
||||
DestroyIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (stunIndicator == null || !IsStunned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Bounds bounds = new Bounds(transform.position, Vector3.zero);
|
||||
Renderer[] renderers = GetComponentsInChildren<Renderer>(true);
|
||||
if (renderers.Length > 0)
|
||||
{
|
||||
bounds = renderers[0].bounds;
|
||||
for (int i = 1; i < renderers.Length; i++)
|
||||
{
|
||||
bounds.Encapsulate(renderers[i].bounds);
|
||||
}
|
||||
}
|
||||
|
||||
indicatorTransform.position = new Vector3(
|
||||
bounds.center.x,
|
||||
bounds.max.y + indicatorHeight,
|
||||
bounds.center.z);
|
||||
}
|
||||
|
||||
private void EnsureIndicator()
|
||||
{
|
||||
if (stunIndicator != null || stunIndicatorPrefab == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
stunIndicator = Instantiate(stunIndicatorPrefab, transform);
|
||||
stunIndicator.name = "StunIndicator_Particle";
|
||||
indicatorTransform = stunIndicator.transform;
|
||||
}
|
||||
|
||||
private void DestroyIndicator()
|
||||
{
|
||||
if (stunIndicator != null)
|
||||
{
|
||||
ParticleSystem[] particleSystems = stunIndicator.GetComponentsInChildren<ParticleSystem>(true);
|
||||
foreach (ParticleSystem particleSystem in particleSystems)
|
||||
{
|
||||
particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
}
|
||||
|
||||
Destroy(stunIndicator);
|
||||
}
|
||||
|
||||
stunIndicator = null;
|
||||
indicatorTransform = null;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
DestroyIndicator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CXxJ5i2tWng6gliTb4vc+EzvyuKbQYGasKmqCCMYXvn0s0NTPXvZPEM=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user