spoint 0.1.48 → 0.1.49
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/SKILL.md +254 -6
- package/apps/arena/index.js +83 -0
- package/apps/box-static/index.js +24 -0
- package/apps/prop-static/index.js +15 -0
- package/apps/world/index.js +2 -0
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -9,6 +9,227 @@ Complete reference for building apps in a spawnpoint project. Engine source code
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## Quick Start — Minimal Working Arena
|
|
13
|
+
|
|
14
|
+
This is the fastest path to a playable scene. Copy this pattern exactly.
|
|
15
|
+
|
|
16
|
+
### Step 1: World config (`apps/world/index.js`)
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default {
|
|
20
|
+
port: 3001,
|
|
21
|
+
tickRate: 128,
|
|
22
|
+
gravity: [0, -9.81, 0],
|
|
23
|
+
movement: { maxSpeed: 4.0, groundAccel: 10.0, airAccel: 1.0, friction: 6.0, stopSpeed: 2.0, jumpImpulse: 4.0 },
|
|
24
|
+
player: { health: 100, capsuleRadius: 0.4, capsuleHalfHeight: 0.9, modelScale: 1.323, feetOffset: 0.212 },
|
|
25
|
+
scene: { skyColor: 0x87ceeb, fogColor: 0x87ceeb, fogNear: 80, fogFar: 200, sunIntensity: 1.5, sunPosition: [20, 40, 20] },
|
|
26
|
+
entities: [
|
|
27
|
+
{ id: 'arena', position: [0, 0, 0], app: 'arena' }
|
|
28
|
+
],
|
|
29
|
+
spawnPoint: [0, 2, 0]
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Step 2: Arena app (`apps/arena/index.js`)
|
|
34
|
+
|
|
35
|
+
This creates a ground plane and 4 walls using box primitives (visual + physics), no GLB required:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
const HALF = 12, WALL_H = 3, WALL_T = 0.5
|
|
39
|
+
const WALLS = [
|
|
40
|
+
{ id: 'wall-n', x: 0, y: WALL_H/2, z: -HALF, hx: HALF, hy: WALL_H/2, hz: WALL_T/2 },
|
|
41
|
+
{ id: 'wall-s', x: 0, y: WALL_H/2, z: HALF, hx: HALF, hy: WALL_H/2, hz: WALL_T/2 },
|
|
42
|
+
{ id: 'wall-e', x: HALF, y: WALL_H/2, z: 0, hx: WALL_T/2, hy: WALL_H/2, hz: HALF },
|
|
43
|
+
{ id: 'wall-w', x: -HALF, y: WALL_H/2, z: 0, hx: WALL_T/2, hy: WALL_H/2, hz: HALF },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
export default {
|
|
47
|
+
server: {
|
|
48
|
+
setup(ctx) {
|
|
49
|
+
ctx.state.ids = ctx.state.ids || []
|
|
50
|
+
if (ctx.state.ids.length > 0) return // hot-reload guard
|
|
51
|
+
|
|
52
|
+
// Ground — this entity IS the ground
|
|
53
|
+
ctx.entity.custom = { mesh: 'box', color: 0x5a7a4a, roughness: 1, sx: HALF*2, sy: 0.5, sz: HALF*2 }
|
|
54
|
+
ctx.physics.setStatic(true)
|
|
55
|
+
ctx.physics.addBoxCollider([HALF, 0.25, HALF])
|
|
56
|
+
|
|
57
|
+
// Walls — spawn children each with box-static app
|
|
58
|
+
for (const w of WALLS) {
|
|
59
|
+
const e = ctx.world.spawn(w.id, {
|
|
60
|
+
position: [w.x, w.y, w.z],
|
|
61
|
+
app: 'box-static',
|
|
62
|
+
config: { hx: w.hx, hy: w.hy, hz: w.hz, color: 0x7a6a5a }
|
|
63
|
+
})
|
|
64
|
+
if (e) ctx.state.ids.push(w.id)
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
teardown(ctx) {
|
|
68
|
+
for (const id of ctx.state.ids || []) ctx.world.destroy(id)
|
|
69
|
+
ctx.state.ids = []
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
client: {
|
|
73
|
+
render(ctx) {
|
|
74
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, custom: ctx.entity.custom }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Step 3: box-static reusable app (`apps/box-static/index.js`)
|
|
81
|
+
|
|
82
|
+
Reusable app for any static box primitive with physics. Config drives size and color:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
export default {
|
|
86
|
+
server: {
|
|
87
|
+
setup(ctx) {
|
|
88
|
+
const c = ctx.config
|
|
89
|
+
ctx.entity.custom = {
|
|
90
|
+
mesh: 'box',
|
|
91
|
+
color: c.color ?? 0x888888,
|
|
92
|
+
roughness: c.roughness ?? 0.9,
|
|
93
|
+
sx: (c.hx ?? 1) * 2, sy: (c.hy ?? 1) * 2, sz: (c.hz ?? 1) * 2
|
|
94
|
+
}
|
|
95
|
+
ctx.physics.setStatic(true)
|
|
96
|
+
ctx.physics.addBoxCollider([c.hx ?? 1, c.hy ?? 1, c.hz ?? 1])
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
client: {
|
|
100
|
+
render(ctx) {
|
|
101
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, custom: ctx.entity.custom }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Asset Loading — How It Works
|
|
110
|
+
|
|
111
|
+
### Loading Sequence Gate
|
|
112
|
+
|
|
113
|
+
The loading screen stays up until ALL of these pass simultaneously:
|
|
114
|
+
1. WebSocket connected
|
|
115
|
+
2. Player VRM model downloaded
|
|
116
|
+
3. Environment model loaded (first entity with a `model` field, OR any entity without a model that creates a mesh)
|
|
117
|
+
4. First snapshot received from server
|
|
118
|
+
5. All entities from the **world config `entities` array** that have a `model` field are loaded
|
|
119
|
+
|
|
120
|
+
**Critical:** Entities declared in `world.entities` that have a `model` are tracked by the client loading gate. Entities spawned later by `ctx.world.spawn()` at runtime are NOT part of the loading gate — they appear after the game starts.
|
|
121
|
+
|
|
122
|
+
If you want a model to be part of the loading sequence, declare it in `world.entities`:
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
// world/index.js
|
|
126
|
+
entities: [
|
|
127
|
+
{ id: 'environment', model: './apps/tps-game/schwust.glb', app: 'environment' },
|
|
128
|
+
// ^^ This model blocks the loading screen until loaded
|
|
129
|
+
]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Local Models
|
|
133
|
+
|
|
134
|
+
Place GLB files inside your app folder. Reference with `./apps/<app-name>/file.glb`:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
apps/my-arena/
|
|
138
|
+
index.js
|
|
139
|
+
floor.glb <-- served automatically by StaticHandler
|
|
140
|
+
wall.glb
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
// In world/index.js:
|
|
145
|
+
{ id: 'env', model: './apps/my-arena/floor.glb', app: 'environment' }
|
|
146
|
+
|
|
147
|
+
// Or spawned at runtime:
|
|
148
|
+
ctx.world.spawn('floor', { model: './apps/my-arena/floor.glb', ... })
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The StaticHandler serves everything under `apps/` at the same path. No CDN, no hosting needed.
|
|
152
|
+
|
|
153
|
+
### Remote Models (Verified Asset Library)
|
|
154
|
+
|
|
155
|
+
The `https://github.com/anEntrypoint/assets` repository contains ~170 free GLB models.
|
|
156
|
+
|
|
157
|
+
**URL pattern:** `https://raw.githubusercontent.com/anEntrypoint/assets/main/FILENAME.glb`
|
|
158
|
+
|
|
159
|
+
**Verified filenames** (use these exactly — wrong filenames silently 404):
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Vehicles & Junk:
|
|
163
|
+
broken_car_b6d2e66d_v1.glb broken_car_b6d2e66d_v2.glb
|
|
164
|
+
crashed_car_f2b577ae_v1.glb crashed_car_f2b577ae_v2.glb
|
|
165
|
+
crashed_pickup_truck_ae555020_v1.glb
|
|
166
|
+
crashed_rusty_minivan_f872ff37_v1.glb
|
|
167
|
+
Bus_junk_1.glb
|
|
168
|
+
|
|
169
|
+
Containers & Industrial:
|
|
170
|
+
blue_shipping_container_60b5ea93_v1.glb
|
|
171
|
+
blue_shipping_container_63cc3905_v1.glb
|
|
172
|
+
dumpster_b076662a_v1.glb dumpster_b076662a_v2.glb
|
|
173
|
+
garbage_can_6b3d052b_v1.glb garbage_can_6b3d052b_v2.glb
|
|
174
|
+
crushed_oil_barrel_e450f43f_v1.glb crushed_oil_barrel_e450f43f_v2.glb
|
|
175
|
+
fire_hydrant_ba0175c1_v1.glb fire_hydrant_ba0175c1_v2.glb
|
|
176
|
+
fire_extinguisher_wall_mounted_bc0dddd4_v1.glb
|
|
177
|
+
|
|
178
|
+
Office & Furniture:
|
|
179
|
+
break_room_chair_14a39c7b_v1.glb break_room_chair_14a39c7b_v2.glb
|
|
180
|
+
break_room_couch_444abf63_v1.glb break_room_table_09b9fd0d_v1.glb
|
|
181
|
+
filing_cabinet_0194476c_v1.glb filing_cabinet_0194476c_v2.glb
|
|
182
|
+
fancy_reception_desk_58fde71d_v1.glb
|
|
183
|
+
cash_register_0c0dcad2_v1.glb
|
|
184
|
+
espresso_machine_e722ed8c_v1.glb
|
|
185
|
+
Couch.glb Couch_2.glb 3chairs.glb
|
|
186
|
+
|
|
187
|
+
Natural & Rocks:
|
|
188
|
+
large_rock_051293c4_v1.glb large_rock_051293c4_v2.glb
|
|
189
|
+
|
|
190
|
+
Misc:
|
|
191
|
+
Tin_Man_1.glb Tin_Man_2.glb Plants_3.glb Urinals.glb V_Machine_2.glb
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Remote models are NOT part of the loading gate.** They appear after game starts. Use them for decorative props, not required environment geometry.
|
|
195
|
+
|
|
196
|
+
**NEVER guess asset filenames.** Only use names from the verified list above. Wrong URLs silently 404 — no model appears, no error thrown.
|
|
197
|
+
|
|
198
|
+
### prop-static reusable app
|
|
199
|
+
|
|
200
|
+
For remote GLB props that need convex hull physics:
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
// apps/prop-static/index.js
|
|
204
|
+
export default {
|
|
205
|
+
server: {
|
|
206
|
+
setup(ctx) {
|
|
207
|
+
ctx.physics.setStatic(true)
|
|
208
|
+
if (ctx.entity.model) ctx.physics.addConvexFromModel(0)
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
client: {
|
|
212
|
+
render(ctx) {
|
|
213
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, model: ctx.entity.model }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Spawn remote props:
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
const BASE = 'https://raw.githubusercontent.com/anEntrypoint/assets/main'
|
|
223
|
+
ctx.world.spawn('dumpster-1', {
|
|
224
|
+
model: `${BASE}/dumpster_b076662a_v1.glb`,
|
|
225
|
+
position: [5, 0, -3],
|
|
226
|
+
rotation: [0, Math.sin(0.5), 0, Math.cos(0.5)],
|
|
227
|
+
app: 'prop-static'
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
12
233
|
## Setup
|
|
13
234
|
|
|
14
235
|
When no `apps/` directory exists in the working directory, scaffold it:
|
|
@@ -821,16 +1042,16 @@ return {
|
|
|
821
1042
|
|
|
822
1043
|
### Entity custom Field - Procedural Mesh Conventions
|
|
823
1044
|
|
|
824
|
-
When no GLB model is set, `custom` drives procedural geometry
|
|
1045
|
+
When no GLB model is set, `custom` drives procedural geometry. This is the primary way to create walls, floors, and visible primitives without any GLB file.
|
|
825
1046
|
|
|
826
1047
|
```js
|
|
827
|
-
// Box
|
|
828
|
-
custom: { mesh: 'box', color: 0xff8800, sx:
|
|
1048
|
+
// Box — sx/sy/sz are FULL dimensions (width/height/depth in world units)
|
|
1049
|
+
custom: { mesh: 'box', color: 0xff8800, roughness: 0.8, sx: 2, sy: 1, sz: 2 }
|
|
829
1050
|
|
|
830
|
-
// Sphere
|
|
831
|
-
custom: { mesh: 'sphere', color: 0x00ff00,
|
|
1051
|
+
// Sphere — r is radius
|
|
1052
|
+
custom: { mesh: 'sphere', color: 0x00ff00, r: 1, seg: 16 }
|
|
832
1053
|
|
|
833
|
-
// Cylinder
|
|
1054
|
+
// Cylinder — r is radius, h is full height, seg is polygon count
|
|
834
1055
|
custom: {
|
|
835
1056
|
mesh: 'cylinder',
|
|
836
1057
|
r: 0.4, h: 0.1, seg: 16,
|
|
@@ -853,6 +1074,10 @@ custom: { ..., glow: true, glowColor: 0x00ff88, glowIntensity: 0.5 }
|
|
|
853
1074
|
custom: { mesh: 'box', label: 'PRESS E' }
|
|
854
1075
|
```
|
|
855
1076
|
|
|
1077
|
+
**Note on sx/sy/sz vs collider half-extents:** The `custom.sx/sy/sz` fields are the FULL visual size. The `addBoxCollider([hx, hy, hz])` takes HALF-extents. Always halve the visual size when computing the collider: `sx: 4, sy: 2, sz: 4` → `addBoxCollider([2, 1, 2])`.
|
|
1078
|
+
|
|
1079
|
+
**Primitives with physics require an app.** Setting `entity.custom` only affects rendering. Physics colliders only activate when `ctx.physics.addBoxCollider()` etc. is called inside an app's `setup()`. Use the `box-static` app pattern (see Quick Start above) for primitive walls/floors.
|
|
1080
|
+
|
|
856
1081
|
---
|
|
857
1082
|
|
|
858
1083
|
## AppLoader Security Restrictions
|
|
@@ -1063,6 +1288,29 @@ ctx.bus.on('combat.*', (event) => {
|
|
|
1063
1288
|
|
|
1064
1289
|
## Critical Caveats
|
|
1065
1290
|
|
|
1291
|
+
### Spawned entities need their own app to have physics
|
|
1292
|
+
|
|
1293
|
+
Calling `ctx.world.spawn()` creates an entity but does NOT create a physics body. Physics is only created when `ctx.physics.addBoxCollider()` (or any other physics call) runs inside that entity's app `setup()`.
|
|
1294
|
+
|
|
1295
|
+
To create a wall/floor with physics via `ctx.world.spawn()`, give it an app:
|
|
1296
|
+
|
|
1297
|
+
```js
|
|
1298
|
+
// WRONG — entity appears visually but has no physics, players fall through
|
|
1299
|
+
const e = ctx.world.spawn('floor', { position: [0, 0, 0] })
|
|
1300
|
+
e.custom = { mesh: 'box', sx: 10, sy: 0.5, sz: 10 }
|
|
1301
|
+
e.bodyType = 'static' // does nothing without an app physics call
|
|
1302
|
+
e.collider = { ... } // does nothing without an app physics call
|
|
1303
|
+
|
|
1304
|
+
// CORRECT — use box-static app which calls ctx.physics.addBoxCollider in setup
|
|
1305
|
+
ctx.world.spawn('floor', {
|
|
1306
|
+
position: [0, 0, 0],
|
|
1307
|
+
app: 'box-static',
|
|
1308
|
+
config: { hx: 5, hy: 0.25, hz: 5, color: 0x448844 }
|
|
1309
|
+
})
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
Alternatively, the entity's own app can create child entities with their own apps, or the entity itself can call `ctx.physics.*` for its own body (the entity the app is attached to).
|
|
1313
|
+
|
|
1066
1314
|
### ctx.state survives hot reload; timers and bus subscriptions do not
|
|
1067
1315
|
|
|
1068
1316
|
Re-register all timers and bus subscriptions in `setup`. Use `||` to preserve state:
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const ASSET_BASE = 'https://raw.githubusercontent.com/anEntrypoint/assets/main'
|
|
2
|
+
|
|
3
|
+
const JUNK_MODELS = [
|
|
4
|
+
`${ASSET_BASE}/dumpster_b076662a_v1.glb`,
|
|
5
|
+
`${ASSET_BASE}/garbage_can_6b3d052b_v1.glb`,
|
|
6
|
+
`${ASSET_BASE}/fire_hydrant_ba0175c1_v1.glb`,
|
|
7
|
+
`${ASSET_BASE}/crushed_oil_barrel_e450f43f_v1.glb`,
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
const HALF = 12
|
|
11
|
+
const WALL_H = 3
|
|
12
|
+
const WALL_T = 0.5
|
|
13
|
+
|
|
14
|
+
const WALLS = [
|
|
15
|
+
{ id: 'arena-wall-n', x: 0, y: WALL_H / 2, z: -HALF, hx: HALF, hy: WALL_H / 2, hz: WALL_T / 2 },
|
|
16
|
+
{ id: 'arena-wall-s', x: 0, y: WALL_H / 2, z: HALF, hx: HALF, hy: WALL_H / 2, hz: WALL_T / 2 },
|
|
17
|
+
{ id: 'arena-wall-e', x: HALF, y: WALL_H / 2, z: 0, hx: WALL_T / 2, hy: WALL_H / 2, hz: HALF },
|
|
18
|
+
{ id: 'arena-wall-w', x: -HALF, y: WALL_H / 2, z: 0, hx: WALL_T / 2, hy: WALL_H / 2, hz: HALF },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const PROPS = [
|
|
22
|
+
{ model: 0, x: -6, z: -6, rot: 0.5 },
|
|
23
|
+
{ model: 0, x: 7, z: 5, rot: 2.1 },
|
|
24
|
+
{ model: 1, x: -8, z: 3, rot: 1.0 },
|
|
25
|
+
{ model: 1, x: 5, z: -7, rot: 3.2 },
|
|
26
|
+
{ model: 1, x: 3, z: 8, rot: 0.3 },
|
|
27
|
+
{ model: 2, x: -4, z: -9, rot: 1.5 },
|
|
28
|
+
{ model: 2, x: 9, z: -3, rot: 4.0 },
|
|
29
|
+
{ model: 3, x: -7, z: 7, rot: 0.8 },
|
|
30
|
+
{ model: 3, x: 6, z: 2, rot: 2.7 },
|
|
31
|
+
{ model: 3, x: -3, z: -5, rot: 1.2 },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
server: {
|
|
36
|
+
setup(ctx) {
|
|
37
|
+
ctx.state.ids = ctx.state.ids || []
|
|
38
|
+
if (ctx.state.ids.length > 0) return
|
|
39
|
+
|
|
40
|
+
// Ground (this entity itself)
|
|
41
|
+
ctx.entity.custom = { mesh: 'box', color: 0x5a7a4a, roughness: 1, sx: HALF * 2, sy: 0.5, sz: HALF * 2 }
|
|
42
|
+
ctx.physics.setStatic(true)
|
|
43
|
+
ctx.physics.addBoxCollider([HALF, 0.25, HALF])
|
|
44
|
+
|
|
45
|
+
// Walls - each gets box-static app which adds its own collider
|
|
46
|
+
for (const w of WALLS) {
|
|
47
|
+
const e = ctx.world.spawn(w.id, {
|
|
48
|
+
position: [w.x, w.y, w.z],
|
|
49
|
+
app: 'box-static',
|
|
50
|
+
config: { hx: w.hx, hy: w.hy, hz: w.hz, color: 0x7a6a5a, roughness: 0.9 }
|
|
51
|
+
})
|
|
52
|
+
if (e) ctx.state.ids.push(w.id)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Props from remote asset repo (static, convex hull from model)
|
|
56
|
+
for (let i = 0; i < PROPS.length; i++) {
|
|
57
|
+
const p = PROPS[i]
|
|
58
|
+
const id = `arena-prop-${i}`
|
|
59
|
+
const a = p.rot / 2
|
|
60
|
+
const e = ctx.world.spawn(id, {
|
|
61
|
+
model: JUNK_MODELS[p.model],
|
|
62
|
+
position: [p.x, 0, p.z],
|
|
63
|
+
rotation: [0, Math.sin(a), 0, Math.cos(a)],
|
|
64
|
+
app: 'prop-static'
|
|
65
|
+
})
|
|
66
|
+
if (e) ctx.state.ids.push(id)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ctx.debug.log(`[arena] setup: ground + ${WALLS.length} walls + ${PROPS.length} props`)
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
teardown(ctx) {
|
|
73
|
+
for (const id of ctx.state.ids || []) ctx.world.destroy(id)
|
|
74
|
+
ctx.state.ids = []
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
client: {
|
|
79
|
+
render(ctx) {
|
|
80
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, custom: ctx.entity.custom }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
server: {
|
|
3
|
+
setup(ctx) {
|
|
4
|
+
const c = ctx.config
|
|
5
|
+
if (c.color !== undefined) {
|
|
6
|
+
ctx.entity.custom = {
|
|
7
|
+
mesh: 'box',
|
|
8
|
+
color: c.color,
|
|
9
|
+
roughness: c.roughness ?? 0.9,
|
|
10
|
+
sx: (c.hx ?? 1) * 2,
|
|
11
|
+
sy: (c.hy ?? 1) * 2,
|
|
12
|
+
sz: (c.hz ?? 1) * 2
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
ctx.physics.setStatic(true)
|
|
16
|
+
ctx.physics.addBoxCollider([c.hx ?? 1, c.hy ?? 1, c.hz ?? 1])
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
client: {
|
|
20
|
+
render(ctx) {
|
|
21
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, custom: ctx.entity.custom }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
server: {
|
|
3
|
+
setup(ctx) {
|
|
4
|
+
ctx.physics.setStatic(true)
|
|
5
|
+
if (ctx.entity.model) {
|
|
6
|
+
ctx.physics.addConvexFromModel(0)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
client: {
|
|
11
|
+
render(ctx) {
|
|
12
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, model: ctx.entity.model }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/apps/world/index.js
CHANGED
|
@@ -62,6 +62,8 @@ export default {
|
|
|
62
62
|
{ id: 'game', position: [0, 0, 0], app: 'tps-game' },
|
|
63
63
|
{ id: 'power-crates', position: [0, 0, 0], app: 'power-crate' },
|
|
64
64
|
{ id: 'interact-box', position: [-100, 3, -100], app: 'interactable' }
|
|
65
|
+
// To use the primitive arena instead of schwust.glb, replace above with:
|
|
66
|
+
// { id: 'arena', position: [0, 0, 0], app: 'arena' }
|
|
65
67
|
],
|
|
66
68
|
playerModel: './apps/tps-game/cleetus.vrm',
|
|
67
69
|
spawnPoint: [-35, 3, -65]
|