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 deadzone —
Input.GamepadDeadzone(default 0.15).GetGamepadLeftStick()andGetGamepadRightStick()apply a radial deadzone and rescale the remaining range automatically. Manual> 0.15fguard checks are no longer needed. - Trigger helpers —
GetGamepadTrigger(),IsGamepadTriggerPressed(),IsGamepadTriggerReleased() - Axis edge detection —
IsGamepadAxisPressed()/IsGamepadAxisReleased() - Rumble —
RumbleGamepad(lowFreq, highFreq, duration)andRumbleGamepadTriggers(left, right, duration) - Multi-gamepad —
ConnectedGamepadCount,IsAnyGamepadButtonPressedOnAny(out int gamepadIndex) - Mouse —
ScrollWheelDeltaX(horizontal scroll),IsCursorVisible,IsRelativeMouseMode - Any-input helpers —
IsAnyKeyPressed(),IsAnyMouseButtonPressed(),IsAnyGamepadButtonPressed() - Text editing repeat —
IsBackspacePressed(),IsReturnPressed(),IsDeletePressed()fire on key-repeat for text-editing behavior - Key enum —
Key.Enter(was incorrectly documented asKey.Return),Key.F13–Key.F24,Key.LeftSuper,Key.RightSuper,Key.CapsLockadded 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¶
- Materials —
PhysicsMaterialpresets:Default,Ice,Bouncy,Metal,Wood,Rubber - Triggers —
IsTrigger = truefor sensor-only bodies - Sub-shapes —
AddSubShape()for compound bodies with per-sub-shape trigger, friction, restitution, and collision mask - One-way platforms —
IsOneWayPlatform+PlatformNormalDirection - Gravity override — per-body
GravityOverride(ceiling walkers, zero-g zones) - Freeze axes —
FreezePositionX/FreezePositionY - Bullet mode —
IsBullet = truefor CCD on fast objects - Custom filter —
ShouldCollidepredicate for programmatic pair filtering - Sleep/wake events —
OnBodySleep/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.