BattleActionWheel.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using System.Collections.Generic;
  4. using System;
  5. using TMPro;
  6. /// <summary>
  7. /// Radial menu (decision wheel) for selecting battle actions
  8. /// </summary>
  9. public class BattleActionWheel : MonoBehaviour
  10. {
  11. [Header("UI References")]
  12. public Canvas wheelCanvas;
  13. public GameObject wheelBackground;
  14. public GameObject actionButtonPrefab;
  15. public Transform wheelCenter;
  16. [Header("Wheel Settings")]
  17. public float wheelRadius = 100f;
  18. public float buttonSize = 60f;
  19. public float animationDuration = 0.3f;
  20. public AnimationCurve scaleCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
  21. [Header("Action Icons")]
  22. public Sprite moveIcon;
  23. public Sprite attackIcon;
  24. public Sprite itemIcon;
  25. public Sprite spellIcon;
  26. public Sprite runAwayIcon;
  27. public Sprite defendIcon;
  28. public Sprite waitIcon;
  29. [Header("Colors")]
  30. public Color defaultColor = Color.white;
  31. public Color hoverColor = Color.yellow;
  32. public Color disabledColor = Color.gray;
  33. private List<ActionButton> actionButtons = new List<ActionButton>();
  34. private Character currentCharacter;
  35. private bool isVisible = false;
  36. private Vector3 worldPosition;
  37. private Camera mainCamera;
  38. // Events
  39. public event Action<BattleActionType> OnActionSelected;
  40. public event Action OnWheelClosed;
  41. [System.Serializable]
  42. private class ActionButton
  43. {
  44. public GameObject buttonObject;
  45. public Button button;
  46. public Image icon;
  47. public TextMeshProUGUI label;
  48. public BattleActionType actionType;
  49. public bool isEnabled = true;
  50. public void SetEnabled(bool enabled)
  51. {
  52. isEnabled = enabled;
  53. button.interactable = enabled;
  54. icon.color = enabled ? Color.white : Color.gray;
  55. }
  56. public void SetHighlight(bool highlighted, Color normalColor, Color highlightColor)
  57. {
  58. icon.color = highlighted ? highlightColor : (isEnabled ? normalColor : Color.gray);
  59. }
  60. }
  61. void Awake()
  62. {
  63. mainCamera = Camera.main;
  64. if (wheelCanvas == null)
  65. wheelCanvas = GetComponentInChildren<Canvas>();
  66. if (wheelCanvas != null)
  67. wheelCanvas.worldCamera = mainCamera;
  68. SetVisible(false);
  69. }
  70. void Start()
  71. {
  72. InitializeWheel();
  73. }
  74. void Update()
  75. {
  76. if (isVisible)
  77. {
  78. // Update wheel position to follow character or world position
  79. UpdateWheelPosition();
  80. // Handle input for closing wheel
  81. if (Input.GetKeyDown(KeyCode.Escape) || Input.GetMouseButtonDown(1))
  82. {
  83. HideWheel();
  84. }
  85. }
  86. }
  87. private void InitializeWheel()
  88. {
  89. if (actionButtonPrefab == null)
  90. {
  91. Debug.LogError("BattleActionWheel: Action button prefab not assigned!");
  92. return;
  93. }
  94. CreateActionButtons();
  95. }
  96. private void CreateActionButtons()
  97. {
  98. // Define actions and their properties
  99. var actionDefinitions = new[]
  100. {
  101. new { type = BattleActionType.Attack, icon = attackIcon, label = "Attack" },
  102. new { type = BattleActionType.Move, icon = moveIcon, label = "Move" },
  103. new { type = BattleActionType.UseItem, icon = itemIcon, label = "Use Item" },
  104. new { type = BattleActionType.CastSpell, icon = spellIcon, label = "Cast Spell" },
  105. new { type = BattleActionType.Defend, icon = defendIcon, label = "Defend" },
  106. new { type = BattleActionType.RunAway, icon = runAwayIcon, label = "Run Away" }
  107. };
  108. int actionCount = actionDefinitions.Length;
  109. float angleStep = 360f / actionCount;
  110. for (int i = 0; i < actionCount; i++)
  111. {
  112. var actionDef = actionDefinitions[i];
  113. float angle = i * angleStep - 90f; // Start from top
  114. Vector3 position = GetPositionOnCircle(angle, wheelRadius);
  115. CreateActionButton(actionDef.type, actionDef.icon, actionDef.label, position, i);
  116. }
  117. }
  118. private void CreateActionButton(BattleActionType actionType, Sprite icon, string label, Vector3 position, int index)
  119. {
  120. GameObject buttonObj = Instantiate(actionButtonPrefab, wheelCenter);
  121. buttonObj.name = $"ActionButton_{actionType}";
  122. // Position the button
  123. RectTransform rectTransform = buttonObj.GetComponent<RectTransform>();
  124. if (rectTransform != null)
  125. {
  126. rectTransform.anchoredPosition = position;
  127. rectTransform.sizeDelta = Vector2.one * buttonSize;
  128. }
  129. // Get components
  130. Button button = buttonObj.GetComponent<Button>();
  131. Image iconImage = buttonObj.transform.Find("Icon")?.GetComponent<Image>();
  132. TextMeshProUGUI labelText = buttonObj.transform.Find("Label")?.GetComponent<TextMeshProUGUI>();
  133. // If components don't exist, create them
  134. if (iconImage == null)
  135. {
  136. GameObject iconObj = new GameObject("Icon");
  137. iconObj.transform.SetParent(buttonObj.transform, false);
  138. iconImage = iconObj.AddComponent<Image>();
  139. RectTransform iconRect = iconImage.GetComponent<RectTransform>();
  140. iconRect.anchorMin = Vector2.zero;
  141. iconRect.anchorMax = Vector2.one;
  142. iconRect.offsetMin = Vector2.zero;
  143. iconRect.offsetMax = Vector2.zero;
  144. }
  145. if (labelText == null)
  146. {
  147. GameObject labelObj = new GameObject("Label");
  148. labelObj.transform.SetParent(buttonObj.transform, false);
  149. labelText = labelObj.AddComponent<TextMeshProUGUI>();
  150. RectTransform labelRect = labelText.GetComponent<RectTransform>();
  151. labelRect.anchorMin = new Vector2(0f, -0.5f);
  152. labelRect.anchorMax = new Vector2(1f, -0.1f);
  153. labelRect.offsetMin = Vector2.zero;
  154. labelRect.offsetMax = Vector2.zero;
  155. labelText.text = label;
  156. labelText.fontSize = 12;
  157. labelText.alignment = TextAlignmentOptions.Center;
  158. }
  159. // Setup button
  160. if (icon != null && iconImage != null)
  161. iconImage.sprite = icon;
  162. if (labelText != null)
  163. labelText.text = label;
  164. // Create action button data
  165. ActionButton actionButton = new ActionButton
  166. {
  167. buttonObject = buttonObj,
  168. button = button,
  169. icon = iconImage,
  170. label = labelText,
  171. actionType = actionType
  172. };
  173. // Setup button events
  174. if (button != null)
  175. {
  176. button.onClick.AddListener(() => OnActionButtonClicked(actionType));
  177. // Add hover effects
  178. var eventTrigger = buttonObj.GetComponent<UnityEngine.EventSystems.EventTrigger>();
  179. if (eventTrigger == null)
  180. eventTrigger = buttonObj.AddComponent<UnityEngine.EventSystems.EventTrigger>();
  181. // Hover enter
  182. var pointerEnter = new UnityEngine.EventSystems.EventTrigger.Entry();
  183. pointerEnter.eventID = UnityEngine.EventSystems.EventTriggerType.PointerEnter;
  184. pointerEnter.callback.AddListener((data) => { actionButton.SetHighlight(true, defaultColor, hoverColor); });
  185. eventTrigger.triggers.Add(pointerEnter);
  186. // Hover exit
  187. var pointerExit = new UnityEngine.EventSystems.EventTrigger.Entry();
  188. pointerExit.eventID = UnityEngine.EventSystems.EventTriggerType.PointerExit;
  189. pointerExit.callback.AddListener((data) => { actionButton.SetHighlight(false, defaultColor, hoverColor); });
  190. eventTrigger.triggers.Add(pointerExit);
  191. }
  192. actionButtons.Add(actionButton);
  193. buttonObj.SetActive(false); // Start hidden
  194. }
  195. public void ShowWheel(Character character, Vector3 worldPos)
  196. {
  197. currentCharacter = character;
  198. worldPosition = worldPos;
  199. UpdateActionAvailability();
  200. SetVisible(true);
  201. AnimateWheelIn();
  202. }
  203. public void HideWheel()
  204. {
  205. AnimateWheelOut(() =>
  206. {
  207. SetVisible(false);
  208. OnWheelClosed?.Invoke();
  209. });
  210. }
  211. private void UpdateActionAvailability()
  212. {
  213. if (currentCharacter == null) return;
  214. foreach (var actionButton in actionButtons)
  215. {
  216. bool isAvailable = IsActionAvailable(actionButton.actionType);
  217. actionButton.SetEnabled(isAvailable);
  218. }
  219. }
  220. private bool IsActionAvailable(BattleActionType actionType)
  221. {
  222. if (currentCharacter == null) return false;
  223. switch (actionType)
  224. {
  225. case BattleActionType.Attack:
  226. return currentCharacter.Weapon != null;
  227. case BattleActionType.Move:
  228. return true; // Always available
  229. case BattleActionType.UseItem:
  230. // Check if character has any items (placeholder - implement item system)
  231. return HasUsableItems();
  232. case BattleActionType.CastSpell:
  233. // Check if character can cast spells (placeholder - implement spell system)
  234. return HasSpells();
  235. case BattleActionType.Defend:
  236. case BattleActionType.RunAway:
  237. return true; // Usually always available
  238. default:
  239. return true;
  240. }
  241. }
  242. private bool HasUsableItems()
  243. {
  244. // Placeholder implementation
  245. // TODO: Check character's inventory for usable items
  246. return true; // For now, assume characters always have items
  247. }
  248. private bool HasSpells()
  249. {
  250. // Placeholder implementation
  251. // TODO: Check character's spell list or mana
  252. return true; // For now, assume characters can cast spells
  253. }
  254. private void OnActionButtonClicked(BattleActionType actionType)
  255. {
  256. OnActionSelected?.Invoke(actionType);
  257. HideWheel();
  258. }
  259. private Vector3 GetPositionOnCircle(float angleDegrees, float radius)
  260. {
  261. float angleRadians = angleDegrees * Mathf.Deg2Rad;
  262. return new Vector3(
  263. Mathf.Cos(angleRadians) * radius,
  264. Mathf.Sin(angleRadians) * radius,
  265. 0f
  266. );
  267. }
  268. private void UpdateWheelPosition()
  269. {
  270. if (wheelCanvas == null || mainCamera == null) return;
  271. // Convert world position to screen position, then to canvas position
  272. Vector3 screenPos = mainCamera.WorldToScreenPoint(worldPosition);
  273. if (wheelCanvas.renderMode == RenderMode.ScreenSpaceOverlay)
  274. {
  275. wheelCenter.position = screenPos;
  276. }
  277. else if (wheelCanvas.renderMode == RenderMode.ScreenSpaceCamera)
  278. {
  279. Vector2 canvasPos;
  280. RectTransformUtility.ScreenPointToLocalPointInRectangle(
  281. wheelCanvas.transform as RectTransform,
  282. screenPos,
  283. wheelCanvas.worldCamera,
  284. out canvasPos
  285. );
  286. wheelCenter.localPosition = canvasPos;
  287. }
  288. }
  289. private void SetVisible(bool visible)
  290. {
  291. isVisible = visible;
  292. if (wheelCanvas != null)
  293. wheelCanvas.gameObject.SetActive(visible);
  294. }
  295. private void AnimateWheelIn()
  296. {
  297. // Simple scale animation using Unity's built-in animation
  298. if (wheelCenter != null)
  299. {
  300. wheelCenter.localScale = Vector3.zero;
  301. StartCoroutine(AnimateScale(wheelCenter, Vector3.zero, Vector3.one, animationDuration, () =>
  302. {
  303. // Show all buttons after animation
  304. foreach (var button in actionButtons)
  305. {
  306. button.buttonObject.SetActive(true);
  307. }
  308. }));
  309. }
  310. }
  311. private void AnimateWheelOut(Action onComplete = null)
  312. {
  313. // Hide buttons first
  314. foreach (var button in actionButtons)
  315. {
  316. button.buttonObject.SetActive(false);
  317. }
  318. if (wheelCenter != null)
  319. {
  320. StartCoroutine(AnimateScale(wheelCenter, Vector3.one, Vector3.zero, animationDuration * 0.5f, onComplete));
  321. }
  322. else
  323. {
  324. onComplete?.Invoke();
  325. }
  326. }
  327. private System.Collections.IEnumerator AnimateScale(Transform target, Vector3 from, Vector3 to, float duration, Action onComplete = null)
  328. {
  329. float elapsed = 0f;
  330. while (elapsed < duration)
  331. {
  332. elapsed += Time.deltaTime;
  333. float t = elapsed / duration;
  334. // Apply easing curve if available
  335. if (scaleCurve != null && scaleCurve.keys.Length > 0)
  336. {
  337. t = scaleCurve.Evaluate(t);
  338. }
  339. target.localScale = Vector3.Lerp(from, to, t);
  340. yield return null;
  341. }
  342. target.localScale = to;
  343. onComplete?.Invoke();
  344. }
  345. void OnDestroy()
  346. {
  347. // Clean up any running coroutines
  348. StopAllCoroutines();
  349. }
  350. }