Physics Extensions
Stride ships two physics engines, and the toolkit wraps both. The scene setup chooses the engine, new SceneSetup3D().UseBepu() or .UseBullet(), and the core's CreatePrimitive, Add3DGround and Add2DGround attach that engine's bodies and colliders; each package's entity extensions attach its physics explicitly, so both namespaces can be imported together. A third, Box2D.NET, is not a Stride engine at all but a 2D world the toolkit runs beside Stride; its wrapper is at the end of this page.
Bepu is the newer engine and the toolkit's default; the examples choose it with UseBepu() unless they say otherwise.
Bullet is maintained, not extended: the package follows every toolkit change and its three examples stay, but new helpers and examples are written for Bepu, the engine Stride itself is moving to.
Tip
New to Bepu, or seeing a mesh that moves without colliding? Read Bepu: Who Owns the Transform? first. It covers the one-way physics-to-transform sync and the silent failures that follow from it.
Building joints or motors? Bepu: Why Isn't My Constraint Doing Anything? covers the equivalent silent failures on the constraint side - jammed joints, motors that produce no force, and settings that are discarded without warning.
Want something to bounce or slide? Bepu: Bounce and Friction lists the four contact properties, their defaults, and how two collidables combine them.
Bepu
Scene setup
UseBepu()- Makes Bepu the physics engine of aSceneSetup3DorSceneSetup2D:game.Run(new SceneSetup3D().UseBepu(), start: Start)is a scene you can fly around, with a ground that collides.BepuPhysicsSetup- WhatUseBepu()sets: the core'sCreatePrimitivethen attaches aBodyComponent(aBody2DComponentfor a flat shape) with the collider fitted to the shape, and the grounds aStaticComponent.Add3DGround()andAdd2DGround()are the core's; with Bepu chosen, the ground gets a static collider on its own, when you do not want the rest of the scene setup.
Primitives with colliders
CreatePrimitive(Primitive3D)andCreatePrimitive(Primitive2D)are the core's; with Bepu chosen, the entity comes with a matching Bepu collider. ABepu3DPhysicsOptionsorBepu2DPhysicsOptionspassed as the options sets the body component and whether a collider is included;IncludePhysics = falseon any options leaves the physics out.Masson the options is the mass of the fitted collider shape, where Bepu keeps it.
Raycasting
Raycast()- Casts from the camera through a screen position and reports the first hit.RaycastMouse()- The same, reading the mouse position for you.BepuSimulation.RayCast()- Casts aRaySegmentdirectly, with no camera and no maximum distance to choose: the segment's own length is the limit. Pairs withScreenToWorldRaySegment().RayCastPenetrating()- Returns every hit along the segment rather than stopping at the first. An overload fills a collection you supply, to avoid allocating.
A raycast sees colliders only. For what the eye sees under a screen point - a mesh with no body, an instanced model, a teapot - the Effects package has a GPU picker that reads the answer from the rendered frame.
Shape queries
A raycast asks what a line meets first. Bepu also answers what a convex shape meets, in place or along a path, with the same HitInfo a raycast gives:
BepuSimulation.Overlap(shape, pose, buffer)lists every collidable the shape intersects at that pose. TheSpan<CollidableStack>overload writes into stack memory and allocates nothing; theICollection<OverlapInfo>overload appends records that also carry the penetration direction and depth of each.BepuSimulation.SweepCast(shape, pose, velocity, maxDistance, out hit)throws the shape along the velocity and reports the first contact: the point, the normal, the collidable and how far along the path it happened.SweepCastPenetratingcollects every contact on the way.
Span<CollidableStack> buffer = stackalloc CollidableStack[32];
foreach (var collidable in simulation.Overlap(new Box(2f, 1.5f, 2f), new RigidPose(centre, Quaternion.Identity), buffer))
{
// Every body and static the box intersects right now
}
if (simulation.SweepCast(new Sphere(0.35f), new RigidPose(start, Quaternion.Identity), new BodyVelocity(direction, Vector3.Zero), 16f, out var hit))
{
var touch = start + direction * hit.Distance; // Where the sphere's centre is when it first touches hit.Collidable
}
The shapes are Bepu's own (BepuPhysics.Collidables.Box, Sphere, Capsule, a convex hull), not the toolkit's colliders. maxDistance and hit.Distance are measured along the velocity: with a unit direction they are metres, with a real velocity they are seconds. A sweep is a straight line; gravity is not part of it. E05_3D_ShapeQueries shows both queries, and E05_3D_Raycast shows RayCastPenetrating listing every hit along a ray.
Triggers and contact events
A collidable's ContactEventHandler is told about its contacts, and the handler decides whether the collidable is solid:
Triggeris the engine's ready-made handler for a sensor volume. ItsNoContactResponseis true, so bodies pass through as if the collidable were not there, andOnEnterandOnLeavefire with the other collidable. A static with aBoxColliderand no model is a zone.- Your own
IContactHandlerreturnsNoContactResponsefalse for a solid that also wants to know:OnStartedTouching,OnTouchingandOnStoppedTouchingreceive theContacts, withOther, every contact's point, normal and depth, andComputeImpactForce(contact)for how hard a first touch was.
var trigger = new Trigger();
trigger.OnEnter += (_, other) => Console.WriteLine($"{other.Entity.Name} came in");
trigger.OnLeave += (_, other) => Console.WriteLine($"{other.Entity.Name} left");
var zone = new Entity("Zone")
{
new StaticComponent { Collider = new CompoundCollider { Colliders = { new BoxCollider { Size = new Vector3(3f, 2f, 3f) } } }, ContactEventHandler = trigger },
};
The callbacks run on the main thread after the physics step, so they may change entities and materials. OnTouching does not fire for sleeping pairs. One body can touch through several contacts and several children of a compound, so keep a set of who touches, not a count. E05_3D_TriggerZones shows a zone, a solid plate that counts what stands on it, and a crate that measures its hits.
The simulation clock
BepuSimulation runs on a clock of its own, in fixed steps, and three of its properties can change while the game runs:
TimeScaleruns it slower or faster than the game. It stacks withGameTime.Factor, so a game that slows its whole clock slows its physics with it.Enabledpauses it; bodies hang where they are until it is set again.PoseGravitytakes effect on the next step, on every body.
A simulation running faster than the game needs more fixed steps per frame: at four times the speed and sixty frames a second it needs four, and the default MaxStepPerFrame of three lets it fall behind the clock. Raise the cap when a game runs fast. To read the simulation's clock exactly, give a component ISimulationUpdate: it is called once per fixed step with the step's length, before the step and after it. E05_3D_TimeControl puts a dropper on the game clock and a spinning bar on the physics clock, so the difference can be watched.
Grabbing bodies
GrabberScript-game.AddGrabber()puts it on the camera entity, or add it there in Game Studio under Physics, and any dynamic body can be picked up with the mouse, carried on the end of the camera ray, and thrown. The wheel changes the carry distance; T plus mouse movement turns the held body. The pick looks through trigger volumes, so a body inside a zone can still be grabbed; a solid static in front still hides what is behind it. It works through a linear and an angular servo constraint rather than by moving the body, so the held body still collides and pushes, and its force caps scale with mass so heavy and light bodies drag alike.Grab()andRelease()do the same from code.
game.AddGrabber();
The same call serves a 2D scene: under an orthographic camera the pick ray runs along the view, a Body2DComponent is carried across its plane, and the mouse wheel does nothing for a 2D body or under an orthographic camera, since depth there is the plane itself.
Debug views
AddPhysicsDebug()- One entity carrying Bepu's two scene-wide debug views:CollidableGizmoScriptdraws every collider on P, andDebugRenderComponentScriptturns the physics debug meshes on for every Bepu entity on F11. Both act on the whole scene, so one entity is all a scene needs; a second copy of either script answers the same key and toggles the first one back.contactsVisible: trueadds the solver's contact points and normals to the debug meshes (Stride 4.4.1 or later).
game.AddPhysicsDebug(gizmoColor: new Color4(0.4f, 0.843f, 0, 0.9f));
E08_3D_CollidableGizmo and E08_3D_DebugRenderComponent show one view each; E05_3D_TriggerZones, E05_3D_ShapeQueries and E05_3D_MeshColliders carry both.
Convex hulls
In the Stride.CommunityToolkit.Bepu namespace, for giving an arbitrary mesh a collider.
ToConvexHullCollider()- Wraps mesh data in a single convex hull. Cheap, but it fills in any concave detail.ToDecomposedHulls()- Splits the mesh into several hulls so concave shapes collide correctly. More accurate, more expensive to build.
Mesh colliders
The engine's MeshCollider is the model's own triangles, so a hole stays a hole where a hull would fill it. It needs the model, so it is attached once the entity has one: create the primitive with IncludePhysics = false, then add the component.
var ring = game.CreatePrimitive(Primitive3D.Torus(1.2f, 0.35f), new Bepu3DPhysicsOptions { IncludePhysics = false });
ring.Add(new StaticComponent { Collider = new MeshCollider { Model = ring.Get<ModelComponent>().Model, Closed = true } });
What the triangles are not, measured in E05_3D_MeshColliders: a mesh is a surface with no inside, so a body that starts inside a closed mesh falls out through it; two meshes do meet, triangle against triangle, so a moving mesh works but costs more than a hull. Closed says the surface has no holes and is what the inertia of a moving mesh is computed from. The entity's scale scales the mesh.
Bullet
Scene setup
UseBullet()- Makes Bullet the physics engine of a scene setup, the counterpart ofUseBepu().BulletPhysicsSetup- WhatUseBullet()sets: the core'sCreatePrimitivethen attaches aRigidbodyComponentwith the collider fitted to the shape, and the grounds aStaticColliderComponent.AddInfinite3DGround()- A ground plane nothing can fall off the edge of. Bullet only; there is no Bepu equivalent.
Primitives and colliders
CreatePrimitive(Primitive3D)andCreatePrimitive(Primitive2D)are the core's; with Bullet chosen, the entity comes with a matching Bullet collider. ABullet3DPhysicsOptionsorBullet2DPhysicsOptionspassed as the options sets the physics component and whether a collider is included.Masson the options is the mass of the rigid body.AddBulletPhysics()- Attaches a collider to an entity you already built, rather than creating both together.AddBulletPhysics()- The 2D equivalent.
Raycasting
Raycast()- Casts from the camera through a screen position. Takes either aScriptComponentor aSimulation.RaycastMouse()- The same, reading the mouse position for you.Simulation.Raycast()- Casts aRaySegmentdirectly, with no camera involved. Overloads also cast from an entity along a direction.RaycastPenetrating()- Returns every hit along the ray rather than stopping at the first. An overload fills a list you supply, to avoid allocating.
Debugging
ShowColliders()- Draws the collider shapes over the scene, which is how you find out that the collider is not where the model is.
Box2D
In Stride.CommunityToolkit.Box2D, for a 2D world run by Box2D.NET beside Stride. The wrapper is built around Box2DSimulation, which owns the world and steps it; see the Box2D examples for the whole pattern.
Joints
Joints2D- Every Box2D joint type with world-space anchors:CreateRevolute,CreatePrismatic,CreateWheel,CreateDistance,CreateWeld,CreateMotor,CreateFilter, plusDestroy,IsValidandGetAnchorsfor drawing. A raw definition wants each anchor as a local frame in the body's own space; these take the pivot and the axis in world space and work the frames out, with the pose at creation as the joint's zero.Box2DSimulation.Joints- The same factories with the world id filled in, and overloads that take the entities the simulation created bodies for.- Options records -
RevoluteJointOptions,PrismaticJointOptions,WheelJointOptions,DistanceJointOptions,WeldJointOptions,MotorJointOptions- mirror the per-type knobs under Box2D's names. A property left null keeps Box2D's default, so an initialiser changes exactly what it names.
var hinge = simulation.Joints.CreateRevolute(post, arm, pivot, new RevoluteJointOptions
{
EnableMotor = true,
MotorSpeed = 2,
MaxMotorTorque = 500,
});
Debug drawing
Box2DDebugDraw- Box2D's own debug draw, rendered through aShapeBatch: oneDraw(simulation)call a frame shows every shape, joint, contact point, force, bounding box, island and body name the testbed can, with the testbed's toggles as properties. Shapes and joints are on by default; switchDrawShapesoff when your entities already draw themselves.
var debugDraw = new Box2DDebugDraw(shapeBatch) { DrawShapes = false, DrawContactPoints = true };
// each frame, after the simulation update
debugDraw.Draw(simulation);
Terrain
ShapeFixtureBuilder.AttachChain()- A chain of segments for terrain, tracks and room walls: unlike a row of separate segments it has no internal corners for a rolling body to catch on. Two rules Box2D imposes, both handled or documented: a chain collides on the right of its direction of travel, so a floor is listed right to left; and an open chain needs a ghost point at each end, which the method adds for you.SvgPath2D.Parse()- The straight-line commands of an SVG path as points, so a level outline drawn in Inkscape becomes a chain: offset, scale, and the y flip from SVG's downward axis. Curves are refused; flatten them in the editor.
Queries
PhysicsQueries2D.CastCircleClosest()andCastSegmentClosest()- Shape casts: sweep a circle, a point or a segment along a displacement and get the closest hit with its point, normal and fraction, filtered by category bits.CastShapeClosest()takes any convex proxy. The same calls exist onBox2DSimulation, alongside the raycasts and overlaps.
Characters
CharacterMover2D- A platformer character with no rigid body, on Box2D v3's mover API: a capsule the game moves itself, Quake style, that asks the world only what it touches - collect the contact planes, solve a translation that respects them, sweep it - with a pogo shape cast from the feet that floats it above the ground and decides whether it is standing. Register it with the simulation and it steps after every fixed physics step; setThrottleand callJump()from your update; give it anEntityand it moves the transform. How hard a shape pushes back is per shape throughSetResponse(): a small push limit makes a soft obstacle the mover walks through, an elevator gets a firm one and velocity clipping so it carries the mover.
var mover = new CharacterMover2D(new Vector2(2, 8)) { Entity = hero };
simulation.RegisterSimulationUpdate(mover);
// each frame
mover.Throttle = (input.IsKeyDown(Keys.D) ? 1 : 0) - (input.IsKeyDown(Keys.A) ? 1 : 0);
if (input.IsKeyPressed(Keys.Space)) mover.Jump();
Explosions
Box2DSimulation.Explode()- A radial blast: every shape within the radius gets an impulse away from the centre, per metre of its perimeter facing the blast, fading over the falloff distance beyond the radius.
Grabbing bodies
Grabber2DScript- The 2D counterpart ofGrabberScript: on the camera entity, given the simulation, the left button picks any dynamic body up, drags it and throws it. Box2D v3 has no mouse joint, so it uses a kinematic anchor moved to the cursor and a motor joint with a mass-scaled force cap and angular friction, the idiom of Box2D's own sample browser. Every Box2D example carries it.
game.GetCameraEntity().Add(new Grabber2DScript { Simulation = simulation });