updated pixijs
This commit is contained in:
parent
ac318f51f2
commit
aa7b8569d7
|
|
@ -28,6 +28,7 @@ export default [
|
|||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/display-name": "off",
|
||||
"react/no-unknown-property": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -16,9 +16,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^3.1.2",
|
||||
"@pixi/canvas-display": "^7.4.2",
|
||||
"@pixi/canvas-renderer": "^7.4.2",
|
||||
"@pixi/react": "^7.1.2",
|
||||
"@pixi/canvas-display": "^7.4.3",
|
||||
"@pixi/canvas-renderer": "^7.4.3",
|
||||
"@pixi/react": "^8.0.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
|
|
@ -38,9 +38,9 @@
|
|||
"jotai": "^2.14.0",
|
||||
"lucide-react": "^0.544.0",
|
||||
"motion": "^12.23.12",
|
||||
"pixi-viewport": "^5.0.3",
|
||||
"pixi.js": "^7.0.0",
|
||||
"pixi.js-legacy": "^7.4.2",
|
||||
"pixi-viewport": "^6.0.3",
|
||||
"pixi.js": "^8.13.2",
|
||||
"pixi.js-legacy": "^7.4.3",
|
||||
"prom-client": "^15.1.3",
|
||||
"random-seed": "^0.3.0",
|
||||
"react": "^19.1.1",
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ import {
|
|||
type Theme,
|
||||
} from "../themes/Theme";
|
||||
import { useTheme } from "../themes/useTheme";
|
||||
import { Container, Sprite, Stage, useTick } from "@pixi/react";
|
||||
import { extend, useTick, Application } from "@pixi/react";
|
||||
import Viewport from "./pixi/PixiViewport";
|
||||
import type { Viewport as PixiViewport } from "pixi-viewport";
|
||||
import { Container, FederatedPointerEvent, Sprite, Texture } from "pixi.js";
|
||||
import {
|
||||
type ClientGame,
|
||||
getValue,
|
||||
|
|
@ -22,7 +23,6 @@ import {
|
|||
type ServerGame,
|
||||
} from "../../shared/game";
|
||||
import { useWSQuery } from "../hooks";
|
||||
import { Texture } from "pixi.js";
|
||||
import { useAtom } from "jotai";
|
||||
import { cursorXAtom, cursorYAtom } from "../atoms";
|
||||
import Coords from "./Coords";
|
||||
|
|
@ -46,6 +46,11 @@ import { weightedPickRandom } from "../../shared/utils";
|
|||
import gen from "random-seed";
|
||||
import type { UserSettings } from "../../shared/user-settings";
|
||||
|
||||
extend({
|
||||
Container,
|
||||
Sprite,
|
||||
});
|
||||
|
||||
interface BoardProps {
|
||||
className?: string;
|
||||
game: ServerGame | ClientGame;
|
||||
|
|
@ -157,6 +162,26 @@ const Board: React.FC<BoardProps> = (props) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
const clamp = useMemo(
|
||||
() =>
|
||||
theme &&
|
||||
(props.width || props.height
|
||||
? { left: 0, right: boardWidth, top: 0, bottom: boardHeight }
|
||||
: {
|
||||
left: -theme.size,
|
||||
right: boardWidth + theme.size,
|
||||
top: -theme.size,
|
||||
bottom: boardHeight + theme.size,
|
||||
}),
|
||||
[boardHeight, boardWidth, props.height, props.width, theme],
|
||||
);
|
||||
const clampZoom = useMemo(
|
||||
() => ({
|
||||
minScale: 1,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<div
|
||||
|
|
@ -199,11 +224,13 @@ const Board: React.FC<BoardProps> = (props) => {
|
|||
)}
|
||||
</div>
|
||||
{theme && (
|
||||
<Stage
|
||||
options={{ hello: true, forceCanvas: !!props.width }}
|
||||
<Application
|
||||
hello
|
||||
forceFallbackAdapter={!!props.width}
|
||||
width={width}
|
||||
height={height}
|
||||
className="select-none"
|
||||
preference="webgl"
|
||||
>
|
||||
<Viewport
|
||||
viewportRef={viewportRef}
|
||||
|
|
@ -211,19 +238,8 @@ const Board: React.FC<BoardProps> = (props) => {
|
|||
worldHeight={boardHeight}
|
||||
width={width}
|
||||
height={height}
|
||||
clamp={
|
||||
props.width || props.height
|
||||
? { left: 0, right: boardWidth, top: 0, bottom: boardHeight }
|
||||
: {
|
||||
left: -theme.size,
|
||||
right: boardWidth + theme.size,
|
||||
top: -theme.size,
|
||||
bottom: boardHeight + theme.size,
|
||||
}
|
||||
}
|
||||
clampZoom={{
|
||||
minScale: 1,
|
||||
}}
|
||||
clamp={clamp}
|
||||
clampZoom={clampZoom}
|
||||
onViewportChange={onViewportChange}
|
||||
>
|
||||
{Array.from({ length: game.width }).map((_, i) => {
|
||||
|
|
@ -254,7 +270,7 @@ const Board: React.FC<BoardProps> = (props) => {
|
|||
});
|
||||
})}
|
||||
</Viewport>
|
||||
</Stage>
|
||||
</Application>
|
||||
)}
|
||||
</div>
|
||||
{!props.width && !props.height && <Coords />}
|
||||
|
|
@ -311,13 +327,14 @@ const Tile = ({
|
|||
const isQuestionMark = game.isQuestionMark[i][j];
|
||||
const base =
|
||||
isRevealed || (isMine && !isFlagged) ? (
|
||||
<Sprite key="b" texture={resolveSprite(theme.revealed)} />
|
||||
<pixiSprite key="b" texture={resolveSprite(theme.revealed)} />
|
||||
) : (
|
||||
<Sprite key="b" texture={resolveSprite(theme.tile)} />
|
||||
<pixiSprite key="b" texture={resolveSprite(theme.tile)} />
|
||||
);
|
||||
const extra = isLastPos ? (
|
||||
<Sprite key="e" texture={resolveSprite(theme.lastPos)} />
|
||||
<pixiSprite key="e" texture={resolveSprite(theme.lastPos)} />
|
||||
) : null;
|
||||
|
||||
const touchStart = useRef<number>(0);
|
||||
const isMove = useRef<boolean>(false);
|
||||
const startX = useRef<number>(0);
|
||||
|
|
@ -334,13 +351,16 @@ const Tile = ({
|
|||
setDoTick(true);
|
||||
}
|
||||
}, [isMine, isRevealed, userSettings?.showRevealAnimation, value]);
|
||||
useTick((delta) => {
|
||||
frame.current += delta * 0.1;
|
||||
if (frame.current > 3) {
|
||||
setDoTick(false);
|
||||
}
|
||||
setScale(Math.max(1, -2 * Math.pow(frame.current - 0.5, 2) + 1.2));
|
||||
}, doTick);
|
||||
useTick({
|
||||
callback: (delta) => {
|
||||
frame.current += delta.count * 0.1;
|
||||
if (frame.current > 3) {
|
||||
setDoTick(false);
|
||||
}
|
||||
setScale(Math.max(1, -2 * Math.pow(frame.current - 0.5, 2) + 1.2));
|
||||
},
|
||||
isEnabled: doTick,
|
||||
});
|
||||
const baseProps = useMemo(
|
||||
() => ({
|
||||
scale,
|
||||
|
|
@ -353,18 +373,18 @@ const Tile = ({
|
|||
let content: ReactNode = null;
|
||||
if (isFlagged) {
|
||||
content = (
|
||||
<Sprite key="c" texture={resolveSprite(theme.flag)} {...baseProps} />
|
||||
<pixiSprite key="c" texture={resolveSprite(theme.flag)} {...baseProps} />
|
||||
);
|
||||
} else if (isMine) {
|
||||
content = (
|
||||
<Sprite key="c" texture={resolveSprite(theme.mine)} {...baseProps} />
|
||||
<pixiSprite key="c" texture={resolveSprite(theme.mine)} {...baseProps} />
|
||||
);
|
||||
} else if (value !== -1 && isRevealed) {
|
||||
const img = theme[value.toString() as keyof Theme] as Texture;
|
||||
content = img ? <Sprite key="c" texture={img} {...baseProps} /> : null;
|
||||
content = img ? <pixiSprite key="c" texture={img} {...baseProps} /> : null;
|
||||
} else if (isQuestionMark) {
|
||||
content = (
|
||||
<Sprite
|
||||
<pixiSprite
|
||||
key="c"
|
||||
texture={resolveSprite(theme.questionMark)}
|
||||
{...baseProps}
|
||||
|
|
@ -375,16 +395,16 @@ const Tile = ({
|
|||
const [, setCursorY] = useAtom(cursorYAtom);
|
||||
|
||||
return (
|
||||
<Container
|
||||
<pixiContainer
|
||||
eventMode="static"
|
||||
interactive
|
||||
x={i * theme.size}
|
||||
y={j * theme.size}
|
||||
key={`${i},${j}`}
|
||||
onrightup={() => {
|
||||
onRightUp={() => {
|
||||
onRightClick(i, j);
|
||||
}}
|
||||
onpointerup={(e) => {
|
||||
onPointerUp={(e: FederatedPointerEvent) => {
|
||||
if (e.button !== 0) return;
|
||||
if (isMove.current) return;
|
||||
if (Date.now() - touchStart.current > 300) {
|
||||
|
|
@ -393,17 +413,17 @@ const Tile = ({
|
|||
onLeftClick(i, j);
|
||||
}
|
||||
}}
|
||||
onpointerdown={(e) => {
|
||||
onPointerDown={(e: FederatedPointerEvent) => {
|
||||
isMove.current = false;
|
||||
touchStart.current = Date.now();
|
||||
startX.current = e.global.x;
|
||||
startY.current = e.global.y;
|
||||
}}
|
||||
onpointerenter={() => {
|
||||
onPointerEnter={() => {
|
||||
setCursorX(i);
|
||||
setCursorY(j);
|
||||
}}
|
||||
onpointermove={(e) => {
|
||||
onPointerMove={(e: FederatedPointerEvent) => {
|
||||
if (
|
||||
Math.abs(startX.current - e.global.x) > 10 ||
|
||||
Math.abs(startY.current - e.global.y) > 10
|
||||
|
|
@ -415,7 +435,7 @@ const Tile = ({
|
|||
{base}
|
||||
{content}
|
||||
{extra}
|
||||
</Container>
|
||||
</pixiContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import React from "react";
|
||||
import type { Application } from "pixi.js";
|
||||
import {
|
||||
type IClampZoomOptions,
|
||||
Viewport as PixiViewport,
|
||||
} from "pixi-viewport";
|
||||
import { PixiComponent, useApp } from "@pixi/react";
|
||||
import { BaseTexture, SCALE_MODES } from "pixi.js";
|
||||
BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST;
|
||||
import React, { useLayoutEffect } from "react";
|
||||
import { type IClampZoomOptions, Viewport } from "pixi-viewport";
|
||||
import { extend, useApplication } from "@pixi/react";
|
||||
|
||||
export interface ViewportProps {
|
||||
extend({ Viewport });
|
||||
|
||||
import { type PixiReactElementProps } from "@pixi/react";
|
||||
|
||||
declare module "@pixi/react" {
|
||||
interface PixiElements {
|
||||
pixiViewport: PixiReactElementProps<typeof Viewport>;
|
||||
}
|
||||
}
|
||||
|
||||
interface ViewportProps {
|
||||
width: number;
|
||||
height: number;
|
||||
worldWidth: number;
|
||||
|
|
@ -21,26 +25,18 @@ export interface ViewportProps {
|
|||
bottom: number;
|
||||
};
|
||||
clampZoom?: IClampZoomOptions;
|
||||
onViewportChange?: (viewport: PixiViewport) => void;
|
||||
viewportRef?: React.RefObject<PixiViewport | null>;
|
||||
onViewportChange?: (viewport: Viewport) => void;
|
||||
viewportRef?: React.RefObject<Viewport | null>;
|
||||
}
|
||||
|
||||
export interface PixiComponentViewportProps extends ViewportProps {
|
||||
app: Application;
|
||||
}
|
||||
|
||||
const PixiComponentViewport = PixiComponent("Viewport", {
|
||||
create: (props: PixiComponentViewportProps) => {
|
||||
const viewport = new PixiViewport({
|
||||
screenWidth: props.width,
|
||||
screenHeight: props.height,
|
||||
worldWidth: props.worldWidth,
|
||||
worldHeight: props.worldHeight,
|
||||
ticker: props.app.ticker,
|
||||
events: props.app.renderer.events,
|
||||
disableOnContextMenu: true,
|
||||
allowPreserveDragOutside: true,
|
||||
});
|
||||
const PixiViewport = (props: ViewportProps) => {
|
||||
const { app } = useApplication();
|
||||
const ref = React.useRef<Viewport | null>(null);
|
||||
const { clamp, clampZoom, onViewportChange, viewportRef } = props;
|
||||
useLayoutEffect(() => {
|
||||
void app.renderer;
|
||||
const viewport = ref.current;
|
||||
if (!viewport) return () => {};
|
||||
viewport
|
||||
.drag({
|
||||
ignoreKeyToPressOnTouch: true,
|
||||
|
|
@ -48,52 +44,43 @@ const PixiComponentViewport = PixiComponent("Viewport", {
|
|||
})
|
||||
.pinch()
|
||||
.wheel();
|
||||
if (props.clamp) {
|
||||
viewport.clamp(props.clamp);
|
||||
if (clamp) {
|
||||
viewport.clamp(clamp);
|
||||
}
|
||||
if (props.clampZoom) {
|
||||
viewport.clampZoom(props.clampZoom);
|
||||
if (clampZoom) {
|
||||
viewport.clampZoom(clampZoom);
|
||||
}
|
||||
viewport.on("moved", () => {
|
||||
props.onViewportChange?.(viewport);
|
||||
onViewportChange?.(viewport);
|
||||
});
|
||||
viewport.on("zoomed-end", () => {
|
||||
props.onViewportChange?.(viewport);
|
||||
onViewportChange?.(viewport);
|
||||
});
|
||||
|
||||
if (props.viewportRef) {
|
||||
props.viewportRef.current = viewport;
|
||||
if (viewportRef) {
|
||||
viewportRef.current = viewport;
|
||||
}
|
||||
|
||||
return viewport;
|
||||
},
|
||||
applyProps: (
|
||||
viewport: PixiViewport,
|
||||
oldProps: ViewportProps,
|
||||
newProps: ViewportProps,
|
||||
) => {
|
||||
if (
|
||||
oldProps.width !== newProps.width ||
|
||||
oldProps.height !== newProps.height ||
|
||||
oldProps.worldWidth !== newProps.worldWidth ||
|
||||
oldProps.worldHeight !== newProps.worldHeight
|
||||
) {
|
||||
viewport.resize(
|
||||
newProps.width,
|
||||
newProps.height,
|
||||
newProps.worldWidth,
|
||||
newProps.worldHeight,
|
||||
);
|
||||
}
|
||||
if (oldProps.clamp !== newProps.clamp) {
|
||||
viewport.clamp(newProps.clamp);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const Viewport = (props: ViewportProps) => {
|
||||
const app = useApp();
|
||||
return <PixiComponentViewport app={app} {...props} />;
|
||||
return () => {
|
||||
viewport.off("moved");
|
||||
viewport.off("zoomed-end");
|
||||
};
|
||||
}, [clamp, clampZoom, onViewportChange, viewportRef, app.renderer]);
|
||||
if (!app.renderer) return null;
|
||||
return (
|
||||
<pixiViewport
|
||||
ref={ref}
|
||||
screenWidth={props.width}
|
||||
screenHeight={props.height}
|
||||
worldWidth={props.worldWidth}
|
||||
worldHeight={props.worldHeight}
|
||||
ticker={app.ticker}
|
||||
events={app.renderer.events}
|
||||
disableOnContextMenu
|
||||
allowPreserveDragOutside
|
||||
>
|
||||
{props.children}
|
||||
</pixiViewport>
|
||||
);
|
||||
};
|
||||
|
||||
export default Viewport;
|
||||
export default PixiViewport;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Assets } from "pixi.js";
|
||||
import { Assets, Texture } from "pixi.js";
|
||||
import { useState, useEffect } from "react";
|
||||
import type { Theme, LoadedTheme, WeightedLazySprites } from "./Theme";
|
||||
|
||||
|
|
@ -12,14 +12,20 @@ export const useTheme = (theme: Theme) => {
|
|||
Object.entries(theme).map(async ([key, value]) => {
|
||||
let loaded = value;
|
||||
if (typeof value === "function") {
|
||||
loaded = await Assets.load((await value()).default);
|
||||
const texture = await Assets.load<Texture>((await value()).default);
|
||||
texture.source.scaleMode = "nearest";
|
||||
loaded = texture;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
loaded = await Promise.all(
|
||||
loaded.map(async (sprite: WeightedLazySprites) => {
|
||||
const texture = await Assets.load<Texture>(
|
||||
(await sprite.sprite()).default,
|
||||
);
|
||||
texture.source.scaleMode = "nearest";
|
||||
return {
|
||||
weight: sprite.weight,
|
||||
sprite: await Assets.load((await sprite.sprite()).default),
|
||||
sprite: texture,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue