BattleActionWheel.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. Debug.Log($"🎯 Action wheel shown for {character.CharacterName} at {worldPos}");
  203. }
  204. public void HideWheel()
  205. {
  206. AnimateWheelOut(() =>
  207. {
  208. SetVisible(false);
  209. OnWheelClosed?.Invoke();
  210. });
  211. Debug.Log("🎯 Action wheel hidden");
  212. }
  213. private void UpdateActionAvailability()
  214. {
  215. if (currentCharacter == null) return;
  216. foreach (var actionButton in actionButtons)
  217. {
  218. bool isAvailable = IsActionAvailable(actionButton.actionType);
  219. actionButton.SetEnabled(isAvailable);
  220. }
  221. }
  222. private bool IsActionAvailable(BattleActionType actionType)
  223. {
  224. if (currentCharacter == null) return false;
  225. switch (actionType)
  226. {
  227. case BattleActionType.Attack:
  228. return currentCharacter.Weapon != null;
  229. case BattleActionType.Move:
  230. return true; // Always available
  231. case BattleActionType.UseItem:
  232. // Check if character has any items (placeholder - implement item system)
  233. return HasUsableItems();
  234. case BattleActionType.CastSpell:
  235. // Check if character can cast spells (placeholder - implement spell system)
  236. return HasSpells();
  237. case BattleActionType.Defend:
  238. case BattleActionType.RunAway:
  239. return true; // Usually always available
  240. default:
  241. return true;
  242. }
  243. }
  244. private bool HasUsableItems()
  245. {
  246. // Placeholder implementation
  247. // TODO: Check character's inventory for usable items
  248. return true; // For now, assume characters always have items
  249. }
  250. private bool HasSpells()
  251. {
  252. // Placeholder implementation
  253. // TODO: Check character's spell list or mana
  254. return true; // For now, assume characters can cast spells
  255. }
  256. private void OnActionButtonClicked(BattleActionType actionType)
  257. {
  258. Debug.Log($"🎯 Action selected: {actionType}");
  259. OnActionSelected?.Invoke(actionType);
  260. HideWheel();
  261. }
  262. private Vector3 GetPositionOnCircle(float angleDegrees, float radius)
  263. {
  264. float angleRadians = angleDegrees * Mathf.Deg2Rad;
  265. return new Vector3(
  266. Mathf.Cos(angleRadians) * radius,
  267. Mathf.Sin(angleRadians) * radius,
  268. 0f
  269. );
  270. }
  271. private void UpdateWheelPosition()
  272. {
  273. if (wheelCanvas == null || mainCamera == null) return;
  274. // Convert world position to screen position, then to canvas position
  275. Vector3 screenPos = mainCamera.WorldToScreenPoint(worldPosition);
  276. if (wheelCanvas.renderMode == RenderMode.ScreenSpaceOverlay)
  277. {
  278. wheelCenter.position = screenPos;
  279. }
  280. else if (wheelCanvas.renderMode == RenderMode.ScreenSpaceCamera)
  281. {
  282. Vector2 canvasPos;
  283. RectTransformUtility.ScreenPointToLocalPointInRectangle(
  284. wheelCanvas.transform as RectTransform,
  285. screenPos,
  286. wheelCanvas.worldCamera,
  287. out canvasPos
  288. );
  289. wheelCenter.localPosition = canvasPos;
  290. }
  291. }
  292. private void SetVisible(bool visible)
  293. {
  294. isVisible = visible;
  295. if (wheelCanvas != null)
  296. wheelCanvas.gameObject.SetActive(visible);
  297. }
  298. private void AnimateWheelIn()
  299. {
  300. // Simple scale animation using Unity's built-in animation
  301. if (wheelCenter != null)
  302. {
  303. wheelCenter.localScale = Vector3.zero;
  304. StartCoroutine(AnimateScale(wheelCenter, Vector3.zero, Vector3.one, animationDuration, () =>
  305. {
  306. // Show all buttons after animation
  307. foreach (var button in actionButtons)
  308. {
  309. button.buttonObject.SetActive(true);
  310. }
  311. }));
  312. }
  313. }
  314. private void AnimateWheelOut(Action onComplete = null)
  315. {
  316. // Hide buttons first
  317. foreach (var button in actionButtons)
  318. {
  319. button.buttonObject.SetActive(false);
  320. }
  321. if (wheelCenter != null)
  322. {
  323. StartCoroutine(AnimateScale(wheelCenter, Vector3.one, Vector3.zero, animationDuration * 0.5f, onComplete));
  324. }
  325. else
  326. {
  327. onComplete?.Invoke();
  328. }
  329. }
  330. private System.Collections.IEnumerator AnimateScale(Transform target, Vector3 from, Vector3 to, float duration, Action onComplete = null)
  331. {
  332. float elapsed = 0f;
  333. while (elapsed < duration)
  334. {
  335. elapsed += Time.deltaTime;
  336. float t = elapsed / duration;
  337. // Apply easing curve if available
  338. if (scaleCurve != null && scaleCurve.keys.Length > 0)
  339. {
  340. t = scaleCurve.Evaluate(t);
  341. }
  342. target.localScale = Vector3.Lerp(from, to, t);
  343. yield return null;
  344. }
  345. target.localScale = to;
  346. onComplete?.Invoke();
  347. }
  348. void OnDestroy()
  349. {
  350. // Clean up any running coroutines
  351. StopAllCoroutines();
  352. }
  353. }