Skip to main content

Chapter 5: The Physical World: Introducing the Physics Engine

In game development, a physics engine is a critical tool that makes the game world realistic and interactive. Dora provides robust support for physics engines, allowing you to easily implement collision detection, gravity effects, and object movement. This chapter will guide you through introducing a physical world into your game and creating and managing physical entities.


1. What Is the Physical World?

The physical world is a virtual environment that simulates real-world physics, such as collisions between objects, velocity, and acceleration. In Dora, the physical world is represented by the PhysicsWorld node. It allows you to define the behavior and interactions of objects.


2. Creating the Physical World

In Dora, you can create a physical world by adding a PhysicsWorld node to your scene.

Example Code
import { PhysicsWorld } from 'Dora';
// Create a PhysicsWorld node
const physicsWorld = toNode(<physics-world showDebug/>) as PhysicsWorld.Type;
if (!physicsWorld) error('Failed to create the physical world!');

The PhysicsWorld added to the scene serves as the foundation of the entire physics system, managing all physical objects' interactions.


3. Adding Physical Entities

Physical entities are objects that can move or interact within the physical world. Dora provides various types of physical entities, such as Static, Dynamic, and Kinematic. In this tutorial, we will use a dynamic entity to create a physical entity for a player character.

Example Code
import { BodyMoveType } from 'Dora';

// Create a dynamic physical entity
const player = toNode(
<body
world={physicsWorld}
group={1}
type={BodyMoveType.Kinematic}
linearAcceleration={Vec2.zero}>
<disk-fixture radius={40}/>
</body>
);
if (!player) error('Failed to create the player!');

// Add the entity to the physical world
player.addTo(physicsWorld);

Code Explanation

  • type={BodyMoveType.Kinematic}: Specifies the player entity as kinematic, meaning its movement is controlled by code.
  • linearAcceleration={Vec2.zero}: Sets the object's linear acceleration to 0, meaning it is unaffected by gravity.
  • <disk-fixture radius={40}/>: Defines a circular collision area for the physical entity with a radius of 40.

4. Handling Object Collisions

By defining collision rules for the physical world, you can determine which objects can collide and how collision events are handled.

Example Code
// Define collision rules: collisions between Group 0 and Group 1 are enabled
<physics-world>
<contact groupA={0} groupB={1} enabled/>
</physics-world>
// Handle collision events
player.onContactStart(other => {
if (other.group === 0) {
print('Player collided with an enemy!');
}
});

Code Explanation

  • <contact groupA={0} groupB={1} enabled/>: Defines collision rules between Group 0 and Group 1.
  • onContactStart: Adds an event listener to the player entity, triggered when it collides with another object.

5. Dynamically Generating Objects

Dynamically generating objects is essential for adding challenges and interactions. Here's an example of generating enemies:

Example Code
const createEnemy = () => {
const enemy = toNode(
<body
world={physicsWorld}
group={0}
type={BodyMoveType.Dynamic}
linearAcceleration={Vec2.zero}
velocityX={100}
velocityY={100}>
<disk-fixture radius={40}/>
</body>
);
enemy?.addTo(physicsWorld);
};

By calling the createEnemy method in the game loop, you can continuously generate new enemies.


6. Controlling Object Movement

The movement of physical entities can be controlled by setting velocity, applying force, or directly modifying their positions. Here's a simple example of controlling player movement using keyboard input:

Example Code
inputManager.pushContext("Game");

let velocityX = 0;
let velocityY = 0;

player.gslot('Input.Up', () => velocityY = 1);
player.gslot('Input.Down', () => velocityY = -1);
player.gslot('Input.Left', () => velocityX = -1);
player.gslot('Input.Right', () => velocityX = 1);

player.loop(() => {
const newPos = player.position.add(Vec2(velocityX, velocityY).normalize().mul(10));
player.position = newPos;
velocityX = 0;
velocityY = 0;
return false;
});

Code Explanation

  • velocityX and velocityY: Store the player's movement velocity.
  • player.gslot: Binds keyboard input events to the player entity.
  • player.loop: Updates the player's position in the game loop.

7. Combined Example: Enemy Generation and Player Control in the Physical World

The following code combines the examples above to create a simple game scene where the player can move and enemies are generated every second.

Example Code
import { BodyMoveType, PhysicsWorld } from 'Dora';

// Method to create enemies
const Enemy = (world: PhysicsWorld.Type) => {
const enemy = toNode(
<body
world={world}
group={0}
type={BodyMoveType.Dynamic}
linearAcceleration={Vec2.zero}
velocityX={math.random(-100, 100)}
velocityY={math.random(-100, 100)}>
<disk-fixture radius={40}/>
</body>
)?.addTo(world);
if (!enemy) error('Failed to create an enemy!');

// Add animations to the enemy
const enemyAnim = Node().addTo(enemy);
const animations = ['enemyFlyingAlt_', 'enemyWalking_', 'enemySwimming_'];
playAnimation(enemyAnim, animations[math.random(0, animations.length - 1)]);
};

// Method to create the player
const Player = (world: PhysicsWorld.Type) => {
const player = toNode(
<body
world={world}
group={1}
type={BodyMoveType.Kinematic}
linearAcceleration={Vec2.zero}>
<disk-fixture radius={40}/>
</body>
)?.addTo(world);
if (!player) error('Failed to create the player!');

// Add animations to the player
const playerAnim = Node().addTo(player);
playAnimation(playerAnim, "playerGrey_walk");

// Player movement control
let velocityX = 0;
let velocityY = 0;

player.gslot('Input.Up', () => velocityY = 1);
player.gslot('Input.Down', () => velocityY = -1);
player.gslot('Input.Left', () => velocityX = -1);
player.gslot('Input.Right', () => velocityX = 1);

player.loop(() => {
const newPos = player.position.add(Vec2(velocityX, velocityY).normalize().mul(10));
player.position = newPos;
velocityX = 0;
velocityY = 0;
return false;
});
};

// Game scene component
const Game = () => {
// Switch to "Game" input context for player controls
inputManager.pushContext("Game");

// Create the physical world
return (
<physics-world onMount={world => {
Player(world); // Create the player

// Game loop: generate an enemy every second
world.loop(() => {
sleep(1);
Enemy(world); // Create an enemy
return false;
});
}}>
<contact groupA={0} groupB={1} enabled/> {/* Enable collisions between enemies and the player */}
<contact groupA={0} groupB={0} enabled={false}/> {/* Disable collisions between enemies */}
</physics-world>
);
};

// Register the game's start event from the start scene
Director.entry.gslot("Input.Start", () => {
Director.entry.removeAllChildren();
// Transition to the game scene
toNode(<Game/>);
});

8. Summary

In this chapter, we learned how to:

  1. Create a physical world and add it to the scene.
  2. Create physical entities and define collision rules.
  3. Dynamically generate objects and control their movement.

In the next chapters, we will continue enhancing the game with more features and optimizations to make your game richer and more engaging!