Skip to content

What's New in v0.9.7-beta

Release Date: 2026 Target Framework: .NET 10

v0.9.7-beta solidifies the input and physics systems. The input system gains a full action/binding layer with runtime rebinding support, expanded gamepad APIs, and a correctly documented layer system. The old manual collision system has been replaced by a complete Box2D 3.x integration with ECS-native bodies, shapes, events, kinematic character controller, joints, buoyancy, and physics queries.


Highlights

Input System — Action Maps and Bindings

A new InputAction / InputActionMap / InputBinding layer decouples game logic from physical inputs and supports runtime rebinding.

var jump = new InputAction("Jump",
    new KeyBinding(Key.Space),
    new GamepadButtonBinding(GamepadButton.A));

var moveX = new InputAction("MoveX",
    new KeyAxisBinding(Key.D, Key.A),
    new GamepadStickBinding(GamepadStick.Left, GamepadStickAxis.X));

if (jump.IsPressed(Input)) Jump();
float x = moveX.ReadValue(Input); // -1 to 1

Binding types:

Type Description
KeyBinding Single keyboard key
KeyAxisBinding Two keys as a -1/+1 axis
CompositeKeyBinding All keys held simultaneously (e.g. Ctrl+S)
MouseButtonBinding Single mouse button
GamepadButtonBinding Single gamepad button
GamepadAxisBinding Raw gamepad axis with per-axis deadzone
GamepadStickBinding Stick axis with consistent radial deadzone
GamepadTriggerBinding Trigger (0–1) with pressed/released detection

InputActionMap groups actions into named contexts that can be toggled as a unit:

var playerMap = new InputActionMap("Player");
playerMap.AddAction(jump);
playerMap.AddAction(moveX);

playerMap.Enabled = false; // Suppress all actions while in a menu

Input System — Expanded APIs

Several IInputContext members that existed in the implementation but were missing from the docs are now fully documented and supported:

  • Gamepad deadzoneInput.GamepadDeadzone (default 0.15). GetGamepadLeftStick() and GetGamepadRightStick() apply a radial deadzone and rescale the remaining range automatically. Manual > 0.15f guard checks are no longer needed.
  • Trigger helpersGetGamepadTrigger(), IsGamepadTriggerPressed(), IsGamepadTriggerReleased()
  • Axis edge detectionIsGamepadAxisPressed() / IsGamepadAxisReleased()
  • RumbleRumbleGamepad(lowFreq, highFreq, duration) and RumbleGamepadTriggers(left, right, duration)
  • Multi-gamepadConnectedGamepadCount, IsAnyGamepadButtonPressedOnAny(out int gamepadIndex)
  • MouseScrollWheelDeltaX (horizontal scroll), IsCursorVisible, IsRelativeMouseMode
  • Any-input helpersIsAnyKeyPressed(), IsAnyMouseButtonPressed(), IsAnyGamepadButtonPressed()
  • Text editing repeatIsBackspacePressed(), IsReturnPressed(), IsDeletePressed() fire on key-repeat for text-editing behavior
  • Key enumKey.Enter (was incorrectly documented as Key.Return), Key.F13Key.F24, Key.LeftSuper, Key.RightSuper, Key.CapsLock added to documented set

Input Layers — Corrected API

The InputLayerManager API has been corrected. The non-existent CreateLayer factory has been removed. The real API is:

// 1. Register at startup
builder.UseInputLayers();

// 2. Implement IInputLayer
public class UiLayer : IInputLayer
{
    public int Priority => 1000;

    public bool ProcessKeyboardInput(IInputContext input, bool consumed)
    {
        if (consumed) return false;
        if (input.IsKeyPressed(Key.Escape)) { CloseMenu(); return true; }
        return false;
    }

    public bool ProcessMouseInput(IInputContext input, bool consumed) => false;
    public bool ProcessGamepadInput(IInputContext input, bool consumed) => false;
}

// 3. Register and unregister with the scene lifecycle
protected override void OnEnter()  => _layerManager.RegisterLayer(_uiLayer);
protected override void OnExit()   => _layerManager.UnregisterLayer(_uiLayer);

// 4. Process each frame
protected override void OnUpdate(GameTime gameTime)
{
    _layerManager.ProcessInput();

    if (!_layerManager.KeyboardConsumed) HandleGame();
    if (!_layerManager.GamepadConsumed)  HandleGamepad();
}

Every layer is always called each frame. The consumed parameter signals whether a higher-priority layer already claimed that input category — lower layers can still perform cleanup (cancel a drag, reset hover) even when consumed is true.

Physics — Box2D 3.x Integration

The previous manual collision system has been replaced by a full Box2D 3.x integration. Bodies, shapes, events, and queries all work in pixel coordinates.

Registration

services.AddPhysics(opts =>
{
    opts.Gravity        = new Vector2(0, 980); // pixels/s²
    opts.PixelsPerMeter = 64f;
    opts.SubStepCount   = 4;
});

Add to a scene:

world.AddSystem<Box2DPhysicsSystem>();

PhysicsBodyComponent

Attach to any entity to give it a physics body:

