Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 75 additions & 112 deletions src/components/tilemap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ interface TilemapProps {
className?: string;
}

const CURSOR_COLORS = ['#FF4D00', '#F0C800', '#0094FF', '#BC3FDC'];

export default function Tilemap({ tiles, tileSize, tilePaddingWidth, tilePaddingHeight, className, isMoving }: TilemapProps) {
const cursorColors = useMemo(() => ['#FF4D00', '#F0C800', '#0094FF', '#BC3FDC'], []);
// constants
const { flagPaths, tileColors, countColors, boomPaths } = Paths;
// stores
const { zoom } = useCursorStore();
const { windowHeight, windowWidth } = useScreenSize();

// states
const [innerZoom, setInnerZoom] = useState(zoom);

// Generate textures for tiles, boom, and flags
Expand All @@ -35,28 +38,29 @@ export default function Tilemap({ tiles, tileSize, tilePaddingWidth, tilePadding
const textures = useMemo(() => {
const newTileTextures = new Map<string, Texture>();

const createTileTexture = (color1: string, color2: string) => {
const key = `${color1}-${color2}-${tileSize}`;
const createTileTexture = (color0: string, color1: string) => {
const key = `${color0}${color1}${tileSize}`;
const quarterSize = tileSize / 2;
if (newTileTextures.has(key)) return newTileTextures.get(key)!;

const tempCanvas = document.createElement('canvas');
tempCanvas.width = tempCanvas.height = tileSize;
tempCanvas.width = tempCanvas.height = quarterSize;
const ctx = tempCanvas.getContext('2d');
if (!ctx) return;
const gradient = ctx.createLinearGradient(0, 0, tileSize, tileSize);
gradient.addColorStop(0, color1);
gradient.addColorStop(1, color2);
const gradient = ctx.createLinearGradient(0, 0, quarterSize, quarterSize);
gradient.addColorStop(0, color0);
gradient.addColorStop(1, color1);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, tileSize, tileSize);
ctx.fillRect(0, 0, quarterSize, quarterSize);
Comment thread
kimgh06 marked this conversation as resolved.
Outdated
const texture = Texture.from(tempCanvas, { resolution: 0.0001, scaleMode: SCALE_MODES.NEAREST });
newTileTextures.set(key, texture);
return texture;
};

// Textures for outer and inner tiles
for (let i = 0; i < 3; i++) {
createTileTexture(tileColors.outer[i][0], tileColors.outer[i][1]);
createTileTexture(tileColors.inner[i][0], tileColors.inner[i][1]);
for (let idx = 0; idx < 3; idx++) {
createTileTexture(tileColors.outer[idx][0], tileColors.outer[idx][1]);
createTileTexture(tileColors.inner[idx][0], tileColors.inner[idx][1]);
}

// Boom texture
Expand All @@ -65,177 +69,136 @@ export default function Tilemap({ tiles, tileSize, tilePaddingWidth, tilePadding
const boomCtx = boomCanvas.getContext('2d');
if (boomCtx) {
boomCtx.scale(zoom / 4, zoom / 4);
const outer = new Path2D(boomPaths[1]);
const inner = new Path2D(boomPaths[0]);
boomCtx.fillStyle = 'rgba(0, 0, 0, 0.6)';
boomCtx.fill(inner);
const outer = new Path2D(boomPaths[1]);
boomCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
boomCtx.fill(outer);
const boomTexture = Texture.from(boomCanvas, { resolution: 0.5, scaleMode: SCALE_MODES.NEAREST });
newTileTextures.set('boom', boomTexture);
}

// Flag textures
for (let i = 0; i < 4; i++) {
for (let idx = 0; idx < 4; idx++) {
const flagCanvas = document.createElement('canvas');
flagCanvas.width = flagCanvas.height = tileSize;
const flagCtx = flagCanvas.getContext('2d');
if (!flagCtx) continue;
const flagGradient = flagCtx.createLinearGradient(36.5, 212.5, 36.5, 259);
flagGradient.addColorStop(0, '#E8E8E8');
flagGradient.addColorStop(1, 'transparent');
flagCtx.translate(tileSize / 6, tileSize / 6);
flagCtx.translate(flagCanvas.width / 6, flagCanvas.height / 6);
flagCtx.scale(zoom / 4.5, zoom / 4.5);
const flagPath = new Path2D(flagPaths[0]);
const polePath = new Path2D(flagPaths[1]);
flagCtx.fillStyle = cursorColors[i];
flagCtx.fillStyle = CURSOR_COLORS[idx];
flagCtx.fill(flagPath);
flagCtx.fillStyle = flagGradient;
flagCtx.fill(polePath);
const flagTexture = Texture.from(flagCanvas, { resolution: 0.5, scaleMode: SCALE_MODES.NEAREST });
newTileTextures.set(`flag-${i}`, flagTexture);
newTileTextures.set(`flag${idx}`, flagTexture);
}
setInnerZoom(zoom);
return newTileTextures;
}, [tileSize, tileColors, boomPaths, flagPaths, cursorColors, zoom]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tileSize, zoom]);
Comment thread
kimgh06 marked this conversation as resolved.
Outdated

// Cache text styles for numbers using useMemo
const cachedTextStyles = useMemo(
() =>
Array.from(
{ length: 8 },
(_, i) =>
new TextStyle({
fontFamily: 'LOTTERIACHAB',
fontSize: 50 * zoom,
fill: countColors[i],
}),
),
[zoom, countColors],
);
// For UnderLine useMemo function.
const makeCacheTextStyles = () => {
const [style, len] = [{ fontFamily: 'LOTTERIACHAB', fontSize: 50 * zoom }, { length: 8 }];
return Array.from(len, (_, i) => new TextStyle({ ...style, fill: countColors[i] }));
};
const cachedTextStyles = useMemo(makeCacheTextStyles, [zoom, countColors]);
Comment thread
kimgh06 marked this conversation as resolved.
Outdated

// Memoize sprites creation using cached base sprites from useRef
const { outerSprites, innerSprites, boomSprites, flagSprites, textElements } = useMemo(() => {
const outerSpritesArr: JSX.Element[] = [];
const innerSpritesArr: JSX.Element[] = [];
const boomSpritesArr: JSX.Element[] = [];
const flagSpritesArr: JSX.Element[] = [];
const textElementsArr: JSX.Element[] = [];

// Get empty arrays & Caches
const [outerSprites, innerSprites, boomSprites, flagSprites, textElements]: JSX.Element[][] = [[], [], [], [], []];
const { outerCache, innerCache, boomCache, flagCache } = cachesRef.current;

for (let ri = 0; ri < tiles.length; ri++) {
for (let ci = 0; ci < tiles[ri].length; ci++) {
const renderStartX = (ci - tilePaddingWidth) * tileSize;
const renderStartY = (ri - tilePaddingHeight) * tileSize;
if (renderStartX < -tileSize || renderStartY < -tileSize) continue; // out of bounds
if (renderStartX > windowWidth + tileSize || renderStartY > windowHeight + tileSize) continue; // out of bounds
const tileKey = `${ri}${ci}${tileSize}`;
const content = tiles[ri][ci];
const x = (ci - tilePaddingWidth) * tileSize;
const y = (ri - tilePaddingHeight) * tileSize;
if (x < -tileSize || y < -tileSize || x > windowWidth + tileSize || y > windowHeight + tileSize) continue;
const tileKey = `${ri}-${ci}-${tileSize}`;
const { inner, outer } = tileColors;

// Select textures based on tile content
let outerTexture = textures.get(`${tileColors.outer[2][0]}-${tileColors.outer[2][1]}-${tileSize}`);
let innerTexture = textures.get(`${tileColors.inner[2][0]}-${tileColors.inner[2][1]}-${tileSize}`);
let outerTexture = textures.get(`${outer[2][0]}${outer[2][1]}${tileSize}`);
let innerTexture = textures.get(`${inner[2][0]}${inner[2][1]}${tileSize}`);
if (['C', 'F'].includes(content[0])) {
const isEven = content.slice(-1) === '0' ? 0 : 1;
outerTexture = textures.get(`${tileColors.outer[isEven][0]}-${tileColors.outer[isEven][1]}-${tileSize}`);
innerTexture = textures.get(`${tileColors.inner[isEven][0]}-${tileColors.inner[isEven][1]}-${tileSize}`);
const isEven = +content.slice(-1) % 2;
outerTexture = textures.get(`${outer[isEven][0]}${outer[isEven][1]}${tileSize}`);
innerTexture = textures.get(`${inner[isEven][0]}${inner[isEven][1]}${tileSize}`);
}

// Outer sprite
if (outerTexture) {
const outerKey = `${outerTexture.textureCacheIds || outerTexture}-${tileSize}`;
let baseOuter = outerCache.get(outerKey);
if (!baseOuter) {
baseOuter = (
<Sprite scale={0.1} interactive={false} texture={outerTexture} width={tileSize} height={tileSize} cacheAsBitmapResolution={0.1} />
);
outerCache.set(outerKey, baseOuter);
const outerKey = `${outerTexture.textureCacheIds || outerTexture}${tileSize}`;
let outerSprite = outerCache.get(outerKey);
if (!outerSprite) {
const [width, height, cacheAsBitmapResolution] = [tileSize, tileSize, 0.1];
outerSprite = <Sprite scale={0.1} interactive={false} texture={outerTexture} {...{ width, height, cacheAsBitmapResolution }} />;
outerCache.set(outerKey, outerSprite);
}
outerSpritesArr.push(cloneElement(baseOuter, { key: `outer-${tileKey}`, x, y }));
outerSprites.push(cloneElement(outerSprite, { key: `outer${tileKey}`, x: renderStartX, y: renderStartY }));
}

// Inner sprite
if (innerTexture) {
const innerKey = `${innerTexture.textureCacheIds || innerTexture}-${tileSize}`;
let baseInner = innerCache.get(innerKey);
if (!baseInner) {
const size = tileSize - 10 * zoom;
baseInner = <Sprite scale={0.1} interactive={false} texture={innerTexture} width={size} height={size} cacheAsBitmapResolution={0.1} />;
innerCache.set(innerKey, baseInner);
const innerKey = `${innerTexture.textureCacheIds || innerTexture}${tileSize}`;
let innerSprite = innerCache.get(innerKey);
if (!innerSprite) {
const [width, height, cacheAsBitmapResolution] = [tileSize - 10 * zoom, tileSize - 10 * zoom, 0.1];
innerSprite = <Sprite scale={0.1} interactive={false} texture={innerTexture} {...{ width, height, cacheAsBitmapResolution }} />;
innerCache.set(innerKey, innerSprite);
}
innerSpritesArr.push(cloneElement(baseInner, { key: `inner-${tileKey}`, x: x + 5 * zoom, y: y + 5 * zoom }));
innerSprites.push(cloneElement(innerSprite, { key: `inner${tileKey}`, x: renderStartX + 5 * zoom, y: renderStartY + 5 * zoom }));
}

// Boom sprite
if (content === 'B') {
const boomKey = `boom-${tileSize}`;
let baseBoom = boomCache.get(boomKey);
if (!baseBoom) {
baseBoom = (
<Sprite
scale={0.1}
interactive={false}
texture={textures.get('boom')}
width={tileSize}
height={tileSize}
cacheAsBitmapResolution={0.1}
/>
);
boomCache.set(boomKey, baseBoom);
const boomKey = `boom${tileSize}`;
let boomSprite = boomCache.get(boomKey);
if (!boomSprite) {
const [width, height, cacheAsBitmapResolution] = [tileSize, tileSize, 0.1];
boomSprite = <Sprite scale={0.1} interactive={false} texture={textures.get('boom')} {...{ width, height, cacheAsBitmapResolution }} />;
boomCache.set(boomKey, boomSprite);
}
boomSpritesArr.push(cloneElement(baseBoom, { key: `boom-${tileKey}`, x, y }));
boomSprites.push(cloneElement(boomSprite, { key: `boom${tileKey}`, x: renderStartX, y: renderStartY }));
}

// Flag sprite
if (content[0] === 'F') {
const flagIndex = content[1];
const flagKey = `flag-${flagIndex}-${tileSize}`;
const flagColor = content[1];
const flagKey = `flag${flagColor}${tileSize}`;
let baseFlag = flagCache.get(flagKey);
if (!baseFlag) {
baseFlag = (
<Sprite
interactive={false}
texture={textures.get(`flag-${flagIndex}`)}
anchor={0.5}
scale={0.1}
width={tileSize}
height={tileSize}
cacheAsBitmapResolution={0.1}
/>
);
const [width, height, cacheAsBitmapResolution, texture] = [tileSize, tileSize, 0.1, textures.get(`flag${flagColor}`)];
baseFlag = <Sprite scale={0.1} interactive={false} anchor={0.5} {...{ width, height, cacheAsBitmapResolution, texture }} />;
flagCache.set(flagKey, baseFlag);
}
flagSpritesArr.push(cloneElement(baseFlag, { key: `flag-${tileKey}`, x: x + tileSize / 2, y: y + tileSize / 2 }));
flagSprites.push(cloneElement(baseFlag, { key: `flag${tileKey}`, x: renderStartX + tileSize / 2, y: renderStartY + tileSize / 2 }));
}

// Text elements
const num = parseInt(content);
if (num > 0) {
textElementsArr.push(
<Text
key={`text-${tileKey}`}
text={content}
x={x + tileSize / 2}
y={y + tileSize / 2}
resolution={0.8}
anchor={0.5}
style={cachedTextStyles[num - 1]}
/>,
);
if (+content > 0) {
const [x, y, style] = [renderStartX + tileSize / 2, renderStartY + tileSize / 2, cachedTextStyles[+content - 1]];
textElements.push(<Text key={`text${tileKey}`} text={content} resolution={0.8} anchor={0.5} {...{ x, y, style }} />);
}
}
}
return {
outerSprites: outerSpritesArr,
innerSprites: innerSpritesArr,
boomSprites: boomSpritesArr,
flagSprites: flagSpritesArr,
textElements: textElementsArr,
};
return { outerSprites, innerSprites, boomSprites, flagSprites, textElements };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tiles]);

// if no textures or text styles, return null
if (!textures.size || !cachedTextStyles) return null;

return (
<Stage
id="Tilemap"
Expand All @@ -244,7 +207,7 @@ export default function Tilemap({ tiles, tileSize, tilePaddingWidth, tilePadding
height={windowHeight}
options={{
backgroundColor: 0x808080,
resolution: isMoving ? 0.4 : 0.8,
resolution: isMoving ? 0.5 : 0.8,
antialias: false,
powerPreference: 'high-performance',
autoDensity: true,
Expand Down