@lagless/pixi-react 0.0.33

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.
@@ -0,0 +1,96 @@
1
+ // ===== File: src/components/vfx/useVFXContainer.ts =====
2
+ import { useCallback, useEffect, useRef } from 'react';
3
+ import { Assets } from 'pixi.js';
4
+ import { Effect, Pause } from 'neutrinoparticles.pixi';
5
+ import { useTick } from '@pixi/react';
6
+ export const useVFXContainer = () => {
7
+ const containerRef = useRef(null);
8
+ const effectsRef = useRef([]);
9
+ const isUnmountedRef = useRef(false);
10
+ // Обновление всех эффектов
11
+ useTick((ticker) => {
12
+ const now = performance.now();
13
+ const dt = ticker.deltaMS / 1000;
14
+ effectsRef.current = effectsRef.current.filter(({ effect, removeAt, onComplete }) => {
15
+ // Проверяем время жизни
16
+ if (removeAt !== undefined && now >= removeAt) {
17
+ effect.parent?.removeChild(effect);
18
+ effect.destroy();
19
+ onComplete?.();
20
+ return false;
21
+ }
22
+ // Обновляем эффект
23
+ if (effect.ready()) {
24
+ effect.update(dt);
25
+ // Если нет duration — удаляем когда частиц 0
26
+ if (removeAt === undefined && effect.getNumParticles() === 0) {
27
+ effect.parent?.removeChild(effect);
28
+ effect.destroy();
29
+ onComplete?.();
30
+ return false;
31
+ }
32
+ }
33
+ return true;
34
+ });
35
+ });
36
+ // Спавн эффекта
37
+ const spawn = useCallback((effectAlias, position, options) => {
38
+ if (isUnmountedRef.current)
39
+ return null;
40
+ const effectModel = Assets.get(effectAlias);
41
+ if (!effectModel) {
42
+ console.error(`Effect model "${effectAlias}" not found`);
43
+ return null;
44
+ }
45
+ const container = containerRef.current;
46
+ if (!container) {
47
+ console.error('VFX container not mounted');
48
+ return null;
49
+ }
50
+ // Нормализуем scale
51
+ const scale = options?.scale;
52
+ const normalizedScale = typeof scale === 'number' ? [scale, scale, scale] :
53
+ scale ?? [1, 1, 1];
54
+ const effect = new Effect(effectModel, {
55
+ position,
56
+ rotation: options?.rotation ?? 0,
57
+ scale: normalizedScale,
58
+ pause: Pause.BEFORE_UPDATE_OR_RENDER,
59
+ autoInit: true,
60
+ });
61
+ container.addChild(effect);
62
+ effectsRef.current.push({
63
+ effect,
64
+ removeAt: options?.duration ? performance.now() + options.duration : undefined,
65
+ onComplete: options?.onComplete,
66
+ });
67
+ return effect;
68
+ }, []);
69
+ // Очистка при размонтировании
70
+ useEffect(() => {
71
+ isUnmountedRef.current = false;
72
+ return () => {
73
+ isUnmountedRef.current = true;
74
+ effectsRef.current.forEach(({ effect }) => {
75
+ effect.parent?.removeChild(effect);
76
+ effect.destroy();
77
+ });
78
+ effectsRef.current = [];
79
+ };
80
+ }, []);
81
+ // Принудительная очистка всех эффектов
82
+ const clear = useCallback(() => {
83
+ effectsRef.current.forEach(({ effect }) => {
84
+ effect.parent?.removeChild(effect);
85
+ effect.destroy();
86
+ });
87
+ effectsRef.current = [];
88
+ }, []);
89
+ return {
90
+ containerRef,
91
+ spawn,
92
+ clear,
93
+ /** Текущее количество активных эффектов */
94
+ get activeCount() { return effectsRef.current.length; }
95
+ };
96
+ };
@@ -0,0 +1,18 @@
1
+ export type VJDirectionListener = (direction: number) => void;
2
+ export type VJUnsubscribe = () => void;
3
+ export declare class VirtualJoystickCtx {
4
+ private _direction;
5
+ private _axisX;
6
+ private _axisY;
7
+ private _power;
8
+ private readonly _directionListeners;
9
+ get direction(): number;
10
+ get axisX(): number;
11
+ get axisY(): number;
12
+ get power(): number;
13
+ setPower(power: number): void;
14
+ setAxis(x: number, y: number): void;
15
+ setDirection(direction: number): void;
16
+ addDirectionChangeListener(handler: VJDirectionListener): VJUnsubscribe;
17
+ }
18
+ //# sourceMappingURL=virtual-joystick-ctx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-joystick-ctx.d.ts","sourceRoot":"","sources":["../../../src/lib/virtual-joystick/virtual-joystick-ctx.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;AAC9D,MAAM,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC;AAEvC,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAkC;IAEtE,IAAW,SAAS,IAAI,MAAM,CAE7B;IAED,IAAW,KAAK,IAAI,MAAM,CAEzB;IAED,IAAW,KAAK,IAAI,MAAM,CAEzB;IAED,IAAW,KAAK,IAAI,MAAM,CAEzB;IAEM,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAKnC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKrC,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,aAAa;CAM/E"}
@@ -0,0 +1,37 @@
1
+ import { toFloat32 } from '@lagless/binary';
2
+ export class VirtualJoystickCtx {
3
+ _direction = 0;
4
+ _axisX = 0;
5
+ _axisY = 0;
6
+ _power = 0;
7
+ _directionListeners = new Set();
8
+ get direction() {
9
+ return this._direction;
10
+ }
11
+ get axisX() {
12
+ return this._axisX;
13
+ }
14
+ get axisY() {
15
+ return this._axisY;
16
+ }
17
+ get power() {
18
+ return this._power;
19
+ }
20
+ setPower(power) {
21
+ this._power = toFloat32(power);
22
+ }
23
+ setAxis(x, y) {
24
+ this._axisX = toFloat32(x);
25
+ this._axisY = toFloat32(y);
26
+ }
27
+ setDirection(direction) {
28
+ this._direction = toFloat32(direction);
29
+ this._directionListeners.forEach((listener) => listener(this._direction));
30
+ }
31
+ addDirectionChangeListener(handler) {
32
+ this._directionListeners.add(handler);
33
+ return () => {
34
+ this._directionListeners.delete(handler);
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,14 @@
1
+ import { Texture } from 'pixi.js';
2
+ import { FC, ReactNode } from 'react';
3
+ import { VirtualJoystickCtx } from './virtual-joystick-ctx';
4
+ interface VirtualJoystickAssets {
5
+ joystick: Texture;
6
+ joystickHandle: Texture;
7
+ }
8
+ export declare const useVirtualJoystick: () => VirtualJoystickCtx;
9
+ export declare const loadVirtualJoystickAssets: () => Promise<VirtualJoystickAssets>;
10
+ export declare const VirtualJoystickProvider: FC<{
11
+ children: ReactNode;
12
+ }>;
13
+ export {};
14
+ //# sourceMappingURL=virtual-joystick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-joystick.d.ts","sourceRoot":"","sources":["../../../src/lib/virtual-joystick/virtual-joystick.tsx"],"names":[],"mappings":"AACA,OAAO,EAA6B,OAAO,EAAyB,MAAM,SAAS,CAAC;AACpF,OAAO,EAAiB,EAAE,EAAE,SAAS,EAAiE,MAAM,OAAO,CAAC;AACpH,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAI5D,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;CACzB;AAKD,eAAO,MAAM,kBAAkB,0BAE9B,CAAC;AAEF,eAAO,MAAM,yBAAyB,QAAa,OAAO,CAAC,qBAAqB,CAM/E,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,EAAE,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAmB/D,CAAC"}
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useApplication, useExtend } from '@pixi/react';
3
+ import { Assets, Container, Sprite } from 'pixi.js';
4
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
5
+ import { VirtualJoystickCtx } from './virtual-joystick-ctx';
6
+ import joystickUrl from './textures/joystick.png';
7
+ import joystickHandleUrl from './textures/joystick-handle.png';
8
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
9
+ const VirtualJoystickContext = createContext(null);
10
+ export const useVirtualJoystick = () => {
11
+ return useContext(VirtualJoystickContext);
12
+ };
13
+ export const loadVirtualJoystickAssets = async () => {
14
+ await Assets.load([joystickUrl, joystickHandleUrl]);
15
+ return {
16
+ joystick: Assets.get(joystickUrl),
17
+ joystickHandle: Assets.get(joystickHandleUrl),
18
+ };
19
+ };
20
+ export const VirtualJoystickProvider = ({ children }) => {
21
+ useExtend({ Container, Sprite });
22
+ const ctx = useMemo(() => new VirtualJoystickCtx(), []);
23
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
24
+ const [assets, setAssets] = useState(null);
25
+ useEffect(() => {
26
+ loadVirtualJoystickAssets().then(setAssets, console.error);
27
+ }, []);
28
+ if (!assets)
29
+ return null;
30
+ return (_jsxs(_Fragment, { children: [_jsx(VirtualJoystickContext.Provider, { value: ctx, children: children }), _jsx(VirtualJoystick, { ctx: ctx, assets: assets })] }));
31
+ };
32
+ const joystickScale = 0.5;
33
+ const VirtualJoystick = ({ ctx, assets }) => {
34
+ const { app } = useApplication();
35
+ const [handleOffset, setHandleOffset] = useState({ x: 0, y: 0 });
36
+ const joystickSize = useMemo(() => assets.joystick.width * joystickScale, [assets.joystick]);
37
+ const handleSize = useMemo(() => assets.joystickHandle.width * joystickScale, [assets.joystickHandle]);
38
+ const maxOffset = useMemo(() => (joystickSize - handleSize / 2) / 2, [joystickSize, handleSize]);
39
+ const getCanvasDOMRect = useCallback(() => app.renderer.canvas.getBoundingClientRect(), [app]);
40
+ const [canvasDOMRect, setCanvasDOMRect] = useState(() => getCanvasDOMRect());
41
+ useEffect(() => {
42
+ const canvas = app.renderer.canvas;
43
+ const resizeObserver = new ResizeObserver(() => {
44
+ setCanvasDOMRect(getCanvasDOMRect());
45
+ });
46
+ resizeObserver.observe(canvas);
47
+ return () => {
48
+ resizeObserver.unobserve(canvas);
49
+ };
50
+ }, [app, getCanvasDOMRect]);
51
+ const isDraggingRef = useRef(false);
52
+ const updateFromPointer = useCallback((event) => {
53
+ const container = event.currentTarget;
54
+ const localPos = event.getLocalPosition(container);
55
+ const centerX = joystickSize / 2;
56
+ const centerY = joystickSize / 2;
57
+ let dx = localPos.x - centerX;
58
+ let dy = localPos.y - centerY;
59
+ const distance = Math.sqrt(dx * dx + dy * dy);
60
+ if (distance > maxOffset && distance > 0) {
61
+ const k = maxOffset / distance;
62
+ dx *= k;
63
+ dy *= k;
64
+ }
65
+ setHandleOffset({ x: dx, y: dy });
66
+ const radius = maxOffset > 0 ? maxOffset : 1;
67
+ const axisX = dx / radius;
68
+ const axisY = -dy / radius;
69
+ const power = Math.min(Math.sqrt(axisX * axisX + axisY * axisY), 1);
70
+ const direction = power > 0 ? Math.atan2(axisY, axisX) : 0;
71
+ ctx.setAxis(axisX, axisY);
72
+ ctx.setPower(power);
73
+ ctx.setDirection(direction);
74
+ }, [ctx, joystickSize, maxOffset]);
75
+ const resetJoystick = useCallback(() => {
76
+ isDraggingRef.current = false;
77
+ setHandleOffset({ x: 0, y: 0 });
78
+ // Reset context state
79
+ ctx.setAxis(0, 0);
80
+ ctx.setPower(0);
81
+ ctx.setDirection(0);
82
+ }, [ctx]);
83
+ const handlePointerDown = useCallback((event) => {
84
+ isDraggingRef.current = true;
85
+ updateFromPointer(event);
86
+ }, [updateFromPointer]);
87
+ const handlePointerMove = useCallback((event) => {
88
+ if (!isDraggingRef.current)
89
+ return;
90
+ updateFromPointer(event);
91
+ }, [updateFromPointer]);
92
+ const handlePointerUp = useCallback((_event) => {
93
+ resetJoystick();
94
+ }, [resetJoystick]);
95
+ return (_jsxs("pixiContainer", { x: canvasDOMRect.width / 2 - joystickSize / 2, y: canvasDOMRect.height - joystickSize - canvasDOMRect.height * 0.1, eventMode: "static", onPointerDown: handlePointerDown, onGlobalPointerMove: handlePointerMove, onPointerUpOutside: handlePointerUp, onPointerUp: handlePointerUp, children: [_jsx("pixiSprite", { scale: joystickScale, texture: assets.joystick }), _jsx("pixiContainer", { x: joystickSize / 2 - handleSize / 2 + handleOffset.x, y: joystickSize / 2 - handleSize / 2 + handleOffset.y, children: _jsx("pixiSprite", { scale: joystickScale, texture: assets.joystickHandle }) })] }));
96
+ };