AvatarGroup.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. using AppUI.Manager.Role;
  2. using System;
  3. using System.Collections.Generic;
  4. using TMPro;
  5. using UnityEngine;
  6. using UnityEngine.UI;
  7. namespace AppUI.Util.Avatar
  8. {
  9. [Serializable]
  10. public struct AvatarItem
  11. {
  12. public int id;
  13. public Sprite avatar;
  14. public string name;
  15. public Transform avatarLine;
  16. }
  17. /// <summary>
  18. /// 预设头像网格:点击项更新 <see cref="targetImage"/>,并可通过 <see cref="OnAvatarSelected"/> 与 <see cref="AppUI.View.RegisterView"/> 等串联。
  19. /// </summary>
  20. public class AvatarGroup : MonoBehaviour
  21. {
  22. [SerializeField]
  23. Sprite[] _avatars;
  24. [Tooltip("可选:与 _avatars 一一对应的服务端 avatarID;未配置或长度不一致时用 RoleManager.PresetGridIndexToAvatarId")]
  25. [SerializeField]
  26. int[] _presetAvatarIds;
  27. /// <summary>与 Inspector 中配置的头像 Sprite 数组同源,供 <see cref="AppUI.View.RegisterView"/> 等读取,避免重复配置。</summary>
  28. public Sprite[] Avatars => _avatars;
  29. /// <summary>头像数量,与 <see cref="Avatars"/> 长度一致。</summary>
  30. public int AvatarCount => _avatars != null ? _avatars.Length : 0;
  31. List<AvatarItem> _avatarItems;
  32. public GameObject imagePanel;
  33. public GameObject imageObj;
  34. /// <summary>选中后写入的 Image;<see cref="AppUI.View.RegisterView"/> 可在运行时赋值为 <c>avatarPreviewImage</c> 以共用同一预览图。</summary>
  35. public Image targetImage;
  36. [Header("关联输入框文案颜色(可选)")]
  37. [SerializeField]
  38. TMP_InputField linkedInputField;
  39. [SerializeField]
  40. TMP_Text linkedLabelText;
  41. [SerializeField]
  42. Color textColorWhenHasValue = Color.black;
  43. [SerializeField]
  44. Color textColorWhenEmpty = new Color(0.5f, 0.5f, 0.5f, 1f);
  45. [Header("关联输入框图标(可选,与上面同一套 hasValue 逻辑)")]
  46. [SerializeField]
  47. Image linkedStateImage;
  48. [SerializeField]
  49. Sprite spriteWhenHasValue;
  50. [SerializeField]
  51. Sprite spriteWhenEmpty;
  52. /// <summary>当前选中下标,与 <see cref="_avatars"/> 一致。</summary>
  53. public int SelectedIndex { get; private set; }
  54. /// <summary>用户点击或通过 <see cref="SelectByIndex"/> 选中时触发(含索引与 Sprite)。</summary>
  55. public event Action<int, Sprite> OnAvatarSelected;
  56. void OnEnable()
  57. {
  58. BindLinkedInputField();
  59. }
  60. void OnDisable()
  61. {
  62. UnbindLinkedInputField();
  63. }
  64. void Start()
  65. {
  66. BuildListIfNeeded();
  67. if (_avatarItems != null && _avatarItems.Count > 0 && _avatars != null && _avatars.Length > 0)
  68. SelectByIndex(0, notify: false);
  69. RefreshLinkedLabelTextColor();
  70. }
  71. void BindLinkedInputField()
  72. {
  73. if (linkedInputField == null)
  74. return;
  75. linkedInputField.onValueChanged.RemoveListener(OnLinkedInputValueChanged);
  76. linkedInputField.onValueChanged.AddListener(OnLinkedInputValueChanged);
  77. RefreshLinkedLabelTextColor();
  78. }
  79. void UnbindLinkedInputField()
  80. {
  81. if (linkedInputField == null)
  82. return;
  83. linkedInputField.onValueChanged.RemoveListener(OnLinkedInputValueChanged);
  84. }
  85. void OnLinkedInputValueChanged(string _)
  86. {
  87. RefreshLinkedLabelTextColor();
  88. }
  89. /// <summary>
  90. /// 根据 <see cref="linkedInputField"/> 是否有内容:更新 <see cref="linkedLabelText"/> 颜色、
  91. /// <see cref="linkedStateImage"/> 的 Sprite(有值 / 空 两套)。
  92. /// </summary>
  93. public void RefreshLinkedLabelTextColor()
  94. {
  95. var hasValue = linkedInputField != null && linkedInputField.text.Trim().Length > 0;
  96. if (linkedLabelText != null)
  97. linkedLabelText.color = hasValue ? textColorWhenHasValue : textColorWhenEmpty;
  98. if (linkedStateImage == null)
  99. return;
  100. var sp = hasValue ? spriteWhenHasValue : spriteWhenEmpty;
  101. if (sp != null)
  102. linkedStateImage.sprite = sp;
  103. }
  104. void BuildListIfNeeded()
  105. {
  106. if (_avatarItems != null)
  107. return;
  108. _avatarItems = new List<AvatarItem>();
  109. if (_avatars == null || _avatars.Length == 0 || imagePanel == null || imageObj == null)
  110. return;
  111. for (var i = 0; i < _avatars.Length; i++)
  112. {
  113. var item = new AvatarItem
  114. {
  115. id = i,
  116. avatar = _avatars[i],
  117. name = $"Avatar {i}"
  118. };
  119. var o = Instantiate(imageObj, imagePanel.transform);
  120. var img = o.GetComponent<Image>();
  121. if (img != null)
  122. img.sprite = item.avatar;
  123. var lineTr = o.transform.Find("AvatarLine");
  124. if (lineTr != null)
  125. item.avatarLine = lineTr;
  126. _avatarItems.Add(item);
  127. o.SetActive(true);
  128. var index = i;
  129. var btn = o.GetComponent<Button>();
  130. if (btn != null)
  131. btn.onClick.AddListener(() => OnAvatarItemClick(index));
  132. }
  133. }
  134. void OnAvatarItemClick(int index)
  135. {
  136. SelectByIndex(index, notify: true);
  137. if (targetImage != null)
  138. {
  139. var p = targetImage.transform.parent;
  140. if (p != null && p.parent != null)
  141. p.parent.gameObject.SetActive(true);
  142. }
  143. HideObj();
  144. }
  145. /// <summary>代码侧选中(与点击一致),用于与外部列表或 <see cref="AppUI.View.RegisterView.OnSelectPresetAvatar"/> 同步。</summary>
  146. public void SelectByIndex(int index, bool notify = true)
  147. {
  148. BuildListIfNeeded();
  149. if (_avatars == null || index < 0 || index >= _avatars.Length)
  150. return;
  151. SelectedIndex = index;
  152. if (targetImage != null)
  153. targetImage.sprite = _avatars[index];
  154. if (_avatarItems != null)
  155. {
  156. foreach (var avatarItem in _avatarItems)
  157. {
  158. if (avatarItem.avatarLine != null)
  159. avatarItem.avatarLine.gameObject.SetActive(avatarItem.id == index);
  160. }
  161. }
  162. if (notify)
  163. OnAvatarSelected?.Invoke(index, _avatars[index]);
  164. }
  165. public Sprite GetAvatarSprite(int index)
  166. {
  167. if (_avatars == null || index < 0 || index >= _avatars.Length)
  168. return null;
  169. return _avatars[index];
  170. }
  171. public Sprite GetSelectedSprite()
  172. {
  173. return GetAvatarSprite(SelectedIndex);
  174. }
  175. /// <summary>当前选中项对应的服务端 avatarID(与 <see cref="RoleManager.SetAvatarToImage"/> 资源约定一致)。</summary>
  176. public int GetSelectedAvatarId()
  177. {
  178. if (_presetAvatarIds != null
  179. && SelectedIndex >= 0
  180. && SelectedIndex < _presetAvatarIds.Length)
  181. {
  182. return _presetAvatarIds[SelectedIndex];
  183. }
  184. return RoleManager.PresetGridIndexToAvatarId(SelectedIndex);
  185. }
  186. public void HideObj()
  187. {
  188. if (imagePanel != null)
  189. imagePanel.SetActive(false);
  190. }
  191. public void ShowObj()
  192. {
  193. if (imagePanel != null)
  194. imagePanel.SetActive(true);
  195. }
  196. public void ToggleObj()
  197. {
  198. if (imagePanel != null)
  199. imagePanel.SetActive(!imagePanel.activeSelf);
  200. }
  201. }
  202. }