Creating Custom Levels
Relevant source files
The following files were used as context for generating this wiki page:
Creating custom levels in RemyMan involves defining structured data that the game engine interprets to render platforms, hazards, enemies, and objectives. The system relies on a central data structure where each level is an array of entities, each possessing specific spatial properties and behavioral types.
The architecture is designed to support a scrolling world larger than the viewport, managed by a camera system that tracks the player across a defined levelWidth. Developers can construct intricate platforming challenges by positioning entities within a 2D coordinate system and configuring their specific attributes, such as patrol ranges for monsters or hazard dimensions.
Sources: architecture_plan.md:14-23, app/components/Game/levelData.ts:1-7
Level Structure and Entity Types
Levels are defined as arrays of the LevelEntity type. Every entity in the game world must be assigned a unique identifier and a specific EntityType which determines its collision logic and rendering style.
Core Entity Definitions
The following table describes the available entity types used to construct levels:
| Entity Type | Purpose | Behavior |
|---|---|---|
platform | Solid Ground | Prevents player from falling; provides a surface to walk/jump on. |
hazard | Obstacle | Triggers a "Lost" game state immediately upon collision. |
monster | Moving Enemy | Patrols a specific range; causes a "Lost" state on contact. |
goal | Objective | Triggers a "Won" game state and calculates completion points. |
start | Spawn Point | Defines the initial (x, y) coordinates of the player character. |
Sources: app/components/Game/types.ts:1-16, app/components/Game/GameCanvas.tsx:288-340
Entity Data Model
The LevelEntity interface provides the blueprint for every object placed in a level.
export interface LevelEntity {
id: string;
x: number;
y: number;
w: number;
h: number;
type: EntityType;
color?: string; // Optional visual override
patrolStart?: number; // Monster-only: Left bound
patrolEnd?: number; // Monster-only: Right bound
speed?: number; // Monster-only: Movement velocity
}
Sources: app/components/Game/types.ts:3-16
Physics and World Configuration
When creating a level, developers must consider the global physics constants defined in the GameConfig. These constants dictate how the player interacts with the level geometry.
Game Constants
| Constant | Default Value | Description |
|---|---|---|
gravity | 0.6 | Downward acceleration applied every frame. |
friction | 0.85 | Horizontal velocity decay (simulates air/ground resistance). |
moveSpeed | 0.8 | Acceleration rate when movement keys are pressed. |
maxSpeed | 8.0 | Terminal horizontal velocity. |
baseJumpForce | -14 | Initial upward velocity for jumps. |
levelWidth | 7000 | The total horizontal scrollable area of the world. |
Sources: app/components/Game/GameCanvas.tsx:38-45, architecture_plan.md:89-94
The relationship between level data, the physics engine, and the camera is illustrated below:
The diagram shows how levelData.ts acts as the source for entities which are then processed by the game loop to handle physics, AI, and camera clamping.
Sources: app/components/Game/GameCanvas.tsx:210-270, architecture_plan.md:27-35
Implementing Moving Entities
Monsters are a specialized type of level entity that require additional configuration to function correctly. Unlike static platforms, monsters utilize the patrolStart and patrolEnd properties to determine their movement range.
Monster Logic Flow
When the game starts, entities of type monster are mapped into a mutable monstersRef state. The game loop updates their x position based on their speed and reverses direction when they reach their patrol boundaries.
This sequence illustrates how monster movement is initialized from level data and maintained during the game loop.
Sources: app/components/Game/GameCanvas.tsx:103-117, app/components/Game/GameCanvas.tsx:201-213
Technical Implementation Example
A custom level is defined by exporting an array. Below is a snippet demonstrating the construction of a starting area with a platform, a hazard, and a patrolling monster.
export const CUSTOM_LEVEL: LevelEntity[] = [
// Static platform at the start
{ id: 'start_plt', x: 0, y: 550, w: 1000, h: 50, type: 'platform' },
// A hazard pit
{ id: 'hazard_1', x: 500, y: 530, w: 200, h: 20, type: 'hazard' },
// A monster patrolling the first platform
{
id: 'enemy_1',
x: 850, y: 500, w: 40, h: 40,
type: 'monster',
patrolStart: 800,
patrolEnd: 950,
speed: 2
},
// Mandatory player spawn point
{ id: 'spawn', x: 50, y: 350, w: 0, h: 0, type: 'start' }
];
Sources: app/components/Game/levelData.ts:6-25, app/components/Game/types.ts:3-16
Summary of Constraints
- Unique IDs: Every entity requires a unique string ID (typically generated via a helper function).
- Start Position: Exactly one entity of type
startmust be present to define the player's entry point. - Level Boundaries: The
levelWidthinGameCanvas.tsxshould be adjusted to match the furthestxcoordinate in the level data to prevent the player from moving into empty space. - Collision Z-Order: While the engine handles basic AABB (Axis-Aligned Bounding Box) collision, platforms are generally checked before hazards to allow for safe landing on edges.
Sources: app/components/Game/GameCanvas.tsx:216-240, app/components/Game/levelData.ts:98-100