The Unity script needs the Editor and the setup listed below. The separate C# companion practice runs in SharpRune without UnityEngine.
Choose the input package before writing its API calls
SpaceActionProbe uses Unity’s Input System package and its InputAction API. A project using only the legacy backend does not automatically provide that package or configuration. Install a compatible com.unity.inputsystem version through Package Manager and select Input System Package (New) or Both under Active Input Handling, applying any requested restart. The pinned Unity6.0 manual establishes the course baseline; package documentation is separately versioned. The example binds a physical keyboard control, so a touch-only device needs an intentionally supplied binding rather than an assumption that every platform has Space.
One component owns one action and its subscription
Awake creates a Jump action bound to the keyboard space control. OnEnable subscribes the named OnJump handler and enables the action. OnDisable removes that same handler and disables it, and OnDestroy disposes the action the component owns. Initializing in Awake matters because OnEnable precedes Start for the first active instance. The null guards protect teardown paths where initialization did not occur. With the documented setup and Game view focused, ordinary fresh Space presses produce the intended Jump pressed messages.
Count edges without claiming to read a device
The portable PressEdges class retains the previous Boolean state. It reports a press only when the new state is true and the previous state was false. For false, true, true, false, true, there are two such transitions, so the program prints two. A held true observation is not another edge. This tests explicit state tracking in plain C#; it does not synthesize InputAction callbacks, inspect a keyboard, or prove Unity’s input backend and focus are configured correctly.
A shared action map is not private component property
Disabling the private action in this example is appropriate because this component created and owns it. A shared asset or map can serve several consumers, so one component casually disabling the whole map can break another controller or interface. Subscribe and unsubscribe according to the intended lifetime while coordinating enable authority at the shared owner. Also remove the actual handler added earlier: creating a different lambda at unsubscription time is not a general way to remove an earlier captured delegate. Action setup and callback lifetime are independent responsibilities.
Practice: verify focus and repeated activation separately
Enter Play mode, focus Game view, press and release Space twice, and compare those events with holding it down under the unchanged button configuration. Then toggle the component several times and make new presses after re-enabling. Verify that subscriptions do not multiply. In a separate setup test, inspect the active backend and package rather than fixing a missing callback by adding a legacy Input call. Keep a controller’s input ownership visible instead of mixing two unrelated systems until one appears to respond.
- Use fresh presses after activation to avoid confusing initial control-state behavior with the intended press test.
- Record device binding, package and backend when reporting an input integration issue.
Practice: define what repeated state means
For an independent C# change, run an all-false sequence, an all-true sequence, and two separate press/release cycles. Predict zero, one and two rising edges under the current initial-state rule. Then design a reset method and decide whether resetting while the input is held should count a new press on the next observation. That is a policy question about this state model, not an assertion that Unity resets every action in the same way. Make the expected behavior explicit before changing the implementation.
- Keep press-edge detection distinct from held-state movement and text-entry repetition.
- Do not describe the portable Boolean sequence as actual keyboard execution evidence.
Own package setup, device mapping and subscription lifetime
Input behavior becomes easier to test when the project configuration, binding and enabled relationship are separate facts. This lesson supplies a real package-dependent Unity script and a small portable edge model, each with an honest verification boundary. For more devices or shared maps, keep the same reasoning while adding explicit controls and owners. Editor and Player checks still need the actual package and target hardware; compiling or running a .NET9 console lab cannot establish that a touch interface, gamepad or UI input module has been wired correctly.
- Portable baseline counts two rising edges.
- The real script initializes its owned action before OnEnable and removes its named listener on disable.
- Shared maps, device availability and UI input modules require their own setup and ownership policies.
Unity Editor example
Set up in Unity 6
- Install com.unity.inputsystem through Window > Package Manager for the Unity6.0 project.
- Set Project Settings > Player > Active Input Handling to Input System Package (New) or Both and restart if requested.
- Create SpaceActionProbe.cs, attach it to an active empty GameObject, and enter Play mode.
- Focus Game view and press, release and press Space; observe one performed message per ordinary fresh press.
- Toggle the component, then make new presses and verify that subscriptions do not accumulate.
SpaceActionProbe.cs
Download Unity script ↗using UnityEngine;
using UnityEngine.InputSystem;
public sealed class SpaceActionProbe : MonoBehaviour
{
private InputAction jump;
private void Awake() { jump = new InputAction("Jump", InputActionType.Button, "<Keyboard>/space"); }
private void OnEnable() { jump.performed += OnJump; jump.Enable(); }
private void OnDisable() { if (jump != null) { jump.performed -= OnJump; jump.Disable(); } }
private void OnDestroy() { if (jump != null) jump.Dispose(); }
private void OnJump(InputAction.CallbackContext context) { Debug.Log("Jump pressed", this); }
}
What to observe in Unity
Fresh Space presses produce Jump pressed messages while enabled. No input is observed by this component when its owned action is disabled; the package and active input backend must be configured.
The console practice below isolates the C# logic. It does not run a Unity scene or emulate Unity engine callbacks.
C# companion practice
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
var edges = new PressEdges(); int presses = 0;
foreach (bool down in new[] { false, true, true, false, true }) if (edges.Observe(down)) presses++;
Console.WriteLine(presses);
sealed class PressEdges { private bool previous; public bool Observe(bool down) { bool pressed = down && !previous; previous = down; return pressed; } }
C# practice output
2
The real action is owned here; shared action maps need a coordinated enable policy.