feat: add player avatar selection UI

This commit is contained in:
ud18010
2026-08-04 19:42:56 +08:00
parent 16ce58876d
commit fab8e3142f
359 changed files with 27441 additions and 69 deletions
@@ -1,6 +1,9 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using XGame;
#if UNITY_EDITOR
using XGame.EditorTools;
@@ -116,6 +119,232 @@ public sealed class PlayerProfileModuleTests
}
}
[Test]
public void ConfirmedAvatarUpdate_ChangesOnlyAvatarId()
{
PlayerProfileData original = PlayerProfileData.CreateDefault();
original.Nickname = "Alice";
original.Gold = 123;
original.AvatarId = 1;
PlayerProfileStore.Replace(original);
PlayerProfileStore.Update(profile => profile.AvatarId = 17);
PlayerProfileData current = PlayerProfileStore.Current;
Assert.That(current.AvatarId, Is.EqualTo(17));
Assert.That(current.Nickname, Is.EqualTo("Alice"));
Assert.That(current.Gold, Is.EqualTo(123));
}
[Test]
public void ConfirmAvatarPicker_CachesSelectedPickerSpriteForProfileRefresh()
{
GameObject moduleObject = new GameObject("PlayerProfileModuleTest");
GameObject imageObject = new GameObject("SelectedAvatar");
PlayerProfileModule module = moduleObject.AddComponent<PlayerProfileModule>();
Image selectedImage = imageObject.AddComponent<Image>();
Texture2D texture = new Texture2D(1, 1);
Sprite selectedSprite = Sprite.Create(texture, new Rect(0, 0, 1, 1), new Vector2(0.5f, 0.5f));
selectedImage.sprite = selectedSprite;
try
{
FieldInfo pendingAvatarId = typeof(PlayerProfileModule).GetField("pendingAvatarId", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo pickerImages = typeof(PlayerProfileModule).GetField("avatarPickerAvatarImages", BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo confirm = typeof(PlayerProfileModule).GetMethod("ConfirmAvatarPicker", BindingFlags.Instance | BindingFlags.NonPublic);
pendingAvatarId.SetValue(module, 8);
Dictionary<int, Image> images = (Dictionary<int, Image>)pickerImages.GetValue(module);
images[8] = selectedImage;
confirm.Invoke(module, null);
FieldInfo spriteCache = typeof(PlayerProfileModule).GetField("avatarSpriteCache", BindingFlags.Instance | BindingFlags.NonPublic);
Dictionary<int, Sprite> cache = (Dictionary<int, Sprite>)spriteCache.GetValue(module);
Assert.That(PlayerProfileStore.Current.AvatarId, Is.EqualTo(8));
Assert.That(cache[8], Is.SameAs(selectedSprite));
}
finally
{
UnityEngine.Object.DestroyImmediate(selectedSprite);
UnityEngine.Object.DestroyImmediate(texture);
UnityEngine.Object.DestroyImmediate(imageObject);
UnityEngine.Object.DestroyImmediate(moduleObject);
}
}
[Test]
public void CacheAvatarPickerControls_CachesAvatarSpritesFromPickerImages()
{
GameObject moduleObject = new GameObject("PlayerProfileModuleTest");
GameObject pickerRoot = new GameObject("AvatarPicker");
GameObject avatarObject = new GameObject("Img_Avatar_08");
PlayerProfileModule module = moduleObject.AddComponent<PlayerProfileModule>();
Image avatarImage = avatarObject.AddComponent<Image>();
Texture2D texture = new Texture2D(1, 1);
Sprite avatarSprite = Sprite.Create(texture, new Rect(0, 0, 1, 1), new Vector2(0.5f, 0.5f));
avatarImage.sprite = avatarSprite;
avatarObject.transform.SetParent(pickerRoot.transform, false);
try
{
FieldInfo pickerView = typeof(PlayerProfileModule).GetField("avatarPickerView", BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo cacheControls = typeof(PlayerProfileModule).GetMethod("CacheAvatarPickerControls", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo spriteCache = typeof(PlayerProfileModule).GetField("avatarSpriteCache", BindingFlags.Instance | BindingFlags.NonPublic);
pickerView.SetValue(module, pickerRoot);
cacheControls.Invoke(module, null);
Dictionary<int, Sprite> cache = (Dictionary<int, Sprite>)spriteCache.GetValue(module);
Assert.That(cache[8], Is.SameAs(avatarSprite));
}
finally
{
UnityEngine.Object.DestroyImmediate(avatarSprite);
UnityEngine.Object.DestroyImmediate(texture);
UnityEngine.Object.DestroyImmediate(pickerRoot);
UnityEngine.Object.DestroyImmediate(moduleObject);
}
}
[Test]
public void CacheAvatarPickerControls_MovesSelectedStateAboveAvatarFrame()
{
GameObject moduleObject = new GameObject("PlayerProfileModuleTest");
GameObject pickerRoot = new GameObject("AvatarPicker");
GameObject slot = new GameObject("AvatarSlot_08");
GameObject button = new GameObject("Btn_Avatar_08");
GameObject avatar = new GameObject("Img_Avatar_08");
GameObject selected = new GameObject("Img_Selected_08");
PlayerProfileModule module = moduleObject.AddComponent<PlayerProfileModule>();
slot.transform.SetParent(pickerRoot.transform, false);
button.transform.SetParent(slot.transform, false);
avatar.transform.SetParent(slot.transform, false);
selected.transform.SetParent(slot.transform, false);
try
{
FieldInfo pickerView = typeof(PlayerProfileModule).GetField("avatarPickerView", BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo cacheControls = typeof(PlayerProfileModule).GetMethod("CacheAvatarPickerControls", BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(selected.transform.GetSiblingIndex(), Is.GreaterThan(button.transform.GetSiblingIndex()));
pickerView.SetValue(module, pickerRoot);
cacheControls.Invoke(module, null);
Assert.That(selected.transform.GetSiblingIndex(), Is.GreaterThan(button.transform.GetSiblingIndex()));
Assert.That(selected.transform.GetSiblingIndex(), Is.GreaterThan(avatar.transform.GetSiblingIndex()));
}
finally
{
UnityEngine.Object.DestroyImmediate(pickerRoot);
UnityEngine.Object.DestroyImmediate(moduleObject);
}
}
[Test]
public void CacheAvatarPickerControls_MovesPreviewAvatarBehindPreviewFrame()
{
GameObject moduleObject = new GameObject("PlayerProfileModuleTest");
GameObject pickerRoot = new GameObject("AvatarPicker", typeof(RectTransform));
GameObject previewPanel = new GameObject("Panel_Preview", typeof(RectTransform));
GameObject previewFrame = new GameObject("Img_PreviewFrame", typeof(RectTransform));
GameObject previewAvatar = new GameObject("Img_PreviewAvatar", typeof(RectTransform));
PlayerProfileModule module = moduleObject.AddComponent<PlayerProfileModule>();
RectTransform previewFrameRect = previewFrame.GetComponent<RectTransform>();
RectTransform previewAvatarRect = previewAvatar.GetComponent<RectTransform>();
previewPanel.transform.SetParent(pickerRoot.transform, false);
previewFrame.transform.SetParent(previewPanel.transform, false);
previewAvatar.transform.SetParent(previewFrame.transform, false);
previewAvatar.AddComponent<Image>();
previewFrameRect.sizeDelta = new Vector2(112f, 112f);
previewAvatarRect.sizeDelta = new Vector2(92f, 92f);
try
{
FieldInfo pickerView = typeof(PlayerProfileModule).GetField("avatarPickerView", BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo cacheControls = typeof(PlayerProfileModule).GetMethod("CacheAvatarPickerControls", BindingFlags.Instance | BindingFlags.NonPublic);
pickerView.SetValue(module, pickerRoot);
cacheControls.Invoke(module, null);
Assert.That(previewAvatar.transform.parent, Is.EqualTo(previewPanel.transform));
Assert.That(previewAvatar.transform.GetSiblingIndex(), Is.LessThan(previewFrame.transform.GetSiblingIndex()));
Assert.That(previewAvatarRect.sizeDelta.x, Is.LessThanOrEqualTo(65f));
Assert.That(previewAvatarRect.sizeDelta.y, Is.LessThanOrEqualTo(65f));
}
finally
{
UnityEngine.Object.DestroyImmediate(pickerRoot);
UnityEngine.Object.DestroyImmediate(moduleObject);
}
}
[Test]
public void CacheHudControls_MovesAvatarIconBehindAvatarFrame()
{
GameObject moduleObject = new GameObject("PlayerProfileModuleTest");
GameObject hudRoot = new GameObject("HudRoot");
GameObject profileButton = new GameObject("Btn_Profile");
GameObject avatarFrame = new GameObject("Img_AvatarFrame");
GameObject avatarIcon = new GameObject("Img_AvatarIcon");
PlayerProfileModule module = moduleObject.AddComponent<PlayerProfileModule>();
profileButton.transform.SetParent(hudRoot.transform, false);
avatarFrame.transform.SetParent(profileButton.transform, false);
avatarIcon.transform.SetParent(avatarFrame.transform, false);
avatarIcon.AddComponent<Image>();
try
{
FieldInfo hudView = typeof(PlayerProfileModule).GetField("hudView", BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo cacheControls = typeof(PlayerProfileModule).GetMethod("CacheHudControls", BindingFlags.Instance | BindingFlags.NonPublic);
hudView.SetValue(module, hudRoot);
cacheControls.Invoke(module, null);
Assert.That(avatarIcon.transform.parent, Is.EqualTo(profileButton.transform));
Assert.That(avatarIcon.transform.GetSiblingIndex(), Is.LessThan(avatarFrame.transform.GetSiblingIndex()));
}
finally
{
UnityEngine.Object.DestroyImmediate(hudRoot);
UnityEngine.Object.DestroyImmediate(moduleObject);
}
}
[Test]
public void ArrangeAvatarIconBehindFrame_MovesDetailAvatarIconBehindAvatarFrame()
{
GameObject moduleObject = new GameObject("PlayerProfileModuleTest");
GameObject panelRoot = new GameObject("PanelRoot");
GameObject identityPanel = new GameObject("Panel_Identity");
GameObject avatarFrame = new GameObject("Img_AvatarFrame");
GameObject avatarIcon = new GameObject("Img_AvatarIcon");
PlayerProfileModule module = moduleObject.AddComponent<PlayerProfileModule>();
identityPanel.transform.SetParent(panelRoot.transform, false);
avatarFrame.transform.SetParent(identityPanel.transform, false);
avatarIcon.transform.SetParent(avatarFrame.transform, false);
avatarIcon.AddComponent<Image>();
try
{
MethodInfo arrangeAvatarIcon = typeof(PlayerProfileModule).GetMethod("ArrangeAvatarIconBehindFrame", BindingFlags.Instance | BindingFlags.NonPublic);
arrangeAvatarIcon.Invoke(module, new object[] { panelRoot });
Assert.That(avatarIcon.transform.parent, Is.EqualTo(identityPanel.transform));
Assert.That(avatarIcon.transform.GetSiblingIndex(), Is.LessThan(avatarFrame.transform.GetSiblingIndex()));
}
finally
{
UnityEngine.Object.DestroyImmediate(panelRoot);
UnityEngine.Object.DestroyImmediate(moduleObject);
}
}
#if UNITY_EDITOR
[Test]
public void PlayerProfileJsonFiles_PassPrefabBuilderValidation()