Skip to main content

Part 2: Physics Foundation

In this part, we'll implement Box2D physics in our platformer game, creating the foundation for realistic movement and collision detection.

What We'll Accomplish​

By the end of this part, you'll have:

  • A working Box2D physics world with gravity
  • Understanding of physics coordinate systems
  • Helper utilities for physics conversions
  • A basic physics debug renderer
  • Foundation for character and platform physics

Prerequisites​

Step 1: Understanding Box2D Integration​

cocos2d-mono comes with Box2D physics built-in. Box2D uses a different coordinate system and units than cocos2d:

  • Box2D units: Meters (optimal for objects 0.1 to 10 meters)
  • cocos2d units: Pixels
  • Coordinate system: Box2D uses bottom-left origin, cocos2d uses variable origin

We need conversion utilities to bridge these differences.

Step 2: Create Physics Helper Class​

Create a new file called PhysicsHelper.cs:

using Cocos2D;
using Box2D.Dynamics;
using Box2D.Collision.Shapes;
using Box2D.Common;

namespace Platformer
{
public static class PhysicsHelper
{
// Physics constants
public const float PTM_RATIO = 32.0f; // Pixels to meters ratio

// Categories for collision filtering
public const ushort CATEGORY_PLAYER = 0x0001;
public const ushort CATEGORY_PLATFORM = 0x0002;
public const ushort CATEGORY_COLLECTIBLE = 0x0004;

// Convert from cocos2d coordinates to Box2D coordinates
public static b2Vec2 ToPhysicsVector(CCPoint point)
{
return new b2Vec2(point.X / PTM_RATIO, point.Y / PTM_RATIO);
}

// Convert from Box2D coordinates to cocos2d coordinates
public static CCPoint ToCocosVector(b2Vec2 vector)
{
return new CCPoint(vector.x * PTM_RATIO, vector.y * PTM_RATIO);
}

// Create a rectangular physics body
public static b2Body CreateBoxBody(b2World world, float x, float y, float width, float height,
bool isDynamic = false, float density = 1.0f, float friction = 0.3f, float restitution = 0.1f)
{
// Define body
b2BodyDef bodyDef = new b2BodyDef();
bodyDef.position = new b2Vec2(x / PTM_RATIO, y / PTM_RATIO);
bodyDef.type = isDynamic ? b2BodyType.b2_dynamicBody : b2BodyType.b2_staticBody;

// Create body
b2Body body = world.CreateBody(bodyDef);

// Define fixture
b2PolygonShape shape = new b2PolygonShape();
shape.SetAsBox(width / (2 * PTM_RATIO), height / (2 * PTM_RATIO));

b2FixtureDef fixtureDef = new b2FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = density;
fixtureDef.friction = friction;
fixtureDef.restitution = restitution;

// Add fixture to body
body.CreateFixture(fixtureDef);

return body;
}
}
}

Step 3: Update GameLayer with Physics​

Now let's update our GameLayer.cs to include Box2D physics:

using Cocos2D;
using Box2D.Dynamics;
using System;

namespace Platformer
{
public class GameLayer : CCLayer
{
// Physics world
private b2World physicsWorld;
private b2Body groundBody;

// Physics settings
private const int VELOCITY_ITERATIONS = 8;
private const int POSITION_ITERATIONS = 3;
private const float TIME_STEP = 1.0f / 60.0f;

public GameLayer()
{
// Get visible area size
CCSize visibleSize = CCDirector.SharedDirector.WinSize;

// Initialize physics world
InitializePhysics(visibleSize);

// Create background
CCSprite background = new CCSprite("background");
background.Position = new CCPoint(visibleSize.Width / 2, visibleSize.Height / 2);

// Scale background to fit screen
float scaleX = visibleSize.Width / background.ContentSize.Width;
float scaleY = visibleSize.Height / background.ContentSize.Height;
background.Scale = Math.Max(scaleX, scaleY);

AddChild(background, -1);

// Create some test physics objects
CreateTestObjects(visibleSize);

// Add labels for information
CCLabelTTF titleLabel = new CCLabelTTF("Platformer Tutorial - Part 2: Physics", "Arial", 24);
titleLabel.Position = new CCPoint(visibleSize.Width / 2, visibleSize.Height - 30);
titleLabel.Color = CCColor3B.White;
AddChild(titleLabel);

CCLabelTTF infoLabel = new CCLabelTTF("Physics World Active - Objects will fall!", "Arial", 16);
infoLabel.Position = new CCPoint(visibleSize.Width / 2, visibleSize.Height - 60);
infoLabel.Color = CCColor3B.Yellow;
AddChild(infoLabel);

// Enable updates to step the physics world
ScheduleUpdate();
}

private void InitializePhysics(CCSize worldSize)
{
// Create physics world
physicsWorld = PhysicsHelper.CreateWorld();

// Create world boundaries
groundBody = PhysicsHelper.CreateWorldBoundaries(physicsWorld, worldSize);

// Set up contact listener for collision detection (we'll expand this later)
// physicsWorld.SetContactListener(new ContactListener());
}

private void CreateTestObjects(CCSize visibleSize)
{
// Create some test boxes to demonstrate physics
for (int i = 0; i < 3; i++)
{
// Create a visual sprite
CCSprite testBox = new CCSprite("platform"); // Using platform texture as test
testBox.Position = new CCPoint(200 + i * 100, 400 + i * 50);
testBox.Color = new CCColor3B((byte)(100 + i * 50), (byte)(150 - i * 30), (byte)(200));
AddChild(testBox, 1);

// Create corresponding physics body
b2Body physicsBody = PhysicsHelper.CreateDynamicBody(
physicsWorld,
testBox.Position,
testBox.ContentSize,
1.0f
);

// Store reference to sprite in physics body user data
physicsBody.UserData = testBox;
}

// Create a static platform to catch falling objects
CCSprite platform = new CCSprite("platform");
platform.Position = new CCPoint(visibleSize.Width / 2, 150);
platform.ScaleX = 4.0f; // Make it wider
AddChild(platform, 1);

// Create static physics body for platform
b2Body platformBody = PhysicsHelper.CreateStaticBody(
physicsWorld,
platform.Position,
new CCSize(platform.ContentSize.Width * platform.ScaleX, platform.ContentSize.Height)
);
platformBody.UserData = platform;
}

public override void Update(float dt)
{
base.Update(dt);

// Step the physics world
physicsWorld.Step(TIME_STEP, VELOCITY_ITERATIONS, POSITION_ITERATIONS);

// Update visual positions based on physics bodies
UpdateVisualPositions();
}

private void UpdateVisualPositions()
{
// Iterate through all physics bodies and update their corresponding sprites
for (b2Body body = physicsWorld.BodyList; body != null; body = body.Next)
{
if (body.UserData is CCSprite sprite)
{
// Convert physics position back to cocos2d coordinates
CCPoint newPosition = PhysicsHelper.VectorToPoint(body.Position);
sprite.Position = newPosition;

// Update rotation if needed
sprite.Rotation = -CCMathHelper.ToDegrees(body.Angle);
}
}
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
// Clean up physics world
physicsWorld?.Dispose();
}
base.Dispose(disposing);
}
}
}

