@rpgjs/tiledmap 5.0.0-alpha.4 → 5.0.0-alpha.41
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 +12 -7
- package/dist/client/index2.js +16 -1
- package/dist/client/index3.js +5218 -12
- package/dist/client/index4.js +18 -0
- package/dist/client/index5.js +95 -0
- package/dist/index.d.ts +1 -0
- package/dist/physics.d.ts +17 -0
- package/dist/server/index.js +2 -2
- package/dist/server/index2.js +17 -84
- package/dist/server/index3.js +4943 -0
- package/dist/server/index4.js +117 -0
- package/dist/server.d.ts +1 -54
- package/package.json +13 -11
- package/src/client.ts +23 -3
- package/src/index.ts +27 -18
- package/src/physics.ts +143 -0
- package/src/server.ts +24 -156
- package/src/tiled.ce +8 -3
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { MapClass } from "./index3.js";
|
|
2
|
+
function prepareTiledPhysicsData(mapData, map) {
|
|
3
|
+
if (!mapData?.parsedMap) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const tiledMap = new MapClass(mapData.parsedMap);
|
|
7
|
+
map.tiled = tiledMap;
|
|
8
|
+
mapData.hitboxes = mapData.hitboxes || [];
|
|
9
|
+
mapData.width = tiledMap.widthPx;
|
|
10
|
+
mapData.height = tiledMap.heightPx;
|
|
11
|
+
map._tiledTileWidth = tiledMap.tilewidth;
|
|
12
|
+
map._tiledTileHeight = tiledMap.tileheight;
|
|
13
|
+
map._blockedTiles = collectBlockedTiles(tiledMap);
|
|
14
|
+
}
|
|
15
|
+
function applyTiledPointEvents(mapData) {
|
|
16
|
+
const objects = mapData?.parsedMap?.objects;
|
|
17
|
+
if (!Array.isArray(objects) || !Array.isArray(mapData?.events)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const obj of objects) {
|
|
21
|
+
if (!obj?.point) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
mapData.events = mapData.events.map((eventEntry) => {
|
|
25
|
+
if (eventEntry?.name === obj.name) {
|
|
26
|
+
return {
|
|
27
|
+
event: eventEntry,
|
|
28
|
+
x: obj.x,
|
|
29
|
+
y: obj.y
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return eventEntry;
|
|
33
|
+
}).filter((eventEntry) => eventEntry !== null);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function attachTiledCollisionToEntity(owner, map) {
|
|
37
|
+
if (!owner?.id || !map?._blockedTiles) {
|
|
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();
|
|
91
|
+
const mapWidth = tiledMap.width;
|
|
92
|
+
const mapHeight = tiledMap.height;
|
|
93
|
+
const tileWidth = tiledMap.tilewidth;
|
|
94
|
+
const tileHeight = tiledMap.tileheight;
|
|
95
|
+
for (let y = 0; y < mapHeight; y++) {
|
|
96
|
+
for (let x = 0; x < mapWidth; x++) {
|
|
97
|
+
const tileInfo = tiledMap.getTileByPosition(x * tileWidth, y * tileHeight, [0, 0], {
|
|
98
|
+
populateTiles: true
|
|
99
|
+
});
|
|
100
|
+
if (tileInfo.hasCollision) {
|
|
101
|
+
blockedTiles.add(`${x},${y}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return blockedTiles;
|
|
106
|
+
}
|
|
107
|
+
function ensureUnsubscribers(map) {
|
|
108
|
+
map._tiledCollisionUnsubscribers = map._tiledCollisionUnsubscribers || /* @__PURE__ */ new Map();
|
|
109
|
+
return map._tiledCollisionUnsubscribers;
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
applyTiledPointEvents,
|
|
113
|
+
attachTiledCollisionToEntity,
|
|
114
|
+
detachTiledCollisionFromEntity,
|
|
115
|
+
prepareTiledPhysicsData,
|
|
116
|
+
resetTiledCollisionHandlers
|
|
117
|
+
};
|
package/dist/server.d.ts
CHANGED
|
@@ -1,65 +1,12 @@
|
|
|
1
1
|
import { RpgMap } from '@rpgjs/server';
|
|
2
|
-
import { MapClass } from '@
|
|
2
|
+
import { MapClass } from '@canvasengine/tiled';
|
|
3
3
|
declare module "@rpgjs/server" {
|
|
4
4
|
interface RpgMap {
|
|
5
5
|
tiled?: MapClass;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
9
|
-
* Interface for an RpgMap extended with Tiled functionality
|
|
10
|
-
*
|
|
11
|
-
* @description This interface combines RpgMap with MapClass to enable
|
|
12
|
-
* the use of Tiled methods on RPG maps
|
|
13
|
-
*/
|
|
14
8
|
export interface RpgTiledMap extends RpgMap {
|
|
15
9
|
tiled: MapClass;
|
|
16
10
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Tiled Module for RPGJS
|
|
19
|
-
*
|
|
20
|
-
* @description This module extends RPGJS maps with Tiled functionality,
|
|
21
|
-
* allowing TMX map parsing and automatic hitbox creation
|
|
22
|
-
* based on collisions defined in Tiled
|
|
23
|
-
*
|
|
24
|
-
* ## Features
|
|
25
|
-
*
|
|
26
|
-
* - **Automatic parsing**: Parses TMX files from Tiled Map Editor
|
|
27
|
-
* - **Collision detection**: Scans all tiles to detect collisions
|
|
28
|
-
* - **Hitbox creation**: Automatically generates hitboxes for each collision tile
|
|
29
|
-
* - **RpgMap extension**: Adds the `tiled` property to all RpgMap instances
|
|
30
|
-
*
|
|
31
|
-
* ## Usage
|
|
32
|
-
*
|
|
33
|
-
* Once this module is activated, you can use Tiled methods on your maps:
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```ts
|
|
37
|
-
* // In a map class
|
|
38
|
-
* class MyMap extends RpgMap {
|
|
39
|
-
* onLoad() {
|
|
40
|
-
* // Access Tiled functionality
|
|
41
|
-
* const tiles = this.tiled.getTileByPosition(100, 100);
|
|
42
|
-
*
|
|
43
|
-
* if (tiles.hasCollision) {
|
|
44
|
-
* console.log('This position has a collision');
|
|
45
|
-
* }
|
|
46
|
-
*
|
|
47
|
-
* // Iterate through all tiles by index
|
|
48
|
-
* for (let i = 0; i < this.tiled.width * this.tiled.height; i++) {
|
|
49
|
-
* const tileInfo = this.tiled.getTileByIndex(i);
|
|
50
|
-
* if (tileInfo.hasCollision) {
|
|
51
|
-
* console.log(`Tile ${i} has collision`);
|
|
52
|
-
* }
|
|
53
|
-
* }
|
|
54
|
-
*
|
|
55
|
-
* // Get information about a specific layer
|
|
56
|
-
* const layer = this.tiled.getLayerByName('Collision');
|
|
57
|
-
* if (layer) {
|
|
58
|
-
* console.log('Collision layer found:', layer);
|
|
59
|
-
* }
|
|
60
|
-
* }
|
|
61
|
-
* }
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
11
|
declare const _default: any;
|
|
65
12
|
export default _default;
|
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.41",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -22,23 +22,25 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"description": "RPGJS is a framework for creating RPG/MMORPG games",
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"canvasengine": "
|
|
26
|
-
"@
|
|
27
|
-
"@rpgjs/common": "5.0.0-alpha.
|
|
28
|
-
"@rpgjs/server": "5.0.0-alpha.
|
|
29
|
-
"@rpgjs/
|
|
30
|
-
"
|
|
31
|
-
"@rpgjs/vite": "5.0.0-alpha.4"
|
|
25
|
+
"@canvasengine/presets": "*",
|
|
26
|
+
"@rpgjs/client": "5.0.0-alpha.41",
|
|
27
|
+
"@rpgjs/common": "5.0.0-alpha.41",
|
|
28
|
+
"@rpgjs/server": "5.0.0-alpha.41",
|
|
29
|
+
"@rpgjs/vite": "5.0.0-alpha.41",
|
|
30
|
+
"canvasengine": "*"
|
|
32
31
|
},
|
|
33
32
|
"publishConfig": {
|
|
34
33
|
"access": "public"
|
|
35
34
|
},
|
|
36
35
|
"devDependencies": {
|
|
37
|
-
"@canvasengine/compiler": "2.0.0-beta.
|
|
38
|
-
"vite": "^
|
|
39
|
-
"vite-plugin-dts": "^4.5.
|
|
36
|
+
"@canvasengine/compiler": "^2.0.0-beta.44",
|
|
37
|
+
"vite": "^7.3.0",
|
|
38
|
+
"vite-plugin-dts": "^4.5.4"
|
|
40
39
|
},
|
|
41
40
|
"type": "module",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@canvasengine/tiled": "^2.0.0-beta.44"
|
|
43
|
+
},
|
|
42
44
|
"scripts": {
|
|
43
45
|
"dev": "vite build --watch",
|
|
44
46
|
"build": "vite build"
|
package/src/client.ts
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
import { RpgClient
|
|
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
9
|
|
|
4
10
|
export default defineModule<RpgClient>({
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
componentAnimations: [],
|
|
12
|
+
sceneMap: {
|
|
13
|
+
onPhysicsInit(map: any, context: { mapData: any }) {
|
|
14
|
+
prepareTiledPhysicsData(context?.mapData, map);
|
|
15
|
+
},
|
|
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
|
+
},
|
|
26
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -2,11 +2,12 @@ import server from "./server";
|
|
|
2
2
|
import client from "./client";
|
|
3
3
|
import { createModule } from "@rpgjs/common";
|
|
4
4
|
import { provideLoadMap } from "@rpgjs/client";
|
|
5
|
-
import { TiledParser } from "@
|
|
5
|
+
import { TiledParser } from "@canvasengine/tiled";
|
|
6
6
|
import Tiled from "./tiled.ce";
|
|
7
7
|
|
|
8
8
|
export function provideTiledMap(options: {
|
|
9
9
|
basePath: string;
|
|
10
|
+
onLoadMap?: (map: string) => Promise<void>;
|
|
10
11
|
}) {
|
|
11
12
|
return createModule("TiledMap", [
|
|
12
13
|
{
|
|
@@ -14,33 +15,41 @@ export function provideTiledMap(options: {
|
|
|
14
15
|
client,
|
|
15
16
|
},
|
|
16
17
|
provideLoadMap?.(async (map) => {
|
|
17
|
-
|
|
18
|
-
const response = await fetch(`${options.basePath}/${map}.tmx`)
|
|
19
|
-
const mapData = (await response.text())
|
|
18
|
+
"use client";
|
|
19
|
+
const response = await fetch(`${options.basePath}/${map}.tmx`);
|
|
20
|
+
const mapData = (await response.text());
|
|
20
21
|
const parser = new TiledParser(mapData);
|
|
21
22
|
const parsedMap = parser.parseMap();
|
|
22
|
-
const tilesets: any = []
|
|
23
|
+
const tilesets: any = [];
|
|
23
24
|
for (let tileset of parsedMap.tilesets) {
|
|
24
|
-
const response = await fetch(`${tileset.source}`);
|
|
25
|
+
const response = await fetch(`${options.basePath}/${tileset.source}`);
|
|
25
26
|
const tilesetData = await response.text();
|
|
26
27
|
const parser = new TiledParser(tilesetData);
|
|
27
28
|
const parsedTileset = parser.parseTileset();
|
|
28
|
-
parsedTileset.image.source = `${options.basePath}/${parsedTileset.image.source}
|
|
29
|
+
parsedTileset.image.source = `${options.basePath}/${parsedTileset.image.source}`;
|
|
29
30
|
// Preserve firstgid from the original tileset reference
|
|
30
31
|
tilesets.push({
|
|
31
|
-
...tileset,
|
|
32
|
-
...parsedTileset
|
|
33
|
-
})
|
|
32
|
+
...tileset, // Preserve original properties including firstgid
|
|
33
|
+
...parsedTileset, // Merge with parsed tileset data
|
|
34
|
+
});
|
|
34
35
|
}
|
|
35
|
-
parsedMap.tilesets = tilesets
|
|
36
|
-
|
|
36
|
+
parsedMap.tilesets = tilesets;
|
|
37
|
+
|
|
37
38
|
const obj = {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
data: mapData,
|
|
40
|
+
component: Tiled,
|
|
41
|
+
parsedMap,
|
|
42
|
+
id: map,
|
|
43
|
+
params: {
|
|
44
|
+
basePath: options.basePath,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (options.onLoadMap) {
|
|
49
|
+
await options.onLoadMap(map);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return obj;
|
|
44
53
|
}),
|
|
45
54
|
]);
|
|
46
55
|
}
|
package/src/physics.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { MapClass } from "@canvasengine/tiled";
|
|
2
|
+
|
|
3
|
+
type AnyMap = {
|
|
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
|
+
};
|
|
13
|
+
|
|
14
|
+
export function prepareTiledPhysicsData(mapData: any, map: AnyMap): void {
|
|
15
|
+
if (!mapData?.parsedMap) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const tiledMap = new MapClass(mapData.parsedMap);
|
|
20
|
+
map.tiled = tiledMap;
|
|
21
|
+
|
|
22
|
+
mapData.hitboxes = mapData.hitboxes || [];
|
|
23
|
+
mapData.width = tiledMap.widthPx;
|
|
24
|
+
mapData.height = tiledMap.heightPx;
|
|
25
|
+
|
|
26
|
+
map._tiledTileWidth = tiledMap.tilewidth;
|
|
27
|
+
map._tiledTileHeight = tiledMap.tileheight;
|
|
28
|
+
map._blockedTiles = collectBlockedTiles(tiledMap);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function applyTiledPointEvents(mapData: any): void {
|
|
32
|
+
const objects = mapData?.parsedMap?.objects;
|
|
33
|
+
if (!Array.isArray(objects) || !Array.isArray(mapData?.events)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const obj of objects) {
|
|
38
|
+
if (!obj?.point) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
mapData.events = mapData.events
|
|
43
|
+
.map((eventEntry: any) => {
|
|
44
|
+
if (eventEntry?.name === obj.name) {
|
|
45
|
+
return {
|
|
46
|
+
event: eventEntry,
|
|
47
|
+
x: obj.x,
|
|
48
|
+
y: obj.y,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return eventEntry;
|
|
52
|
+
})
|
|
53
|
+
.filter((eventEntry: any) => eventEntry !== null);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function attachTiledCollisionToEntity(owner: any, map: AnyMap): void {
|
|
58
|
+
if (!owner?.id || !map?._blockedTiles) {
|
|
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>();
|
|
121
|
+
const mapWidth = tiledMap.width;
|
|
122
|
+
const mapHeight = tiledMap.height;
|
|
123
|
+
const tileWidth = tiledMap.tilewidth;
|
|
124
|
+
const tileHeight = tiledMap.tileheight;
|
|
125
|
+
|
|
126
|
+
for (let y = 0; y < mapHeight; y++) {
|
|
127
|
+
for (let x = 0; x < mapWidth; x++) {
|
|
128
|
+
const tileInfo = tiledMap.getTileByPosition(x * tileWidth, y * tileHeight, [0, 0], {
|
|
129
|
+
populateTiles: true,
|
|
130
|
+
});
|
|
131
|
+
if (tileInfo.hasCollision) {
|
|
132
|
+
blockedTiles.add(`${x},${y}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return blockedTiles;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function ensureUnsubscribers(map: AnyMap): Map<string, () => void> {
|
|
141
|
+
map._tiledCollisionUnsubscribers = map._tiledCollisionUnsubscribers || new Map();
|
|
142
|
+
return map._tiledCollisionUnsubscribers;
|
|
143
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -1,176 +1,44 @@
|
|
|
1
1
|
import { RpgMap, RpgServer } from "@rpgjs/server";
|
|
2
|
-
import {
|
|
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
11
|
|
|
5
|
-
// Import TileInfo depuis le bon endroit
|
|
6
|
-
import type { TileInfo } from "@rpgjs/tiled/src/classes/Map";
|
|
7
|
-
|
|
8
|
-
// Extend RpgMap interface to include tiled property
|
|
9
12
|
declare module "@rpgjs/server" {
|
|
10
13
|
interface RpgMap {
|
|
11
14
|
tiled?: MapClass;
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
/**
|
|
16
|
-
* Interface for an RpgMap extended with Tiled functionality
|
|
17
|
-
*
|
|
18
|
-
* @description This interface combines RpgMap with MapClass to enable
|
|
19
|
-
* the use of Tiled methods on RPG maps
|
|
20
|
-
*/
|
|
21
18
|
export interface RpgTiledMap extends RpgMap {
|
|
22
19
|
tiled: MapClass;
|
|
23
20
|
}
|
|
24
21
|
|
|
25
|
-
/**
|
|
26
|
-
* Tiled Module for RPGJS
|
|
27
|
-
*
|
|
28
|
-
* @description This module extends RPGJS maps with Tiled functionality,
|
|
29
|
-
* allowing TMX map parsing and automatic hitbox creation
|
|
30
|
-
* based on collisions defined in Tiled
|
|
31
|
-
*
|
|
32
|
-
* ## Features
|
|
33
|
-
*
|
|
34
|
-
* - **Automatic parsing**: Parses TMX files from Tiled Map Editor
|
|
35
|
-
* - **Collision detection**: Scans all tiles to detect collisions
|
|
36
|
-
* - **Hitbox creation**: Automatically generates hitboxes for each collision tile
|
|
37
|
-
* - **RpgMap extension**: Adds the `tiled` property to all RpgMap instances
|
|
38
|
-
*
|
|
39
|
-
* ## Usage
|
|
40
|
-
*
|
|
41
|
-
* Once this module is activated, you can use Tiled methods on your maps:
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```ts
|
|
45
|
-
* // In a map class
|
|
46
|
-
* class MyMap extends RpgMap {
|
|
47
|
-
* onLoad() {
|
|
48
|
-
* // Access Tiled functionality
|
|
49
|
-
* const tiles = this.tiled.getTileByPosition(100, 100);
|
|
50
|
-
*
|
|
51
|
-
* if (tiles.hasCollision) {
|
|
52
|
-
* console.log('This position has a collision');
|
|
53
|
-
* }
|
|
54
|
-
*
|
|
55
|
-
* // Iterate through all tiles by index
|
|
56
|
-
* for (let i = 0; i < this.tiled.width * this.tiled.height; i++) {
|
|
57
|
-
* const tileInfo = this.tiled.getTileByIndex(i);
|
|
58
|
-
* if (tileInfo.hasCollision) {
|
|
59
|
-
* console.log(`Tile ${i} has collision`);
|
|
60
|
-
* }
|
|
61
|
-
* }
|
|
62
|
-
*
|
|
63
|
-
* // Get information about a specific layer
|
|
64
|
-
* const layer = this.tiled.getLayerByName('Collision');
|
|
65
|
-
* if (layer) {
|
|
66
|
-
* console.log('Collision layer found:', layer);
|
|
67
|
-
* }
|
|
68
|
-
* }
|
|
69
|
-
* }
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
22
|
export default defineModule<RpgServer>({
|
|
73
23
|
map: {
|
|
74
|
-
/**
|
|
75
|
-
* Hook called before map update
|
|
76
|
-
*
|
|
77
|
-
* @description Parses Tiled data and creates collision hitboxes
|
|
78
|
-
* automatically by iterating through all tiles on the map.
|
|
79
|
-
*
|
|
80
|
-
* This method:
|
|
81
|
-
* 1. Parses TMX data with TiledParser
|
|
82
|
-
* 2. Creates a MapClass instance with parsed data
|
|
83
|
-
* 3. Attaches the Tiled instance to the RpgMap
|
|
84
|
-
* 4. Scans all tiles to detect collisions
|
|
85
|
-
* 5. Automatically creates hitboxes for each collision tile
|
|
86
|
-
*
|
|
87
|
-
* @param mapData - Map data containing TMX information
|
|
88
|
-
* @param map - RpgMap instance to extend
|
|
89
|
-
* @returns The modified map instance with tiled property
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* ```ts
|
|
93
|
-
* // Created hitboxes will have this structure:
|
|
94
|
-
* {
|
|
95
|
-
* id: 'collision_x_y', // Unique identifier
|
|
96
|
-
* x: x * tileWidth, // X position in pixels
|
|
97
|
-
* y: y * tileHeight, // Y position in pixels
|
|
98
|
-
* width: tileWidth, // Tile width
|
|
99
|
-
* height: tileHeight, // Tile height
|
|
100
|
-
* properties: {
|
|
101
|
-
* type: 'collision', // Hitbox type
|
|
102
|
-
* tileX: x, // X position in tiles
|
|
103
|
-
* tileY: y, // Y position in tiles
|
|
104
|
-
* tileIndex: tileIndex // Tile index
|
|
105
|
-
* }
|
|
106
|
-
* }
|
|
107
|
-
* ```
|
|
108
|
-
*/
|
|
109
24
|
onBeforeUpdate<T = RpgMap>(mapData: any, map: T): T {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// Attach Tiled instance to the map
|
|
113
|
-
(map as any).tiled = tiledMap;
|
|
114
|
-
|
|
115
|
-
// Initialize hitboxes array
|
|
116
|
-
mapData.hitboxes = mapData.hitboxes || [];
|
|
117
|
-
mapData.width = tiledMap.widthPx;
|
|
118
|
-
mapData.height = tiledMap.heightPx;
|
|
119
|
-
|
|
120
|
-
// Iterate through all map tiles to detect collisions
|
|
121
|
-
const mapWidth = tiledMap.width;
|
|
122
|
-
const mapHeight = tiledMap.height;
|
|
123
|
-
const tileWidth = tiledMap.tilewidth;
|
|
124
|
-
const tileHeight = tiledMap.tileheight;
|
|
125
|
-
|
|
126
|
-
// Iterate through each tile on the map
|
|
127
|
-
for (let y = 0; y < mapHeight; y++) {
|
|
128
|
-
for (let x = 0; x < mapWidth; x++) {
|
|
129
|
-
// Use getTileByPosition which is simpler and handles pixel coordinates directly
|
|
130
|
-
const pixelX = x * tileWidth;
|
|
131
|
-
const pixelY = y * tileHeight;
|
|
132
|
-
const tileInfo = tiledMap.getTileByPosition(pixelX, pixelY, [0, 0], {
|
|
133
|
-
populateTiles: true,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// If tile has collision, create a hitbox
|
|
137
|
-
if (tileInfo.hasCollision) {
|
|
138
|
-
const hitbox = {
|
|
139
|
-
id: `collision_${x}_${y}`,
|
|
140
|
-
x: pixelX,
|
|
141
|
-
y: pixelY,
|
|
142
|
-
width: tileWidth,
|
|
143
|
-
height: tileHeight,
|
|
144
|
-
properties: {
|
|
145
|
-
type: "collision",
|
|
146
|
-
tileX: x,
|
|
147
|
-
tileY: y,
|
|
148
|
-
tileIndex: tileInfo.tileIndex,
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
mapData.hitboxes.push(hitbox);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
for (let obj of mapData.parsedMap.objects) {
|
|
158
|
-
if (obj.point) {
|
|
159
|
-
mapData.events = mapData.events
|
|
160
|
-
.map((e) => {
|
|
161
|
-
if (e.name === obj.name) {
|
|
162
|
-
return {
|
|
163
|
-
event: e,
|
|
164
|
-
x: obj.x,
|
|
165
|
-
y: obj.y,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
return e;
|
|
169
|
-
})
|
|
170
|
-
.filter((e) => e !== null);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
25
|
+
prepareTiledPhysicsData(mapData, map as any);
|
|
26
|
+
applyTiledPointEvents(mapData);
|
|
173
27
|
return map;
|
|
174
28
|
},
|
|
29
|
+
onPhysicsInit(map: any, context: { mapData: any }) {
|
|
30
|
+
if (!map?._blockedTiles || !map?.tiled) {
|
|
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);
|
|
42
|
+
},
|
|
175
43
|
},
|
|
176
44
|
});
|
package/src/tiled.ce
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
<Container>
|
|
2
|
-
<TiledMap map basePath
|
|
2
|
+
<TiledMap map basePath createLayersPerTilesZ={true} objectLayer={() => <EventLayerComponent />} />
|
|
3
3
|
</Container>
|
|
4
4
|
|
|
5
5
|
<script>
|
|
6
6
|
import { EventLayerComponent } from "@rpgjs/client";
|
|
7
7
|
import { TiledMap } from "@canvasengine/presets";
|
|
8
|
-
import { signal } from "canvasengine";
|
|
8
|
+
import { signal, effect } from "canvasengine";
|
|
9
9
|
|
|
10
|
-
const { data,
|
|
10
|
+
const { data, params } = defineProps()
|
|
11
11
|
|
|
12
12
|
const map = signal(data())
|
|
13
|
+
const basePath = signal(params().basePath)
|
|
14
|
+
|
|
15
|
+
effect(() => {
|
|
16
|
+
map.set(data())
|
|
17
|
+
})
|
|
13
18
|
</script>
|