@mertdogar/phaser-procedural-walls 0.3.0 → 0.4.0

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Wallcraft Prototype</title>
7
- <script type="module" crossorigin src="/assets/index-DvPiYA-G.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-BEDA_QpG.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-BVmRKDUb.css">
9
9
  </head>
10
10
  <body>
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { WallMapPlugin } from "./WallMapPlugin";
2
2
  export { WallMap } from "./WallMap";
3
+ export { WallEditor } from "./WallEditor";
4
+ export type { WallEditorCallbacks, WallEditorOptions, WallEditorState, WallEditorTool } from "./WallEditor";
3
5
  export { resolveWalls } from "./geometry";
4
6
  export type * from "./types";
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
- var g = Object.defineProperty;
2
- var x = (n, t, i) => t in n ? g(n, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : n[t] = i;
3
- var a = (n, t, i) => x(n, typeof t != "symbol" ? t + "" : t, i);
4
- import b from "phaser";
5
- import { resolveWalls as p } from "./geometry.js";
6
- class R {
1
+ var D = Object.defineProperty;
2
+ var p = (w, t, i) => t in w ? D(w, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : w[t] = i;
3
+ var r = (w, t, i) => p(w, typeof t != "symbol" ? t + "" : t, i);
4
+ import a from "phaser";
5
+ import { resolveWalls as g } from "./geometry.js";
6
+ class m {
7
7
  constructor(t, i) {
8
- a(this, "scene");
9
- a(this, "containers", []);
10
- a(this, "bodies", null);
11
- a(this, "config");
8
+ r(this, "scene");
9
+ r(this, "containers", []);
10
+ r(this, "bodies", null);
11
+ r(this, "config");
12
12
  this.scene = t, this.config = i, this.build();
13
13
  }
14
14
  setWalls(t) {
@@ -26,47 +26,245 @@ class R {
26
26
  this.containers = [], (t = this.bodies) == null || t.destroy(!0), this.bodies = null;
27
27
  }
28
28
  build() {
29
- const { presets: t, walls: i, collide: s } = this.config, o = p(i, t);
30
- s && (this.bodies = this.scene.physics.add.staticGroup());
31
- for (const l of o)
32
- this.containers.push(this.drawWall(l, t[l.spec.preset])), this.bodies && this.addBody(l);
29
+ const { presets: t, walls: i, collide: e } = this.config, n = g(i, t);
30
+ e && (this.bodies = this.scene.physics.add.staticGroup());
31
+ for (const s of n)
32
+ this.containers.push(this.drawWall(s, t[s.spec.preset])), this.bodies && this.addBody(s);
33
33
  }
34
34
  drawWall(t, i) {
35
- const { body: s, lip: o, bodyPieces: l, lipPieces: r, windows: f, sills: w } = t, c = this.scene.add.container(0, 0), h = this.scene.add.graphics(), d = this.scene.add.graphics(), y = i.edgeWidth ?? 2;
36
- for (const e of l) this.fillRect(c, h, e, i.fill, i.texture);
37
- for (const e of r) this.fillRect(c, h, e, i.lipFill ?? i.fill, i.lipTexture);
38
- const u = i.windowFill ?? 4030344;
39
- for (const e of f)
40
- h.fillStyle(u, i.windowAlpha ?? 0.5), h.fillRect(e.x, e.y, e.w, e.h);
41
- c.add(h);
42
- for (const e of w) this.fillRect(c, d, e, i.fill, i.texture);
43
- for (const e of w) d.lineStyle(1, i.edge).lineBetween(e.x, e.y, e.x + e.w, e.y);
35
+ const { body: e, lip: n, bodyPieces: s, lipPieces: l, windows: h, sills: f } = t, d = this.scene.add.container(0, 0), u = this.scene.add.graphics(), c = this.scene.add.graphics(), x = i.edgeWidth ?? 2;
36
+ for (const o of s) this.fillRect(d, u, o, i.fill, i.texture);
37
+ for (const o of l) this.fillRect(d, u, o, i.lipFill ?? i.fill, i.lipTexture);
38
+ const y = i.windowFill ?? 4030344;
39
+ for (const o of h)
40
+ u.fillStyle(y, i.windowAlpha ?? 0.5), u.fillRect(o.x, o.y, o.w, o.h);
41
+ d.add(u);
42
+ for (const o of f) this.fillRect(d, c, o, i.fill, i.texture);
43
+ for (const o of f) c.lineStyle(1, i.edge).lineBetween(o.x, o.y, o.x + o.w, o.y);
44
44
  if (i.windowFrame !== void 0)
45
- for (const e of f) d.lineStyle(1, i.windowFrame).strokeRect(e.x, e.y, e.w, e.h);
46
- return d.lineStyle(y, i.edge), d.strokeRect(s.x, s.y, s.w, s.h), o && d.lineStyle(y, i.edge).strokeRect(o.x, o.y, o.w, o.h), c.add(d), c.setDepth(t.depth), c;
45
+ for (const o of h) c.lineStyle(1, i.windowFrame).strokeRect(o.x, o.y, o.w, o.h);
46
+ return c.lineStyle(x, i.edge), c.strokeRect(e.x, e.y, e.w, e.h), n && c.lineStyle(x, i.edge).strokeRect(n.x, n.y, n.w, n.h), d.add(c), d.setDepth(t.depth), d;
47
47
  }
48
- fillRect(t, i, s, o, l) {
49
- if (!l) {
50
- i.fillStyle(o).fillRect(s.x, s.y, s.w, s.h);
48
+ fillRect(t, i, e, n, s) {
49
+ if (!s) {
50
+ i.fillStyle(n).fillRect(e.x, e.y, e.w, e.h);
51
51
  return;
52
52
  }
53
- const r = this.scene.add.tileSprite(s.x + s.w / 2, s.y + s.h / 2, s.w, s.h, l);
54
- r.setTilePosition(s.x, s.y), t.add(r);
53
+ const l = this.scene.add.tileSprite(e.x + e.w / 2, e.y + e.h / 2, e.w, e.h, s);
54
+ l.setTilePosition(e.x, e.y), t.add(l);
55
55
  }
56
56
  addBody(t) {
57
- const { collider: i } = t, s = this.scene.add.zone(i.x + i.w / 2, i.y + i.h / 2, i.w, i.h);
58
- this.scene.physics.add.existing(s, !0), this.bodies.add(s);
57
+ const { collider: i } = t, e = this.scene.add.zone(i.x + i.w / 2, i.y + i.h / 2, i.w, i.h);
58
+ this.scene.physics.add.existing(e, !0), this.bodies.add(e);
59
59
  }
60
60
  }
61
- class P extends b.Plugins.ScenePlugin {
62
- constructor(t, i, s) {
63
- super(t, i, s), typeof t.add.wallMap != "function" && i.registerGameObject("wallMap", function(o) {
64
- return new R(this.scene, o);
61
+ class P extends a.Plugins.ScenePlugin {
62
+ constructor(t, i, e) {
63
+ super(t, i, e), typeof t.add.wallMap != "function" && i.registerGameObject("wallMap", function(n) {
64
+ return new m(this.scene, n);
65
65
  });
66
66
  }
67
67
  }
68
+ class W {
69
+ constructor(t, i) {
70
+ r(this, "configData");
71
+ r(this, "selectedIndex");
72
+ r(this, "tool");
73
+ r(this, "enabled");
74
+ r(this, "camera");
75
+ r(this, "gridSize");
76
+ r(this, "overlay");
77
+ r(this, "callbacks");
78
+ r(this, "newWall");
79
+ r(this, "drawStart", null);
80
+ r(this, "pointerPosition", null);
81
+ r(this, "anchorDrag", null);
82
+ r(this, "windowDrag", null);
83
+ var e;
84
+ if (this.scene = t, this.gridSize = i.gridSize ?? 32, !Number.isFinite(this.gridSize) || this.gridSize <= 0) throw new Error("gridSize must be positive and finite");
85
+ this.camera = i.camera ?? t.cameras.main, this.configData = i.config, this.selectedIndex = i.selectedIndex, this.tool = i.tool, this.enabled = i.enabled ?? !0, this.callbacks = i, this.newWall = { ...i.newWall, thickness: ((e = i.newWall) == null ? void 0 : e.thickness) ?? 16 }, this.overlay = t.add.graphics().setDepth(i.overlayDepth ?? 1e4), t.input.on("pointerdown", this.handlePointerDown, this), t.input.on("pointermove", this.handlePointerMove, this), t.input.on("pointerup", this.handlePointerUp, this), t.input.on("pointerupoutside", this.handlePointerUp, this), t.events.once("shutdown", this.destroy, this), this.refresh();
86
+ }
87
+ setState(t) {
88
+ (t.config !== this.configData || t.selectedIndex !== this.selectedIndex || t.tool !== this.tool || (t.enabled ?? !0) !== this.enabled) && this.cancel(), this.configData = t.config, this.selectedIndex = t.selectedIndex, this.tool = t.tool, this.enabled = t.enabled ?? !0, this.refresh();
89
+ }
90
+ setNewWall(t) {
91
+ this.newWall = { ...t, thickness: t.thickness ?? 16 };
92
+ }
93
+ get dragging() {
94
+ return !!(this.drawStart || this.anchorDrag || this.windowDrag);
95
+ }
96
+ cancel() {
97
+ this.drawStart = null, this.pointerPosition = null, this.anchorDrag = null, this.windowDrag = null, this.scene.input.setDefaultCursor("default"), this.refresh();
98
+ }
99
+ destroy() {
100
+ this.scene.input.off("pointerdown", this.handlePointerDown, this), this.scene.input.off("pointermove", this.handlePointerMove, this), this.scene.input.off("pointerup", this.handlePointerUp, this), this.scene.input.off("pointerupoutside", this.handlePointerUp, this), this.scene.events.off("shutdown", this.destroy, this), this.scene.input.setDefaultCursor("default"), this.overlay.destroy();
101
+ }
102
+ handlePointerDown(t) {
103
+ if (!this.enabled || !t.leftButtonDown()) return;
104
+ t.updateWorldPoint(this.camera);
105
+ const i = this.snapPoint(t.worldX, t.worldY);
106
+ if (this.tool === "select") {
107
+ const e = this.findAnchor(t.worldX, t.worldY);
108
+ if (e && this.selectedIndex !== null) {
109
+ this.anchorDrag = {
110
+ index: this.selectedIndex,
111
+ endpoint: e,
112
+ wall: { ...this.configData.walls[this.selectedIndex] }
113
+ }, this.scene.input.setDefaultCursor("grabbing");
114
+ return;
115
+ }
116
+ const n = this.findWindow(t.worldX, t.worldY);
117
+ if (n !== null && this.selectedIndex !== null) {
118
+ this.windowDrag = {
119
+ index: this.selectedIndex,
120
+ windowIndex: n,
121
+ wall: { ...this.configData.walls[this.selectedIndex] }
122
+ }, this.scene.input.setDefaultCursor("grabbing");
123
+ return;
124
+ }
125
+ this.callbacks.onSelectWall(this.findWall(t.worldX, t.worldY));
126
+ return;
127
+ }
128
+ this.drawStart = i, this.pointerPosition = i.clone(), this.refresh();
129
+ }
130
+ handlePointerMove(t) {
131
+ if (this.enabled) {
132
+ if (t.updateWorldPoint(this.camera), this.windowDrag && t.isDown) {
133
+ this.windowDrag.wall = this.moveWindow(
134
+ this.windowDrag.wall,
135
+ this.windowDrag.windowIndex,
136
+ t.worldX,
137
+ t.worldY
138
+ ), this.refresh();
139
+ return;
140
+ }
141
+ if (this.anchorDrag && t.isDown) {
142
+ this.anchorDrag.wall = this.moveEndpoint(
143
+ this.anchorDrag.wall,
144
+ this.anchorDrag.endpoint,
145
+ this.snapPoint(t.worldX, t.worldY)
146
+ ), this.refresh();
147
+ return;
148
+ }
149
+ if (this.tool === "select" && !t.isDown)
150
+ if (this.findWindow(t.worldX, t.worldY) !== null && this.selectedIndex !== null) {
151
+ const e = this.configData.walls[this.selectedIndex];
152
+ this.scene.input.setDefaultCursor(e.y1 === e.y2 ? "ew-resize" : "ns-resize");
153
+ } else
154
+ this.scene.input.setDefaultCursor(this.findAnchor(t.worldX, t.worldY) ? "grab" : "default");
155
+ !this.drawStart || !t.isDown || (this.pointerPosition = this.axisLock(this.drawStart, this.snapPoint(t.worldX, t.worldY)), this.refresh());
156
+ }
157
+ }
158
+ handlePointerUp(t) {
159
+ if (!this.enabled) return;
160
+ if (t.updateWorldPoint(this.camera), this.windowDrag) {
161
+ const n = this.windowDrag, s = this.moveWindow(n.wall, n.windowIndex, t.worldX, t.worldY);
162
+ this.windowDrag = null, this.scene.input.setDefaultCursor("default"), this.callbacks.onUpdateWall(n.index, { windows: s.windows }), this.refresh();
163
+ return;
164
+ }
165
+ if (this.anchorDrag) {
166
+ const n = this.anchorDrag, s = this.moveEndpoint(
167
+ n.wall,
168
+ n.endpoint,
169
+ this.snapPoint(t.worldX, t.worldY)
170
+ );
171
+ this.anchorDrag = null, this.scene.input.setDefaultCursor("default"), a.Math.Distance.Between(s.x1, s.y1, s.x2, s.y2) >= this.gridSize && this.callbacks.onUpdateWall(n.index, {
172
+ x1: s.x1,
173
+ y1: s.y1,
174
+ x2: s.x2,
175
+ y2: s.y2
176
+ }), this.refresh();
177
+ return;
178
+ }
179
+ if (!this.drawStart) return;
180
+ const i = this.axisLock(this.drawStart, this.snapPoint(t.worldX, t.worldY)), e = this.drawStart;
181
+ if (this.drawStart = null, this.pointerPosition = null, a.Math.Distance.Between(e.x, e.y, i.x, i.y) >= this.gridSize) {
182
+ const n = this.newWall.preset ?? (Object.hasOwn(this.configData.presets, "interior") ? "interior" : Object.keys(this.configData.presets)[0]);
183
+ if (!n || !Object.hasOwn(this.configData.presets, n)) throw new Error("Choose an existing wall preset before drawing");
184
+ this.callbacks.onAddWall({
185
+ x1: e.x,
186
+ y1: e.y,
187
+ x2: i.x,
188
+ y2: i.y,
189
+ ...this.newWall,
190
+ preset: n
191
+ });
192
+ }
193
+ this.refresh();
194
+ }
195
+ refresh() {
196
+ var i, e, n;
197
+ if (!this.overlay || (this.overlay.clear(), !this.enabled)) return;
198
+ const t = ((i = this.windowDrag) == null ? void 0 : i.wall) ?? ((e = this.anchorDrag) == null ? void 0 : e.wall) ?? (this.selectedIndex === null ? null : this.configData.walls[this.selectedIndex]);
199
+ if (t) {
200
+ const s = 8 / this.camera.zoom;
201
+ this.overlay.lineStyle(4, 2980428, 1).lineBetween(t.x1, t.y1, t.x2, t.y2), this.overlay.fillStyle(16777215, 1).fillCircle(t.x1, t.y1, s).fillCircle(t.x2, t.y2, s), this.overlay.lineStyle(3 / this.camera.zoom, 2980428, 1).strokeCircle(t.x1, t.y1, s).strokeCircle(t.x2, t.y2, s);
202
+ for (const [l, h] of this.getWindowRects(t).entries()) {
203
+ const f = ((n = this.windowDrag) == null ? void 0 : n.windowIndex) === l;
204
+ this.overlay.fillStyle(f ? 2980428 : 16777215, f ? 0.35 : 0.75).fillRect(h.x, h.y, h.w, h.h), this.overlay.lineStyle(3, 2980428, 1).strokeRect(h.x, h.y, h.w, h.h);
205
+ }
206
+ }
207
+ this.drawStart && this.pointerPosition && (this.overlay.lineStyle(4, 14916150, 0.95).lineBetween(
208
+ this.drawStart.x,
209
+ this.drawStart.y,
210
+ this.pointerPosition.x,
211
+ this.pointerPosition.y
212
+ ), this.overlay.fillStyle(14916150).fillCircle(this.drawStart.x, this.drawStart.y, 5));
213
+ }
214
+ findWall(t, i) {
215
+ let e = null;
216
+ for (const [n, s] of this.configData.walls.entries()) {
217
+ const l = s.x1 === s.x2 ? Math.hypot(t - s.x1, i - a.Math.Clamp(i, Math.min(s.y1, s.y2), Math.max(s.y1, s.y2))) : Math.hypot(t - a.Math.Clamp(t, Math.min(s.x1, s.x2), Math.max(s.x1, s.x2)), i - s.y1);
218
+ l <= s.thickness / 2 + 10 / this.camera.zoom && (!e || l < e.distance) && (e = { index: n, distance: l });
219
+ }
220
+ return (e == null ? void 0 : e.index) ?? null;
221
+ }
222
+ findAnchor(t, i) {
223
+ if (this.selectedIndex === null) return null;
224
+ const e = this.configData.walls[this.selectedIndex];
225
+ if (!e) return null;
226
+ const n = a.Math.Distance.Between(t, i, e.x1, e.y1), s = a.Math.Distance.Between(t, i, e.x2, e.y2);
227
+ return Math.min(n, s) > 16 / this.camera.zoom ? null : n <= s ? "start" : "end";
228
+ }
229
+ findWindow(t, i) {
230
+ if (this.selectedIndex === null) return null;
231
+ const e = this.configData.walls[this.selectedIndex];
232
+ if (!e) return null;
233
+ const n = 10 / this.camera.zoom, s = this.getWindowRects(e).findIndex((l) => t >= l.x - n && t <= l.x + l.w + n && i >= l.y - n && i <= l.y + l.h + n);
234
+ return s === -1 ? null : s;
235
+ }
236
+ getWindowRects(t) {
237
+ var i;
238
+ return ((i = g([t], this.configData.presets)[0]) == null ? void 0 : i.windows) ?? [];
239
+ }
240
+ moveWindow(t, i, e, n) {
241
+ var y;
242
+ const s = ((y = t.windows) == null ? void 0 : y.map((o) => ({ ...o }))) ?? [], l = s[i];
243
+ if (!l) return t;
244
+ const h = t.y1 === t.y2, f = h ? Math.sign(t.x2 - t.x1) || 1 : Math.sign(t.y2 - t.y1) || 1, d = h ? (e - t.x1) * f : (n - t.y1) * f, u = Math.abs(t.x2 - t.x1) + Math.abs(t.y2 - t.y1), c = Math.max(0, u - l.width), x = a.Math.Clamp(
245
+ Math.round((d - l.width / 2) / this.gridSize) * this.gridSize,
246
+ 0,
247
+ c
248
+ );
249
+ return s[i] = { ...l, offset: x }, { ...t, windows: s };
250
+ }
251
+ moveEndpoint(t, i, e) {
252
+ const n = i === "start" ? new a.Math.Vector2(t.x2, t.y2) : new a.Math.Vector2(t.x1, t.y1), s = this.axisLock(n, e);
253
+ return i === "start" ? { ...t, x1: s.x, y1: s.y } : { ...t, x2: s.x, y2: s.y };
254
+ }
255
+ snapPoint(t, i) {
256
+ return new a.Math.Vector2(
257
+ Math.round(t / this.gridSize) * this.gridSize,
258
+ Math.round(i / this.gridSize) * this.gridSize
259
+ );
260
+ }
261
+ axisLock(t, i) {
262
+ return Math.abs(i.x - t.x) >= Math.abs(i.y - t.y) ? new a.Math.Vector2(i.x, t.y) : new a.Math.Vector2(t.x, i.y);
263
+ }
264
+ }
68
265
  export {
69
- R as WallMap,
266
+ W as WallEditor,
267
+ m as WallMap,
70
268
  P as WallMapPlugin,
71
- p as resolveWalls
269
+ g as resolveWalls
72
270
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mertdogar/phaser-procedural-walls",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Phaser 4 scene plugin that renders procedural top-down walls with faces, see-through windows, depth sorting and Arcade colliders from wall centerline data.",
5
5
  "keywords": [
6
6
  "phaser",
@@ -53,16 +53,6 @@
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  },
56
- "scripts": {
57
- "dev": "vite",
58
- "build": "vite build && tsc -p tsconfig.lib.json && vite build --mode editor",
59
- "editor": "node bin/cli.mjs editor",
60
- "test:cli": "node --test tests/cli.test.mjs",
61
- "test": "vitest run",
62
- "typecheck": "tsc --noEmit",
63
- "lint": "oxlint src demo tests bin",
64
- "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm test:cli"
65
- },
66
56
  "peerDependencies": {
67
57
  "phaser": "^4.0.0"
68
58
  },
@@ -88,5 +78,14 @@
88
78
  "react-dom": "^19.3.0",
89
79
  "react-resizable-panels": "^4.12.4",
90
80
  "tailwind-merge": "^3.7.0"
81
+ },
82
+ "scripts": {
83
+ "dev": "vite",
84
+ "build": "vite build && tsc -p tsconfig.lib.json && vite build --mode editor",
85
+ "editor": "node bin/cli.mjs editor",
86
+ "test:cli": "node --test tests/cli.test.mjs",
87
+ "test": "vitest run",
88
+ "typecheck": "tsc --noEmit",
89
+ "lint": "oxlint src demo tests bin"
91
90
  }
92
- }
91
+ }