feat(player): 添加玩家阵型保存功能

新增savedFormation字段用于存储玩家的阵型配置,
包括角色ID列表和对应的格子索引位置信息。
```
This commit is contained in:
huadexuan committed 2026-09-29 23:21:44 +08:00
1 parent e127da5416
commit 1abd0de32f
16 files changed
+432 -2930

No files matched your search

-137
View File
@@ -1,137 +0,0 @@
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 自动绑定友方英雄到UI卡片(Play Mode自动运行)
/// </summary>
public class AutoBindHeroes : MonoBehaviour
{
[Header("UI引用")]
[Tooltip("英雄卡片面板的父物体")]
public Transform heroCardsPanel;
[Header("英雄头像资源")]
public Sprite soldierIcon;
public Sprite archerIcon;
public Sprite shieldIcon;
public Sprite mageIcon;
public Sprite eliteSoldierIcon;
private bool hasBindOnce = false;
void Start()
{
// 延迟1秒等待所有英雄生成
Invoke(nameof(BindHeroesToCards), 1f);
}
void BindHeroesToCards()
{
if (hasBindOnce)
{
Debug.LogWarning("英雄已经绑定过了,跳过重复绑定");
return;
}
if (heroCardsPanel == null)
{
Debug.LogError("AutoBindHeroes: heroCardsPanel 未赋值!");
return;
}
// 1. 查找所有友方英雄(teamId=0)
var allUnits = FindObjectsOfType<UnitBase>();
var heroes = new List<UnitBase>();
foreach (var unit in allUnits)
{
var team = unit.GetComponent<UnitTeam>();
if (team != null && team.teamId == 0)
{
heroes.Add(unit);
}
}
if (heroes.Count == 0)
{
Debug.LogWarning("AutoBindHeroes: 未找到友方英雄(teamId=0)");
return;
}
Debug.Log($"AutoBindHeroes: 找到 {heroes.Count} 个友方英雄");
// 2. 设置所有英雄为可复活
foreach (var hero in heroes)
{
var health = hero.GetComponent<WorldHealthBar>();
if (health != null)
{
health.canRevive = true;
}
}
// 3. 绑定到UI卡片(最多5个)
int bindCount = Mathf.Min(heroes.Count, 5);
for (int i = 0; i < bindCount; i++)
{
var hero = heroes[i];
var cardObj = heroCardsPanel.Find($"HeroCard{i + 1}");
if (cardObj == null)
{
Debug.LogWarning($"AutoBindHeroes: 未找到 HeroCard{i + 1}");
continue;
}
var heroCardUI = cardObj.GetComponent<HeroCardUI>();
if (heroCardUI == null)
{
Debug.LogWarning($"AutoBindHeroes: HeroCard{i + 1} 缺少 HeroCardUI 组件");
continue;
}
// 根据英雄名字匹配头像
Sprite icon = GetHeroIcon(hero.name);
if (icon == null)
{
Debug.LogWarning($"AutoBindHeroes: {hero.name} 没有匹配的头像,使用默认图标");
}
// 绑定英雄
heroCardUI.SetHero(hero, icon, hero.transform.position);
Debug.Log($"✅ HeroCard{i + 1} 已绑定: {hero.name}");
}
hasBindOnce = true;
Debug.Log($"<color=green>✅ 英雄自动绑定完成!共绑定 {bindCount} 个英雄</color>");
}
/// <summary>
/// 根据英雄名字获取对应头像
/// </summary>
Sprite GetHeroIcon(string heroName)
{
if (heroName.Contains("人类士兵") || heroName.Contains("士兵"))
{
return soldierIcon;
}
else if (heroName.Contains("弓箭手") || heroName.Contains("弓箭"))
{
return archerIcon;
}
else if (heroName.Contains("盾卫"))
{
return shieldIcon;
}
else if (heroName.Contains("法师"))
{
return mageIcon;
}
else if (heroName.Contains("精锐"))
{
return eliteSoldierIcon;
}
return null;
}
}
-11
View File
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: BntLsXv+VCiekq1pfBLqrcgPxyOKiyT9GMwu39MKq/rePWiFpCWwjlw=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+93 -13
View File
@@ -25,7 +25,19 @@ public class BattleFormationSpawner : MonoBehaviour
private void SpawnHeroes()
{
// 1. 玩家编排过的阵型
// 1. 优先从 PlayerData.json 读取保存的阵型
if (PlayerDataManager.Instance != null)
{
SavedFormationData formationData = PlayerDataManager.Instance.LoadFormation();
if (formationData != null && formationData.characterIds != null && formationData.characterIds.Length > 0)
{
Debug.Log($"从 PlayerData.json 加载阵型,共 {formationData.characterIds.Length} 个英雄");
SpawnFromSavedFormation(formationData);
return;
}
}
// 2. 回退到 FormationData(场景间临时传递的数据)
if (FormationData.HasCustomFormation
&& FormationData.HeroPrefabs != null
&& FormationData.HeroPrefabs.Length > 0)
@@ -42,10 +54,54 @@ public class BattleFormationSpawner : MonoBehaviour
return;
}
// 2. 没有编排数据:默认阵型
// 3. 没有任何编排数据:使用默认阵型
SpawnDefaultFormation();
}
/// <summary>
/// 从保存的阵型数据生成英雄
/// </summary>
private void SpawnFromSavedFormation(SavedFormationData formationData)
{
for (int i = 0; i < formationData.characterIds.Length; i++)
{
string characterId = formationData.characterIds[i];
int cell = i < formationData.cellIndices.Length ? formationData.cellIndices[i] : -1;
if (string.IsNullOrEmpty(characterId) || !DeployGrid.IsValidIndex(cell))
{
continue;
}
// 根据 characterId 找到对应的 Prefab
GameObject prefab = FindPrefabByCharacterId(characterId);
if (prefab != null)
{
Spawn(prefab, cell);
}
else
{
Debug.LogWarning($"未找到 characterId={characterId} 对应的 Prefab");
}
}
}
/// <summary>
/// 根据 characterId 查找对应的 Prefab
/// </summary>
private GameObject FindPrefabByCharacterId(string characterId)
{
var allData = Resources.LoadAll<CharacterData>("Data");
foreach (var data in allData)
{
if (data.characterId == characterId)
{
return data.unitPrefab;
}
}
return null;
}
private void SpawnDefaultFormation()
{
if (roster == null || roster.heroes == null) return;
@@ -89,7 +145,7 @@ public class BattleFormationSpawner : MonoBehaviour
return -1;
}
private static int spawnedHeroCount = 0; // 记录已生成英雄数量
private int spawnedHeroCount = 0; // 记录已生成英雄数量(改为实例变量)
private void Spawn(GameObject prefab, int cell)
{
@@ -141,25 +197,49 @@ public class BattleFormationSpawner : MonoBehaviour
private void BindHeroToCard(GameObject heroUnit, CharacterData characterData, Vector3 spawnPos)
{
if (spawnedHeroCount >= 5) return; // 最多5个英雄
if (spawnedHeroCount >= 5)
{
Debug.LogWarning($"已达到最大英雄数量5,跳过绑定 {heroUnit.name}");
return; // 最多5个英雄
}
// 查找对应的卡片
Canvas canvas = FindObjectOfType<Canvas>();
if (canvas == null) return;
// 查找 TowerDefenseCanvas
GameObject canvasObj = GameObject.Find("TowerDefenseCanvas");
if (canvasObj == null)
{
Debug.LogError("BindHeroToCard: 未找到 TowerDefenseCanvas");
return;
}
Transform heroPanel = canvas.transform.Find("BottomUI/HeroCardsPanel");
if (heroPanel == null) return;
Transform heroPanel = canvasObj.transform.Find("BottomUI/HeroCardsPanel");
if (heroPanel == null)
{
Debug.LogError("BindHeroToCard: 未找到 BottomUI/HeroCardsPanel");
return;
}
string cardName = $"HeroCard{spawnedHeroCount + 1}";
Transform cardObj = heroPanel.Find(cardName);
if (cardObj == null) return;
if (cardObj == null)
{
Debug.LogError($"BindHeroToCard: 未找到 {cardName}");
return;
}
HeroCardUI heroCardUI = cardObj.GetComponent<HeroCardUI>();
if (heroCardUI == null) return;
if (heroCardUI == null)
{
Debug.LogError($"BindHeroToCard: {cardName} 缺少 HeroCardUI 组件");
return;
}
// 获取英雄单位组件
UnitBase unitBase = heroUnit.GetComponent<UnitBase>();
if (unitBase == null) return;
if (unitBase == null)
{
Debug.LogError($"BindHeroToCard: {heroUnit.name} 缺少 UnitBase 组件");
return;
}
// 加载对应的头像
UnityEngine.Sprite icon = characterData != null ? characterData.cardIcon : null;
@@ -169,7 +249,7 @@ public class BattleFormationSpawner : MonoBehaviour
spawnedHeroCount++;
Debug.Log($"英雄 {heroUnit.name} 已绑定到 {cardName}");
Debug.Log($"✅ {cardName} 已绑定英雄: {heroUnit.name}(第 {spawnedHeroCount} 个)");
}
private CharacterData FindCharacterDataForPrefab(GameObject prefab)
+6
View File
@@ -56,6 +56,12 @@ public class CastleSpawnManager : MonoBehaviour
float atk = playerData.GetAttackPower(data);
float spd = playerData.GetMoveSpeed(data);
unitBase.InitializeFromPlayerData(hp, atk, spd);
// 如果是敌人(teamId != 0),增大寻敌距离
if (teamId != 0)
{
unitBase.detectionRange = 100f; // 从默认30增加到100
}
}
Debug.Log($"城堡出兵: {data.characterName} teamId={teamId} pos={spawnPoint.position}");
@@ -114,6 +114,14 @@ public class DefenseWaveLevelController : MonoBehaviour
: (spawned % 2 == 0 ? -3f : 3f);
var enemy = Instantiate(prefab, new Vector3(x, spawnY, spawnZ), Quaternion.identity);
enemy.tag = "Enemy";
// 增大敌人的寻敌距离
UnitBase unitBase = enemy.GetComponent<UnitBase>();
if (unitBase != null)
{
unitBase.detectionRange = 100f; // 从默认30增加到100
}
ApplyCharacterData(enemy, wave.enemyIds[typeIndex]);
activeEnemies.Add(enemy);
spawned++;
+4 -4
View File
@@ -8,11 +8,11 @@ public static class DeployGrid
{
public const int Cols = 6;
public const int Rows = 5;
public const float ColStep = 2.8f;
public const float RowStep = 3.0f;
public const float BackZ = 2.5f; // 最靠后一排的中心 Z(让部署区落在底部卡片栏上方)
public const float ColStep = 2.4f; // 列间距改为2.4,让网格更紧凑,避免最边缘格子太靠外
public const float RowStep = 2.8f; // 行间距保持2.8
public const float BackZ = -15.0f; // 最靠后一排的中心 Z(调整到靠近摄像机的底部区域)
public const float CellWidth = 2.5f;
public const float CellDepth = 2.7f;
public const float CellDepth = 2.5f; // 格子深度改为2.5,与宽度相同,形成正方形
public const float CellHeight = 0.03f;
public const float GridY = 0.12f; // 格位可视块的中心高度
public const float UnitY = 0.16f; // 单位落点高度(与 NavMesh 高度一致)
+6
View File
@@ -236,6 +236,12 @@ public class EnemyCastleAI : MonoBehaviour
if (unitBase != null)
{
unitBase.InitializeFromPlayerData(data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1));
// 如果是敌人(teamId != 0),增大寻敌距离
if (spawnTeamId != 0)
{
unitBase.detectionRange = 100f; // 从默认30增加到100
}
}
return true;
+54 -4
View File
@@ -310,17 +310,67 @@ public class FormationManager : MonoBehaviour
return -1;
}
// ===== 进入战斗 =====
// ===== 保存阵型 =====
public void StartBattle()
{
FillUnplacedWithDefault();
SaveFormation();
SaveFormationToJson();
}
if (!string.IsNullOrEmpty(battleSceneName))
private void SaveFormationToJson()
{
int count = HeroCount;
var characterIds = new string[count];
var cells = new int[count];
// 初始化数组
for (int i = 0; i < count; i++)
{
SceneManager.LoadScene(battleSceneName);
HeroEntry entry = GetHero(i);
characterIds[i] = entry != null ? GetCharacterId(entry.prefab) : "";
cells[i] = -1;
}
// 填充已放置的格位
foreach (var pair in placed)
{
int heroIndex = pair.Value.heroIndex;
if (heroIndex >= 0 && heroIndex < count)
{
cells[heroIndex] = pair.Key;
}
}
// 保存到 PlayerData.json
if (PlayerDataManager.Instance != null)
{
PlayerDataManager.Instance.SaveFormation(characterIds, cells);
}
else
{
Debug.LogError("PlayerDataManager.Instance 未找到,无法保存阵型");
}
}
/// <summary>
/// 根据 Prefab 获取 characterId
/// </summary>
private string GetCharacterId(GameObject prefab)
{
if (prefab == null) return "";
// 查找对应的 CharacterData
var allData = Resources.LoadAll<CharacterData>("Data");
foreach (var data in allData)
{
if (data.unitPrefab == prefab)
{
return data.characterId;
}
}
return "";
}
private void SaveFormation()
+27
View File
@@ -195,16 +195,43 @@ public class HeroCardUI : MonoBehaviour
var navAgent = heroUnit.GetComponent<UnityEngine.AI.NavMeshAgent>();
if (navAgent != null)
{
navAgent.enabled = true; // 确保启用
if (!navAgent.isOnNavMesh)
{
navAgent.enabled = false;
navAgent.enabled = true;
}
navAgent.ResetPath(); // 清空旧路径
}
// 重置 UnitBase 状态(关键修复:清空目标让单位重新寻敌)
UnitBase unitBase = heroUnit.GetComponent<UnitBase>();
if (unitBase != null)
{
unitBase.enabled = true; // 确保脚本启用
unitBase.target = null; // 清空目标
unitBase.ResetAnchorPosition(); // 重置锚点位置到复活位置(修复一直尝试返回旧锚点的问题)
}
// 重置 Animator
Animator animator = heroUnit.GetComponent<Animator>();
if (animator != null)
{
animator.enabled = true;
animator.Rebind(); // 重置动画状态
animator.Update(0f);
}
// 使用WorldHealthBar的Revive方法(会重置isDead并恢复生命)
heroHealthBar.Revive(heroHealthBar.maxHealth);
// 重新显示血条(修复复活后没有血条的问题)
Transform barTransform = heroHealthBar.transform.Find("WorldHealthBar");
if (barTransform != null)
{
barTransform.gameObject.SetActive(true);
}
// 重置UI状态
isDead = false;
if (deathPanel != null)
+70
View File
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using System.IO;
[System.Serializable]
public class PlayerOwnedCharacterJson
@@ -9,12 +10,20 @@ public class PlayerOwnedCharacterJson
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
@@ -103,4 +112,65 @@ public class PlayerDataManager : MonoBehaviour
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();
}
}
+26 -3
View File
@@ -276,6 +276,7 @@ public class UnitBase : MonoBehaviour
if (!summonerMode)
{
animator.SetInteger("speed", 0);
animator.speed = 1f; // 重置动画播放速度(修复攻击后动画变慢的问题)
}
}
@@ -401,11 +402,24 @@ public class UnitBase : MonoBehaviour
return Vector3.Distance(current, anchor) <= anchorArrivalDistance;
}
/// <summary>
/// 重置锚点位置到当前位置(用于复活等场景)
/// </summary>
public void ResetAnchorPosition()
{
anchorPosition = transform.position;
anchorInitialized = true;
}
private void ReturnToAnchor()
{
if (navAgent == null || !anchorInitialized)
{
if (!summonerMode) animator.SetInteger("speed", 0);
if (!summonerMode)
{
animator.SetInteger("speed", 0);
animator.speed = 1f; // 重置动画播放速度
}
return;
}
@@ -416,7 +430,11 @@ public class UnitBase : MonoBehaviour
if (!navAgent.isOnNavMesh)
{
if (!summonerMode) animator.SetInteger("speed", 0);
if (!summonerMode)
{
animator.SetInteger("speed", 0);
animator.speed = 1f; // 重置动画播放速度
}
return;
}
@@ -426,7 +444,11 @@ public class UnitBase : MonoBehaviour
navAgent.ResetPath();
transform.position = new Vector3(anchorPosition.x, transform.position.y, anchorPosition.z);
navAgent.nextPosition = transform.position;
if (!summonerMode) animator.SetInteger("speed", 0);
if (!summonerMode)
{
animator.SetInteger("speed", 0);
animator.speed = 1f; // 重置动画播放速度
}
return;
}
@@ -437,6 +459,7 @@ public class UnitBase : MonoBehaviour
nextPathUpdateTime = Time.time + PathUpdateInterval;
}
animator.SetInteger("speed", navAgent.velocity.sqrMagnitude > 0.01f ? 1 : 0);
animator.speed = 1f; // 确保移动时动画速度正常
}
//目标决策:只寻找 detectionRange 内的敌方战斗单位;范围内没有敌人时原地待机或回到锚点。
+8
View File
@@ -85,6 +85,14 @@ public class WaveSpawner : MonoBehaviour
Vector3 spawnPos = transform.position + wave.spawnPositions[i];
GameObject enemy = Instantiate(wave.enemyPrefabs[i], spawnPos, Quaternion.identity);
enemy.tag = "Enemy";
// 增大敌人的寻敌距离
UnitBase unitBase = enemy.GetComponent<UnitBase>();
if (unitBase != null)
{
unitBase.detectionRange = 100f; // 从默认30增加到100
}
activeEnemies.Add(enemy);
Debug.Log($"生成敌人:{enemy.name} 位置={spawnPos}");