In my game, the character has to parry projectiles thrown in his direction. When the projectiles collides with the player, the player is either not blocking, blocking, or has pressed down the block button within the last 0.2 seconds (parry). If the character releases the block button, this parry window is immediately cancelled.
However, this system just doesn’t feel right compared to the games I play because
Spamming the block button (releasing then pressing rapidly) successfully parry’s all the projectiles.
In other games, simply tapping the parry button within a window seems to parry the attack, unlike in my game where you have to be actively pressing down the block button within the parry window when the projectile hits.
I’d appreciate any ideas about a parry/blocking system that addresses these issues and plays like other games (ex: dark souls, sekiro, ronin the last samurai).
You would need a Blocking Controller and Blocking Manager.
The controller is what interfaces with the input controller and the Blocking Manager.
The Blocking Manager will control whether a parry or block is performed.
When you press block you would want to make a flag that indicators a parry window which is controlled by an IEnumerator or Update function.
(I like IEnumerators personally because its in my opinion better for performance and you can stop and start Coroutines if you want to “refresh the timer”)
It would look something like this:
(Input Controller)-Recognized Block Command and send a block pressed event
(Block Controller)- Received the Block Pressed Event and invokes HandleBlockPressed function
(Block ControllerHandleBlockPressed) - Invokes the BlockManager to StartParryWindow() which for the IEnumerator would have
Then when the attack his the player the Attack would raise an AttackEvent and this event would have details as to whether canParry or canBlock.
Something Like:
public class BlockController : MonoBehaviour
{
public BlockManager blockManager;
private void Update()
{
if (Input.GetButtonDown("Block"))
{
HandleBlockPressed();
}
if (Input.GetButtonUp("Block"))
{
HandleBlockReleased();
}
}
private void HandleBlockPressed()
{
blockManager.StartParryWindow();
}
private void HandleBlockReleased()
{
blockManager.ResetParryWindow();
}
}
public class BlockManager : MonoBehaviour
{
private IEnumerator parryAttackWindow;
private bool isParryEnabled = false;
private bool isParryWindowActive = false; // Flag to track the coroutine status
private float parryWindow = 0.2f;
public void StartParryWindow()
{
if (parryAttackWindow != null)
{
StopCoroutine(parryAttackWindow);
}
parryAttackWindow = ParryWindowCoroutine();
StartCoroutine(parryAttackWindow);
}
private IEnumerator ParryWindowCoroutine()
{
isParryEnabled = true;
isParryWindowActive = true; // Coroutine is running
yield return new WaitForSeconds(parryWindow);
ResetParryWindow();
}
public void ResetParryWindow()
{
if (parryAttackWindow != null)
{
StopCoroutine(parryAttackWindow);
parryAttackWindow = null;
}
isParryEnabled = false;
isParryWindowActive = false; // Coroutine has completed
}
public bool IsParryWindowActive()
{
return isParryWindowActive;
}
public void HandleAttack(bool canParry, bool canBlock)
{
if (isParryEnabled && canParry)
{
// Perform parry
}
else if (canBlock)
{
// Perform block
}
else
{
// Take damage or other consequence
}
}
}
public class Player : MonoBehaviour
{
public BlockManager blockManager;
private void OnAttackReceived(bool canParry, bool canBlock)
{
blockManager.HandleAttack(canParry, canBlock);
}
}
Typically a parry system will have a couple of mechanics to avoid the issues you mentioned.
The first is a cooldown on the parry itself. The reason you are able to effectively spam parry and have it succeed is because there is no punishment for missing the timing. Some kind of cooldown timer that disallows its use for a short time (or long, depends on your game and how you want it to feel) after missing a parry.
Another is some kind of cooldown or disadvantage system for the block after a missed parry. You probably don’t want the player to be able to option-select any defensive action simply by mashing everything so a missed parry will typically also disable other defensive options such as blocks and maybe dodges as well for a short period.
Many games also deliberately seperate their parry and block inputs so that a split second decision to block isn’t considered a parry and doesn’t activate any such punishment systems. The idea being that you can play it safe or you can play higher risk/reward.