fix: load lobby characters from config

This commit is contained in:
ud18010
2026-07-27 12:19:12 +08:00
parent ba6595217b
commit 3200235537
3 changed files with 116 additions and 28 deletions
@@ -41,6 +41,12 @@ public sealed class CharacterConfigDataTests
Assert.That(item.prefabPath, Is.EqualTo("Assets/Game/Art/Actor/Prefab/cityboy_sk.prefab")); 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] [Test]
public void GetByFigureUsesDefaultForAnInvalidIndex() public void GetByFigureUsesDefaultForAnInvalidIndex()
{ {
@@ -70,6 +70,12 @@ namespace XGame
return GetDefault(); return GetDefault();
} }
public string GetPrefabPath(int figure)
{
CharacterConfigItem item = GetByFigure(figure);
return item == null ? null : item.prefabPath;
}
} }
[Serializable] [Serializable]
@@ -29,18 +29,8 @@ namespace XGame
public bool IsPlaceholder; // Obj 当前是胶囊占位(真实角色预设还在异步加载),加载完成后会原地升级 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 JoystickPrefabPath = "Assets/Game/Art/UI/Prefab/UI_LobbyJoystick.prefab";
private const string CharacterConfigResourcePath = "Config/CharacterConfig";
private const string SelectedCharacterKey = "CharacterSwitch.SelectedCharacterId"; private const string SelectedCharacterKey = "CharacterSwitch.SelectedCharacterId";
private const float MoveSpeed = 1f; private const float MoveSpeed = 1f;
private const float StopDistance = 0.05f; private const float StopDistance = 0.05f;
@@ -68,6 +58,7 @@ namespace XGame
private readonly Dictionary<int, GameObject> prefabCache = new Dictionary<int, GameObject>(); private readonly Dictionary<int, GameObject> prefabCache = new Dictionary<int, GameObject>();
private readonly HashSet<int> prefabLoading = new HashSet<int>(); private readonly HashSet<int> prefabLoading = new HashSet<int>();
private Action<FrameworkOpcode, byte[]> send; private Action<FrameworkOpcode, byte[]> send;
private CharacterConfigData characterConfig;
private Transform worldRoot; private Transform worldRoot;
private int selfPlayerId; private int selfPlayerId;
private int selfFigure; private int selfFigure;
@@ -103,12 +94,25 @@ namespace XGame
send = sendFramework; send = sendFramework;
selfPlayerId = playerId; selfPlayerId = playerId;
selfName = string.IsNullOrEmpty(playerName) ? "P" + playerId : playerName; 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(); EnsureWorldRoot();
worldRoot.gameObject.SetActive(true); worldRoot.gameObject.SetActive(true);
EnsureCamera(); EnsureCamera();
StartCoroutine(EnsureJoystickUIAsync()); StartCoroutine(EnsureJoystickUIAsync());
GetOrCreateAvatar(selfPlayerId, selfFigure, selfName); if (GetOrCreateAvatar(selfPlayerId, selfFigure, selfName) == null)
{
return;
}
receivedSelfState = false; receivedSelfState = false;
lastMoveDirection = Vector3.zero; lastMoveDirection = Vector3.zero;
nextMoveCommandTime = 0f; nextMoveCommandTime = 0f;
@@ -151,7 +155,12 @@ namespace XGame
public void RefreshSelectedFigure() 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) if (selfFigure == nextFigure)
{ {
return; return;
@@ -174,6 +183,10 @@ namespace XGame
} }
Avatar nextAvatar = GetOrCreateAvatar(selfPlayerId, selfFigure, selfName); Avatar nextAvatar = GetOrCreateAvatar(selfPlayerId, selfFigure, selfName);
if (nextAvatar == null)
{
return;
}
if (hadAvatar && nextAvatar.Obj != null) if (hadAvatar && nextAvatar.Obj != null)
{ {
nextAvatar.Obj.transform.position = position; nextAvatar.Obj.transform.position = position;
@@ -214,6 +227,10 @@ namespace XGame
{ {
seen.Add(player.PlayerId); seen.Add(player.PlayerId);
Avatar avatar = GetOrCreateAvatar(player.PlayerId, player.Figure, player.Name); Avatar avatar = GetOrCreateAvatar(player.PlayerId, player.Figure, player.Name);
if (avatar == null)
{
continue;
}
Vector3 pos = new Vector3(player.X, player.Y, player.Z); Vector3 pos = new Vector3(player.X, player.Y, player.Z);
if (player.PlayerId == selfPlayerId) if (player.PlayerId == selfPlayerId)
{ {
@@ -330,6 +347,10 @@ namespace XGame
{ {
avatar = GetOrCreateAvatar(move.PlayerId, 1, "P" + move.PlayerId); avatar = GetOrCreateAvatar(move.PlayerId, 1, "P" + move.PlayerId);
} }
if (avatar == null)
{
return;
}
Vector3 target = new Vector3(move.X, move.Y, move.Z); Vector3 target = new Vector3(move.X, move.Y, move.Z);
if (!avatar.HasServerPosition && avatar.Obj != null) if (!avatar.HasServerPosition && avatar.Obj != null)
{ {
@@ -511,7 +532,12 @@ namespace XGame
private Avatar GetOrCreateAvatar(int playerId, int figure, string name) 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 (avatars.TryGetValue(playerId, out Avatar avatar))
{ {
if (avatar.Figure == figure) if (avatar.Figure == figure)
@@ -594,8 +620,16 @@ namespace XGame
private IEnumerator CoLoadActorPrefab(int figure) 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; 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); prefabLoading.Remove(figure);
GameObject prefab = loaded as GameObject; GameObject prefab = loaded as GameObject;
if (prefab == null) if (prefab == null)
@@ -1201,21 +1235,63 @@ namespace XGame
return Vector2.Distance(screenPosition, center) <= radius * JoystickAcquireRadiusMultiplier; return Vector2.Distance(screenPosition, center) <= radius * JoystickAcquireRadiusMultiplier;
} }
private static int GetSelectedFigure() private bool EnsureCharacterConfig()
{ {
string selected = PlayerPrefs.GetString(SelectedCharacterKey, string.Empty).ToLowerInvariant(); if (characterConfig != null)
for (int i = 0; i < ActorPaths.Length; i++)
{ {
string path = ActorPaths[i].ToLowerInvariant(); return characterConfig.GetDefault() != null;
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 1;
TextAsset asset = Resources.Load<TextAsset>(CharacterConfigResourcePath);
if (asset == null)
{
Debug.LogError("[LobbyWorldController] missing character config: Resources/" + CharacterConfigResourcePath + ".json");
return false;
}
try
{
characterConfig = JsonUtility.FromJson<CharacterConfigData>(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;
} }
} }
} }