import { PixiComponent, applyDefaultProps } from '@pixi/react'; import * as PIXI from 'pixi.js'; import { AnimatedSprite, WorldMap, TileLayer } from '../../convex/aiTown/worldMap'; import * as campfire from '../../data/animations/campfire.json'; import * as gentlesparkle from '../../data/animations/gentlesparkle.json'; import * as gentlewaterfall from '../../data/animations/gentlewaterfall.json'; import * as gentlesplash from '../../data/animations/gentlesplash.json'; import * as windmill from '../../data/animations/windmill.json'; import React from 'react'; export type MapProps = { map: WorldMap; [k: string]: any; }; const animations = { 'campfire.json': { spritesheet: campfire, url: '/assets/spritesheets/campfire.png' }, 'gentlesparkle.json': { spritesheet: gentlesparkle, url: '/assets/spritesheets/gentlesparkle32.png', }, 'gentlewaterfall.json': { spritesheet: gentlewaterfall, url: '/assets/spritesheets/gentlewaterfall32.png', }, 'windmill.json': { spritesheet: windmill, url: '/assets/spritesheets/windmill.png' }, 'gentlesplash.json': { spritesheet: gentlesplash, url: '/assets/spritesheets/gentlewaterfall32.png',}, }; const createTiles = (map: WorldMap) => { const numxtiles = Math.floor(map.tileSetDimX / map.tileDim); const numytiles = Math.floor(map.tileSetDimY / map.tileDim); const bt = PIXI.BaseTexture.from(map.tileSetUrl, { scaleMode: PIXI.SCALE_MODES.NEAREST, }); const tiles = []; for (let x = 0; x < numxtiles; x++) { for (let y = 0; y < numytiles; y++) { tiles[x + y * numxtiles] = new PIXI.Texture( bt, new PIXI.Rectangle(x * map.tileDim, y * map.tileDim, map.tileDim, map.tileDim), ); } } return tiles; }; const renderMap = (container: PIXI.Container, map:WorldMap, tiles: PIXI.Texture[]) => { const screenxtiles = map.bgTiles[0].length; const screenytiles = map.bgTiles[0][0].length; const allLayers = [...map.bgTiles, ...map.objectTiles, ...map.decorTiles]; // blit bg & object layers of map onto canvas for (let i = 0; i < screenxtiles * screenytiles; i++) { const x = i % screenxtiles; const y = Math.floor(i / screenxtiles); const xPx = x * map.tileDim; const yPx = y * map.tileDim; // Add all layers of backgrounds. for (const layer of allLayers) { const tileIndex = layer[x][y]; // Some layers may not have tiles at this location. if (tileIndex === -1) continue; const ctile = new PIXI.Sprite(tiles[tileIndex]); ctile.x = xPx; ctile.y = yPx; container.addChild(ctile); } } }; const createAnimatedSprites = (container: PIXI.Container, map: WorldMap) => { const spritesBySheet = new Map(); for (const sprite of map.animatedSprites) { const sheet = sprite.sheet; if (!spritesBySheet.has(sheet)) { spritesBySheet.set(sheet, []); } spritesBySheet.get(sheet)!.push(sprite); } for (const [sheet, sprites] of spritesBySheet.entries()) { const animation = (animations as any)[sheet]; if (!animation) { console.error('Could not find animation', sheet); continue; } const { spritesheet, url } = animation; const texture = PIXI.BaseTexture.from(url, { scaleMode: PIXI.SCALE_MODES.NEAREST, }); const spriteSheet = new PIXI.Spritesheet(texture, spritesheet); spriteSheet.parse().then(() => { for (const sprite of sprites) { const pixiAnimation = spriteSheet.animations[sprite.animation]; if (!pixiAnimation) { console.error('Failed to load animation', sprite); continue; } const pixiSprite = new PIXI.AnimatedSprite(pixiAnimation); pixiSprite.animationSpeed = 0.1; pixiSprite.autoUpdate = true; pixiSprite.x = sprite.x; pixiSprite.y = sprite.y; pixiSprite.width = sprite.w; pixiSprite.height = sprite.h; container.addChild(pixiSprite); pixiSprite.play(); } }); } }; export const PixiStaticMapComponent = PixiComponent('StaticMap', { create: (props: { map: WorldMap; [k: string]: any }) => { const container = new PIXI.Container(); const tiles = createTiles(props.map); renderMap(container, props.map, tiles); createAnimatedSprites(container, props.map); container.x = 0; container.y = 0; // Set the hit area manually to ensure `pointerdown` events are delivered to this container. const screenxtiles = props.map.bgTiles[0].length; const screenytiles = props.map.bgTiles[0][0].length; container.interactive = true; container.hitArea = new PIXI.Rectangle( 0, 0, screenxtiles * props.map.tileDim, screenytiles * props.map.tileDim, ); return container; }, applyProps: (instance, oldProps: any, newProps: any) => { if (oldProps.map !== newProps.map) { instance.removeChildren(); const tiles = createTiles(newProps.map); renderMap(instance, newProps.map, tiles); createAnimatedSprites(instance, newProps.map); } applyDefaultProps(instance, oldProps, newProps); }, }); const PixiStaticMap = React.memo( (props: MapProps) => { return ; }, (prevProps, nextProps) => { return ( prevProps.map.bgTiles === nextProps.map.bgTiles && prevProps.map.objectTiles === nextProps.map.objectTiles && prevProps.map.decorTiles === nextProps.map.decorTiles ); } ); export default PixiStaticMap;