@dcl-regenesislabs/opendcl 0.1.0-22234509684.commit-63dfd19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -0
- package/context/components-reference.md +113 -0
- package/context/open-source-3d-assets.md +705 -0
- package/context/sdk7-complete-reference.md +3684 -0
- package/context/sdk7-examples.md +1709 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/scene-context.d.ts +97 -0
- package/dist/scene-context.d.ts.map +1 -0
- package/dist/scene-context.js +203 -0
- package/dist/scene-context.js.map +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -0
- package/extensions/dcl-context.ts +123 -0
- package/extensions/dcl-deploy.ts +89 -0
- package/extensions/dcl-header.ts +162 -0
- package/extensions/dcl-init.ts +62 -0
- package/extensions/dcl-preview.ts +144 -0
- package/extensions/dcl-setup-ollama.ts +312 -0
- package/extensions/dcl-setup.ts +96 -0
- package/extensions/dcl-status.ts +88 -0
- package/extensions/dcl-tasks.ts +102 -0
- package/extensions/dcl-update-check.ts +79 -0
- package/extensions/dcl-validate.ts +80 -0
- package/extensions/plan-mode/index.ts +340 -0
- package/extensions/plan-mode/utils.ts +168 -0
- package/extensions/process-registry.ts +25 -0
- package/extensions/scene-utils.ts +31 -0
- package/package.json +65 -0
- package/prompts/explain.md +16 -0
- package/prompts/review.md +19 -0
- package/prompts/system.md +126 -0
- package/skills/add-3d-models/SKILL.md +115 -0
- package/skills/add-interactivity/SKILL.md +176 -0
- package/skills/advanced-input/SKILL.md +238 -0
- package/skills/advanced-rendering/SKILL.md +235 -0
- package/skills/animations-tweens/SKILL.md +173 -0
- package/skills/audio-video/SKILL.md +167 -0
- package/skills/authoritative-server/SKILL.md +329 -0
- package/skills/build-ui/SKILL.md +231 -0
- package/skills/camera-control/SKILL.md +199 -0
- package/skills/create-scene/SKILL.md +67 -0
- package/skills/deploy-scene/SKILL.md +106 -0
- package/skills/deploy-worlds/SKILL.md +107 -0
- package/skills/lighting-environment/SKILL.md +216 -0
- package/skills/multiplayer-sync/SKILL.md +132 -0
- package/skills/nft-blockchain/SKILL.md +246 -0
- package/skills/optimize-scene/SKILL.md +160 -0
- package/skills/player-avatar/SKILL.md +239 -0
- package/skills/smart-items/SKILL.md +181 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: system
|
|
3
|
+
description: OpenDCL system identity and Decentraland SDK7 knowledge base
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are **OpenDCL**, an AI coding assistant specialized in Decentraland SDK7 scene development.
|
|
7
|
+
|
|
8
|
+
## Your Identity
|
|
9
|
+
- You help creators build interactive 3D scenes for Decentraland using SDK7.
|
|
10
|
+
- You are beginner-friendly: always explain what you're doing and why.
|
|
11
|
+
- You are precise about SDK7 APIs and never invent components or functions that don't exist.
|
|
12
|
+
- When unsure, read the context files in the `context/` directory for accurate SDK7 reference.
|
|
13
|
+
|
|
14
|
+
## Decentraland SDK7 Fundamentals
|
|
15
|
+
|
|
16
|
+
### Architecture
|
|
17
|
+
- **Entity-Component System (ECS)**: Scenes are built with entities (IDs), components (data), and systems (logic).
|
|
18
|
+
- **Runtime**: Sandboxed QuickJS — **no** Node.js APIs (`fs`, `http`, `path`, `process` are unavailable).
|
|
19
|
+
- **Imports**: Use `@dcl/sdk/ecs`, `@dcl/sdk/math`, `@dcl/sdk/react-ecs` — never `~system/` or internal paths.
|
|
20
|
+
- **Entry point**: `export function main() {}` in `src/index.ts` — the engine calls this on scene load.
|
|
21
|
+
|
|
22
|
+
### Scene Constraints
|
|
23
|
+
- Each **parcel** = 16m × 16m × 20m height.
|
|
24
|
+
- Scenes have **entity limits**, **triangle budgets**, and **texture memory limits** based on parcel count.
|
|
25
|
+
- 1 parcel: ~512 entities, ~10,000 triangles. Scales with parcel count.
|
|
26
|
+
- All coordinates are in meters. Y is up. Scene origin (0,0,0) is the southwest corner of the base parcel at ground level.
|
|
27
|
+
|
|
28
|
+
### Key Patterns
|
|
29
|
+
|
|
30
|
+
**Creating an entity with components:**
|
|
31
|
+
```typescript
|
|
32
|
+
import { engine, Transform, MeshRenderer, Material } from '@dcl/sdk/ecs'
|
|
33
|
+
import { Vector3, Color4 } from '@dcl/sdk/math'
|
|
34
|
+
|
|
35
|
+
const cube = engine.addEntity()
|
|
36
|
+
Transform.create(cube, { position: Vector3.create(8, 1, 8) })
|
|
37
|
+
MeshRenderer.setBox(cube)
|
|
38
|
+
Material.setPbrMaterial(cube, { albedoColor: Color4.Red() })
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Adding interactivity:**
|
|
42
|
+
```typescript
|
|
43
|
+
import { pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
|
|
44
|
+
|
|
45
|
+
pointerEventsSystem.onPointerDown({ entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Click me' } }, () => {
|
|
46
|
+
// Handle click
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Systems (per-frame logic):**
|
|
51
|
+
```typescript
|
|
52
|
+
engine.addSystem((dt: number) => {
|
|
53
|
+
// Runs every frame, dt = delta time in seconds
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**UI with React-ECS:**
|
|
58
|
+
```tsx
|
|
59
|
+
import ReactEcs, { UiEntity, Label, Button } from '@dcl/sdk/react-ecs'
|
|
60
|
+
|
|
61
|
+
function MyUI() {
|
|
62
|
+
return (
|
|
63
|
+
<UiEntity uiTransform={{ width: 200, height: 50, positionType: 'absolute' }}>
|
|
64
|
+
<Label value="Hello" fontSize={18} />
|
|
65
|
+
</UiEntity>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function setupUi() {
|
|
70
|
+
ReactEcs.setUiRenderer(MyUI)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Project Structure
|
|
75
|
+
```
|
|
76
|
+
scene-project/
|
|
77
|
+
├── scene.json # Scene metadata (parcels, title, main entry)
|
|
78
|
+
├── package.json # Dependencies (@dcl/sdk)
|
|
79
|
+
├── tsconfig.json # TypeScript config
|
|
80
|
+
└── src/
|
|
81
|
+
├── index.ts # Main entry point (export function main)
|
|
82
|
+
└── ui.tsx # UI components (optional)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### scene.json Required Fields
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"ecs7": true,
|
|
89
|
+
"runtimeVersion": "7",
|
|
90
|
+
"display": { "title": "My Scene" },
|
|
91
|
+
"scene": { "parcels": ["0,0"], "base": "0,0" },
|
|
92
|
+
"main": "bin/index.js"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## How to Help Users
|
|
97
|
+
|
|
98
|
+
### Empty Folder (No scene.json)
|
|
99
|
+
1. Ask the user what they want to build.
|
|
100
|
+
2. **Use the `init` tool first** — this uses the official SDK scaffolding to create scene.json, package.json, tsconfig.json, and src/index.ts with the correct, up-to-date configuration. Never create these files manually.
|
|
101
|
+
3. After init completes, customize `scene.json` (title, description, parcels) and `src/index.ts` (scene code) based on what the user wants.
|
|
102
|
+
4. Run `npm install`, then use the `preview` tool to start the preview server.
|
|
103
|
+
|
|
104
|
+
### Existing Scene
|
|
105
|
+
1. Read scene.json and src/index.ts to understand the project.
|
|
106
|
+
2. Offer contextual help — adding features, fixing bugs, optimizing.
|
|
107
|
+
3. Always preserve existing code when making edits.
|
|
108
|
+
|
|
109
|
+
### Best Practices
|
|
110
|
+
- Always position objects within the scene boundaries (based on parcels).
|
|
111
|
+
- Use `Vector3.create()` and `Quaternion.fromEulerDegrees()` for transforms.
|
|
112
|
+
- For 3D models, use `GltfContainer.create(entity, { src: 'models/myModel.glb' })`.
|
|
113
|
+
- Place `.glb` files in a `models/` directory, textures in `images/`.
|
|
114
|
+
- After writing TypeScript, use the `preview` tool to start the preview server.
|
|
115
|
+
- If the user asks about 3D models, reference the open-source-3D-assets catalog in `context/open-source-3d-assets.md`.
|
|
116
|
+
|
|
117
|
+
## Tools & Commands
|
|
118
|
+
|
|
119
|
+
You have these Decentraland-specific tools — **use them directly** when the user's request matches:
|
|
120
|
+
- `init` — Scaffold a new scene (**always use this first** in an empty folder)
|
|
121
|
+
- `preview` — Start the Bevy-web preview server
|
|
122
|
+
- `deploy` — Deploy to Genesis City or a World (auto-detects from scene.json)
|
|
123
|
+
- `tasks` — List or stop running background processes
|
|
124
|
+
|
|
125
|
+
The user can also type these as `/init`, `/preview`, `/deploy`, `/tasks` slash commands directly.
|
|
126
|
+
Additional user-only commands: `/review`, `/explain`, `/setup`, `/setup-ollama`
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-3d-models
|
|
3
|
+
description: Add 3D models (.glb/.gltf) to a Decentraland scene using GltfContainer. Covers loading models, positioning, scaling, colliders, and browsing the open-source 3D assets catalog for free CC0 models. Use when user wants to add models, import GLB files, or find free 3D assets.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding 3D Models to Decentraland Scenes
|
|
7
|
+
|
|
8
|
+
## Loading a 3D Model
|
|
9
|
+
|
|
10
|
+
Use `GltfContainer` to load `.glb` or `.gltf` files:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { engine, Transform, GltfContainer } from '@dcl/sdk/ecs'
|
|
14
|
+
import { Vector3, Quaternion } from '@dcl/sdk/math'
|
|
15
|
+
|
|
16
|
+
const model = engine.addEntity()
|
|
17
|
+
Transform.create(model, {
|
|
18
|
+
position: Vector3.create(8, 0, 8),
|
|
19
|
+
rotation: Quaternion.fromEulerDegrees(0, 0, 0),
|
|
20
|
+
scale: Vector3.create(1, 1, 1)
|
|
21
|
+
})
|
|
22
|
+
GltfContainer.create(model, {
|
|
23
|
+
src: 'models/myModel.glb'
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## File Organization
|
|
28
|
+
|
|
29
|
+
Place model files in a `models/` directory at the project root:
|
|
30
|
+
```
|
|
31
|
+
project/
|
|
32
|
+
├── models/
|
|
33
|
+
│ ├── building.glb
|
|
34
|
+
│ ├── tree.glb
|
|
35
|
+
│ └── furniture/
|
|
36
|
+
│ ├── chair.glb
|
|
37
|
+
│ └── table.glb
|
|
38
|
+
├── src/
|
|
39
|
+
│ └── index.ts
|
|
40
|
+
└── scene.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Colliders
|
|
44
|
+
|
|
45
|
+
### Using Model's Built-in Colliders
|
|
46
|
+
Models exported with collision meshes work automatically. Set the collision mask:
|
|
47
|
+
```typescript
|
|
48
|
+
GltfContainer.create(model, {
|
|
49
|
+
src: 'models/building.glb',
|
|
50
|
+
visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER,
|
|
51
|
+
invisibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Adding Simple Colliders
|
|
56
|
+
For basic shapes, add `MeshCollider`:
|
|
57
|
+
```typescript
|
|
58
|
+
import { MeshCollider } from '@dcl/sdk/ecs'
|
|
59
|
+
MeshCollider.setBox(model) // Box collider
|
|
60
|
+
MeshCollider.setSphere(model) // Sphere collider
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Common Model Operations
|
|
64
|
+
|
|
65
|
+
### Scaling
|
|
66
|
+
```typescript
|
|
67
|
+
Transform.create(model, {
|
|
68
|
+
position: Vector3.create(8, 0, 8),
|
|
69
|
+
scale: Vector3.create(2, 2, 2) // 2x size
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Rotation
|
|
74
|
+
```typescript
|
|
75
|
+
Transform.create(model, {
|
|
76
|
+
position: Vector3.create(8, 0, 8),
|
|
77
|
+
rotation: Quaternion.fromEulerDegrees(0, 90, 0) // Rotate 90° on Y axis
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Parenting (Attach to Another Entity)
|
|
82
|
+
```typescript
|
|
83
|
+
const parent = engine.addEntity()
|
|
84
|
+
Transform.create(parent, { position: Vector3.create(8, 0, 8) })
|
|
85
|
+
|
|
86
|
+
const child = engine.addEntity()
|
|
87
|
+
Transform.create(child, {
|
|
88
|
+
position: Vector3.create(0, 2, 0), // 2m above parent
|
|
89
|
+
parent: parent
|
|
90
|
+
})
|
|
91
|
+
GltfContainer.create(child, { src: 'models/hat.glb' })
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Free 3D Models
|
|
95
|
+
|
|
96
|
+
For free CC0-licensed 3D models suitable for Decentraland, read the catalog at:
|
|
97
|
+
`context/open-source-3d-assets.md` (in the opendcl repo root). If unavailable locally, fetch from:
|
|
98
|
+
https://raw.githubusercontent.com/dcl-regenesislabs/opendcl/main/context/open-source-3d-assets.md
|
|
99
|
+
|
|
100
|
+
This catalog contains 991+ models organized by themed collections (Cyberpunk, Medieval, MomusPark, etc.) with direct download URLs.
|
|
101
|
+
|
|
102
|
+
When the user asks for 3D models:
|
|
103
|
+
1. Read the open-source-3d-assets.md file
|
|
104
|
+
2. Suggest models that match their description
|
|
105
|
+
3. Show them how to download and add the model to their scene
|
|
106
|
+
|
|
107
|
+
## Model Best Practices
|
|
108
|
+
|
|
109
|
+
- Keep models under 50MB per file for good loading times
|
|
110
|
+
- Use `.glb` format (binary GLTF) — smaller than `.gltf`
|
|
111
|
+
- Optimize triangle count: aim for under 1,500 triangles per model for small props
|
|
112
|
+
- Use texture atlases when possible to reduce draw calls
|
|
113
|
+
- Models with embedded animations can be played with the `Animator` component
|
|
114
|
+
- Test model orientation — Decentraland uses Y-up coordinate system
|
|
115
|
+
- Materials in models should use PBR (physically-based rendering) for best results
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-interactivity
|
|
3
|
+
description: Add click handlers, hover effects, pointer events, triggers, and raycasting to Decentraland scene entities. Use when user wants to make objects clickable, add interactions, detect player proximity, or handle user input.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding Interactivity to Decentraland Scenes
|
|
7
|
+
|
|
8
|
+
## Pointer Events (Click / Hover)
|
|
9
|
+
|
|
10
|
+
### Using the Helper System (Recommended)
|
|
11
|
+
```typescript
|
|
12
|
+
import { engine, Transform, MeshRenderer, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
|
|
13
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
14
|
+
|
|
15
|
+
const cube = engine.addEntity()
|
|
16
|
+
Transform.create(cube, { position: Vector3.create(8, 1, 8) })
|
|
17
|
+
MeshRenderer.setBox(cube)
|
|
18
|
+
|
|
19
|
+
// Add click handler
|
|
20
|
+
pointerEventsSystem.onPointerDown(
|
|
21
|
+
{
|
|
22
|
+
entity: cube,
|
|
23
|
+
opts: {
|
|
24
|
+
button: InputAction.IA_POINTER, // Left click
|
|
25
|
+
hoverText: 'Click me!',
|
|
26
|
+
maxDistance: 10
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
(event) => {
|
|
30
|
+
console.log('Cube clicked!', event.hit?.position)
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Available Input Actions
|
|
36
|
+
```typescript
|
|
37
|
+
InputAction.IA_POINTER // Left click / primary
|
|
38
|
+
InputAction.IA_PRIMARY // E key
|
|
39
|
+
InputAction.IA_SECONDARY // F key
|
|
40
|
+
InputAction.IA_ACTION_3 // Key 1
|
|
41
|
+
InputAction.IA_ACTION_4 // Key 2
|
|
42
|
+
InputAction.IA_ACTION_5 // Key 3
|
|
43
|
+
InputAction.IA_ACTION_6 // Key 4
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Pointer Up (Release)
|
|
47
|
+
```typescript
|
|
48
|
+
pointerEventsSystem.onPointerDown(
|
|
49
|
+
{ entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Hold me' } },
|
|
50
|
+
() => { console.log('Pressed!') }
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
pointerEventsSystem.onPointerUp(
|
|
54
|
+
{ entity: cube, opts: { button: InputAction.IA_POINTER } },
|
|
55
|
+
() => { console.log('Released!') }
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Removing Handlers
|
|
60
|
+
```typescript
|
|
61
|
+
pointerEventsSystem.removeOnPointerDown(cube)
|
|
62
|
+
pointerEventsSystem.removeOnPointerUp(cube)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Important: Colliders Required
|
|
66
|
+
Pointer events only work on entities with a **collider**. Add one if your entity doesn't have a mesh:
|
|
67
|
+
```typescript
|
|
68
|
+
import { MeshCollider } from '@dcl/sdk/ecs'
|
|
69
|
+
MeshCollider.setBox(entity) // Invisible box collider
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For GLTF models, set the collision mask:
|
|
73
|
+
```typescript
|
|
74
|
+
GltfContainer.create(entity, {
|
|
75
|
+
src: 'models/button.glb',
|
|
76
|
+
visibleMeshesCollisionMask: ColliderLayer.CL_POINTER
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Trigger Areas (Proximity Detection)
|
|
81
|
+
|
|
82
|
+
Detect when the player enters or exits an area:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { engine, Transform, TriggerArea, TriggerAreaResult } from '@dcl/sdk/ecs'
|
|
86
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
87
|
+
|
|
88
|
+
const trigger = engine.addEntity()
|
|
89
|
+
Transform.create(trigger, { position: Vector3.create(8, 0, 8) })
|
|
90
|
+
|
|
91
|
+
TriggerArea.create(trigger, {
|
|
92
|
+
area: Vector3.create(4, 4, 4) // 4x4x4 meter box
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// System to check trigger
|
|
96
|
+
engine.addSystem(() => {
|
|
97
|
+
const result = TriggerAreaResult.getOrNull(trigger)
|
|
98
|
+
if (result && result.isTriggered) {
|
|
99
|
+
// Player is inside the trigger area
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Raycasting
|
|
105
|
+
|
|
106
|
+
Cast rays to detect objects in a direction:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { engine, Raycast, RaycastResult, RaycastQueryType } from '@dcl/sdk/ecs'
|
|
110
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
111
|
+
|
|
112
|
+
const rayEntity = engine.addEntity()
|
|
113
|
+
Raycast.create(rayEntity, {
|
|
114
|
+
direction: { $case: 'localDirection', localDirection: Vector3.Forward() },
|
|
115
|
+
maxDistance: 16,
|
|
116
|
+
queryType: RaycastQueryType.RQT_HIT_FIRST,
|
|
117
|
+
continuous: false // Set true for continuous raycasting
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Check results
|
|
121
|
+
engine.addSystem(() => {
|
|
122
|
+
const result = RaycastResult.getOrNull(rayEntity)
|
|
123
|
+
if (result && result.hits.length > 0) {
|
|
124
|
+
const hit = result.hits[0]
|
|
125
|
+
console.log('Hit entity:', hit.entityId, 'at', hit.position)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Global Input Handling
|
|
131
|
+
|
|
132
|
+
Listen for key presses anywhere (not entity-specific):
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
|
|
136
|
+
|
|
137
|
+
engine.addSystem(() => {
|
|
138
|
+
// Check if E key was just pressed this frame
|
|
139
|
+
if (inputSystem.isTriggered(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN)) {
|
|
140
|
+
console.log('E key pressed!')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check if a key is currently held down
|
|
144
|
+
if (inputSystem.isPressed(InputAction.IA_SECONDARY)) {
|
|
145
|
+
console.log('F key is held!')
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Toggle Pattern (Click to Switch States)
|
|
151
|
+
|
|
152
|
+
Common pattern for toggleable objects:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
let doorOpen = false
|
|
156
|
+
|
|
157
|
+
pointerEventsSystem.onPointerDown(
|
|
158
|
+
{ entity: door, opts: { button: InputAction.IA_POINTER, hoverText: 'Toggle door' } },
|
|
159
|
+
() => {
|
|
160
|
+
doorOpen = !doorOpen
|
|
161
|
+
const mutableTransform = Transform.getMutable(door)
|
|
162
|
+
mutableTransform.rotation = doorOpen
|
|
163
|
+
? Quaternion.fromEulerDegrees(0, 90, 0)
|
|
164
|
+
: Quaternion.fromEulerDegrees(0, 0, 0)
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Best Practices
|
|
170
|
+
|
|
171
|
+
- Always set `maxDistance` on pointer events (8-16m is typical)
|
|
172
|
+
- Always set `hoverText` so users know they can interact
|
|
173
|
+
- Clean up handlers when entities are removed
|
|
174
|
+
- Use `MeshCollider` for invisible trigger surfaces
|
|
175
|
+
- For complex interactions, use a system with state tracking
|
|
176
|
+
- Test interactions in preview — hover text should be visible and clear
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: advanced-input
|
|
3
|
+
description: Advanced input handling in Decentraland scenes. Use PointerLock to detect cursor capture state, InputModifier to freeze or restrict player movement, PrimaryPointerInfo for cursor position and world ray, inputSystem.getInputCommand for per-entity input polling, and keyboard patterns for WASD movement controls. Use when user wants custom input, cursor control, movement restriction, keyboard handling, FPS controls, or input polling.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Advanced Input Handling in Decentraland
|
|
7
|
+
|
|
8
|
+
For basic click/hover events, see the `add-interactivity` skill. This skill covers advanced input patterns.
|
|
9
|
+
|
|
10
|
+
## Pointer Lock State
|
|
11
|
+
|
|
12
|
+
Detect whether the cursor is captured (first-person mode) or free:
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { engine, PointerLock } from '@dcl/sdk/ecs'
|
|
16
|
+
|
|
17
|
+
function checkPointerLock() {
|
|
18
|
+
const isLocked = PointerLock.get(engine.CameraEntity).isPointerLocked
|
|
19
|
+
|
|
20
|
+
if (isLocked) {
|
|
21
|
+
// Cursor is captured — player is in first-person control
|
|
22
|
+
} else {
|
|
23
|
+
// Cursor is free — player can click UI elements
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
engine.addSystem(checkPointerLock)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Pointer Lock Change Detection
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
PointerLock.onChange(engine.CameraEntity, (pointerLock) => {
|
|
34
|
+
if (pointerLock?.isPointerLocked) {
|
|
35
|
+
console.log('Cursor locked')
|
|
36
|
+
} else {
|
|
37
|
+
console.log('Cursor unlocked')
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Cursor Position and World Ray
|
|
43
|
+
|
|
44
|
+
Get the cursor's screen position and the ray it casts into the 3D world:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { engine, PrimaryPointerInfo } from '@dcl/sdk/ecs'
|
|
48
|
+
|
|
49
|
+
function readPointer() {
|
|
50
|
+
const pointerInfo = PrimaryPointerInfo.get(engine.RootEntity)
|
|
51
|
+
console.log('Cursor position:', pointerInfo.screenCoordinates)
|
|
52
|
+
console.log('Cursor delta:', pointerInfo.screenDelta)
|
|
53
|
+
console.log('World ray direction:', pointerInfo.worldRayDirection)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
engine.addSystem(readPointer)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Input Polling with inputSystem
|
|
60
|
+
|
|
61
|
+
### Per-Entity Input Commands
|
|
62
|
+
|
|
63
|
+
Check if a specific input action occurred on a specific entity:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { engine, inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
|
|
67
|
+
|
|
68
|
+
function myInputSystem() {
|
|
69
|
+
// Check for click on a specific entity
|
|
70
|
+
const clickData = inputSystem.getInputCommand(
|
|
71
|
+
InputAction.IA_POINTER,
|
|
72
|
+
PointerEventType.PET_DOWN,
|
|
73
|
+
myEntity
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if (clickData) {
|
|
77
|
+
console.log('Entity clicked via system:', clickData.hit.entityId)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
engine.addSystem(myInputSystem)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Global Input Checks
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
function globalInputSystem() {
|
|
88
|
+
// Was the key just pressed this frame?
|
|
89
|
+
if (inputSystem.isTriggered(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN)) {
|
|
90
|
+
console.log('E key pressed!')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Is the key currently held down?
|
|
94
|
+
if (inputSystem.isPressed(InputAction.IA_SECONDARY)) {
|
|
95
|
+
console.log('F key is held!')
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
engine.addSystem(globalInputSystem)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## All InputAction Values
|
|
103
|
+
|
|
104
|
+
| InputAction | Key/Button |
|
|
105
|
+
|-------------|-----------|
|
|
106
|
+
| `IA_POINTER` | Left mouse button |
|
|
107
|
+
| `IA_PRIMARY` | E key |
|
|
108
|
+
| `IA_SECONDARY` | F key |
|
|
109
|
+
| `IA_ACTION_3` | 1 key |
|
|
110
|
+
| `IA_ACTION_4` | 2 key |
|
|
111
|
+
| `IA_ACTION_5` | 3 key |
|
|
112
|
+
| `IA_ACTION_6` | 4 key |
|
|
113
|
+
| `IA_JUMP` | Space key |
|
|
114
|
+
| `IA_FORWARD` | W key |
|
|
115
|
+
| `IA_BACKWARD` | S key |
|
|
116
|
+
| `IA_LEFT` | A key |
|
|
117
|
+
| `IA_RIGHT` | D key |
|
|
118
|
+
| `IA_WALK` | Shift key |
|
|
119
|
+
|
|
120
|
+
## Event Types
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
PointerEventType.PET_DOWN // Button/key pressed
|
|
124
|
+
PointerEventType.PET_UP // Button/key released
|
|
125
|
+
PointerEventType.PET_HOVER_ENTER // Cursor enters entity
|
|
126
|
+
PointerEventType.PET_HOVER_LEAVE // Cursor leaves entity
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## InputModifier (Movement Restriction)
|
|
130
|
+
|
|
131
|
+
Restrict or freeze the player's movement:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { engine, InputModifier } from '@dcl/sdk/ecs'
|
|
135
|
+
|
|
136
|
+
// Freeze player completely
|
|
137
|
+
InputModifier.create(engine.PlayerEntity, {
|
|
138
|
+
mode: InputModifier.Mode.Standard({ disableAll: true })
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Restrict specific movement
|
|
142
|
+
InputModifier.createOrReplace(engine.PlayerEntity, {
|
|
143
|
+
mode: InputModifier.Mode.Standard({
|
|
144
|
+
disableRun: true,
|
|
145
|
+
disableJump: true,
|
|
146
|
+
disableEmote: true
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Restore normal movement
|
|
151
|
+
InputModifier.deleteFrom(engine.PlayerEntity)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Important:** InputModifier only works in the DCL 2.0 desktop client. It has no effect in the web browser explorer.
|
|
155
|
+
|
|
156
|
+
### Cutscene Pattern
|
|
157
|
+
|
|
158
|
+
Freeze the player during a cinematic sequence:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
function startCutscene() {
|
|
162
|
+
// Freeze player
|
|
163
|
+
InputModifier.create(engine.PlayerEntity, {
|
|
164
|
+
mode: InputModifier.Mode.Standard({ disableAll: true })
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// ... play cinematic with VirtualCamera ...
|
|
168
|
+
|
|
169
|
+
// After cutscene ends, restore movement
|
|
170
|
+
// InputModifier.deleteFrom(engine.PlayerEntity)
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## WASD Movement Pattern
|
|
175
|
+
|
|
176
|
+
Poll movement keys to control custom entities:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { engine, inputSystem, InputAction, PointerEventType, Transform } from '@dcl/sdk/ecs'
|
|
180
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
181
|
+
|
|
182
|
+
const MOVE_SPEED = 5
|
|
183
|
+
|
|
184
|
+
function customMovementSystem(dt: number) {
|
|
185
|
+
const transform = Transform.getMutable(controllableEntity)
|
|
186
|
+
let moveX = 0
|
|
187
|
+
let moveZ = 0
|
|
188
|
+
|
|
189
|
+
if (inputSystem.isPressed(InputAction.IA_FORWARD)) moveZ += 1
|
|
190
|
+
if (inputSystem.isPressed(InputAction.IA_BACKWARD)) moveZ -= 1
|
|
191
|
+
if (inputSystem.isPressed(InputAction.IA_LEFT)) moveX -= 1
|
|
192
|
+
if (inputSystem.isPressed(InputAction.IA_RIGHT)) moveX += 1
|
|
193
|
+
|
|
194
|
+
transform.position.x += moveX * MOVE_SPEED * dt
|
|
195
|
+
transform.position.z += moveZ * MOVE_SPEED * dt
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
engine.addSystem(customMovementSystem)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Combining Input Patterns
|
|
202
|
+
|
|
203
|
+
### Action Bar with Number Keys
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
function actionBarSystem() {
|
|
207
|
+
if (inputSystem.isTriggered(InputAction.IA_ACTION_3, PointerEventType.PET_DOWN)) {
|
|
208
|
+
console.log('Slot 1 activated')
|
|
209
|
+
useAbility(1)
|
|
210
|
+
}
|
|
211
|
+
if (inputSystem.isTriggered(InputAction.IA_ACTION_4, PointerEventType.PET_DOWN)) {
|
|
212
|
+
console.log('Slot 2 activated')
|
|
213
|
+
useAbility(2)
|
|
214
|
+
}
|
|
215
|
+
if (inputSystem.isTriggered(InputAction.IA_ACTION_5, PointerEventType.PET_DOWN)) {
|
|
216
|
+
console.log('Slot 3 activated')
|
|
217
|
+
useAbility(3)
|
|
218
|
+
}
|
|
219
|
+
if (inputSystem.isTriggered(InputAction.IA_ACTION_6, PointerEventType.PET_DOWN)) {
|
|
220
|
+
console.log('Slot 4 activated')
|
|
221
|
+
useAbility(4)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
engine.addSystem(actionBarSystem)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Best Practices
|
|
229
|
+
|
|
230
|
+
- Use `isTriggered()` for one-shot actions (fire weapon, open door) — it returns true only on the frame the key is first pressed
|
|
231
|
+
- Use `isPressed()` for continuous actions (movement, holding a shield) — it returns true every frame while held
|
|
232
|
+
- `getInputCommand()` gives hit data (position, entity) — use it when you need to know what was clicked
|
|
233
|
+
- Prefer `pointerEventsSystem.onPointerDown()` for simple entity clicks — use `inputSystem` for complex multi-key or polling patterns
|
|
234
|
+
- InputModifier only works in the DCL 2.0 desktop client — test with the desktop client if your scene relies on it
|
|
235
|
+
- WASD keys (`IA_FORWARD`, etc.) also control player movement — polling them reads the movement state but doesn't override it
|
|
236
|
+
|
|
237
|
+
For basic pointer events and click handlers, see the `add-interactivity` skill.
|
|
238
|
+
For component field details, see `context/components-reference.md`.
|