Advertisement
Guest User

Combo Script (Fin?)

a guest
Nov 14th, 2019
295
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.56 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using UnityEngine;
  6.  
  7. public class ComboScript : MonoBehaviour
  8. {
  9. /// <summary>
  10. /// Converts <see cref="KeyPressLifetimeMs"/> to a form that <see cref="Time.deltaTime"/> can interact with.
  11. /// </summary>
  12. private const double KeyPressLifetime = KeyPressLifetimeMs / 1000;
  13.  
  14. /// <summary>
  15. /// Stores the length of time after which a key press is no longer considered 'live', and is thus ineligible for
  16. /// consideration in a combo chain.
  17. /// </summary>
  18. private const double KeyPressLifetimeMs = 150;
  19.  
  20. /// <summary>
  21. /// Allows for tracking when a key press lifetime runs out, and keys should start being removed from the key queue.
  22. /// </summary>
  23. private double keyPressTimer;
  24.  
  25. /// <summary>
  26. /// The queue of pressed keys to track the entered combo.
  27. /// </summary>
  28. private Queue<Key> pressedKeys;
  29.  
  30. /// <summary>
  31. /// The list of registered combos that will be looked for by the script.
  32. /// </summary>
  33. private List<Combo> registeredCombos;
  34.  
  35. public KeyCode AttackKey; // temp key
  36.  
  37. public KeyCode DodgeKey; // temp key
  38.  
  39. public KeyCode HeavyAttackKey; // temp key
  40.  
  41. public KeyCode JumpKey; // temp key
  42.  
  43. /// <summary>
  44. /// The maximum number of key presses that will be registered for combos.
  45. /// </summary>
  46. public int MaxPressedKeys;
  47.  
  48. // called before Start() (before Start() is called on any script), similar to a constructor
  49. private void Awake()
  50. {
  51. MaxPressedKeys = MaxPressedKeys < 2 ? 2 : MaxPressedKeys;
  52.  
  53. pressedKeys = new Queue<Key>(MaxPressedKeys);
  54.  
  55. registeredCombos = new List<Combo>
  56. {
  57. new Combo(new []{ new Key(AttackKey), new Key(HeavyAttackKey) }, () => true, () => Debug.Log("Executed Combo A!")),
  58. new Combo(new []{ new Key(DodgeKey), new Key(AttackKey) }, () => true, () => Debug.Log("Executed Combo B!"))
  59. };
  60.  
  61. StringBuilder registeredComboBuilder = new StringBuilder("[");
  62.  
  63. foreach (Combo combo in registeredCombos)
  64. {
  65. registeredComboBuilder.Append($"\"{combo.PrettyKeyPresses}\",");
  66. }
  67.  
  68. registeredComboBuilder.Append(']');
  69.  
  70. Debug.Log($"Registered {registeredCombos.Count} combos: {registeredComboBuilder}");
  71.  
  72. keyPressTimer = KeyPressLifetime;
  73. }
  74.  
  75. /// <summary>
  76. /// Dumps out the contents of the key press queue to the debug log.
  77. /// </summary>
  78. private void DisplayComboQueueContents()
  79. {
  80. StringBuilder inputQueueBuilder = new StringBuilder("[");
  81.  
  82. foreach (Key pressedKey in pressedKeys.ToArray())
  83. {
  84. inputQueueBuilder.Append($"{pressedKey.KeyCode},");
  85. }
  86.  
  87. inputQueueBuilder.Append("]");
  88. Debug.Log($"Input queue contents: {inputQueueBuilder}");
  89. }
  90.  
  91. // called on every physics frame
  92. private void FixedUpdate()
  93. {
  94. if (TryExecuteCombos(out Combo comboToExecute))
  95. {
  96. comboToExecute.ExecuteCombo();
  97. }
  98. }
  99.  
  100. // Start is called before the first frame update
  101. private void Start()
  102. {
  103. }
  104.  
  105. /// <summary>
  106. /// Attempts to enqueue the given key press to the key press queue.
  107. /// </summary>
  108. /// <param name="key">The key that has been pressed.</param>
  109. /// <returns>Whether the key was successfully enqueued.</returns>
  110. private bool TryEnqueueKey(KeyCode key)
  111. {
  112. if (pressedKeys.Count < MaxPressedKeys)
  113. {
  114. pressedKeys.Enqueue(new Key(key));
  115.  
  116. // also reset key pressed timer so that the combo stays live
  117. keyPressTimer = KeyPressLifetime;
  118.  
  119. return true;
  120. }
  121.  
  122. Debug.LogWarning($"{key} key press could not be enqueued as maximum key press limit has been reached " +
  123. $"({pressedKeys.Count}/{MaxPressedKeys})");
  124. return false;
  125. }
  126.  
  127. /// <summary>
  128. /// Attempts to execute all of th registered combos.
  129. /// </summary>
  130. /// <param name="executedCombo">The combo that was first executed.</param>
  131. /// <returns>Whether a combo could be executed.</returns>
  132. private bool TryExecuteCombos(out Combo executedCombo)
  133. {
  134. Key[] currentKeyPresses = pressedKeys.ToArray();
  135.  
  136. bool KeyPressesEqual(ArraySegment<Key> segment, IReadOnlyList<Key> keyPresses)
  137. {
  138. if (segment.Array == default)
  139. {
  140. Debug.LogError($"Could not compare key presses, as array segment was null.");
  141. return false;
  142. }
  143.  
  144. for (int i = 0; i < segment.Count; i++)
  145. {
  146. if (!segment.Array[i].Equals(keyPresses[i]))
  147. {
  148. return false;
  149. }
  150. }
  151.  
  152. return true;
  153. }
  154.  
  155. foreach (Combo combo in registeredCombos)
  156. {
  157. Key[] comboTrigger = combo.KeyPresses;
  158.  
  159. // check for the combo as a sliding window, and execute combo if found
  160. // this will override any other key presses made and thus might be a bit whack
  161. // use with caution!
  162. for (int offset = 0; offset < currentKeyPresses.Length - comboTrigger.Length + 1; offset++)
  163. {
  164. ArraySegment<Key> comboSlidingWindow = new ArraySegment<Key>(currentKeyPresses, offset, comboTrigger.Length);
  165. if (KeyPressesEqual(comboSlidingWindow, comboTrigger))
  166. {
  167. // TODO: Properly consider how to handle the input queue when a combo is activated
  168. pressedKeys.Clear();
  169.  
  170. executedCombo = combo;
  171. return true;
  172. }
  173. }
  174. }
  175.  
  176. executedCombo = default;
  177. return false;
  178. }
  179.  
  180. // Update is called once per frame
  181. private void Update()
  182. {
  183. if (keyPressTimer <= 0 && pressedKeys.Any())
  184. {
  185. // TODO: Either clear the queue (if it is only for combos) or just dequeue the key to cancel the combo
  186. // this will clear the queue rapidly over the course of the next few frames
  187. pressedKeys.Dequeue();
  188. keyPressTimer = KeyPressLifetime;
  189. }
  190. else if (keyPressTimer > 0 && pressedKeys.Any())
  191. {
  192. keyPressTimer -= Time.deltaTime;
  193. }
  194.  
  195. if (Input.GetKeyDown(AttackKey))
  196. {
  197. TryEnqueueKey(AttackKey);
  198. }
  199. else if (Input.GetKeyDown(DodgeKey))
  200. {
  201. TryEnqueueKey(DodgeKey);
  202. }
  203. else if (Input.GetKeyDown(HeavyAttackKey))
  204. {
  205. TryEnqueueKey(HeavyAttackKey);
  206. }
  207. else if (Input.GetKeyDown(JumpKey))
  208. {
  209. TryEnqueueKey(JumpKey);
  210. }
  211. }
  212.  
  213. /// <summary>
  214. /// Stores a combo as a set of <see cref="Key"/> instances.
  215. /// </summary>
  216. public readonly struct Combo
  217. {
  218. /// <summary>
  219. /// The predicate that checks whether the combo can be executed.
  220. /// </summary>
  221. private readonly Func<bool> comboPredicate;
  222.  
  223. /// <summary>
  224. /// The action to invoke the combo.
  225. /// </summary>
  226. private readonly Action invokeComboAction;
  227.  
  228. /// <summary>
  229. /// Initialises a new instance of the <see cref="Combo"/> struct.
  230. /// </summary>
  231. /// <param name="keyPresses">The key presses that must be inputted to achieve this combo.</param>
  232. /// <param name="canComboExecute">The function that determines whether the combo can be executed.</param>
  233. /// <param name="invokeCombo">The action delegate that should be executed when the combo is activated.</param>
  234. public Combo(Key[] keyPresses, Func<bool> canComboExecute, Action invokeCombo)
  235. {
  236. KeyPresses = keyPresses;
  237.  
  238. comboPredicate = canComboExecute;
  239. invokeComboAction = invokeCombo;
  240. }
  241.  
  242. /// <summary>
  243. /// The key presses that define this combo.
  244. /// </summary>
  245. public Key[] KeyPresses { get; }
  246.  
  247. /// <summary>
  248. /// Gets a prettily formatted list of the key presses that activate this combo.
  249. /// </summary>
  250. public string PrettyKeyPresses
  251. {
  252. get
  253. {
  254. StringBuilder keyPressBuilder = new StringBuilder();
  255.  
  256. foreach (Key keyPress in KeyPresses)
  257. {
  258. keyPressBuilder.Append($"{keyPress.KeyCode}/");
  259. }
  260.  
  261. return keyPressBuilder.ToString();
  262. }
  263. }
  264.  
  265. /// <summary>
  266. /// Executes the combo by invoking the builtin action
  267. /// TODO: Consider moving combo condition check in here.
  268. /// </summary>
  269. public void ExecuteCombo()
  270. {
  271. if (comboPredicate())
  272. {
  273. invokeComboAction();
  274. }
  275. }
  276. }
  277.  
  278. /// <summary>
  279. /// Stores a <see cref="UnityEngine.KeyCode"/> for combo registration.
  280. /// </summary>
  281. public readonly struct Key
  282. {
  283. /// <summary>
  284. /// Initialises a new instance of the <see cref="Key"/> struct.
  285. /// </summary>
  286. /// <param name="keyCode">The key code for this key.</param>
  287. public Key(KeyCode keyCode)
  288. {
  289. KeyCode = keyCode;
  290. }
  291.  
  292. /// <summary>
  293. /// Stores the key code for the particular key.
  294. /// </summary>
  295. public KeyCode KeyCode { get; }
  296. }
  297. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement