In Box2D, it's often useful to have a reference from a physics object back to an entity in your game. This is called user data, and it's simply a value that you can set to hold information that may be useful for your application. The following objects support user data:

  • Bodies
  • Shapes
  • Joints

Box2D doesn't care what this information is, and it doesn't do anything with it. It just holds it and returns it when you ask. The API provides these functions to work with user data:

// For bodies, shapes, and joints
b2Body_SetUserData(bodyId, userData);
b2Body_GetUserData(bodyId);

b2Shape_SetUserData(shapeId, userData); 
b2Shape_GetUserData(shapeId);

b2Joint_SetUserData(jointId, userData);
b2Joint_GetUserData(jointId);

Let's try a simple example using body user data. We'll store a reference to a Ball object in each physics body's user data, and update the ball object's properties after each physics step.

To access the user data for each physics body, we'll need to iterate through all bodies in the scene.

First, set the Ball instance in the body's user data during creation:

class Ball {
    constructor(worldId, radius) {
        this.radius = radius;
        this.position = new b2Vec2(0, 0);
        this.angle = 0;
        this.linearVelocity = new b2Vec2(0, 0);

        // Create dynamic body
        const bodyDef = b2DefaultBodyDef();
        bodyDef.type = DYNAMIC;
        bodyDef.position = new b2Vec2(0, 20);
        const bodyId = b2CreateBody(worldId, bodyDef);

        // Set this Ball object as the body's user data
        b2Body_SetUserData(bodyId, this);

        // Add circle shape
        const shapeDef = b2DefaultShapeDef();
        shapeDef.density = 1.0;

        const circle = {
            center: new b2Vec2(0, 0),
            radius: this.radius
        };

        b2CreateCircleShape(bodyId, shapeDef, circle);
    }
}

After each physics step, update the ball objects after obtaining the reference from their user data:

// After b2World_Step()
const bodyEvents = b2World_GetBodyEvents(worldId);
for (let i = 0; i < bodyEvents.count; i++) {
    const bodyId = bodyEvents.bodies[i];
    const ball = b2Body_GetUserData(bodyId);
    if (ball) {
        ball.position = b2Body_GetPosition(bodyId);
        ball.angle = b2Body_GetRotation(bodyId).angle;
        ball.linearVelocity = b2Body_GetLinearVelocity(bodyId);
    }
}

User data becomes even more valuable when handling collision events, ray-casting, or AABB queries where you need to identify the game entities involved.

Setting Complex User Data

Since user data accepts any object, you can store:

  • Numbers
  • Objects
  • Class instances
  • Custom data structures

Here are some examples:

// Setting and retrieving a number
b2Body_SetUserData(bodyId, 123);
const numValue = b2Body_GetUserData(bodyId);

// Setting and retrieving a data object
const bodyData = {
    entityType: 'enemy',
    health: 100,
    stunnedTimeout: 0
};
b2Body_SetUserData(bodyId, bodyData);
const data = b2Body_GetUserData(bodyId);

For consistency, you should often use the same type of user data across similar objects. For example, if you give shapes number values, all shapes should store numbers. If you mix types, it becomes difficult to safely handle the data when receiving it in collision callbacks. However, when using custom data structures, you might be comfortable having an embedded custom data structure. The outer data can then store the object type, which will be a hint for how the internal data is structured.

// GameObjectData is the outer custom data structure (a class in this example)
const player = new GameObjectData(
    ObjectType.PLAYER,
    // PlayerData is expected when the ObjectType is PLAYER
    new PlayerData(100, 10, [])
);

const enemy = new GameObjectData(
    ObjectType.ENEMY,
    // EnemyData is expected when the ObjectType is ENEMY
    new EnemyData(20, 'circular', { x: 0, y: 0 })
);

Complex data objects are very useful for game entities. Box2D won't clean up your user data when destroying physics objects, so remember to handle cleanup yourself when needed.

Credits

This tutorial is adapted from an original piece of work created by Chris Campbell and is used under license.