-
Notifications
You must be signed in to change notification settings - Fork 0
Core.Utilities.EdgeDetector
In game development, the Edge Detector pattern is essential for handling input and state changes. While it shares logic with PLC (machine) programming—where it is used to handle continuous scan cycles via R_TRIG and F_TRIG blocks—in your engine, it serves to turn continuous data (like "is this key held down?") into discrete events (like "was this key just tapped?").
The EdgeDetector monitors a boolean value across two consecutive frames:
-
Rising Edge (
IsRisingEdge): Becomestrueonly on the exact frame an input changes fromfalsetotrue. This is a "Button Down" event. -
Falling Edge (
IsFallingEdge): Becomestrueonly on the exact frame an input changes fromtruetofalse. This is a "Button Up" (release) event. -
The "Memory" (
_previous): The class stores the state of the previous frame. Without this memory, the engine wouldn't know if atruevalue is a new press or just the user continuing to hold the button.
- Jumping: You want the player to jump once when they press the button. If you didn't use a Rising Edge, the player would jump every single frame the button is held, resulting in "moon gravity" or infinite jumping.
- Menu Navigation: When navigating a list, you want one "click" to move the selection down one item. Edge detection prevents the selection from flying to the bottom of the list instantly.
-
Weapon Fire:
-
Semi-auto: Use
IsRisingEdgeto fire one bullet per click. -
Charging: Start charging on
IsRisingEdgeand release the projectile onIsFallingEdge.
-
Semi-auto: Use
- Toggling UI: Opening and closing a pause menu or a map with a single button.
-
Animation Triggers: Detecting the moment a "Ground Sensor" becomes
falseto trigger a falling animation.
Here is how you might use the EdgeDetector logic to handle different weapon behaviors in a game:
public class CombatSystem
{
private EdgeDetector _attackDetector = new();
// Called every frame
public void Update(bool isAttackButtonHeld)
{
// Update the detector with the raw input state
_attackDetector.Update(isAttackButtonHeld);
// 1. Rising Edge: Triggered the moment the button is pressed
if (_attackDetector.IsRisingEdge)
{
PlaySwordSwingSound();
StartChargingHeavyAttack();
}
// 2. Continuous State: (Standard boolean logic, not part of EdgeDetector)
if (isAttackButtonHeld)
{
ShowChargingParticleEffects();
}
// 3. Falling Edge: Triggered the moment the button is released
if (_attackDetector.IsFallingEdge)
{
ReleaseHeavyAttack();
StopChargingEffects();
}
}
private void PlaySwordSwingSound() => Console.WriteLine("Swoosh!");
private void StartChargingHeavyAttack() => Console.WriteLine("Charging...");
private void ReleaseHeavyAttack() => Console.WriteLine("Boom! Attack Released.");
private void StopChargingEffects() => Console.WriteLine("Effects Off.");
}As seen in your BaseInput.cs, your engine uses this to populate the IsPressed property. This allows your game logic to distinguish between Down (the button is currently at the bottom) and IsPressed (the button was just clicked this frame). This distinction is the difference between a character walking (continuous Down) and a character jumping (discrete IsPressed).