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()
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d351657d8a8001d46979039dab8f06e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,4 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -10,19 +12,34 @@ namespace XGame
{
private const string HudPrefabPath = "Assets/Game/Art/UI/Prefab/UI_PlayerProfileHud.prefab";
private const string PanelPrefabPath = "Assets/Game/Art/UI/Prefab/UI_PlayerProfilePanel.prefab";
private const string AvatarPickerPrefabPath = "Assets/Game/Art/UI/Prefab/UI_PlayerAvatarPicker.prefab";
private const float PreviewAvatarFrameFill = 0.58f;
private GameObject hudView;
private GameObject detailView;
private GameObject avatarPickerView;
private bool openingHud;
private bool openingDetail;
private bool openingAvatarPicker;
private int openVersion;
private int avatarPickerVersion;
private int pendingAvatarId;
private TMP_Text hudNicknameText;
private TMP_Text hudLevelText;
private TMP_Text hudExpText;
private Image hudAvatarImage;
private Image detailAvatarImage;
private RectTransform hudExpFill;
private RectTransform hudExpTrack;
private Image avatarPickerPreviewImage;
private TMP_Text avatarPickerHintText;
private readonly Dictionary<int, Sprite> avatarSpriteCache = new Dictionary<int, Sprite>();
private readonly HashSet<int> loadingAvatarIds = new HashSet<int>();
private readonly Dictionary<int, Image> avatarPickerAvatarImages = new Dictionary<int, Image>();
private readonly Dictionary<int, GameObject> avatarPickerSelectedStates = new Dictionary<int, GameObject>();
private void OnEnable()
{
PlayerProfileStore.Changed += OnProfileChanged;
@@ -73,6 +90,8 @@ namespace XGame
openVersion++;
openingHud = false;
openingDetail = false;
openingAvatarPicker = false;
avatarPickerVersion++;
if (hudView != null)
{
Destroy(hudView);
@@ -81,6 +100,10 @@ namespace XGame
{
Destroy(detailView);
}
if (avatarPickerView != null)
{
Destroy(avatarPickerView);
}
hudView = null;
detailView = null;
hudNicknameText = null;
@@ -88,6 +111,16 @@ namespace XGame
hudExpText = null;
hudExpFill = null;
hudExpTrack = null;
hudAvatarImage = null;
detailAvatarImage = null;
avatarPickerView = null;
avatarPickerPreviewImage = null;
avatarPickerHintText = null;
pendingAvatarId = 0;
avatarPickerAvatarImages.Clear();
avatarPickerSelectedStates.Clear();
loadingAvatarIds.Clear();
avatarSpriteCache.Clear();
}
private void OnProfileChanged(PlayerProfileData profile)
@@ -110,6 +143,8 @@ namespace XGame
hudNicknameText = FindComponent<TMP_Text>(hudView, "Txt_Nickname");
hudLevelText = FindComponent<TMP_Text>(hudView, "Txt_Level");
hudExpText = FindComponent<TMP_Text>(hudView, "Txt_Exp");
hudAvatarImage = FindComponent<Image>(hudView, "Img_AvatarIcon");
ArrangeAvatarIconBehindFrame(hudView);
hudExpTrack = FindRect(hudView, "Img_ExpBg");
hudExpFill = FindRect(hudView, "Img_ExpFill");
}
@@ -136,6 +171,7 @@ namespace XGame
if (hudNicknameText != null) hudNicknameText.text = profile.Nickname;
if (hudLevelText != null) hudLevelText.text = "Lv." + profile.Lv;
if (hudExpText != null) hudExpText.text = profile.Exp + "/" + profile.RequiredExp;
RefreshAvatarImage(hudAvatarImage, profile.AvatarId);
if (hudExpTrack != null && hudExpFill != null)
{
@@ -170,6 +206,8 @@ namespace XGame
detailView = Instantiate(prefab);
UIManager.AttachTo(UILayer.Dialog, detailView);
detailAvatarImage = FindComponent<Image>(detailView, "Img_AvatarIcon");
ArrangeAvatarIconBehindFrame(detailView);
BindDetailEvents();
RefreshDetail(PlayerProfileStore.Current);
}
@@ -178,6 +216,12 @@ namespace XGame
{
BindCloseButton("Btn_Close");
BindCloseButton("Img_Mask");
Button avatarButton = FindComponent<Button>(detailView, "Img_AvatarFrame");
if (avatarButton != null)
{
avatarButton.onClick.RemoveAllListeners();
avatarButton.onClick.AddListener(OpenAvatarPicker);
}
}
private void BindCloseButton(string nodeName)
@@ -193,6 +237,7 @@ namespace XGame
private void CloseDetail()
{
CloseAvatarPicker();
if (detailView != null)
{
Destroy(detailView);
@@ -208,6 +253,7 @@ namespace XGame
}
profile = profile ?? PlayerProfileStore.Current;
ArrangeAvatarIconBehindFrame(detailView);
SetText("Txt_Nickname", profile.Nickname);
SetText("Txt_Uid", "UID: " + profile.Uid);
SetText("Txt_RoleType", profile.RoleType.ToString());
@@ -221,6 +267,358 @@ namespace XGame
SetText("Txt_Ap", profile.Ap.ToString());
SetText("Txt_AtkSpeed", profile.AtkSpeed.ToString("0.##"));
SetText("Txt_MoveSpeed", profile.MoveSpeed.ToString("0.##"));
RefreshAvatarImage(detailAvatarImage, profile.AvatarId);
}
private void ArrangeAvatarIconBehindFrame(GameObject root)
{
ArrangeGraphicBehindFrame(root, "Img_AvatarFrame", "Img_AvatarIcon");
}
private void ArrangeGraphicBehindFrame(GameObject root, string frameName, string graphicName)
{
Transform frame = FindTransform(root, frameName);
Transform graphic = FindTransform(root, graphicName);
if (frame == null || graphic == null || frame.parent == null)
{
return;
}
RectTransform frameRect = frame as RectTransform;
RectTransform graphicRect = graphic as RectTransform;
Vector2 graphicSize = graphicRect == null ? Vector2.zero : graphicRect.sizeDelta;
if (graphic.parent != frame.parent)
{
graphic.SetParent(frame.parent, true);
}
if (frameRect != null && graphicRect != null)
{
graphicRect.anchorMin = frameRect.anchorMin;
graphicRect.anchorMax = frameRect.anchorMax;
graphicRect.pivot = frameRect.pivot;
graphicRect.anchoredPosition = frameRect.anchoredPosition;
graphicRect.sizeDelta = graphicSize;
}
if (graphic.GetSiblingIndex() > frame.GetSiblingIndex())
{
graphic.SetSiblingIndex(frame.GetSiblingIndex());
}
}
private void RefreshAvatarImage(Image image, int avatarId)
{
if (image == null)
{
return;
}
int normalizedId = PlayerAvatarCatalog.NormalizeId(avatarId);
if (avatarSpriteCache.TryGetValue(normalizedId, out Sprite cachedSprite))
{
image.sprite = cachedSprite;
return;
}
if (loadingAvatarIds.Add(normalizedId))
{
XWCoroutine.StartCo(CoLoadAvatarSprite(normalizedId, image));
}
}
private IEnumerator CoLoadAvatarSprite(int avatarId, Image target)
{
Sprite sprite = null;
if (XResLoader.m_ABMode == 1)
{
yield return CoCacheAvatarSpritesFromPickerPrefab();
avatarSpriteCache.TryGetValue(avatarId, out sprite);
}
if (sprite == null)
{
string path = PlayerAvatarCatalog.GetAssetPath(avatarId);
UObject loaded = null;
yield return XResLoader.coLoadRes(path, typeof(Sprite), obj => loaded = obj);
sprite = loaded as Sprite;
}
if (sprite == null)
{
yield return CoCacheAvatarSpritesFromPickerPrefab();
avatarSpriteCache.TryGetValue(avatarId, out sprite);
}
loadingAvatarIds.Remove(avatarId);
if (sprite == null)
{
Debug.LogError("[PlayerProfileModule] load avatar failed: " + PlayerAvatarCatalog.GetAssetPath(avatarId));
yield break;
}
avatarSpriteCache[avatarId] = sprite;
if (PlayerAvatarCatalog.NormalizeId(PlayerProfileStore.Current.AvatarId) != avatarId)
{
yield break;
}
ApplyAvatarSprite(target, sprite);
ApplyAvatarSprite(hudAvatarImage, sprite);
ApplyAvatarSprite(detailAvatarImage, sprite);
}
private IEnumerator CoCacheAvatarSpritesFromPickerPrefab()
{
if (avatarPickerView != null)
{
CacheAvatarSpritesFromRoot(avatarPickerView);
yield break;
}
UObject loaded = null;
yield return XResLoader.coLoadRes(AvatarPickerPrefabPath, typeof(GameObject), obj => loaded = obj);
CacheAvatarSpritesFromRoot(loaded as GameObject);
}
private static void ApplyAvatarSprite(Image target, Sprite sprite)
{
if (target != null)
{
target.sprite = sprite;
}
}
private void OpenAvatarPicker()
{
if (avatarPickerView != null || openingAvatarPicker)
{
RefreshAvatarPicker();
return;
}
XWCoroutine.StartCo(CoOpenAvatarPicker());
}
private IEnumerator CoOpenAvatarPicker()
{
openingAvatarPicker = true;
int version = ++avatarPickerVersion;
UObject loaded = null;
yield return XResLoader.coLoadRes(AvatarPickerPrefabPath, typeof(GameObject), obj => loaded = obj);
openingAvatarPicker = false;
if (version != avatarPickerVersion || detailView == null)
{
yield break;
}
GameObject prefab = loaded as GameObject;
if (prefab == null)
{
Debug.LogError("[PlayerProfileModule] load avatar picker prefab failed: " + AvatarPickerPrefabPath);
yield break;
}
avatarPickerView = Instantiate(prefab);
UIManager.AttachTo(UILayer.Dialog, avatarPickerView);
pendingAvatarId = PlayerAvatarCatalog.NormalizeId(PlayerProfileStore.Current.AvatarId);
BindAvatarPickerEvents();
CacheAvatarPickerControls();
RefreshAvatarPicker();
}
private void BindAvatarPickerEvents()
{
BindAvatarPickerButton("Img_Mask", CancelAvatarPicker);
BindAvatarPickerButton("Btn_Close", CancelAvatarPicker);
BindAvatarPickerButton("Btn_Cancel", CancelAvatarPicker);
BindAvatarPickerButton("Btn_Confirm", ConfirmAvatarPicker);
for (int avatarId = 1; avatarId <= PlayerAvatarCatalog.Count; avatarId++)
{
int selectedId = avatarId;
Button button = FindComponent<Button>(avatarPickerView, AvatarButtonName(selectedId));
if (button == null)
{
continue;
}
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() => SelectPendingAvatar(selectedId));
}
}
private void BindAvatarPickerButton(string nodeName, UnityEngine.Events.UnityAction action)
{
Button button = FindComponent<Button>(avatarPickerView, nodeName);
if (button == null)
{
return;
}
button.onClick.RemoveAllListeners();
button.onClick.AddListener(action);
}
private void CacheAvatarPickerControls()
{
avatarPickerPreviewImage = FindComponent<Image>(avatarPickerView, "Img_PreviewAvatar");
ArrangeGraphicBehindFrame(avatarPickerView, "Img_PreviewFrame", "Img_PreviewAvatar");
ClampGraphicToFrameFill(avatarPickerView, "Img_PreviewFrame", "Img_PreviewAvatar", PreviewAvatarFrameFill);
avatarPickerHintText = FindComponent<TMP_Text>(avatarPickerView, "Txt_PreviewHint");
avatarPickerAvatarImages.Clear();
avatarPickerSelectedStates.Clear();
for (int avatarId = 1; avatarId <= PlayerAvatarCatalog.Count; avatarId++)
{
Image avatarImage = FindComponent<Image>(avatarPickerView, AvatarImageName(avatarId));
avatarPickerAvatarImages[avatarId] = avatarImage;
if (avatarImage != null && avatarImage.sprite != null)
{
avatarSpriteCache[avatarId] = avatarImage.sprite;
}
Transform selected = FindTransform(avatarPickerView, AvatarSelectedName(avatarId));
if (selected != null)
{
MoveSelectedStateAboveAvatarFrame(selected);
avatarPickerSelectedStates[avatarId] = selected.gameObject;
}
}
}
private void CacheAvatarSpritesFromRoot(GameObject root)
{
if (root == null)
{
return;
}
for (int avatarId = 1; avatarId <= PlayerAvatarCatalog.Count; avatarId++)
{
Image avatarImage = FindComponent<Image>(root, AvatarImageName(avatarId));
if (avatarImage != null && avatarImage.sprite != null)
{
avatarSpriteCache[avatarId] = avatarImage.sprite;
}
}
}
private void MoveSelectedStateAboveAvatarFrame(Transform selected)
{
if (selected == null)
{
return;
}
selected.SetAsLastSibling();
}
private void ClampGraphicToFrameFill(GameObject root, string frameName, string graphicName, float fill)
{
Transform frame = FindTransform(root, frameName);
Transform graphic = FindTransform(root, graphicName);
RectTransform frameRect = frame as RectTransform;
RectTransform graphicRect = graphic as RectTransform;
if (frameRect == null || graphicRect == null)
{
return;
}
float frameWidth = Mathf.Abs(frameRect.rect.width) > 0f ? Mathf.Abs(frameRect.rect.width) : Mathf.Abs(frameRect.sizeDelta.x);
float frameHeight = Mathf.Abs(frameRect.rect.height) > 0f ? Mathf.Abs(frameRect.rect.height) : Mathf.Abs(frameRect.sizeDelta.y);
float maxSide = Mathf.Min(frameWidth, frameHeight) * Mathf.Clamp01(fill);
if (maxSide <= 0f)
{
return;
}
float currentWidth = Mathf.Abs(graphicRect.sizeDelta.x);
float currentHeight = Mathf.Abs(graphicRect.sizeDelta.y);
float currentSide = Mathf.Max(currentWidth, currentHeight);
float side = currentSide > 0f ? Mathf.Min(currentSide, maxSide) : maxSide;
graphicRect.sizeDelta = new Vector2(side, side);
}
private void SelectPendingAvatar(int avatarId)
{
pendingAvatarId = PlayerAvatarCatalog.NormalizeId(avatarId);
RefreshAvatarPicker();
}
private void ConfirmAvatarPicker()
{
int selectedId = PlayerAvatarCatalog.NormalizeId(pendingAvatarId);
if (avatarPickerAvatarImages.TryGetValue(selectedId, out Image selectedImage) &&
selectedImage != null &&
selectedImage.sprite != null)
{
avatarSpriteCache[selectedId] = selectedImage.sprite;
}
PlayerProfileStore.Update(profile => profile.AvatarId = selectedId);
CloseAvatarPicker();
}
private void CancelAvatarPicker()
{
CloseAvatarPicker();
}
private void CloseAvatarPicker()
{
avatarPickerVersion++;
openingAvatarPicker = false;
if (avatarPickerView != null)
{
Destroy(avatarPickerView);
}
avatarPickerView = null;
avatarPickerPreviewImage = null;
avatarPickerHintText = null;
avatarPickerAvatarImages.Clear();
avatarPickerSelectedStates.Clear();
}
private void RefreshAvatarPicker()
{
if (avatarPickerView == null)
{
return;
}
int selectedId = PlayerAvatarCatalog.NormalizeId(pendingAvatarId);
pendingAvatarId = selectedId;
for (int avatarId = 1; avatarId <= PlayerAvatarCatalog.Count; avatarId++)
{
if (avatarPickerSelectedStates.TryGetValue(avatarId, out GameObject selectedState) && selectedState != null)
{
selectedState.SetActive(avatarId == selectedId);
}
}
if (avatarPickerAvatarImages.TryGetValue(selectedId, out Image selectedImage) && selectedImage != null && avatarPickerPreviewImage != null)
{
avatarPickerPreviewImage.sprite = selectedImage.sprite;
}
if (avatarPickerHintText != null)
{
avatarPickerHintText.text = "已选择头像 " + selectedId.ToString("00", CultureInfo.InvariantCulture);
}
}
private static string AvatarButtonName(int avatarId)
{
return "Btn_Avatar_" + avatarId.ToString("00", CultureInfo.InvariantCulture);
}
private static string AvatarImageName(int avatarId)
{
return "Img_Avatar_" + avatarId.ToString("00", CultureInfo.InvariantCulture);
}
private static string AvatarSelectedName(int avatarId)
{
return "Img_Selected_" + avatarId.ToString("00", CultureInfo.InvariantCulture);
}
private void SetText(string nodeName, string value)
@@ -243,5 +641,10 @@ namespace XGame
Transform t = root == null ? null : root.transform.FindTransform(nodeName);
return t == null ? null : t.GetComponent<T>();
}
private static Transform FindTransform(GameObject root, string nodeName)
{
return root == null ? null : root.transform.FindTransform(nodeName);
}
}
}