Step 4: Understanding Physics Concepts​

Key Physics Concepts:​

  1. World: The physics simulation container

  2. Bodies: Objects that can move and collide

    • Static: Never moves (platforms, walls)
    • Dynamic: Affected by forces (player, enemies)
    • Kinematic: Moves but not affected by forces (moving platforms)
  3. Fixtures: Attach shapes to bodies and define material properties

  4. Shapes: Define collision geometry (box, circle, polygon)

Important Settings:​

  • Density: Mass per unit area (affects how heavy objects feel)
  • Friction: Resistance to sliding (0 = ice, 1 = rough surface)
  • Restitution: Bounciness (0 = no bounce, 1 = perfectly elastic)

Step 5: Build and Test​

  1. Build the project (F6)
  2. Run the game (F5)

You should see:

  • Your background
  • Several colored boxes falling due to gravity
  • Boxes landing on a platform and coming to rest
  • Title showing "Part 2: Physics"

Step 6: Adding Debug Rendering (Optional)​

For development, it's helpful to see physics shapes. Add this to your GameLayer:

// Add to the top of GameLayer.cs
using Box2D.Dynamics;

// Add these fields to GameLayer class
private bool showPhysicsDebug = true;

// Add this method to GameLayer
private void DrawPhysicsDebug()
{
if (!showPhysicsDebug) return;

// Simple debug drawing - draw rectangles for physics bodies
for (b2Body body = physicsWorld.BodyList; body != null; body = body.Next)
{
if (body.UserData is CCSprite sprite)
{
// Draw a simple outline around physics bodies
CCPoint position = PhysicsHelper.VectorToPoint(body.Position);

// This is a simplified debug visualization
// In a full implementation, you'd want proper debug drawing
}
}
}

Checkpoint: What Your Project Should Look Like​

At this point, your project should have:

New Files:​

  • PhysicsHelper.cs - Physics utility functions
  • Updated GameLayer.cs - Physics integration

What You Should See:​

  • Boxes falling from the top of the screen
  • Objects landing on a platform and stopping
  • Realistic physics behavior with gravity
  • Smooth 60 FPS with physics simulation

Key Concepts Learned:​

  • Box2D coordinate system and unit conversion
  • Creating physics worlds and bodies
  • Static vs dynamic physics bodies
  • Physics simulation loop
  • Synchronizing visual sprites with physics bodies

Troubleshooting​

Common issues and solutions:

  1. Objects falling through platforms: Check that static bodies are created correctly
  2. Objects moving too fast: Adjust TIME_STEP or increase iteration counts
  3. Jittery movement: Ensure consistent frame rate and proper conversion ratios
  4. Memory issues: Make sure to dispose of physics world properly

Understanding Performance​

Physics simulation can be expensive. Key performance tips:

  • Use appropriate iteration counts (8 velocity, 3 position is usually good)
  • Don't create too many dynamic bodies
  • Use static bodies for non-moving objects
  • Consider sleeping inactive bodies

Next Steps​

Great work! You now have a solid physics foundation. In Part 3: Player Character, we'll create a controllable player character that uses physics for movement and can interact with the world.

Key topics in Part 3:

  • Creating a player character class
  • Implementing keyboard/gamepad input
  • Physics-based movement and jumping
  • Character animations
  • Basic state management

Download Checkpoint Project​

You can download the complete Part 2 project here to compare with your implementation.