├── .gitignore
├── .npmignore
├── demo
├── src
│ ├── react-app-env.d.ts
│ ├── setupTests.js
│ ├── App.test.js
│ ├── reportWebVitals.js
│ ├── index.css
│ ├── index.js
│ ├── App.css
│ ├── App.js
│ └── logo.svg
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── stages
│ │ ├── TX Plant.png
│ │ ├── TX Props.png
│ │ ├── TX Struct.png
│ │ ├── TX Shadow Plant.png
│ │ ├── TX Tileset Wall.png
│ │ ├── TX Tileset Grass.png
│ │ ├── TX Shadow Plant.tsx
│ │ ├── TX Tileset Grass.tsx
│ │ ├── TX Plant.tsx
│ │ ├── TX Props.tsx
│ │ ├── TX Struct.tsx
│ │ ├── TX Tileset Wall.tsx
│ │ └── map.tmx
│ ├── manifest.json
│ └── index.html
├── .gitignore
└── package.json
├── img
├── example.png
└── layers-pane.png
├── src
├── TilemapContext.js
├── useMap.js
├── useObjects.js
├── useSpawnPoint.js
├── useInteractables.js
├── index.js
├── useTiles.js
├── useCollisions.js
├── useTilemapLoader.js
├── Layer.jsx
├── Tilemap.jsx
└── getSprite.js
├── .editorconfig
├── LICENSE
├── package.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | src/
--------------------------------------------------------------------------------
/demo/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/img/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/img/example.png
--------------------------------------------------------------------------------
/demo/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/img/layers-pane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/img/layers-pane.png
--------------------------------------------------------------------------------
/demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/favicon.ico
--------------------------------------------------------------------------------
/demo/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/logo192.png
--------------------------------------------------------------------------------
/demo/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/logo512.png
--------------------------------------------------------------------------------
/demo/public/stages/TX Plant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/stages/TX Plant.png
--------------------------------------------------------------------------------
/demo/public/stages/TX Props.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/stages/TX Props.png
--------------------------------------------------------------------------------
/demo/public/stages/TX Struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/stages/TX Struct.png
--------------------------------------------------------------------------------
/demo/public/stages/TX Shadow Plant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/stages/TX Shadow Plant.png
--------------------------------------------------------------------------------
/demo/public/stages/TX Tileset Wall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/stages/TX Tileset Wall.png
--------------------------------------------------------------------------------
/demo/public/stages/TX Tileset Grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vocksel/react-pixi-tilemap/HEAD/demo/public/stages/TX Tileset Grass.png
--------------------------------------------------------------------------------
/src/TilemapContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const TilemapContext = createContext()
4 |
5 | export default TilemapContext
6 |
--------------------------------------------------------------------------------
/src/useMap.js:
--------------------------------------------------------------------------------
1 | import { useContext } from "react"
2 | import TilemapContext from './TilemapContext'
3 |
4 | const useMap = () => {
5 | const { map } = useContext(TilemapContext)
6 | return map
7 | }
8 |
9 | export default useMap
--------------------------------------------------------------------------------
/demo/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/demo/public/stages/TX Shadow Plant.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/public/stages/TX Tileset Grass.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 4
9 | end_of_line = crlf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | [*.json]
15 | indent_size = 2
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/demo/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/useObjects.js:
--------------------------------------------------------------------------------
1 | import { useContext, useMemo } from "react"
2 | import TilemapContext from "./TilemapContext"
3 |
4 | const useObjects = () => {
5 | const { map } = useContext(TilemapContext)
6 |
7 | const objects = useMemo(() => map.layers
8 | .filter(layer => layer.type === 'object')
9 | .map(layer => layer.objects)
10 | .flat(),
11 | [ map ])
12 |
13 | return objects
14 | }
15 |
16 | export default useObjects
--------------------------------------------------------------------------------
/demo/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | overflow: hidden;
5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
6 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
7 | sans-serif;
8 | -webkit-font-smoothing: antialiased;
9 | -moz-osx-font-smoothing: grayscale;
10 | }
11 |
12 | code {
13 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
14 | monospace;
15 | }
16 |
--------------------------------------------------------------------------------
/src/useSpawnPoint.js:
--------------------------------------------------------------------------------
1 | import { Point } from "pixi.js"
2 | import { useMemo } from "react"
3 | import useObjects from "./useObjects"
4 |
5 | const useSpawnPoint = () => {
6 | const objects = useObjects()
7 |
8 | const point = useMemo(() => {
9 | const spawn = objects.find(object => object.name === 'Spawn')
10 | return new Point(spawn.x + spawn.width / 2, spawn.y + spawn.height / 2)
11 | })
12 |
13 | return point
14 | }
15 |
16 | export default useSpawnPoint
17 |
--------------------------------------------------------------------------------
/src/useInteractables.js:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import useObjects from './useObjects'
3 |
4 | const useInteractableObjects = () => {
5 | const objects = useObjects()
6 |
7 | return useMemo(() => {
8 | const interactables = []
9 |
10 | for (const object of objects) {
11 | if (object.properties.isInteractable) {
12 | interactables.push(object)
13 | }
14 | }
15 |
16 | return interactables
17 | }, [ objects ])
18 | }
19 |
20 | export default useInteractableObjects
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Tilemap from './Tilemap'
2 | import useTilemapLoader from './useTilemapLoader'
3 | import useMap from './useMap'
4 | import useSpawnPoint from './useSpawnPoint'
5 | import useCollisions from './useCollisions'
6 | import useInteractables from './useInteractables'
7 | import useObjects from './useObjects'
8 | import useTiles from './useTiles'
9 |
10 | export {
11 | Tilemap,
12 | useTilemapLoader,
13 | useInteractables,
14 | useMap,
15 | useSpawnPoint,
16 | useCollisions,
17 | useObjects,
18 | useTiles,
19 | }
20 |
--------------------------------------------------------------------------------
/demo/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/demo/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/useTiles.js:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect, useState } from "react"
2 | import TilemapContext from "./TilemapContext"
3 |
4 | const useTiles = () => {
5 | const { map } = useContext(TilemapContext)
6 | const [tiles, set] = useState([])
7 |
8 | useEffect(() => {
9 | if (map) {
10 | const newTiles = map.layers
11 | .filter(layer => layer.type === 'tile')
12 | .map(layer => layer.tiles)
13 | .flat()
14 | .filter(tile => tile !== null)
15 |
16 | set(newTiles)
17 | }
18 | }, [ map ])
19 |
20 | return tiles
21 | }
22 |
23 | export default useTiles
--------------------------------------------------------------------------------
/demo/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/demo/src/App.js:
--------------------------------------------------------------------------------
1 | import { Stage, Sprite } from '@inlet/react-pixi';
2 | import { Texture } from '@pixi/core';
3 | import { Tilemap, useTilemapLoader } from 'react-pixi-tilemap'
4 |
5 | const tilemape = process.env.PUBLIC_URL + '/stages/map.tmx'
6 |
7 | const App = () => {
8 | const map = useTilemapLoader(tilemape)
9 |
10 | return (
11 |
12 |
13 | {/* These sprites show off the layering order */}
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@inlet/react-pixi": "^6.5.4",
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "react-pixi-tilemap": "../",
11 | "pixi.js": "^6.0.4",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "react-scripts": "4.0.3",
15 | "web-vitals": "^1.0.1"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test",
21 | "eject": "react-scripts eject"
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app",
26 | "react-app/jest"
27 | ]
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2021 David Minnerly
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/src/useCollisions.js:
--------------------------------------------------------------------------------
1 | import { Rectangle } from "pixi.js"
2 | import { useContext, useMemo } from "react"
3 | import TilemapContext from "./TilemapContext"
4 |
5 | const useCollisions = () => {
6 | const { map } = useContext(TilemapContext)
7 |
8 | const collisions = useMemo(() => {
9 | return map.layers
10 | .filter(layer => layer.type === 'tile')
11 | .map(layer => {
12 | const rects = []
13 |
14 | for (const tile of layer.tiles) {
15 | if (tile) {
16 | const { objectGroups } = tile
17 |
18 | if (objectGroups.length > 0) {
19 | for (const rectangle of objectGroups) {
20 | const { x, y, width, height } = rectangle
21 | rects.push(new Rectangle(tile.x + x, tile.y + y, width, height))
22 | }
23 | }
24 | }
25 | }
26 |
27 | return rects
28 | })
29 | .flat()
30 | }, [ map ])
31 |
32 | return collisions
33 | }
34 |
35 | export default useCollisions
--------------------------------------------------------------------------------
/src/useTilemapLoader.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import tmx from 'tmx-parser'
3 |
4 | const useTilemapLoader = (tilemapPath) => {
5 | const [map, setMap] = useState(null)
6 |
7 | useEffect(() => {
8 | tmx.parseFile(tilemapPath, (err, map) => {
9 | if (err) throw err
10 |
11 | // Add (x,y) coordinates to each tile so it is easy to align collision
12 | // rectangles later.
13 | map.layers = map.layers.map(layer => {
14 | return {
15 | ...layer,
16 | tiles: layer.tiles?.map((tile, index) => {
17 | const x = (index % map.width) * map.tileWidth
18 | const y = Math.floor(index / map.width) * map.tileHeight
19 | const width = map.tileWidth
20 | const height = map.tileHeight
21 |
22 | return { ...tile, x, y, width, height}
23 | })
24 | }
25 | })
26 |
27 | map.path = tilemapPath
28 |
29 | setMap(map)
30 | })
31 | }, [ tilemapPath ])
32 |
33 | return map
34 | }
35 |
36 | export default useTilemapLoader
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-pixi-tilemap",
3 | "version": "2.0.2",
4 | "description": "Use maps created with Tiled in your React-based Pixi.js games",
5 | "source": "src/index.js",
6 | "main": "dist/index.js",
7 | "files": [
8 | "dist"
9 | ],
10 | "scripts": {
11 | "dev": "cd demo && yarn start",
12 | "watch": "microbundle watch --jsx React.createElement",
13 | "start": "concurrently \"yarn:watch\" \"yarn:dev\"",
14 | "build": "microbundle build --jsx React.createElement",
15 | "test": "echo \"Error: no test specified\" && exit 1"
16 | },
17 | "keywords": [
18 | "react",
19 | "pixi",
20 | "tiled",
21 | "tmx",
22 | "tilemap",
23 | "tileset"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/vocksel/react-pixi-tilemap"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/vocksel/react-pixi-tilemap/issues"
31 | },
32 | "author": "David Minnerly",
33 | "license": "MIT",
34 | "peerDependencies": {
35 | "@inlet/react-pixi": ">=6.5.2",
36 | "pixi.js": ">=6.0.4",
37 | "react": ">=17.0.2"
38 | },
39 | "dependencies": {
40 | "tmx-parser": "github:eioo/node-tmx-parser"
41 | },
42 | "devDependencies": {
43 | "concurrently": "^6.2.0",
44 | "microbundle": "^0.13.0"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Layer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { Container } from "@inlet/react-pixi"
3 | import getSprite from './getSprite'
4 |
5 | const getTileSprites = (layer, map) => {
6 | const sprites = []
7 |
8 | for (let y = 0; y < map.height; y++) {
9 | for (let x = 0; x < map.width; x++) {
10 | const i = x + y * map.width
11 | const tile = layer.tiles[i]
12 |
13 | if (tile?.gid > 0) {
14 | sprites.push(getSprite('tile', tile, map))
15 | }
16 | }
17 | }
18 |
19 | return sprites
20 | }
21 |
22 | const getObjectSprites = (layer, map) => {
23 | return layer.objects.map(object => {
24 | if (object.gid) {
25 | return getSprite('object', object, map)
26 | }
27 | })
28 | }
29 |
30 | const Layer = ({ layer, map }) => {
31 | if (!layer.visible) {
32 | return null
33 | }
34 |
35 | const sprites = useMemo(() => {
36 | if (layer.type === 'tile') {
37 | return getTileSprites(layer, map)
38 | } else if (layer.type === 'object') {
39 | return getObjectSprites(layer, map)
40 | }
41 | }, [ layer ] )
42 |
43 | return
44 | {sprites}
45 |
46 | }
47 |
48 | export default Layer
49 |
--------------------------------------------------------------------------------
/src/Tilemap.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useState } from 'react'
2 | import { Container } from '@inlet/react-pixi'
3 | import TilemapContext from './TilemapContext'
4 | import Layer from './Layer'
5 |
6 | const Tilemap = ({ map, children, ...props }) => {
7 | const [hasForeground, setHasForeground] = useState(false)
8 |
9 | const layers = useMemo(() => {
10 | return map?.layers.map((layer, index) => {
11 | const { name } = layer
12 | if (name === 'Foreground') {
13 | setHasForeground(true)
14 | return children
15 | } else {
16 | return
17 | }
18 | })
19 | }, [ map, children ])
20 |
21 | // Only render once the map is loaded so that our hooks don't need to
22 | // conditionally check if anything in TilemapContext exists.
23 | if (map) {
24 | return
25 |
26 | {layers}
27 |
28 | {/* If there's no foreground layer specified in the map, we simply render the children on top */}
29 | {!hasForeground && children}
30 |
31 |
32 | } else {
33 | return null
34 | }
35 |
36 | }
37 |
38 | export default Tilemap
39 |
--------------------------------------------------------------------------------
/src/getSprite.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import { Rectangle, Texture } from 'pixi.js'
3 | import { Sprite } from "@inlet/react-pixi"
4 | import React from "react"
5 |
6 | const getTilesetForGID = (gid, tilesets) => {
7 | let result
8 | for (const tileset of tilesets) {
9 | if (gid >= tileset.firstGid) {
10 | result = tileset
11 | }
12 | }
13 | return result
14 | }
15 |
16 | const getTileTexture = (tile, map, tileset) => {
17 | const { image, tileHeight, tileWidth } = tileset
18 | const spriteIndex = tile.gid - tileset.firstGid
19 |
20 | const x = (spriteIndex % (image.width / tileWidth)) * tileWidth
21 | const y = Math.floor(spriteIndex / (image.height / tileHeight)) * tileHeight
22 |
23 | const rootDir = path.dirname(map.path)
24 | const baseTexture = Texture.from(`${rootDir}/${tileset.image.source}`)
25 |
26 | return new Texture(baseTexture, new Rectangle(x, y, tileHeight, tileWidth))
27 | }
28 |
29 | const getTileSprite = (tileType, tile, map) => {
30 | const tileset = getTilesetForGID(tile.gid, map.tileSets)
31 |
32 | if (tileset) {
33 | const { x, width, height } = tile
34 | const texture = getTileTexture(tile, map, tileset)
35 |
36 | // Objects are bottom aligned for some reason. This pushes them up so they align with the rest of the tilemap.
37 | let y = tile.y
38 | if (tileType === 'object') {
39 | y -= height
40 | }
41 |
42 | return
49 | }
50 | }
51 |
52 | export default getTileSprite
53 |
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/demo/public/stages/TX Plant.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/demo/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/public/stages/TX Props.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-pixi-tilemap
2 |
3 | This package allows you to easily make use of levels created with [Tiled](https://www.mapeditor.org/) in your React and Pixi based games.
4 |
5 | Supports embedded and external tilesets, collisions, child layering, and comes with a bunch of different hooks to work with tiles and objects from within your components.
6 |
7 | 
8 |
9 | ## Install
10 |
11 | ```sh
12 | $ npm install react-pixi-tilemap
13 | # Or
14 | $ yarn add react-pixi-tilemap
15 | ```
16 |
17 | ## Usage
18 |
19 | Your `.tmx` files, and their associated `.tsx` and atlas files, should be in a static location, such as the `public` folder if you are using create-react-app. This makes it easy for the Tilemap component to find assets used in the map.
20 |
21 | Using the component is dead simple. Simply load your map and pass it into the Tilemap component.
22 |
23 | ```js
24 | import { Tilemap, useTilemapLoader } from 'react-pixi-tilemap'
25 |
26 | const tilemap = process.env.PUBLIC_URL + '/stages/map.tmx'
27 |
28 | const App = (props) => {
29 | const map = useTilemapLoader(tilemap)
30 |
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 | ```
42 |
43 | The return value of `useTilemapLoader` is an Object represented by [TMX Map Format#map](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map). Make sure to look over those docs to learn more about what properties you have access to.
44 |
45 | Be advised that you should not use a typical `import` statement. If you `import map from './map.tmx'`, Webpack (or similar build system) will take your .tmx file away from your tilesets, which breaks the Tilemap component's discovery. As such, keeping your tilemaps and tilesets in a static location will give you the best results.
46 |
47 | Your tilesets can either be saved as `.tsx` files, or embedded in the `.tmx` file itself. Either works with this package. Keep in mind however that you _cannot_ save your tilesets as their `.json` equivalents. This seems to be an issue with tmx-parser.
48 |
49 | ## Example
50 |
51 | Check out the [demo project](demo) for an example of how this all comes together. Be sure to open up the [map.tmx]() file in Tiled and explore around.
52 |
53 | To get setup, run the following commands:
54 |
55 | ```sh
56 | $ cd demo
57 | $ yarn install
58 | $ yarn start
59 | ```
60 |
61 | ## Foreground Layer
62 |
63 | Adding a `Foreground` layer to your `.tmx` file determines where children of the Tilemap component are rendered. Without this layer, children are rendered on top of the map. If you want children to appear above some layers, and behind others, then make sure to include this layer. The order of the Foreground layer in Tiled's `Layer` pane controls where the children are rendered.
64 |
65 | 
66 |
67 | ## Hooks
68 |
69 | **`useCollisions()`**
70 |
71 | Returns a flat list of Pixi.js Rectangle instances that represent the collisions defined in the `.tmx` file. These can be configured with Tiled's [Collision Editor](https://doc.mapeditor.org/en/stable/manual/editing-tilesets/#tile-collision-editor).
72 |
73 | ```js
74 | const collisions = useCollisions()
75 | ```
76 |
77 | **`useInteractables()`**
78 |
79 | Any object that has a bool `isInteractable` custom property in Tiled will be picked up by this hook. This allows you to easily define objects in the game (such as NPCs, items on the ground, etc.) as something the user can interact with, and use them in your code.
80 |
81 | ```js
82 | const interactables = useInteractables()
83 | ```
84 |
85 | **`useMap()`**
86 |
87 | Returns the `map` property passed into Tilemap. This can be helpful if you need to access metadata about the map, or if you need to write a hook that is not included in this package.
88 |
89 | See [TMX Map Format#map](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map).
90 |
91 | ```js
92 | const map = useMap()
93 | ```
94 |
95 | **`useObjects()`**
96 |
97 | Returns a flat list of every object across every layer.
98 |
99 | See [TMX Map Format#object](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#object)
100 |
101 | ```js
102 | const objects = useObjects()
103 | ```
104 |
105 | **`useSpawnPoint()`**
106 |
107 | This hook is fairly specific, but if you add an object named `Spawn`, this hook will return it for you. This makes it easy to setup a spawn location for player characters.
108 |
109 | **`useTilemapLoader(tilemapPath)`**
110 |
111 | This hook is what you use to load your .tmx map before passing it into the Tilemap component.
112 |
113 | **`useTiles()`**
114 |
115 | Returns a flat array of every tile in the map.
116 |
117 | See [TMX Map Format#tile](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tile).
118 |
119 | ## Credits
120 |
121 | Thanks to Cainos' for their awesome [Pixel Art Top Down - Basic](https://cainos.itch.io/pixel-art-top-down-basic) tiles.
122 |
123 | Also very big shoutout to the following repos that were very invaluable in figuring out how to create this package.
124 |
125 | - [Reynau/tiled-to-pixi](https://github.com/Reynau/tiled-to-pixi)
126 | - [knervous/tiled-pixi-react](https://github.com/knervous/tiled-pixi-react)
127 |
128 | ## Developing
129 |
130 | Run the following commands from the root directory to install all dependencies, and get setup with a dev environment that will live-reload as you make changes to the package.
131 |
132 | ```sh
133 | $ yarn install
134 | $ yarn link
135 | $ cd demo
136 | $ yarn link react-pixi-tilemap
137 | $ yarn install
138 | $ cd ..
139 | $ yarn start
140 | ```
141 | ## License
142 |
143 | MIT
144 |
--------------------------------------------------------------------------------
/demo/public/stages/TX Struct.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
61 |
64 |
65 |
66 |
67 |
68 |
71 |
74 |
75 |
76 |
77 |
78 |
81 |
84 |
85 |
86 |
87 |
88 |
91 |
92 |
93 |
94 |
95 |
98 |
99 |
100 |
101 |
102 |
105 |
106 |
107 |
108 |
109 |
112 |
113 |
114 |
115 |
116 |
119 |
120 |
121 |
122 |
123 |
126 |
127 |
128 |
129 |
130 |
133 |
134 |
135 |
136 |
137 |
140 |
141 |
142 |
143 |
144 |
147 |
148 |
149 |
150 |
151 |
154 |
155 |
156 |
157 |
158 |
161 |
162 |
163 |
164 |
165 |
168 |
169 |
170 |
171 |
172 |
175 |
176 |
177 |
178 |
179 |
182 |
183 |
184 |
185 |
186 |
189 |
192 |
193 |
194 |
195 |
196 |
199 |
202 |
203 |
204 |
205 |
206 |
209 |
210 |
211 |
212 |
213 |
216 |
217 |
218 |
219 |
220 |
223 |
224 |
225 |
226 |
227 |
230 |
231 |
232 |
233 |
234 |
237 |
238 |
239 |
240 |
241 |
244 |
245 |
246 |
247 |
248 |
251 |
252 |
253 |
254 |
255 |
258 |
259 |
260 |
261 |
262 |
265 |
266 |
267 |
268 |
269 |
272 |
273 |
274 |
275 |
276 |
279 |
280 |
281 |
282 |
283 |
286 |
287 |
288 |
289 |
290 |
293 |
294 |
295 |
296 |
297 |
300 |
301 |
302 |
303 |
304 |
307 |
308 |
309 |
310 |
311 |
314 |
315 |
316 |
317 |
318 |
321 |
322 |
323 |
324 |
325 |
328 |
329 |
330 |
331 |
332 |
335 |
336 |
337 |
338 |
339 |
342 |
343 |
344 |
345 |
--------------------------------------------------------------------------------
/demo/public/stages/TX Tileset Wall.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
26 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
62 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
97 |
98 |
99 |
100 |
101 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
126 |
127 |
128 |
129 |
130 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
145 |
148 |
149 |
150 |
151 |
152 |
155 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
181 |
182 |
183 |
184 |
185 |
188 |
189 |
190 |
191 |
192 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
--------------------------------------------------------------------------------
/demo/public/stages/map.tmx:
--------------------------------------------------------------------------------
1 |
2 |
245 |
--------------------------------------------------------------------------------