@rpgjs/tiledmap 5.0.0-alpha.42 → 5.0.0-alpha.44
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/client/index.js +2 -0
- package/dist/client/index2.js +1 -10
- package/dist/client/index5.js +23 -68
- package/dist/physics.d.ts +0 -10
- package/dist/server/index2.js +2 -13
- package/dist/server/index4.js +23 -68
- package/package.json +5 -5
- package/src/client.ts +1 -15
- package/src/index.ts +5 -1
- package/src/physics.spec.ts +90 -0
- package/src/physics.ts +36 -81
- package/src/server.ts +2 -19
package/dist/client/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createModule } from "@rpgjs/common";
|
|
|
3
3
|
import { provideLoadMap } from "@rpgjs/client";
|
|
4
4
|
import { TiledParser } from "./index3.js";
|
|
5
5
|
import component from "./index4.js";
|
|
6
|
+
import { prepareTiledPhysicsData } from "./index5.js";
|
|
6
7
|
const server = null;
|
|
7
8
|
function provideTiledMap(options) {
|
|
8
9
|
return createModule("TiledMap", [
|
|
@@ -39,6 +40,7 @@ function provideTiledMap(options) {
|
|
|
39
40
|
basePath: options.basePath
|
|
40
41
|
}
|
|
41
42
|
};
|
|
43
|
+
prepareTiledPhysicsData(obj, obj);
|
|
42
44
|
if (options.onLoadMap) {
|
|
43
45
|
await options.onLoadMap(map);
|
|
44
46
|
}
|
package/dist/client/index2.js
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
import { defineModule } from "@rpgjs/common";
|
|
2
|
-
import {
|
|
2
|
+
import { prepareTiledPhysicsData } from "./index5.js";
|
|
3
3
|
const client = defineModule({
|
|
4
4
|
componentAnimations: [],
|
|
5
5
|
sceneMap: {
|
|
6
6
|
onPhysicsInit(map, context) {
|
|
7
7
|
prepareTiledPhysicsData(context?.mapData, map);
|
|
8
|
-
},
|
|
9
|
-
onPhysicsEntityAdd(map, context) {
|
|
10
|
-
attachTiledCollisionToEntity(context?.owner, map);
|
|
11
|
-
},
|
|
12
|
-
onPhysicsEntityRemove(map, context) {
|
|
13
|
-
detachTiledCollisionFromEntity(context?.owner, map);
|
|
14
|
-
},
|
|
15
|
-
onPhysicsReset(map) {
|
|
16
|
-
resetTiledCollisionHandlers(map);
|
|
17
8
|
}
|
|
18
9
|
}
|
|
19
10
|
});
|
package/dist/client/index5.js
CHANGED
|
@@ -1,72 +1,18 @@
|
|
|
1
1
|
import { MapClass } from "./index3.js";
|
|
2
|
+
const TILED_HITBOX_ID_PREFIX = "__tiled_collision__:";
|
|
2
3
|
function prepareTiledPhysicsData(mapData, map) {
|
|
3
4
|
if (!mapData?.parsedMap) {
|
|
4
5
|
return;
|
|
5
6
|
}
|
|
6
7
|
const tiledMap = new MapClass(mapData.parsedMap);
|
|
7
8
|
map.tiled = tiledMap;
|
|
8
|
-
|
|
9
|
+
const tiledHitboxes = collectBlockedTileHitboxes(tiledMap);
|
|
10
|
+
mapData.hitboxes = mergeTiledHitboxes(mapData.hitboxes, tiledHitboxes);
|
|
9
11
|
mapData.width = tiledMap.widthPx;
|
|
10
12
|
mapData.height = tiledMap.heightPx;
|
|
11
|
-
map._tiledTileWidth = tiledMap.tilewidth;
|
|
12
|
-
map._tiledTileHeight = tiledMap.tileheight;
|
|
13
|
-
map._blockedTiles = collectBlockedTiles(tiledMap);
|
|
14
13
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const entity = map.physic?.getEntityByUUID(owner.id);
|
|
20
|
-
if (!entity || typeof entity.canEnterTile !== "function") {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const unsubscribers = ensureUnsubscribers(map);
|
|
24
|
-
const previousUnsubscribe = unsubscribers.get(owner.id);
|
|
25
|
-
if (previousUnsubscribe) {
|
|
26
|
-
previousUnsubscribe();
|
|
27
|
-
unsubscribers.delete(owner.id);
|
|
28
|
-
}
|
|
29
|
-
const blockedTiles = map._blockedTiles;
|
|
30
|
-
const tiledTileWidth = map._tiledTileWidth ?? 32;
|
|
31
|
-
const tiledTileHeight = map._tiledTileHeight ?? 32;
|
|
32
|
-
const physicsTileWidth = 32;
|
|
33
|
-
const physicsTileHeight = 32;
|
|
34
|
-
const unsubscribe = entity.canEnterTile(({ x, y }) => {
|
|
35
|
-
const tiledX = Math.floor(x * physicsTileWidth / tiledTileWidth);
|
|
36
|
-
const tiledY = Math.floor(y * physicsTileHeight / tiledTileHeight);
|
|
37
|
-
return !blockedTiles.has(`${tiledX},${tiledY}`);
|
|
38
|
-
});
|
|
39
|
-
unsubscribers.set(owner.id, unsubscribe);
|
|
40
|
-
}
|
|
41
|
-
function detachTiledCollisionFromEntity(owner, map) {
|
|
42
|
-
if (!owner?.id) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const unsubscribers = map._tiledCollisionUnsubscribers;
|
|
46
|
-
if (!unsubscribers) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const unsubscribe = unsubscribers.get(owner.id);
|
|
50
|
-
if (!unsubscribe) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
unsubscribe();
|
|
54
|
-
unsubscribers.delete(owner.id);
|
|
55
|
-
}
|
|
56
|
-
function resetTiledCollisionHandlers(map) {
|
|
57
|
-
const unsubscribers = map._tiledCollisionUnsubscribers;
|
|
58
|
-
if (unsubscribers) {
|
|
59
|
-
for (const unsubscribe of unsubscribers.values()) {
|
|
60
|
-
unsubscribe();
|
|
61
|
-
}
|
|
62
|
-
unsubscribers.clear();
|
|
63
|
-
}
|
|
64
|
-
map._blockedTiles = void 0;
|
|
65
|
-
map._tiledTileWidth = void 0;
|
|
66
|
-
map._tiledTileHeight = void 0;
|
|
67
|
-
}
|
|
68
|
-
function collectBlockedTiles(tiledMap) {
|
|
69
|
-
const blockedTiles = /* @__PURE__ */ new Set();
|
|
14
|
+
function collectBlockedTileHitboxes(tiledMap) {
|
|
15
|
+
const hitboxes = [];
|
|
70
16
|
const mapWidth = tiledMap.width;
|
|
71
17
|
const mapHeight = tiledMap.height;
|
|
72
18
|
const tileWidth = tiledMap.tilewidth;
|
|
@@ -77,19 +23,28 @@ function collectBlockedTiles(tiledMap) {
|
|
|
77
23
|
populateTiles: true
|
|
78
24
|
});
|
|
79
25
|
if (tileInfo.hasCollision) {
|
|
80
|
-
|
|
26
|
+
hitboxes.push({
|
|
27
|
+
id: createTiledHitboxId(x, y),
|
|
28
|
+
x: x * tileWidth,
|
|
29
|
+
y: y * tileHeight,
|
|
30
|
+
width: tileWidth,
|
|
31
|
+
height: tileHeight
|
|
32
|
+
});
|
|
81
33
|
}
|
|
82
34
|
}
|
|
83
35
|
}
|
|
84
|
-
return
|
|
36
|
+
return hitboxes;
|
|
37
|
+
}
|
|
38
|
+
function mergeTiledHitboxes(existingHitboxes, tiledHitboxes) {
|
|
39
|
+
const preservedHitboxes = Array.isArray(existingHitboxes) ? existingHitboxes.filter((hitbox) => !isGeneratedTiledHitbox(hitbox)) : [];
|
|
40
|
+
return [...preservedHitboxes, ...tiledHitboxes];
|
|
41
|
+
}
|
|
42
|
+
function isGeneratedTiledHitbox(hitbox) {
|
|
43
|
+
return typeof hitbox?.id === "string" && hitbox.id.startsWith(TILED_HITBOX_ID_PREFIX);
|
|
85
44
|
}
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
return map._tiledCollisionUnsubscribers;
|
|
45
|
+
function createTiledHitboxId(x, y) {
|
|
46
|
+
return `${TILED_HITBOX_ID_PREFIX}${x},${y}`;
|
|
89
47
|
}
|
|
90
48
|
export {
|
|
91
|
-
|
|
92
|
-
detachTiledCollisionFromEntity,
|
|
93
|
-
prepareTiledPhysicsData,
|
|
94
|
-
resetTiledCollisionHandlers
|
|
49
|
+
prepareTiledPhysicsData
|
|
95
50
|
};
|
package/dist/physics.d.ts
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { MapClass } from '@canvasengine/tiled';
|
|
2
2
|
type AnyMap = {
|
|
3
3
|
tiled?: MapClass;
|
|
4
|
-
physic?: {
|
|
5
|
-
getEntityByUUID(id: string): any;
|
|
6
|
-
};
|
|
7
|
-
_blockedTiles?: Set<string>;
|
|
8
|
-
_tiledTileWidth?: number;
|
|
9
|
-
_tiledTileHeight?: number;
|
|
10
|
-
_tiledCollisionUnsubscribers?: Map<string, () => void>;
|
|
11
4
|
};
|
|
12
5
|
export declare function prepareTiledPhysicsData(mapData: any, map: AnyMap): void;
|
|
13
6
|
export declare function applyTiledPointEvents(mapData: any): void;
|
|
14
|
-
export declare function attachTiledCollisionToEntity(owner: any, map: AnyMap): void;
|
|
15
|
-
export declare function detachTiledCollisionFromEntity(owner: any, map: AnyMap): void;
|
|
16
|
-
export declare function resetTiledCollisionHandlers(map: AnyMap): void;
|
|
17
7
|
export {};
|
package/dist/server/index2.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineModule } from "@rpgjs/common";
|
|
2
|
-
import {
|
|
2
|
+
import { prepareTiledPhysicsData, applyTiledPointEvents } from "./index4.js";
|
|
3
3
|
const server = defineModule({
|
|
4
4
|
map: {
|
|
5
5
|
onBeforeUpdate(mapData, map) {
|
|
@@ -8,18 +8,7 @@ const server = defineModule({
|
|
|
8
8
|
return map;
|
|
9
9
|
},
|
|
10
10
|
onPhysicsInit(map, context) {
|
|
11
|
-
|
|
12
|
-
prepareTiledPhysicsData(context?.mapData, map);
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
onPhysicsEntityAdd(map, context) {
|
|
16
|
-
attachTiledCollisionToEntity(context?.owner, map);
|
|
17
|
-
},
|
|
18
|
-
onPhysicsEntityRemove(map, context) {
|
|
19
|
-
detachTiledCollisionFromEntity(context?.owner, map);
|
|
20
|
-
},
|
|
21
|
-
onPhysicsReset(map) {
|
|
22
|
-
resetTiledCollisionHandlers(map);
|
|
11
|
+
prepareTiledPhysicsData(context?.mapData, map);
|
|
23
12
|
}
|
|
24
13
|
}
|
|
25
14
|
});
|
package/dist/server/index4.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { MapClass } from "./index3.js";
|
|
2
|
+
const TILED_HITBOX_ID_PREFIX = "__tiled_collision__:";
|
|
2
3
|
function prepareTiledPhysicsData(mapData, map) {
|
|
3
4
|
if (!mapData?.parsedMap) {
|
|
4
5
|
return;
|
|
5
6
|
}
|
|
6
7
|
const tiledMap = new MapClass(mapData.parsedMap);
|
|
7
8
|
map.tiled = tiledMap;
|
|
8
|
-
|
|
9
|
+
const tiledHitboxes = collectBlockedTileHitboxes(tiledMap);
|
|
10
|
+
mapData.hitboxes = mergeTiledHitboxes(mapData.hitboxes, tiledHitboxes);
|
|
9
11
|
mapData.width = tiledMap.widthPx;
|
|
10
12
|
mapData.height = tiledMap.heightPx;
|
|
11
|
-
map._tiledTileWidth = tiledMap.tilewidth;
|
|
12
|
-
map._tiledTileHeight = tiledMap.tileheight;
|
|
13
|
-
map._blockedTiles = collectBlockedTiles(tiledMap);
|
|
14
13
|
}
|
|
15
14
|
function applyTiledPointEvents(mapData) {
|
|
16
15
|
const objects = mapData?.parsedMap?.objects;
|
|
@@ -33,61 +32,8 @@ function applyTiledPointEvents(mapData) {
|
|
|
33
32
|
}).filter((eventEntry) => eventEntry !== null);
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const entity = map.physic?.getEntityByUUID(owner.id);
|
|
41
|
-
if (!entity || typeof entity.canEnterTile !== "function") {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const unsubscribers = ensureUnsubscribers(map);
|
|
45
|
-
const previousUnsubscribe = unsubscribers.get(owner.id);
|
|
46
|
-
if (previousUnsubscribe) {
|
|
47
|
-
previousUnsubscribe();
|
|
48
|
-
unsubscribers.delete(owner.id);
|
|
49
|
-
}
|
|
50
|
-
const blockedTiles = map._blockedTiles;
|
|
51
|
-
const tiledTileWidth = map._tiledTileWidth ?? 32;
|
|
52
|
-
const tiledTileHeight = map._tiledTileHeight ?? 32;
|
|
53
|
-
const physicsTileWidth = 32;
|
|
54
|
-
const physicsTileHeight = 32;
|
|
55
|
-
const unsubscribe = entity.canEnterTile(({ x, y }) => {
|
|
56
|
-
const tiledX = Math.floor(x * physicsTileWidth / tiledTileWidth);
|
|
57
|
-
const tiledY = Math.floor(y * physicsTileHeight / tiledTileHeight);
|
|
58
|
-
return !blockedTiles.has(`${tiledX},${tiledY}`);
|
|
59
|
-
});
|
|
60
|
-
unsubscribers.set(owner.id, unsubscribe);
|
|
61
|
-
}
|
|
62
|
-
function detachTiledCollisionFromEntity(owner, map) {
|
|
63
|
-
if (!owner?.id) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const unsubscribers = map._tiledCollisionUnsubscribers;
|
|
67
|
-
if (!unsubscribers) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const unsubscribe = unsubscribers.get(owner.id);
|
|
71
|
-
if (!unsubscribe) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
unsubscribe();
|
|
75
|
-
unsubscribers.delete(owner.id);
|
|
76
|
-
}
|
|
77
|
-
function resetTiledCollisionHandlers(map) {
|
|
78
|
-
const unsubscribers = map._tiledCollisionUnsubscribers;
|
|
79
|
-
if (unsubscribers) {
|
|
80
|
-
for (const unsubscribe of unsubscribers.values()) {
|
|
81
|
-
unsubscribe();
|
|
82
|
-
}
|
|
83
|
-
unsubscribers.clear();
|
|
84
|
-
}
|
|
85
|
-
map._blockedTiles = void 0;
|
|
86
|
-
map._tiledTileWidth = void 0;
|
|
87
|
-
map._tiledTileHeight = void 0;
|
|
88
|
-
}
|
|
89
|
-
function collectBlockedTiles(tiledMap) {
|
|
90
|
-
const blockedTiles = /* @__PURE__ */ new Set();
|
|
35
|
+
function collectBlockedTileHitboxes(tiledMap) {
|
|
36
|
+
const hitboxes = [];
|
|
91
37
|
const mapWidth = tiledMap.width;
|
|
92
38
|
const mapHeight = tiledMap.height;
|
|
93
39
|
const tileWidth = tiledMap.tilewidth;
|
|
@@ -98,20 +44,29 @@ function collectBlockedTiles(tiledMap) {
|
|
|
98
44
|
populateTiles: true
|
|
99
45
|
});
|
|
100
46
|
if (tileInfo.hasCollision) {
|
|
101
|
-
|
|
47
|
+
hitboxes.push({
|
|
48
|
+
id: createTiledHitboxId(x, y),
|
|
49
|
+
x: x * tileWidth,
|
|
50
|
+
y: y * tileHeight,
|
|
51
|
+
width: tileWidth,
|
|
52
|
+
height: tileHeight
|
|
53
|
+
});
|
|
102
54
|
}
|
|
103
55
|
}
|
|
104
56
|
}
|
|
105
|
-
return
|
|
57
|
+
return hitboxes;
|
|
58
|
+
}
|
|
59
|
+
function mergeTiledHitboxes(existingHitboxes, tiledHitboxes) {
|
|
60
|
+
const preservedHitboxes = Array.isArray(existingHitboxes) ? existingHitboxes.filter((hitbox) => !isGeneratedTiledHitbox(hitbox)) : [];
|
|
61
|
+
return [...preservedHitboxes, ...tiledHitboxes];
|
|
62
|
+
}
|
|
63
|
+
function isGeneratedTiledHitbox(hitbox) {
|
|
64
|
+
return typeof hitbox?.id === "string" && hitbox.id.startsWith(TILED_HITBOX_ID_PREFIX);
|
|
106
65
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
return map._tiledCollisionUnsubscribers;
|
|
66
|
+
function createTiledHitboxId(x, y) {
|
|
67
|
+
return `${TILED_HITBOX_ID_PREFIX}${x},${y}`;
|
|
110
68
|
}
|
|
111
69
|
export {
|
|
112
70
|
applyTiledPointEvents,
|
|
113
|
-
|
|
114
|
-
detachTiledCollisionFromEntity,
|
|
115
|
-
prepareTiledPhysicsData,
|
|
116
|
-
resetTiledCollisionHandlers
|
|
71
|
+
prepareTiledPhysicsData
|
|
117
72
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpgjs/tiledmap",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.44",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"description": "RPGJS is a framework for creating RPG/MMORPG games",
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@canvasengine/presets": "*",
|
|
26
|
-
"@rpgjs/client": "5.0.0-alpha.
|
|
27
|
-
"@rpgjs/common": "5.0.0-alpha.
|
|
28
|
-
"@rpgjs/server": "5.0.0-alpha.
|
|
29
|
-
"@rpgjs/vite": "5.0.0-alpha.
|
|
26
|
+
"@rpgjs/client": "5.0.0-alpha.44",
|
|
27
|
+
"@rpgjs/common": "5.0.0-alpha.44",
|
|
28
|
+
"@rpgjs/server": "5.0.0-alpha.44",
|
|
29
|
+
"@rpgjs/vite": "5.0.0-alpha.44",
|
|
30
30
|
"canvasengine": "*"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
package/src/client.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { RpgClient } from "@rpgjs/client";
|
|
2
2
|
import { defineModule } from "@rpgjs/common";
|
|
3
|
-
import {
|
|
4
|
-
attachTiledCollisionToEntity,
|
|
5
|
-
detachTiledCollisionFromEntity,
|
|
6
|
-
prepareTiledPhysicsData,
|
|
7
|
-
resetTiledCollisionHandlers,
|
|
8
|
-
} from "./physics";
|
|
3
|
+
import { prepareTiledPhysicsData } from "./physics";
|
|
9
4
|
|
|
10
5
|
export default defineModule<RpgClient>({
|
|
11
6
|
componentAnimations: [],
|
|
@@ -13,14 +8,5 @@ export default defineModule<RpgClient>({
|
|
|
13
8
|
onPhysicsInit(map: any, context: { mapData: any }) {
|
|
14
9
|
prepareTiledPhysicsData(context?.mapData, map);
|
|
15
10
|
},
|
|
16
|
-
onPhysicsEntityAdd(map: any, context: { owner: any }) {
|
|
17
|
-
attachTiledCollisionToEntity(context?.owner, map);
|
|
18
|
-
},
|
|
19
|
-
onPhysicsEntityRemove(map: any, context: { owner: any }) {
|
|
20
|
-
detachTiledCollisionFromEntity(context?.owner, map);
|
|
21
|
-
},
|
|
22
|
-
onPhysicsReset(map: any) {
|
|
23
|
-
resetTiledCollisionHandlers(map);
|
|
24
|
-
},
|
|
25
11
|
},
|
|
26
12
|
});
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createModule } from "@rpgjs/common";
|
|
|
4
4
|
import { provideLoadMap } from "@rpgjs/client";
|
|
5
5
|
import { TiledParser } from "@canvasengine/tiled";
|
|
6
6
|
import Tiled from "./tiled.ce";
|
|
7
|
+
import { prepareTiledPhysicsData } from "./physics";
|
|
7
8
|
|
|
8
9
|
export function provideTiledMap(options: {
|
|
9
10
|
basePath: string;
|
|
@@ -35,7 +36,7 @@ export function provideTiledMap(options: {
|
|
|
35
36
|
}
|
|
36
37
|
parsedMap.tilesets = tilesets;
|
|
37
38
|
|
|
38
|
-
const obj = {
|
|
39
|
+
const obj: any = {
|
|
39
40
|
data: mapData,
|
|
40
41
|
component: Tiled,
|
|
41
42
|
parsedMap,
|
|
@@ -45,6 +46,9 @@ export function provideTiledMap(options: {
|
|
|
45
46
|
},
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
// Populate dimensions and static hitboxes before the first loadPhysic() call.
|
|
50
|
+
prepareTiledPhysicsData(obj, obj);
|
|
51
|
+
|
|
48
52
|
if (options.onLoadMap) {
|
|
49
53
|
await options.onLoadMap(map);
|
|
50
54
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("@canvasengine/tiled", () => {
|
|
4
|
+
class MapClass {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
tilewidth: number;
|
|
8
|
+
tileheight: number;
|
|
9
|
+
widthPx: number;
|
|
10
|
+
heightPx: number;
|
|
11
|
+
private blockedTiles: Set<string>;
|
|
12
|
+
|
|
13
|
+
constructor(parsedMap: any) {
|
|
14
|
+
this.width = parsedMap.width ?? 0;
|
|
15
|
+
this.height = parsedMap.height ?? 0;
|
|
16
|
+
this.tilewidth = parsedMap.tilewidth ?? 32;
|
|
17
|
+
this.tileheight = parsedMap.tileheight ?? 32;
|
|
18
|
+
this.widthPx = this.width * this.tilewidth;
|
|
19
|
+
this.heightPx = this.height * this.tileheight;
|
|
20
|
+
this.blockedTiles = new Set(parsedMap.blockedTiles ?? []);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getTileByPosition(x: number, y: number) {
|
|
24
|
+
const tileX = Math.floor(x / this.tilewidth);
|
|
25
|
+
const tileY = Math.floor(y / this.tileheight);
|
|
26
|
+
return {
|
|
27
|
+
hasCollision: this.blockedTiles.has(`${tileX},${tileY}`),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { MapClass };
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
import { prepareTiledPhysicsData } from "./physics";
|
|
36
|
+
|
|
37
|
+
describe("prepareTiledPhysicsData", () => {
|
|
38
|
+
it("adds tiled collision hitboxes without duplicating them on repeated preparation", () => {
|
|
39
|
+
const mapData = {
|
|
40
|
+
parsedMap: {
|
|
41
|
+
width: 3,
|
|
42
|
+
height: 2,
|
|
43
|
+
tilewidth: 16,
|
|
44
|
+
tileheight: 20,
|
|
45
|
+
blockedTiles: ["1,0", "2,1"],
|
|
46
|
+
},
|
|
47
|
+
hitboxes: [{ id: "custom-hitbox", x: 4, y: 5, width: 6, height: 7 }],
|
|
48
|
+
};
|
|
49
|
+
const map: any = {};
|
|
50
|
+
const expectedHitboxes = [
|
|
51
|
+
{ id: "custom-hitbox", x: 4, y: 5, width: 6, height: 7 },
|
|
52
|
+
{ id: "__tiled_collision__:1,0", x: 16, y: 0, width: 16, height: 20 },
|
|
53
|
+
{ id: "__tiled_collision__:2,1", x: 32, y: 20, width: 16, height: 20 },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
prepareTiledPhysicsData(mapData, map);
|
|
57
|
+
|
|
58
|
+
expect(mapData.width).toBe(48);
|
|
59
|
+
expect(mapData.height).toBe(40);
|
|
60
|
+
expect(mapData.hitboxes).toEqual(expectedHitboxes);
|
|
61
|
+
|
|
62
|
+
prepareTiledPhysicsData(mapData, map);
|
|
63
|
+
|
|
64
|
+
expect(mapData.hitboxes).toEqual(expectedHitboxes);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("removes stale generated tiled hitboxes when blocked tiles change", () => {
|
|
68
|
+
const mapData = {
|
|
69
|
+
parsedMap: {
|
|
70
|
+
width: 2,
|
|
71
|
+
height: 1,
|
|
72
|
+
tilewidth: 32,
|
|
73
|
+
tileheight: 32,
|
|
74
|
+
blockedTiles: ["0,0"],
|
|
75
|
+
},
|
|
76
|
+
hitboxes: [],
|
|
77
|
+
};
|
|
78
|
+
const map: any = {};
|
|
79
|
+
|
|
80
|
+
prepareTiledPhysicsData(mapData, map);
|
|
81
|
+
expect(mapData.hitboxes).toEqual([
|
|
82
|
+
{ id: "__tiled_collision__:0,0", x: 0, y: 0, width: 32, height: 32 },
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
mapData.parsedMap.blockedTiles = [];
|
|
86
|
+
prepareTiledPhysicsData(mapData, map);
|
|
87
|
+
|
|
88
|
+
expect(mapData.hitboxes).toEqual([]);
|
|
89
|
+
});
|
|
90
|
+
});
|
package/src/physics.ts
CHANGED
|
@@ -2,15 +2,18 @@ import { MapClass } from "@canvasengine/tiled";
|
|
|
2
2
|
|
|
3
3
|
type AnyMap = {
|
|
4
4
|
tiled?: MapClass;
|
|
5
|
-
physic?: {
|
|
6
|
-
getEntityByUUID(id: string): any;
|
|
7
|
-
};
|
|
8
|
-
_blockedTiles?: Set<string>;
|
|
9
|
-
_tiledTileWidth?: number;
|
|
10
|
-
_tiledTileHeight?: number;
|
|
11
|
-
_tiledCollisionUnsubscribers?: Map<string, () => void>;
|
|
12
5
|
};
|
|
13
6
|
|
|
7
|
+
type RectHitbox = {
|
|
8
|
+
id: string;
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const TILED_HITBOX_ID_PREFIX = "__tiled_collision__:";
|
|
16
|
+
|
|
14
17
|
export function prepareTiledPhysicsData(mapData: any, map: AnyMap): void {
|
|
15
18
|
if (!mapData?.parsedMap) {
|
|
16
19
|
return;
|
|
@@ -19,13 +22,10 @@ export function prepareTiledPhysicsData(mapData: any, map: AnyMap): void {
|
|
|
19
22
|
const tiledMap = new MapClass(mapData.parsedMap);
|
|
20
23
|
map.tiled = tiledMap;
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
const tiledHitboxes = collectBlockedTileHitboxes(tiledMap);
|
|
26
|
+
mapData.hitboxes = mergeTiledHitboxes(mapData.hitboxes, tiledHitboxes);
|
|
23
27
|
mapData.width = tiledMap.widthPx;
|
|
24
28
|
mapData.height = tiledMap.heightPx;
|
|
25
|
-
|
|
26
|
-
map._tiledTileWidth = tiledMap.tilewidth;
|
|
27
|
-
map._tiledTileHeight = tiledMap.tileheight;
|
|
28
|
-
map._blockedTiles = collectBlockedTiles(tiledMap);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function applyTiledPointEvents(mapData: any): void {
|
|
@@ -54,70 +54,8 @@ export function applyTiledPointEvents(mapData: any): void {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const entity = map.physic?.getEntityByUUID(owner.id);
|
|
63
|
-
if (!entity || typeof entity.canEnterTile !== "function") {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const unsubscribers = ensureUnsubscribers(map);
|
|
68
|
-
const previousUnsubscribe = unsubscribers.get(owner.id);
|
|
69
|
-
if (previousUnsubscribe) {
|
|
70
|
-
previousUnsubscribe();
|
|
71
|
-
unsubscribers.delete(owner.id);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const blockedTiles = map._blockedTiles;
|
|
75
|
-
const tiledTileWidth = map._tiledTileWidth ?? 32;
|
|
76
|
-
const tiledTileHeight = map._tiledTileHeight ?? 32;
|
|
77
|
-
const physicsTileWidth = 32;
|
|
78
|
-
const physicsTileHeight = 32;
|
|
79
|
-
|
|
80
|
-
const unsubscribe = entity.canEnterTile(({ x, y }) => {
|
|
81
|
-
const tiledX = Math.floor((x * physicsTileWidth) / tiledTileWidth);
|
|
82
|
-
const tiledY = Math.floor((y * physicsTileHeight) / tiledTileHeight);
|
|
83
|
-
return !blockedTiles.has(`${tiledX},${tiledY}`);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
unsubscribers.set(owner.id, unsubscribe);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function detachTiledCollisionFromEntity(owner: any, map: AnyMap): void {
|
|
90
|
-
if (!owner?.id) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const unsubscribers = map._tiledCollisionUnsubscribers;
|
|
94
|
-
if (!unsubscribers) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const unsubscribe = unsubscribers.get(owner.id);
|
|
98
|
-
if (!unsubscribe) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
unsubscribe();
|
|
102
|
-
unsubscribers.delete(owner.id);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function resetTiledCollisionHandlers(map: AnyMap): void {
|
|
106
|
-
const unsubscribers = map._tiledCollisionUnsubscribers;
|
|
107
|
-
if (unsubscribers) {
|
|
108
|
-
for (const unsubscribe of unsubscribers.values()) {
|
|
109
|
-
unsubscribe();
|
|
110
|
-
}
|
|
111
|
-
unsubscribers.clear();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
map._blockedTiles = undefined;
|
|
115
|
-
map._tiledTileWidth = undefined;
|
|
116
|
-
map._tiledTileHeight = undefined;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function collectBlockedTiles(tiledMap: MapClass): Set<string> {
|
|
120
|
-
const blockedTiles = new Set<string>();
|
|
57
|
+
function collectBlockedTileHitboxes(tiledMap: MapClass): RectHitbox[] {
|
|
58
|
+
const hitboxes: RectHitbox[] = [];
|
|
121
59
|
const mapWidth = tiledMap.width;
|
|
122
60
|
const mapHeight = tiledMap.height;
|
|
123
61
|
const tileWidth = tiledMap.tilewidth;
|
|
@@ -129,15 +67,32 @@ function collectBlockedTiles(tiledMap: MapClass): Set<string> {
|
|
|
129
67
|
populateTiles: true,
|
|
130
68
|
});
|
|
131
69
|
if (tileInfo.hasCollision) {
|
|
132
|
-
|
|
70
|
+
hitboxes.push({
|
|
71
|
+
id: createTiledHitboxId(x, y),
|
|
72
|
+
x: x * tileWidth,
|
|
73
|
+
y: y * tileHeight,
|
|
74
|
+
width: tileWidth,
|
|
75
|
+
height: tileHeight,
|
|
76
|
+
});
|
|
133
77
|
}
|
|
134
78
|
}
|
|
135
79
|
}
|
|
136
80
|
|
|
137
|
-
return
|
|
81
|
+
return hitboxes;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function mergeTiledHitboxes(existingHitboxes: any, tiledHitboxes: RectHitbox[]): any[] {
|
|
85
|
+
const preservedHitboxes = Array.isArray(existingHitboxes)
|
|
86
|
+
? existingHitboxes.filter((hitbox) => !isGeneratedTiledHitbox(hitbox))
|
|
87
|
+
: [];
|
|
88
|
+
|
|
89
|
+
return [...preservedHitboxes, ...tiledHitboxes];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isGeneratedTiledHitbox(hitbox: any): boolean {
|
|
93
|
+
return typeof hitbox?.id === "string" && hitbox.id.startsWith(TILED_HITBOX_ID_PREFIX);
|
|
138
94
|
}
|
|
139
95
|
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
return map._tiledCollisionUnsubscribers;
|
|
96
|
+
function createTiledHitboxId(x: number, y: number): string {
|
|
97
|
+
return `${TILED_HITBOX_ID_PREFIX}${x},${y}`;
|
|
143
98
|
}
|
package/src/server.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { RpgMap, RpgServer } from "@rpgjs/server";
|
|
2
2
|
import { MapClass } from "@canvasengine/tiled";
|
|
3
3
|
import { defineModule } from "@rpgjs/common";
|
|
4
|
-
import {
|
|
5
|
-
applyTiledPointEvents,
|
|
6
|
-
attachTiledCollisionToEntity,
|
|
7
|
-
detachTiledCollisionFromEntity,
|
|
8
|
-
prepareTiledPhysicsData,
|
|
9
|
-
resetTiledCollisionHandlers,
|
|
10
|
-
} from "./physics";
|
|
4
|
+
import { applyTiledPointEvents, prepareTiledPhysicsData } from "./physics";
|
|
11
5
|
|
|
12
6
|
declare module "@rpgjs/server" {
|
|
13
7
|
interface RpgMap {
|
|
@@ -27,18 +21,7 @@ export default defineModule<RpgServer>({
|
|
|
27
21
|
return map;
|
|
28
22
|
},
|
|
29
23
|
onPhysicsInit(map: any, context: { mapData: any }) {
|
|
30
|
-
|
|
31
|
-
prepareTiledPhysicsData(context?.mapData, map);
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
onPhysicsEntityAdd(map: any, context: { owner: any }) {
|
|
35
|
-
attachTiledCollisionToEntity(context?.owner, map);
|
|
36
|
-
},
|
|
37
|
-
onPhysicsEntityRemove(map: any, context: { owner: any }) {
|
|
38
|
-
detachTiledCollisionFromEntity(context?.owner, map);
|
|
39
|
-
},
|
|
40
|
-
onPhysicsReset(map: any) {
|
|
41
|
-
resetTiledCollisionHandlers(map);
|
|
24
|
+
prepareTiledPhysicsData(context?.mapData, map);
|
|
42
25
|
},
|
|
43
26
|
},
|
|
44
27
|
});
|