DEV Community

Cover image for Build a Physics-Based RagDoll with Pixalo Engine
Salar Izadi
Salar Izadi

Posted on

Build a Physics-Based RagDoll with Pixalo Engine

In this tutorial, we'll walk through the creation of a simple physics-based doll using the Pixalo game engine. We will cover each part of the code step by step, explaining its purpose and functionality.

1. Importing Pixalo

First, we need to import the Pixalo engine into our project. This can be done through a CDN link. Here's how we do it:

import Pixalo from 'https://cdn.jsdelivr.net/gh/pixalo/pixalo/dist/pixalo.esm.js';
Enter fullscreen mode Exit fullscreen mode

2. Initializing the Game

Next, we'll create a new instance of the Pixalo engine and set up our game canvas. We define the dimensions and background color, as well as the physics properties (in this case, gravity).

const game = new Pixalo('#canvas', {
    width: window.innerWidth,
    height: window.innerHeight,
    background: '#031C1B',
    physics: { gravity: { y: 800 } }
});
game.start();
Enter fullscreen mode Exit fullscreen mode
  • #canvas: The ID of the HTML element where the game will be rendered.
  • width & height: Set to the window's dimensions.
  • background: The game background color.
  • gravity: Gravity is set to 800 units in the y-direction.

3. Creating a Static Ground

Now, let's create a static ground object that will serve as the base for our doll. This ground will be immovable.

game.append('ground', {
    x: 0,
    y: game.baseHeight - 40,
    width: game.baseWidth,
    height: 40,
    backgroundColor: '#268985',
    physics: { bodyType: 'static' }
});
Enter fullscreen mode Exit fullscreen mode
  • We set its position, dimensions, and color, and define it as a static body using bodyType: 'static'.

4. Defining Body Part Dimensions

Next, we need to define the dimensions of the different body parts of our doll. This includes the head, torso, arms, and legs.

const HEAD_D = 44;  // head diameter
const TORSO_W = 35, TORSO_H = 70; // torso
const ARM_W = 45, ARM_H = 10; // upper arms
const LEG_W = 14, LEG_H = 65; // upper legs
Enter fullscreen mode Exit fullscreen mode

These constants will help us position and size the body parts accurately.

5. Factory Function for Body Parts

We create a helper function called part to simplify creating draggable body parts.

function part(id, opts) {
    opts.draggable = true; // Pixalo flag
    return game.append(id, opts); // returns the sprite
}
Enter fullscreen mode Exit fullscreen mode

This function sets the draggable property and appends a new part to the game.

6. Assembling the Doll

Now we can assemble the doll using the part function to create each body part. All parts will be positioned initially to look connected before physics kicks in.

const head = part('head', {
    shape: 'circle', width: HEAD_D, height: HEAD_D,
    backgroundColor: '#F3A71A',
    physics: { density: 1, friction: 0.3, restitution: 0.2 },
    x: (game.baseWidth - HEAD_D) / 2,
    y: 50,
    text: '> <\n_',             // Face
    lineHeight: 0.1
});

const torso = part('torso', {
    width: TORSO_W, height: TORSO_H,
    backgroundColor: '#268985',
    borderRadius: 6,
    physics: { density: 1, friction: 0.3, restitution: 0.1 },
    x: (game.baseWidth - TORSO_W) / 2,
    y: head.y + HEAD_D          // Top edge = bottom edge of head
});

const armL = part('armL', {
    width: ARM_W, height: ARM_H,
    backgroundColor: '#F3A71A',
    borderRadius: 6,
    physics: { density: 0.8, friction: 0.3 },
    x: torso.x - ARM_W,        // Right edge = left edge of trunk
    y: torso.y + 25
});

const armR = part('armR', {
    width: ARM_W, height: ARM_H,
    backgroundColor: '#F3A71A',
    borderRadius: 6,
    physics: { density: 0.8, friction: 0.3 },
    x: torso.x + TORSO_W,      // Left edge = right edge of trunk
    y: torso.y + 25
});

