Events
Box3D buffers what happened during a step and hands it back afterwards.
world.Events exposes six lists as views over engine
memory, so reading a frame's worth allocates nothing.
world.Step(FixedStep);
foreach (ContactHitEvent hit in world.Events.ContactHits)
{
PlayImpactSound(hit.Point, volume: hit.ApproachSpeed / 20.0f);
}
| List | Raised when | Needs |
|---|---|---|
ContactBegins |
two shapes start touching | EnableContactEvents |
ContactEnds |
two shapes stop touching | EnableContactEvents |
ContactHits |
an impact above the world's hit threshold | EnableHitEvents |
SensorBegins |
a shape enters a sensor | EnableSensorEvents on both |
SensorEnds |
a shape leaves a sensor | EnableSensorEvents on both |
BodyMoves |
a body moved during the step | nothing |
Events are opt-in
Collecting them is not free, so every list except BodyMoves is off until a
shape asks for it:
var reporting = ShapeDefinition.Default with
{
EnableContactEvents = true,
EnableHitEvents = true,
};
body.AddBox(Box.Cube(0.5f), reporting);
Turn on what you read, and nothing else. A world where every shape reports every contact spends real time filling lists nobody walks.
What moved
BodyMoves is the list to drive rendering from. It contains only the bodies
that actually moved, so a settled scene produces nothing:
foreach (BodyMoveEvent moved in world.Events.BodyMoves)
{
ref Transform transform = ref transforms[moved.Body.UserData];
transform.Position = moved.Position;
transform.Rotation = moved.Rotation;
if (moved.FellAsleep)
{
StopAnimating(moved.Body.UserData);
}
}
FellAsleep is the last report you get about that body until something wakes
it, which makes it the natural place to release whatever you were spending on
it.
Impacts
A hit event is what you want for impact sounds and damage. It carries the point,
the normal and the closing speed, plus each surface's
UserMaterialId:
foreach (ContactHitEvent hit in world.Events.ContactHits)
{
Material surface = Materials[hit.UserMaterialIdA];
PlayImpactSound(surface, hit.Point, hit.ApproachSpeed);
}
Only impacts above WorldSettings.HitEventThreshold are reported, which is what
keeps a settling stack from firing a hundred of them.
Begin and end touch
ContactBegins and ContactEnds are for state: a foot on the ground, a card in
a slot, a fuse burning while two things touch.
An end-touch event is often raised because a shape was destroyed, so check before using the handle:
foreach (ContactEndEvent touch in world.Events.ContactEnds)
{
if (touch.ShapeA.IsValid && touch.ShapeB.IsValid)
{
Separate(touch.ShapeA, touch.ShapeB);
}
}
IsValid never throws, so asking is always safe. See
Handle validity.
Sensors
A sensor reports overlaps without pushing anything. Both the sensor and the
visitor need EnableSensorEvents:
Body trigger = world.CreateStaticBody(doorway);
trigger.AddBox(volume, ShapeDefinition.Default with
{
IsSensor = true,
EnableSensorEvents = true,
Density = 0.0f,
});
foreach (SensorBeginEvent entered in world.Events.SensorBegins)
{
OpenDoor(entered.Sensor.UserData, by: entered.Visitor.Body.UserData);
}
Sensors have no continuous collision, so something fast enough to cross the volume within one step passes through unreported. Use a shape cast for a trigger that has to catch a bullet.
Lifetime
Events are valid only until the next Step. Read what you need before stepping
again; copy anything you want to keep.
It is safe to create and destroy bodies while walking the lists — that is the whole reason Box3D buffers events instead of calling back mid-step. Be aware that doing so can invalidate handles carried by events you have not read yet.