skills/game-design/SKILL.md
Plan and design Decentraland games and interactive experiences. Scene limit formulas, performance budgets, texture requirements, asset preloading, state management patterns (module-level, component-based, state machines), object pooling, UX/UI guidelines, input design, and MVP planning. Use when the user wants game design advice, scene architecture, performance planning, or help structuring a game. Do NOT use for specific implementation (see add-interactivity, build-ui, multiplayer-sync).
npx skillsauth add dcl-regenesislabs/opendcl game-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Decentraland is a continuous, shared 3D world. Design around these constraints:
All limits scale with parcel count n. Except for hard MB file-size limits on deploy, all other limits CAN be exceeded — scenes won't crash, but performance degrades and the scene may be unusable on low-end devices. Treat the table as guidelines, not enforcement.
| Resource | Formula | 1 parcel | 2 parcels | 4 parcels | 9 parcels | 16 parcels | |---|---|---|---|---|---|---| | Triangles | n x 10,000 | 10,000 | 20,000 | 40,000 | 90,000 | 160,000 | | Entities | n x 200 | 200 | 400 | 800 | 1,800 | 3,200 | | Physics bodies | n x 300 | 300 | 600 | 1,200 | 2,700 | 4,800 | | Materials | log2(n+1) x 20 | 20 | 31 | 46 | 66 | 81 | | Textures | log2(n+1) x 10 | 10 | 15 | 23 | 33 | 40 | | Height limit | log2(n+1) x 20m | 20m | 31m | 46m | 66m | 81m | | Draw calls | n x 300 (target) | 300 | 600 | 1,200 | 2,700 | 4,800 |
File limits: 15 MB per parcel, 300 MB max total, 200 files per parcel, 50 MB max per individual file.
For large assets that would cause visible pop-in, use AssetLoad to pre-download before rendering:
import { engine, AssetLoad, LoadingState, GltfContainer, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const preloadEntity = engine.addEntity()
AssetLoad.create(preloadEntity, { src: 'models/large-model.glb' })
function assetLoadingSystem(dt: number) {
for (const [entity] of engine.getEntitiesWith(AssetLoad)) {
const state = AssetLoad.get(entity)
if (state.loadingState === LoadingState.FINISHED) {
GltfContainer.create(entity, { src: 'models/large-model.glb' })
Transform.create(entity, { position: Vector3.create(8, 0, 8) })
AssetLoad.deleteFrom(entity)
}
}
}
engine.addSystem(assetLoadingSystem)
Use this pattern for any model over ~1 MB or for assets that should be ready before a game phase begins.
Reuse entities instead of creating and destroying them:
const pool: Entity[] = []
function getFromPool(): Entity {
const existing = pool.pop()
if (existing) return existing
return engine.addEntity()
}
function returnToPool(entity: Entity) {
Transform.getMutable(entity).position = Vector3.create(0, -100, 0)
pool.push(entity)
}
Swap models or hide entities based on distance from the player:
function lodSystem() {
const playerPos = Transform.get(engine.PlayerEntity).position
for (const [entity, transform] of engine.getEntitiesWith(Transform, GltfContainer)) {
const distance = Vector3.distance(playerPos, transform.position)
VisibilityComponent.createOrReplace(entity, { visible: distance <= 30 })
}
}
engine.addSystem(lodSystem)
let timer = 0
function heavySystem(dt: number) {
timer += dt
if (timer < 0.5) return // Run every 500ms, not every frame
timer = 0
// ... expensive work here
}
engine.getEntitiesWith() queries — cache results when entity sets are stableRemove collision meshes from decorative objects that players never interact with. This reduces physics body count significantly.
| Input | Action | Notes |
|---|---|---|
| E key | Primary action (IA_PRIMARY) | Main interaction |
| F key | Secondary action (IA_SECONDARY) | Alternate interaction |
| Pointer click | IA_POINTER | Left mouse click / tap |
| Keys 1-4 | IA_ACTION_3 through IA_ACTION_6 | Action bar slots |
maxDistance on pointer events (8-10 meters typical) to prevent interactions from across the scenehoverText to communicate what an interaction does before the player commits// game-state.ts
export let score = 0
export let gamePhase: 'waiting' | 'playing' | 'ended' = 'waiting'
export function addScore(points: number) { score += points }
Use custom components as structured data containers:
import { engine, Schemas } from '@dcl/sdk/ecs'
const EnemyState = engine.defineComponent('EnemyState', {
health: Schemas.Number,
speed: Schemas.Number,
target: Schemas.Entity
})
Model game phases as explicit states with clear transitions:
type GameState = 'lobby' | 'countdown' | 'active' | 'cooldown'
let currentState: GameState = 'lobby'
function gameStateSystem(dt: number) {
switch (currentState) {
case 'lobby': handleLobby(dt); break
case 'countdown': handleCountdown(dt); break
case 'active': handleActive(dt); break
case 'cooldown': handleCooldown(dt); break
}
}
TextShape on entities and 3D signs over screen-space UI whenever the information is tied to a place or object.Ask: What does the player DO? The answer should be a single sentence:
Starting from scratch? See the create-scene skill first to scaffold the project before designing the game.
| Topic | Skill | When to Use | |---|---|---| | Interactivity, input handling, raycasting | add-interactivity | Implementing click handlers, triggers, input | | Multiplayer sync, server communication | multiplayer-sync | Networked game state, real-time sync | | Screen UI, React-ECS, HUD elements | build-ui | Building menus, scoreboards, dialogs | | Performance optimization, entity/triangle budgets | optimize-scene | Detailed optimization techniques |
This skill focuses on the design decisions and optimization constraints that shape implementations. For detailed code patterns, see the referenced skills.
development
Capture screenshots of the running Decentraland preview to verify scene changes visually. Covers camera movement, interaction actions, and visual debugging. Use when the preview is running and you need to check what the scene looks like, debug visual issues, or verify layout. Do NOT use for code changes (make changes first, then screenshot).
development
Cross-cutting runtime APIs for Decentraland SDK7 scenes. Use when the user needs async operations (executeTask), HTTP requests (fetch, signedFetch), WebSocket connections, timers, realm/scene detection, restricted actions (movePlayerTo, teleportTo, triggerEmote, openExternalUrl), portable experiences, or the testing framework. Do NOT use for UI (see build-ui), multiplayer sync (see multiplayer-sync), or avatar/player data (see player-avatar).
development
Apply physics forces to the player in Decentraland scenes. Impulses (one-shot pushes), knockback (push away from a point with falloff), continuous forces (wind tunnels, anti-gravity, lift, levitation, hover), timed forces, and repulsion fields. Use when the user wants launch pads, knockback on hit, wind zones, gravity fields, jumps, lifting/floating the player, pushing the player up/sideways/back, hover effects, or any scene-applied force on the player. THIS is also the right skill when an agent's first instinct is to mutate `Transform` on `engine.PlayerEntity` to move/lift/push the player — that does NOT work (the player Transform is engine-controlled and read-only); use the Physics API instead. Do NOT use for player movement speed (see player-avatar AvatarLocomotionSettings) or platform movement (see animations-tweens).
data-ai
Player and avatar system in Decentraland. Read player position/profile, customize appearance (AvatarBase), trigger emotes (triggerEmote/triggerSceneEmote), read equipped wearables (AvatarEquippedData), attach objects to players (AvatarAttach), create NPC avatars (AvatarShape), avatar modifier areas, and locomotion settings. Use when the user wants player data, emotes, wearables, NPC avatars, avatar attachments, or movement speed changes. Do NOT use for wallet/blockchain interactions (see nft-blockchain).