entity.AddComponent<PhysicsBodyComponent>(b =>
{
    b.Shape    = new BoxShape(32, 32);
    b.BodyType = PhysicsBodyType.Dynamic;
    b.Mass     = 1f;
    b.Material = PhysicsMaterial.Wood;
});

Body types: Dynamic (simulated), Static (immovable), Kinematic (user-driven)

Shape Types

Shape Description
CircleShape(radius) Circle with optional local offset
BoxShape(w, h) Axis-aligned or rotated box
CapsuleShape(c1, c2, r) Two circles + connecting rectangle
PolygonShape(vertices) Convex polygon, up to 8 vertices
ChainShape(points) Smooth static terrain segments

Collision Events

b.OnCollisionEnter += (other, contact) => { };
b.OnCollisionStay  += (other, contact) => { };
b.OnCollisionExit  += other            => { };
b.OnCollisionHit   += (other, contact) => PlayImpactSound(contact.ImpactSpeed);

// Trigger (sensor) events
b.OnTriggerEnter   += other => { };
b.OnTriggerStay    += other => { };
b.OnTriggerExit    += other => { };

Sub-shape variants (OnCollisionEnterWithShape etc.) identify which sub-shape on a compound body was involved.

Physics Layers

services.AddPhysics()
        .AddPhysicsLayers(layers =>
        {
            layers.Register("Terrain",  0);
            layers.Register("Player",   1);
            layers.Register("Enemies",  2);
            layers.Register("Triggers", 4);
        });

b.Layer         = _layers.GetLayer("Player");
b.CollisionMask = _layers.GetMask("Terrain", "Enemies");

Additional Body Features

  • MaterialsPhysicsMaterial presets: Default, Ice, Bouncy, Metal, Wood, Rubber
  • TriggersIsTrigger = true for sensor-only bodies
  • Sub-shapesAddSubShape() for compound bodies with per-sub-shape trigger, friction, restitution, and collision mask
  • One-way platformsIsOneWayPlatform + PlatformNormalDirection
  • Gravity override — per-body GravityOverride (ceiling walkers, zero-g zones)
  • Freeze axesFreezePositionX / FreezePositionY
  • Bullet modeIsBullet = true for CCD on fast objects
  • Custom filterShouldCollide predicate for programmatic pair filtering
  • Sleep/wake eventsOnBodySleep / OnBodyWake
  • Teleport(pos) — moves body without generating phantom velocity

Kinematic Character Controller

A purpose-built character controller for platformers and top-down games:

// Add both pre- and post-step systems
world.AddSystem<PrePhysicsKinematicCharacterSystem>();
world.AddSystem<PostPhysicsKinematicCharacterSystem>();

// Add the component alongside a Kinematic PhysicsBodyComponent
entity.AddComponent<KinematicCharacterBody>(c =>
{
    c.SnapDistance = 8f;
    c.StopOnSlope  = true;
    c.PushForce    = 400f;
});

// Drive it in OnFixedUpdate
character.MoveAndSlide(new Vector2(horizontal * speed, verticalVelocity));

// Read state after the step
if (character.IsGrounded) { ... }
if (character.IsOnWall)   { ... }
character.OnLanded   += _ => PlayLandSound();
character.OnAirborne += _ => StartFallAnimation();

MoveAndCollide is also available for discrete, non-sliding movement with a full ShapeCastHit result.

PhysicsWorld Queries

// Overlap circle
var hits = _world.OverlapCircle(center, radius, filter);

// Raycast
if (_world.Raycast(origin, direction, distance, filter, out var hit)) { ... }

// Suppress collision between two specific bodies
_world.IgnoreCollision(bodyA, bodyB);

Breaking Changes

The old manual collision system (CollisionSystem, Collider, SpatialGrid, GetPenetration, CheckCollision) has been removed. All collision is now handled through PhysicsBodyComponent and Box2DPhysicsSystem.

Area Before (v0.9.5) After (v0.9.7)
Collision setup _collisionSystem.Register(collider) entity.AddComponent<PhysicsBodyComponent>()
Collision check CheckCollision(a, b) Event-driven: OnCollisionEnter
Penetration GetPenetration(a, b) CollisionContact.Normal + Depth
Spatial grid new SpatialGrid(cellSize) Built into Box2D broad-phase
Input layer factory _layerManager.CreateLayer("UI", 1000) _layerManager.RegisterLayer(layer)
Key.Return Key.Return Key.Enter

Migration Guide

1. Replace the Collision System

Remove all CollisionSystem, Collider, and SpatialGrid usage. Add AddPhysics() to startup and attach PhysicsBodyComponent to entities.

2. Update Input Layers

Replace CreateLayer(name, priority) calls with classes that implement IInputLayer, and register them with RegisterLayer / UnregisterLayer.

3. Fix Key.Return

Find and replace Key.Return with Key.Enter throughout your codebase.

4. Remove Manual Deadzone Guards

The if (stick.Length() > 0.15f) guard pattern is no longer needed — GetGamepadLeftStick() and GetGamepadRightStick() apply the radial deadzone and rescale automatically.