const legL = part('legL', {
    width: LEG_W, height: LEG_H,
    backgroundColor: '#268985',
    borderRadius: 6,
    physics: { density: 1, friction: 0.3 },
    x: torso.x,
    y: torso.y + TORSO_H
});

const legR = part('legR', {
    width: LEG_W, height: LEG_H,
    backgroundColor: '#268985',
    borderRadius: 6,
    physics: { density: 1, friction: 0.3 },
    x: torso.x + TORSO_W - LEG_W,
    y: torso.y + TORSO_H
});
Enter fullscreen mode Exit fullscreen mode

Each body part is created with specific colors, physics properties (density, friction, and restitution), and initial positions calculated to center them horizontally.

7. Setting Joint Stiffness

Next, we establish a joint stiffness variable to control the flexibility of the doll.

const stiffness = 6; // 0-100 scale
Enter fullscreen mode Exit fullscreen mode

This variable will affect the joints between body parts.

8. Helper Function for Joints

We will create revolute joints that connect two body parts together this way:

function join(entityA, entityB, anchorX, anchorY, lowerAngle = -45, upperAngle = 45) {
    const config = {
        type: 'revolute',
        anchor: { x: anchorX, y: anchorY },
        collideConnected: false
    };

    // Configure limits and motor based on stiffness
    if (stiffness > 5) {
        config.limits = { lower: lowerAngle, upper: upperAngle };
    }

    if (stiffness > 2) {
        config.motor = { speed: 0, torque: stiffness * 0.6 };
    }

    return game.physics.joint(entityA, entityB, config);
}
Enter fullscreen mode Exit fullscreen mode

This function sets up the constraints and motors for the joints based on the stiffness level.

9. Wiring the Skeleton

In this step, we use the join function to connect all body parts together once the physics bodies are ready.

game.one('update', () => {
    join(head, torso, head.x + HEAD_D / 2, head.y + HEAD_D, -25, 25);
    join(torso, armL, torso.x, torso.y + 25, -70, 40);
    join(torso, armR, torso.x + TORSO_W, torso.y + 25, -40, 70);
    join(torso, legL, torso.x + LEG_W / 2, torso.y + TORSO_H, -30, 20);
    join(torso, legR, torso.x + TORSO_W - LEG_W / 2, torso.y + TORSO_H, -20, 30);
});
Enter fullscreen mode Exit fullscreen mode

10. Adding Interactivity

Let's add keyboard controls so we can reset the doll or toggle gravity.

game.on('keydown', (e) => {
    switch (e.key) {
        case 'r':
            // Reset doll position
            head.style({ x: (game.baseWidth - HEAD_D) / 2, y: 50 });
            torso.style({ x: (game.baseWidth - TORSO_W) / 2, y: head.y + HEAD_D });
            break;
        case 'g':
            // Toggle gravity
            const currentGravity = game.physics.world.GetGravity();
            if (currentGravity.y === 0) {
                game.physics.setGravity(0, 800);
            } else {
                game.physics.setGravity(0, 0);
            }
            break;
    }
});
Enter fullscreen mode Exit fullscreen mode

The keydown event allows us to reset the doll's position and control gravity dynamically.

11. Displaying Instructions

Lastly, we will add a set of instructions for the player.

game.append('instructions', {
    x: 100, y: 20,
    text: 'Drag body parts around!\nPress R to reset\nPress G to toggle gravity',
    fontSize: 16,
    color: '#FFF'
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations! You've successfully created a simple physics-based doll using the Pixalo engine. This project illustrates how to work with physics, joints, and interactivity. Feel free to modify the stiffness or play around with the body's parts to create your own unique characters!

๐Ÿ‘‰ Website: https://pixalo.xyz
๐Ÿ‘‰ GitHub: https://github.com/pixalo/pixalo

Happy coding!

Top comments (0)