Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using UnityEngine;
- public class ComboScript : MonoBehaviour
- {
- /// <summary>
- /// Converts <see cref="KeyPressLifetimeMs"/> to a form that <see cref="Time.deltaTime"/> can interact with.
- /// </summary>
- private const double KeyPressLifetime = KeyPressLifetimeMs / 1000;
- /// <summary>
- /// Stores the length of time after which a key press is no longer considered 'live', and is thus ineligible for
- /// consideration in a combo chain.
- /// </summary>
- private const double KeyPressLifetimeMs = 150;
- /// <summary>
- /// Allows for tracking when a key press lifetime runs out, and keys should start being removed from the key queue.
- /// </summary>
- private double keyPressTimer;
- /// <summary>
- /// The queue of pressed keys to track the entered combo.
- /// </summary>
- private Queue<Key> pressedKeys;
- /// <summary>
- /// The list of registered combos that will be looked for by the script.
- /// </summary>
- private List<Combo> registeredCombos;
- public KeyCode AttackKey; // temp key
- public KeyCode DodgeKey; // temp key
- public KeyCode HeavyAttackKey; // temp key
- public KeyCode JumpKey; // temp key
- /// <summary>
- /// The maximum number of key presses that will be registered for combos.
- /// </summary>
- public int MaxPressedKeys;
- // called before Start() (before Start() is called on any script), similar to a constructor
- private void Awake()
- {
- MaxPressedKeys = MaxPressedKeys < 2 ? 2 : MaxPressedKeys;
- pressedKeys = new Queue<Key>(MaxPressedKeys);
- registeredCombos = new List<Combo>
- {
- new Combo(new []{ new Key(AttackKey), new Key(HeavyAttackKey) }, () => true, () => Debug.Log("Executed Combo A!")),
- new Combo(new []{ new Key(DodgeKey), new Key(AttackKey) }, () => true, () => Debug.Log("Executed Combo B!"))
- };
- StringBuilder registeredComboBuilder = new StringBuilder("[");
- foreach (Combo combo in registeredCombos)
- {
- registeredComboBuilder.Append($"\"{combo.PrettyKeyPresses}\",");
- }
- registeredComboBuilder.Append(']');
- Debug.Log($"Registered {registeredCombos.Count} combos: {registeredComboBuilder}");
- keyPressTimer = KeyPressLifetime;
- }
- /// <summary>
- /// Dumps out the contents of the key press queue to the debug log.
- /// </summary>
- private void DisplayComboQueueContents()
- {
- StringBuilder inputQueueBuilder = new StringBuilder("[");
- foreach (Key pressedKey in pressedKeys.ToArray())
- {
- inputQueueBuilder.Append($"{pressedKey.KeyCode},");
- }
- inputQueueBuilder.Append("]");
- Debug.Log($"Input queue contents: {inputQueueBuilder}");
- }
- // called on every physics frame
- private void FixedUpdate()
- {
- if (TryExecuteCombos(out Combo comboToExecute))
- {
- comboToExecute.ExecuteCombo();
- }
- }
- // Start is called before the first frame update
- private void Start()
- {
- }
- /// <summary>
- /// Attempts to enqueue the given key press to the key press queue.
- /// </summary>
- /// <param name="key">The key that has been pressed.</param>
- /// <returns>Whether the key was successfully enqueued.</returns>
- private bool TryEnqueueKey(KeyCode key)
- {
- if (pressedKeys.Count < MaxPressedKeys)
- {
- pressedKeys.Enqueue(new Key(key));
- // also reset key pressed timer so that the combo stays live
- keyPressTimer = KeyPressLifetime;
- return true;
- }
- Debug.LogWarning($"{key} key press could not be enqueued as maximum key press limit has been reached " +
- $"({pressedKeys.Count}/{MaxPressedKeys})");
- return false;
- }
- /// <summary>
- /// Attempts to execute all of th registered combos.
- /// </summary>
- /// <param name="executedCombo">The combo that was first executed.</param>
- /// <returns>Whether a combo could be executed.</returns>
- private bool TryExecuteCombos(out Combo executedCombo)
- {
- Key[] currentKeyPresses = pressedKeys.ToArray();
- bool KeyPressesEqual(ArraySegment<Key> segment, IReadOnlyList<Key> keyPresses)
- {
- if (segment.Array == default)
- {
- Debug.LogError($"Could not compare key presses, as array segment was null.");
- return false;
- }
- for (int i = 0; i < segment.Count; i++)
- {
- if (!segment.Array[i].Equals(keyPresses[i]))
- {
- return false;
- }
- }
- return true;
- }
- foreach (Combo combo in registeredCombos)
- {
- Key[] comboTrigger = combo.KeyPresses;
- // check for the combo as a sliding window, and execute combo if found
- // this will override any other key presses made and thus might be a bit whack
- // use with caution!
- for (int offset = 0; offset < currentKeyPresses.Length - comboTrigger.Length + 1; offset++)
- {
- ArraySegment<Key> comboSlidingWindow = new ArraySegment<Key>(currentKeyPresses, offset, comboTrigger.Length);
- if (KeyPressesEqual(comboSlidingWindow, comboTrigger))
- {
- // TODO: Properly consider how to handle the input queue when a combo is activated
- pressedKeys.Clear();
- executedCombo = combo;
- return true;
- }
- }
- }
- executedCombo = default;
- return false;
- }
- // Update is called once per frame
- private void Update()
- {
- if (keyPressTimer <= 0 && pressedKeys.Any())
- {
- // TODO: Either clear the queue (if it is only for combos) or just dequeue the key to cancel the combo
- // this will clear the queue rapidly over the course of the next few frames
- pressedKeys.Dequeue();
- keyPressTimer = KeyPressLifetime;
- }
- else if (keyPressTimer > 0 && pressedKeys.Any())
- {
- keyPressTimer -= Time.deltaTime;
- }
- if (Input.GetKeyDown(AttackKey))
- {
- TryEnqueueKey(AttackKey);
- }
- else if (Input.GetKeyDown(DodgeKey))
- {
- TryEnqueueKey(DodgeKey);
- }
- else if (Input.GetKeyDown(HeavyAttackKey))
- {
- TryEnqueueKey(HeavyAttackKey);
- }
- else if (Input.GetKeyDown(JumpKey))
- {
- TryEnqueueKey(JumpKey);
- }
- }
- /// <summary>
- /// Stores a combo as a set of <see cref="Key"/> instances.
- /// </summary>
- public readonly struct Combo
- {
- /// <summary>
- /// The predicate that checks whether the combo can be executed.
- /// </summary>
- private readonly Func<bool> comboPredicate;
- /// <summary>
- /// The action to invoke the combo.
- /// </summary>
- private readonly Action invokeComboAction;
- /// <summary>
- /// Initialises a new instance of the <see cref="Combo"/> struct.
- /// </summary>
- /// <param name="keyPresses">The key presses that must be inputted to achieve this combo.</param>
- /// <param name="canComboExecute">The function that determines whether the combo can be executed.</param>
- /// <param name="invokeCombo">The action delegate that should be executed when the combo is activated.</param>
- public Combo(Key[] keyPresses, Func<bool> canComboExecute, Action invokeCombo)
- {
- KeyPresses = keyPresses;
- comboPredicate = canComboExecute;
- invokeComboAction = invokeCombo;
- }
- /// <summary>
- /// The key presses that define this combo.
- /// </summary>
- public Key[] KeyPresses { get; }
- /// <summary>
- /// Gets a prettily formatted list of the key presses that activate this combo.
- /// </summary>
- public string PrettyKeyPresses
- {
- get
- {
- StringBuilder keyPressBuilder = new StringBuilder();
- foreach (Key keyPress in KeyPresses)
- {
- keyPressBuilder.Append($"{keyPress.KeyCode}/");
- }
- return keyPressBuilder.ToString();
- }
- }
- /// <summary>
- /// Executes the combo by invoking the builtin action
- /// TODO: Consider moving combo condition check in here.
- /// </summary>
- public void ExecuteCombo()
- {
- if (comboPredicate())
- {
- invokeComboAction();
- }
- }
- }
- /// <summary>
- /// Stores a <see cref="UnityEngine.KeyCode"/> for combo registration.
- /// </summary>
- public readonly struct Key
- {
- /// <summary>
- /// Initialises a new instance of the <see cref="Key"/> struct.
- /// </summary>
- /// <param name="keyCode">The key code for this key.</param>
- public Key(KeyCode keyCode)
- {
- KeyCode = keyCode;
- }
- /// <summary>
- /// Stores the key code for the particular key.
- /// </summary>
- public KeyCode KeyCode { get; }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement