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"));
}
[Test]
public void GetPrefabPathReturnsConfiguredPathForCityboy()
{
Assert.That(CreateConfig().GetPrefabPath(5), Is.EqualTo("Assets/Game/Art/Actor/Prefab/cityboy_sk.prefab"));
}
[Test]
public void GetByFigureUsesDefaultForAnInvalidIndex()
{
@@ -70,6 +70,12 @@ namespace XGame
return GetDefault();
}
public string GetPrefabPath(int figure)
{
CharacterConfigItem item = GetByFigure(figure);
return item == null ? null : item.prefabPath;
}
}
[Serializable]
@@ -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<int, GameObject> prefabCache = new Dictionary<int, GameObject>();
private readonly HashSet<int> prefabLoading = new HashSet<int>();
private Action<FrameworkOpcode, byte[]> 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<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;
}
}
}