castle-web-cli 0.4.3 → 0.4.5
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/dist/index.js +8 -2
- package/dist/init.js +1 -1
- package/kits/basic-2d/CLAUDE.md +3 -3
- package/package.json +1 -1
- package/kits/basic-2d-frozen/.prettierrc +0 -8
- package/kits/basic-2d-frozen/CLAUDE.md +0 -131
- package/kits/basic-2d-frozen/behaviors/Camera.jsx +0 -43
- package/kits/basic-2d-frozen/behaviors/Collider.jsx +0 -71
- package/kits/basic-2d-frozen/behaviors/Drawing.jsx +0 -139
- package/kits/basic-2d-frozen/behaviors/Layout.jsx +0 -16
- package/kits/basic-2d-frozen/drawings/floor.drawing +0 -70
- package/kits/basic-2d-frozen/editors/App.jsx +0 -152
- package/kits/basic-2d-frozen/editors/CodeEditor.jsx +0 -112
- package/kits/basic-2d-frozen/editors/DrawingEditor.jsx +0 -222
- package/kits/basic-2d-frozen/editors/FileBrowser.jsx +0 -143
- package/kits/basic-2d-frozen/editors/PlayOnly.jsx +0 -21
- package/kits/basic-2d-frozen/editors/SceneEditor.jsx +0 -1012
- package/kits/basic-2d-frozen/editors/behaviorRegistry.js +0 -24
- package/kits/basic-2d-frozen/editors/editorHistory.js +0 -52
- package/kits/basic-2d-frozen/engine/ScenePlayer.jsx +0 -83
- package/kits/basic-2d-frozen/engine/SceneUI.jsx +0 -67
- package/kits/basic-2d-frozen/engine/TouchControls.jsx +0 -136
- package/kits/basic-2d-frozen/engine/autoInspector.jsx +0 -51
- package/kits/basic-2d-frozen/engine/files.js +0 -62
- package/kits/basic-2d-frozen/engine/scene.js +0 -420
- package/kits/basic-2d-frozen/engine/ui.jsx +0 -344
- package/kits/basic-2d-frozen/engine/ui.module.css +0 -928
- package/kits/basic-2d-frozen/eslint.config.js +0 -50
- package/kits/basic-2d-frozen/index.html +0 -11
- package/kits/basic-2d-frozen/main.jsx +0 -10
- package/kits/basic-2d-frozen/package-lock.json +0 -2706
- package/kits/basic-2d-frozen/package.json +0 -41
- package/kits/basic-2d-frozen/scenes/main.scene +0 -108
- package/kits/basic-2d-frozen/vite.config.js +0 -1
- package/kits/rpg-2d/.prettierrc +0 -8
- package/kits/rpg-2d/behaviors/Camera.tsx +0 -52
- package/kits/rpg-2d/behaviors/Collider.tsx +0 -98
- package/kits/rpg-2d/behaviors/Dialog.tsx +0 -184
- package/kits/rpg-2d/behaviors/Drawing.tsx +0 -161
- package/kits/rpg-2d/behaviors/Friend.tsx +0 -45
- package/kits/rpg-2d/behaviors/Layout.tsx +0 -29
- package/kits/rpg-2d/behaviors/PlayerController.tsx +0 -255
- package/kits/rpg-2d/behaviors/Portal.tsx +0 -60
- package/kits/rpg-2d/behaviors/QuestLog.tsx +0 -90
- package/kits/rpg-2d/behaviors/SaveMenu.tsx +0 -123
- package/kits/rpg-2d/behaviors/Tilemap.tsx +0 -90
- package/kits/rpg-2d/drawings/bld-home.drawing +0 -8136
- package/kits/rpg-2d/drawings/env-crate.drawing +0 -509
- package/kits/rpg-2d/drawings/env-fence.drawing +0 -536
- package/kits/rpg-2d/drawings/env-flower-bed.drawing +0 -607
- package/kits/rpg-2d/drawings/env-fountain.drawing +0 -2622
- package/kits/rpg-2d/drawings/env-hedge.drawing +0 -601
- package/kits/rpg-2d/drawings/env-house-blue.drawing +0 -1
- package/kits/rpg-2d/drawings/env-house-green.drawing +0 -1
- package/kits/rpg-2d/drawings/env-tree-oak.drawing +0 -1540
- package/kits/rpg-2d/drawings/env-tree-pine.drawing +0 -1315
- package/kits/rpg-2d/drawings/floor.drawing +0 -70
- package/kits/rpg-2d/drawings/fx-sparkle.drawing +0 -926
- package/kits/rpg-2d/drawings/npc-juno-idle-down.drawing +0 -1099
- package/kits/rpg-2d/drawings/npc-juno-walk-down.drawing +0 -4177
- package/kits/rpg-2d/drawings/npc-opal-idle-down.drawing +0 -1099
- package/kits/rpg-2d/drawings/npc-opal-walk-down.drawing +0 -4177
- package/kits/rpg-2d/drawings/player-idle-down.drawing +0 -1070
- package/kits/rpg-2d/drawings/player-idle-left.drawing +0 -1070
- package/kits/rpg-2d/drawings/player-idle-right.drawing +0 -1070
- package/kits/rpg-2d/drawings/player-idle-up.drawing +0 -1070
- package/kits/rpg-2d/drawings/player-walk-down.drawing +0 -4148
- package/kits/rpg-2d/drawings/player-walk-left.drawing +0 -4148
- package/kits/rpg-2d/drawings/player-walk-right.drawing +0 -4148
- package/kits/rpg-2d/drawings/player-walk-up.drawing +0 -4148
- package/kits/rpg-2d/editors/App.tsx +0 -163
- package/kits/rpg-2d/editors/CodeEditor.tsx +0 -120
- package/kits/rpg-2d/editors/DrawingEditor.tsx +0 -278
- package/kits/rpg-2d/editors/FileBrowser.tsx +0 -191
- package/kits/rpg-2d/editors/PlayOnly.tsx +0 -26
- package/kits/rpg-2d/editors/SceneEditor.tsx +0 -1093
- package/kits/rpg-2d/editors/behaviorRegistry.ts +0 -33
- package/kits/rpg-2d/editors/editorHistory.ts +0 -75
- package/kits/rpg-2d/editors/editorProps.ts +0 -10
- package/kits/rpg-2d/engine/ScenePlayer.tsx +0 -130
- package/kits/rpg-2d/engine/SceneUI.tsx +0 -74
- package/kits/rpg-2d/engine/TouchControls.tsx +0 -157
- package/kits/rpg-2d/engine/autoInspector.tsx +0 -111
- package/kits/rpg-2d/engine/drawing.ts +0 -81
- package/kits/rpg-2d/engine/files.ts +0 -215
- package/kits/rpg-2d/engine/scene.ts +0 -484
- package/kits/rpg-2d/engine/ui.module.css +0 -928
- package/kits/rpg-2d/engine/ui.tsx +0 -483
- package/kits/rpg-2d/eslint.config.js +0 -46
- package/kits/rpg-2d/index.html +0 -11
- package/kits/rpg-2d/main.tsx +0 -14
- package/kits/rpg-2d/package-lock.json +0 -3149
- package/kits/rpg-2d/package.json +0 -46
- package/kits/rpg-2d/scenes/main.scene +0 -203
- package/kits/rpg-2d/tsconfig.json +0 -17
- package/kits/rpg-2d/vite-env.d.ts +0 -7
- package/kits/rpg-2d/vite.config.js +0 -1
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
const CARD_WIDTH = 500;
|
|
2
|
-
const CARD_HEIGHT = 700;
|
|
3
|
-
|
|
4
|
-
export const cardSize = { width: CARD_WIDTH, height: CARD_HEIGHT };
|
|
5
|
-
|
|
6
|
-
export class SceneRuntime {
|
|
7
|
-
constructor(sceneData, behaviors, drawings) {
|
|
8
|
-
this.behaviors = new Map(behaviors.map((Behavior) => [Behavior.behaviorName, Behavior]));
|
|
9
|
-
this.drawings = drawings;
|
|
10
|
-
this.time = 0;
|
|
11
|
-
this.keys = new Set();
|
|
12
|
-
this.pointer = { x: 0, y: 0, down: false };
|
|
13
|
-
this.data = { actors: [] };
|
|
14
|
-
this.actors = new Map();
|
|
15
|
-
this.camera = undefined;
|
|
16
|
-
this.status = undefined;
|
|
17
|
-
this.load(sceneData);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
load(sceneData) {
|
|
21
|
-
this.data = structuredClone(sceneData);
|
|
22
|
-
this.actors = new Map();
|
|
23
|
-
for (const actor of this.data.actors ?? []) {
|
|
24
|
-
actor.runtime = {};
|
|
25
|
-
this.actors.set(actor.id, actor);
|
|
26
|
-
for (const [behaviorName, Behavior] of this.behaviors) {
|
|
27
|
-
const component = actor.components[behaviorName];
|
|
28
|
-
if (component) {
|
|
29
|
-
actor.components[behaviorName] = {
|
|
30
|
-
...Behavior.defaultProps,
|
|
31
|
-
...component,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
clone() {
|
|
39
|
-
return new SceneRuntime(this.serialize(), [...this.behaviors.values()], this.drawings);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
serialize() {
|
|
43
|
-
return structuredClone(this.data);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
getActor(actorId) {
|
|
47
|
-
return this.actors.get(actorId);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
getActors() {
|
|
51
|
-
return [...this.actors.values()].sort((a, b) => {
|
|
52
|
-
const az = getLayout(a)?.z ?? 0;
|
|
53
|
-
const bz = getLayout(b)?.z ?? 0;
|
|
54
|
-
return az - bz;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
getComponent(actor, behaviorName) {
|
|
59
|
-
return actor?.components?.[behaviorName] ?? null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
actorWith(behaviorName) {
|
|
63
|
-
for (const actor of this.actors.values()) {
|
|
64
|
-
if (actor.components?.[behaviorName]) return actor;
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
actorsWith(behaviorName) {
|
|
70
|
-
const out = [];
|
|
71
|
-
for (const actor of this.getActors()) {
|
|
72
|
-
if (actor.components?.[behaviorName]) out.push(actor);
|
|
73
|
-
}
|
|
74
|
-
return out;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
colliderRect(actorOrId) {
|
|
78
|
-
const actor = typeof actorOrId === 'string' ? this.getActor(actorOrId) : actorOrId;
|
|
79
|
-
return getColliderRect(actor);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
overlaps(first, second) {
|
|
83
|
-
const a = this.colliderRect(first);
|
|
84
|
-
const b = this.colliderRect(second);
|
|
85
|
-
return Boolean(a && b && intersects(a, b));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
spawnActor(actor) {
|
|
89
|
-
const id = actor.id ?? mintActorId(new Set(this.actors.keys()));
|
|
90
|
-
const components = {};
|
|
91
|
-
for (const [name, props] of Object.entries(actor.components ?? {})) {
|
|
92
|
-
const Behavior = this.behaviors.get(name);
|
|
93
|
-
components[name] = Behavior ? { ...Behavior.defaultProps, ...props } : { ...props };
|
|
94
|
-
}
|
|
95
|
-
const next = { ...actor, id, components, runtime: {} };
|
|
96
|
-
this.data.actors.push(next);
|
|
97
|
-
this.actors.set(id, next);
|
|
98
|
-
return next;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
despawnActor(actorId) {
|
|
102
|
-
const actor = this.actors.get(actorId);
|
|
103
|
-
if (!actor) return false;
|
|
104
|
-
this.actors.delete(actorId);
|
|
105
|
-
const idx = this.data.actors.findIndex((a) => a.id === actorId);
|
|
106
|
-
if (idx !== -1) this.data.actors.splice(idx, 1);
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Translate a screen-space pointer event into the scene's world-space
|
|
111
|
-
// `pointer`. The runtime owns this mapping (it knows the camera) so every
|
|
112
|
-
// input path -- the standalone ScenePlayer and the editor's play mode --
|
|
113
|
-
// agree on it; behaviors read `scene.pointer` in world coordinates. Pass
|
|
114
|
-
// `down` to also update the press state.
|
|
115
|
-
setPointerFromScreen(canvas, clientX, clientY, down) {
|
|
116
|
-
const point = screenToCard(canvas, clientX, clientY);
|
|
117
|
-
this.pointer.x = point.x + (this.camera?.x ?? 0);
|
|
118
|
-
this.pointer.y = point.y + (this.camera?.y ?? 0);
|
|
119
|
-
if (down !== undefined) this.pointer.down = down;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
update(dt) {
|
|
123
|
-
this.time += dt;
|
|
124
|
-
for (const actor of this.getActors()) {
|
|
125
|
-
this.forEachBehavior(actor, (instance) => instance.update?.(actor, this, dt));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
forEachBehavior(actor, callback) {
|
|
130
|
-
for (const [behaviorName, props] of Object.entries(actor.components)) {
|
|
131
|
-
if (!props) continue;
|
|
132
|
-
const Behavior = this.behaviors.get(behaviorName);
|
|
133
|
-
if (!Behavior) continue;
|
|
134
|
-
callback(new Behavior(props));
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
draw(ctx, options = {}) {
|
|
139
|
-
ctx.clearRect(0, 0, CARD_WIDTH, CARD_HEIGHT);
|
|
140
|
-
ctx.fillStyle = this.data.background ?? '#0f2a1d';
|
|
141
|
-
ctx.fillRect(0, 0, CARD_WIDTH, CARD_HEIGHT);
|
|
142
|
-
|
|
143
|
-
ctx.save();
|
|
144
|
-
if (options.useCamera && this.camera) {
|
|
145
|
-
ctx.translate(-Math.round(this.camera.x ?? 0), -Math.round(this.camera.y ?? 0));
|
|
146
|
-
}
|
|
147
|
-
for (const actor of this.getActors()) {
|
|
148
|
-
// Per-actor transform: Layout's `rotation` (degrees) rotates every draw
|
|
149
|
-
// behavior of the actor about its center.
|
|
150
|
-
ctx.save();
|
|
151
|
-
applyLayoutRotation(ctx, getLayout(actor));
|
|
152
|
-
if (options.editPlaceholders) drawEditPlaceholder(ctx, actor);
|
|
153
|
-
this.forEachBehavior(actor, (instance) => instance.draw?.(actor, this, ctx, options));
|
|
154
|
-
ctx.restore();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (options.showGrid) drawGrid(ctx);
|
|
158
|
-
if (options.selectedActorIds) {
|
|
159
|
-
for (const id of options.selectedActorIds) {
|
|
160
|
-
const actor = this.getActor(id);
|
|
161
|
-
if (actor) drawSelection(ctx, actor);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (options.marquee) drawMarquee(ctx, options.marquee);
|
|
165
|
-
ctx.restore();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
actorAt(x, y) {
|
|
169
|
-
const actors = this.getActors().slice().reverse();
|
|
170
|
-
for (const actor of actors) {
|
|
171
|
-
const layout = getLayout(actor);
|
|
172
|
-
if (!layout) continue;
|
|
173
|
-
if (
|
|
174
|
-
x >= layout.x &&
|
|
175
|
-
x <= layout.x + layout.width &&
|
|
176
|
-
y >= layout.y &&
|
|
177
|
-
y <= layout.y + layout.height
|
|
178
|
-
) {
|
|
179
|
-
return actor;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
actorIdsInRect(rect) {
|
|
186
|
-
const minX = rect.x;
|
|
187
|
-
const minY = rect.y;
|
|
188
|
-
const maxX = rect.x + rect.width;
|
|
189
|
-
const maxY = rect.y + rect.height;
|
|
190
|
-
const ids = [];
|
|
191
|
-
for (const actor of this.getActors()) {
|
|
192
|
-
const layout = getLayout(actor);
|
|
193
|
-
if (!layout) continue;
|
|
194
|
-
if (
|
|
195
|
-
layout.x + layout.width >= minX &&
|
|
196
|
-
layout.x <= maxX &&
|
|
197
|
-
layout.y + layout.height >= minY &&
|
|
198
|
-
layout.y <= maxY
|
|
199
|
-
) {
|
|
200
|
-
ids.push(actor.id);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return ids;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function makeScene(sceneData, behaviors, drawings) {
|
|
208
|
-
return new SceneRuntime(sceneData, behaviors, drawings);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export function setActorComponent(sceneData, actorId, behaviorName, nextProps) {
|
|
212
|
-
const next = structuredClone(sceneData);
|
|
213
|
-
const actor = next.actors.find((candidate) => candidate.id === actorId);
|
|
214
|
-
if (!actor) return sceneData;
|
|
215
|
-
actor.components[behaviorName] = {
|
|
216
|
-
...(actor.components[behaviorName] ?? {}),
|
|
217
|
-
...nextProps,
|
|
218
|
-
};
|
|
219
|
-
return next;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function removeActorComponent(sceneData, actorId, behaviorName) {
|
|
223
|
-
const next = structuredClone(sceneData);
|
|
224
|
-
const actor = next.actors.find((candidate) => candidate.id === actorId);
|
|
225
|
-
if (!actor || !(behaviorName in actor.components)) return sceneData;
|
|
226
|
-
delete actor.components[behaviorName];
|
|
227
|
-
return next;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export function moveActors(sceneData, actorIds, dx, dy) {
|
|
231
|
-
if (actorIds.length === 0) return sceneData;
|
|
232
|
-
const next = structuredClone(sceneData);
|
|
233
|
-
let changed = false;
|
|
234
|
-
for (const id of actorIds) {
|
|
235
|
-
const actor = next.actors.find((candidate) => candidate.id === id);
|
|
236
|
-
const layout = actor ? actor.components.Layout : null;
|
|
237
|
-
if (!actor || !layout) continue;
|
|
238
|
-
actor.components.Layout = {
|
|
239
|
-
...layout,
|
|
240
|
-
x: Math.round(layout.x + dx),
|
|
241
|
-
y: Math.round(layout.y + dy),
|
|
242
|
-
};
|
|
243
|
-
changed = true;
|
|
244
|
-
}
|
|
245
|
-
return changed ? next : sceneData;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function removeActors(sceneData, actorIds) {
|
|
249
|
-
if (actorIds.length === 0) return sceneData;
|
|
250
|
-
const toRemove = new Set(actorIds);
|
|
251
|
-
const next = structuredClone(sceneData);
|
|
252
|
-
next.actors = next.actors.filter((actor) => !toRemove.has(actor.id));
|
|
253
|
-
return next.actors.length === sceneData.actors.length ? sceneData : next;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function duplicateActors(sceneData, actorIds) {
|
|
257
|
-
if (actorIds.length === 0) return { sceneData, newIds: [] };
|
|
258
|
-
const next = structuredClone(sceneData);
|
|
259
|
-
const existingIds = new Set(next.actors.map((actor) => actor.id));
|
|
260
|
-
const newIds = [];
|
|
261
|
-
for (const id of actorIds) {
|
|
262
|
-
const source = next.actors.find((candidate) => candidate.id === id);
|
|
263
|
-
if (!source) continue;
|
|
264
|
-
const copy = structuredClone(source);
|
|
265
|
-
copy.id = mintActorId(existingIds);
|
|
266
|
-
existingIds.add(copy.id);
|
|
267
|
-
const layout = copy.components.Layout;
|
|
268
|
-
if (layout) {
|
|
269
|
-
copy.components.Layout = {
|
|
270
|
-
...layout,
|
|
271
|
-
x: Math.round(layout.x + 1),
|
|
272
|
-
y: Math.round(layout.y + 1),
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
next.actors.push(copy);
|
|
276
|
-
newIds.push(copy.id);
|
|
277
|
-
}
|
|
278
|
-
return { sceneData: next, newIds };
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export function addActor(sceneData, _files, drawings) {
|
|
282
|
-
const next = structuredClone(sceneData);
|
|
283
|
-
const existingIds = new Set(next.actors.map((actor) => actor.id));
|
|
284
|
-
const id = mintActorId(existingIds);
|
|
285
|
-
const width = 64;
|
|
286
|
-
const height = 64;
|
|
287
|
-
const layout = {
|
|
288
|
-
x: Math.round((CARD_WIDTH - width) / 2),
|
|
289
|
-
y: Math.round((CARD_HEIGHT - height) / 2),
|
|
290
|
-
width,
|
|
291
|
-
height,
|
|
292
|
-
z: 0,
|
|
293
|
-
rotation: 0,
|
|
294
|
-
};
|
|
295
|
-
const components = { Layout: layout };
|
|
296
|
-
const drawingPath = Object.keys(drawings).sort()[0];
|
|
297
|
-
if (drawingPath) {
|
|
298
|
-
components.Drawing = { file: drawingPath };
|
|
299
|
-
}
|
|
300
|
-
next.actors.push({ id, components });
|
|
301
|
-
return { sceneData: next, newId: id };
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function mintActorId(existing) {
|
|
305
|
-
for (let attempt = 0; attempt < 64; attempt++) {
|
|
306
|
-
const candidate = Math.floor(Math.random() * 0xffffffff)
|
|
307
|
-
.toString(16)
|
|
308
|
-
.padStart(8, '0');
|
|
309
|
-
if (!existing.has(candidate)) return candidate;
|
|
310
|
-
}
|
|
311
|
-
return `${Date.now().toString(16)}${Math.floor(Math.random() * 0xffff).toString(16)}`;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
export function screenToCard(canvas, clientX, clientY) {
|
|
315
|
-
const rect = canvas.getBoundingClientRect();
|
|
316
|
-
return {
|
|
317
|
-
x: ((clientX - rect.left) / rect.width) * CARD_WIDTH,
|
|
318
|
-
y: ((clientY - rect.top) / rect.height) * CARD_HEIGHT,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export function configureSceneCanvas(canvas, ctx = canvas.getContext('2d')) {
|
|
323
|
-
if (!ctx) return null;
|
|
324
|
-
const rect = canvas.getBoundingClientRect();
|
|
325
|
-
const dpr = window.devicePixelRatio || 1;
|
|
326
|
-
const displayWidth = rect.width || CARD_WIDTH;
|
|
327
|
-
const displayHeight = rect.height || CARD_HEIGHT;
|
|
328
|
-
const pixelWidth = Math.max(1, Math.round(displayWidth * dpr));
|
|
329
|
-
const pixelHeight = Math.max(1, Math.round(displayHeight * dpr));
|
|
330
|
-
if (canvas.width !== pixelWidth) canvas.width = pixelWidth;
|
|
331
|
-
if (canvas.height !== pixelHeight) canvas.height = pixelHeight;
|
|
332
|
-
ctx.setTransform(pixelWidth / CARD_WIDTH, 0, 0, pixelHeight / CARD_HEIGHT, 0, 0);
|
|
333
|
-
ctx.imageSmoothingEnabled = true;
|
|
334
|
-
return ctx;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function drawGrid(ctx) {
|
|
338
|
-
ctx.save();
|
|
339
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)';
|
|
340
|
-
ctx.lineWidth = 1;
|
|
341
|
-
for (let x = 0; x <= CARD_WIDTH; x += 25) {
|
|
342
|
-
ctx.beginPath();
|
|
343
|
-
ctx.moveTo(x + 0.5, 0);
|
|
344
|
-
ctx.lineTo(x + 0.5, CARD_HEIGHT);
|
|
345
|
-
ctx.stroke();
|
|
346
|
-
}
|
|
347
|
-
for (let y = 0; y <= CARD_HEIGHT; y += 25) {
|
|
348
|
-
ctx.beginPath();
|
|
349
|
-
ctx.moveTo(0, y + 0.5);
|
|
350
|
-
ctx.lineTo(CARD_WIDTH, y + 0.5);
|
|
351
|
-
ctx.stroke();
|
|
352
|
-
}
|
|
353
|
-
ctx.restore();
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function drawEditPlaceholder(ctx, actor) {
|
|
357
|
-
const layout = getLayout(actor);
|
|
358
|
-
if (!layout) return;
|
|
359
|
-
ctx.save();
|
|
360
|
-
ctx.fillStyle = 'rgba(255, 255, 255, 0.04)';
|
|
361
|
-
ctx.fillRect(layout.x, layout.y, layout.width, layout.height);
|
|
362
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.25)';
|
|
363
|
-
ctx.lineWidth = 1;
|
|
364
|
-
ctx.strokeRect(layout.x + 0.5, layout.y + 0.5, layout.width - 1, layout.height - 1);
|
|
365
|
-
ctx.restore();
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function drawMarquee(ctx, rect) {
|
|
369
|
-
if (rect.width === 0 || rect.height === 0) return;
|
|
370
|
-
ctx.save();
|
|
371
|
-
ctx.fillStyle = 'rgba(255, 255, 255, 0.12)';
|
|
372
|
-
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
373
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.85)';
|
|
374
|
-
ctx.lineWidth = 1;
|
|
375
|
-
ctx.setLineDash([4, 3]);
|
|
376
|
-
ctx.strokeRect(rect.x + 0.5, rect.y + 0.5, rect.width - 1, rect.height - 1);
|
|
377
|
-
ctx.restore();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function drawSelection(ctx, actor) {
|
|
381
|
-
const layout = getLayout(actor);
|
|
382
|
-
if (!layout) return;
|
|
383
|
-
ctx.save();
|
|
384
|
-
applyLayoutRotation(ctx, layout);
|
|
385
|
-
ctx.strokeStyle = '#fff';
|
|
386
|
-
ctx.lineWidth = 2;
|
|
387
|
-
ctx.setLineDash([6, 4]);
|
|
388
|
-
ctx.strokeRect(layout.x + 1, layout.y + 1, layout.width - 2, layout.height - 2);
|
|
389
|
-
ctx.restore();
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function applyLayoutRotation(ctx, layout) {
|
|
393
|
-
const rotation = layout?.rotation ?? 0;
|
|
394
|
-
if (!rotation || !layout) return;
|
|
395
|
-
const cx = layout.x + layout.width / 2;
|
|
396
|
-
const cy = layout.y + layout.height / 2;
|
|
397
|
-
ctx.translate(cx, cy);
|
|
398
|
-
ctx.rotate((rotation * Math.PI) / 180);
|
|
399
|
-
ctx.translate(-cx, -cy);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function getLayout(actor) {
|
|
403
|
-
return actor?.components?.Layout;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function getColliderRect(actor) {
|
|
407
|
-
const layout = getLayout(actor);
|
|
408
|
-
const collider = actor?.components?.Collider;
|
|
409
|
-
if (!layout || !collider) return null;
|
|
410
|
-
return {
|
|
411
|
-
x: layout.x + (collider.offsetX ?? 0),
|
|
412
|
-
y: layout.y + (collider.offsetY ?? 0),
|
|
413
|
-
width: collider.width ?? layout.width,
|
|
414
|
-
height: collider.height ?? layout.height,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function intersects(a, b) {
|
|
419
|
-
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
420
|
-
}
|