diff --git a/Client/Assets/Script/Tests/EditMode/CharacterConfigDataTests.cs b/Client/Assets/Script/Tests/EditMode/CharacterConfigDataTests.cs index 65ce1141..9c1800ed 100644 --- a/Client/Assets/Script/Tests/EditMode/CharacterConfigDataTests.cs +++ b/Client/Assets/Script/Tests/EditMode/CharacterConfigDataTests.cs @@ -41,6 +41,12 @@ public sealed class CharacterConfigDataTests Assert.That(item.prefabPath, Is.EqualTo("Assets/Game/Art/Actor/Prefab/cityboy_sk.prefab")); } + [Test] + public void GetPrefabPathReturnsConfiguredPathForCityboy() + { + Assert.That(CreateConfig().GetPrefabPath(5), Is.EqualTo("Assets/Game/Art/Actor/Prefab/cityboy_sk.prefab")); + } + [Test] public void GetByFigureUsesDefaultForAnInvalidIndex() { diff --git a/Client/Assets/Script/xmain/Character/CharacterConfigData.cs b/Client/Assets/Script/xmain/Character/CharacterConfigData.cs index bceac702..e331c0c6 100644 --- a/Client/Assets/Script/xmain/Character/CharacterConfigData.cs +++ b/Client/Assets/Script/xmain/Character/CharacterConfigData.cs @@ -70,6 +70,12 @@ namespace XGame return GetDefault(); } + + public string GetPrefabPath(int figure) + { + CharacterConfigItem item = GetByFigure(figure); + return item == null ? null : item.prefabPath; + } } [Serializable] diff --git a/Client/Assets/Script/xmain/Client/LobbyWorldController.cs b/Client/Assets/Script/xmain/Client/LobbyWorldController.cs index b9bffac3..c95db2c3 100644 --- a/Client/Assets/Script/xmain/Client/LobbyWorldController.cs +++ b/Client/Assets/Script/xmain/Client/LobbyWorldController.cs @@ -29,18 +29,8 @@ namespace XGame public bool IsPlaceholder; // Obj 当前是胶囊占位(真实角色预设还在异步加载),加载完成后会原地升级 } - private static readonly string[] ActorPaths = - { - "Game/Art/Actor/Prefab/Assasin.prefab", - "Game/Art/Actor/Prefab/bls.prefab", - "Game/Art/Actor/Prefab/boy.prefab", - "Game/Art/Actor/Prefab/captain.prefab", - "Game/Art/Actor/Prefab/habit.prefab", - "Game/Art/Actor/Prefab/rhino.prefab", - "Game/Art/Actor/Prefab/tiger.prefab", - }; - private const string JoystickPrefabPath = "Assets/Game/Art/UI/Prefab/UI_LobbyJoystick.prefab"; + private const string CharacterConfigResourcePath = "Config/CharacterConfig"; private const string SelectedCharacterKey = "CharacterSwitch.SelectedCharacterId"; private const float MoveSpeed = 1f; private const float StopDistance = 0.05f; @@ -68,6 +58,7 @@ namespace XGame private readonly Dictionary prefabCache = new Dictionary(); private readonly HashSet prefabLoading = new HashSet(); private Action send; + private CharacterConfigData characterConfig; private Transform worldRoot; private int selfPlayerId; private int selfFigure; @@ -103,12 +94,25 @@ namespace XGame send = sendFramework; selfPlayerId = playerId; selfName = string.IsNullOrEmpty(playerName) ? "P" + playerId : playerName; - selfFigure = Mathf.Clamp(figure <= 0 ? GetSelectedFigure() : figure, 1, ActorPaths.Length); + if (!EnsureCharacterConfig()) + { + return; + } + + selfFigure = figure <= 0 ? GetSelectedFigure() : ResolveFigure(figure); + if (selfFigure <= 0) + { + Debug.LogError("[LobbyWorldController] cannot initialize without a valid character figure."); + return; + } EnsureWorldRoot(); worldRoot.gameObject.SetActive(true); EnsureCamera(); StartCoroutine(EnsureJoystickUIAsync()); - GetOrCreateAvatar(selfPlayerId, selfFigure, selfName); + if (GetOrCreateAvatar(selfPlayerId, selfFigure, selfName) == null) + { + return; + } receivedSelfState = false; lastMoveDirection = Vector3.zero; nextMoveCommandTime = 0f; @@ -151,7 +155,12 @@ namespace XGame public void RefreshSelectedFigure() { - int nextFigure = Mathf.Clamp(GetSelectedFigure(), 1, ActorPaths.Length); + int nextFigure = GetSelectedFigure(); + if (nextFigure <= 0) + { + Debug.LogError("[LobbyWorldController] cannot refresh the selected figure because the character catalog is unavailable."); + return; + } if (selfFigure == nextFigure) { return; @@ -174,6 +183,10 @@ namespace XGame } Avatar nextAvatar = GetOrCreateAvatar(selfPlayerId, selfFigure, selfName); + if (nextAvatar == null) + { + return; + } if (hadAvatar && nextAvatar.Obj != null) { nextAvatar.Obj.transform.position = position; @@ -214,6 +227,10 @@ namespace XGame { seen.Add(player.PlayerId); Avatar avatar = GetOrCreateAvatar(player.PlayerId, player.Figure, player.Name); + if (avatar == null) + { + continue; + } Vector3 pos = new Vector3(player.X, player.Y, player.Z); if (player.PlayerId == selfPlayerId) { @@ -330,6 +347,10 @@ namespace XGame { avatar = GetOrCreateAvatar(move.PlayerId, 1, "P" + move.PlayerId); } + if (avatar == null) + { + return; + } Vector3 target = new Vector3(move.X, move.Y, move.Z); if (!avatar.HasServerPosition && avatar.Obj != null) { @@ -511,7 +532,12 @@ namespace XGame private Avatar GetOrCreateAvatar(int playerId, int figure, string name) { - figure = Mathf.Clamp(figure <= 0 ? 1 : figure, 1, ActorPaths.Length); + figure = ResolveFigure(figure); + if (figure <= 0) + { + Debug.LogError("[LobbyWorldController] cannot resolve figure because the character catalog is unavailable."); + return null; + } if (avatars.TryGetValue(playerId, out Avatar avatar)) { if (avatar.Figure == figure) @@ -594,8 +620,16 @@ namespace XGame private IEnumerator CoLoadActorPrefab(int figure) { + string prefabPath = GetPrefabPath(figure); + if (string.IsNullOrEmpty(prefabPath)) + { + prefabLoading.Remove(figure); + Debug.LogError("[LobbyWorldController] missing prefab path for figure=" + figure); + yield break; + } + UObject loaded = null; - yield return XResLoader.coLoadRes(ActorPaths[figure - 1], typeof(GameObject), o => loaded = o); + yield return XResLoader.coLoadRes(prefabPath, typeof(GameObject), o => loaded = o); prefabLoading.Remove(figure); GameObject prefab = loaded as GameObject; if (prefab == null) @@ -1201,21 +1235,63 @@ namespace XGame return Vector2.Distance(screenPosition, center) <= radius * JoystickAcquireRadiusMultiplier; } - private static int GetSelectedFigure() + private bool EnsureCharacterConfig() { - string selected = PlayerPrefs.GetString(SelectedCharacterKey, string.Empty).ToLowerInvariant(); - for (int i = 0; i < ActorPaths.Length; i++) + if (characterConfig != null) { - string path = ActorPaths[i].ToLowerInvariant(); - int slash = path.LastIndexOf('/'); - int dot = path.LastIndexOf('.'); - string id = dot > slash ? path.Substring(slash + 1, dot - slash - 1) : path; - if (id == selected) - { - return i + 1; - } + return characterConfig.GetDefault() != null; } - return 1; + + TextAsset asset = Resources.Load(CharacterConfigResourcePath); + if (asset == null) + { + Debug.LogError("[LobbyWorldController] missing character config: Resources/" + CharacterConfigResourcePath + ".json"); + return false; + } + + try + { + characterConfig = JsonUtility.FromJson(asset.text); + } + catch (Exception ex) + { + Debug.LogError("[LobbyWorldController] parse character config failed: " + ex.Message); + characterConfig = null; + } + + if (characterConfig == null || characterConfig.GetDefault() == null) + { + Debug.LogError("[LobbyWorldController] character config is empty or invalid."); + characterConfig = null; + return false; + } + + return true; + } + + private int ResolveFigure(int figure) + { + if (!EnsureCharacterConfig()) + { + return 0; + } + + return characterConfig.GetFigure(characterConfig.GetByFigure(figure)); + } + + private int GetSelectedFigure() + { + if (!EnsureCharacterConfig()) + { + return 0; + } + + return characterConfig.GetFigure(PlayerPrefs.GetString(SelectedCharacterKey, string.Empty)); + } + + private string GetPrefabPath(int figure) + { + return EnsureCharacterConfig() ? characterConfig.GetPrefabPath(figure) : null; } } }