Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: The codepen example for pixi-viewport is outdated and does not work #449

Open
ben4d85 opened this issue Jul 16, 2023 · 1 comment
Open

Comments

@ben4d85
Copy link

ben4d85 commented Jul 16, 2023

Current Behavior

I have taken the code from the pixi-viewport codepen example and tried to get it to work in a fresh installation of Next.js 13 (React 18).

I have received a lot of errors. I have come fairly close to solving almost all errors, but ultimately cannot get the section to work that actually uses pixi-viewport. The code in the example is clearly outdated and doesn't use standard import syntax, especially for pixi-viewport, and also lacks accurate types, making it unclear how to use this in a React app in practice.

FYI: A similar example can be found in the React Pixi Custom Components docs.

Expected Behavior

  • The example should be up-to-date with the latest versions of pixi and pixi-viewport
  • The example should use standard import {x} from 'y' syntax, especially for pixi-viewport
  • The example should use accurate types where relevant
  • I should be able to copy the code into a fresh React project and it should work

Steps to Reproduce

  • Take the code from the example and copy it into a React app
  • It won't work for the reasons described above (imports wrong, types missing, versions outdated)

Environment

I don't know about the versions used in the codepen, but I am using the following:

"pixi.js": "^7.2.4",
"pixi-viewport": "^5.0.1",
"@pixi/react": "^7.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"next": "13.4.3",
"typescript": "5.0.4",

Possible Solution

  • Fix the example code (as described above)

Additional Information

Here is how far I got with updating the example. This runs. However, if you comment the PixiViewport component back in, it stops working.

// ========================================================
// Imports
// ========================================================

import { Stage, Container, Sprite, PixiComponent, useApp, useTick } from '@pixi/react';
import { Viewport } from 'pixi-viewport'

import { useState, useEffect, useCallback, useRef, forwardRef } from 'react';

// ========================================================
// Misc
// ========================================================

const stageOptions = {
    antialias: true,
    autoDensity: true,
    backgroundColor: 0x222222,
};

type AreasArrIndex = 'world' | 'center' | 'tl' | 'tr' | 'bl' | 'br';
const areas: {
    [K in AreasArrIndex]: Array<number>;
} = {
    'world': [1000, 1000, 2000, 2000],
    'center': [1000, 1000, 400, 400],
    'tl': [100, 100, 200, 200],
    'tr': [1900, 100, 200, 200],
    'bl': [100, 1900, 200, 200],
    'br': [1900, 1900, 200, 200]
}

const useIteration = (incr = 0.1) => {
    const [i, setI] = useState(0);

    useTick((delta) => {
        setI(i => i + incr * delta);
    });

    return i;
};

const useResize = () => {
    const [size, setSize] = useState([global?.window.innerWidth, global?.window.innerHeight]);

    useEffect(() => {
        const onResize = () => {
            requestAnimationFrame(() => {
                setSize([global?.window.innerWidth, global?.window.innerHeight])
            })
        };

        global?.window.addEventListener('resize', onResize);

        return () => {
            global?.window.removeEventListener('resize', onResize);
        }
    }, []);

    return size;
};

// create and instantiate the viewport component
// we share the ticker and interaction from app
// FIXME: This is the culprit!
const PixiViewportComponent = PixiComponent("Viewport", {

    create(props) {
        const { app, ...viewportProps } = props;

        const viewport = new Viewport({
            ticker: props.app.ticker,
            // interaction: props.app.renderer.plugins.interaction, // deprecated
            events: props.app.renderer.events,
            ...viewportProps
        });

        //activate plugins
        // viewport.drag();
        // viewport.pinch();
        // viewport.wheel();
        // viewport.decelerate();
        // (props.plugins || []).forEach((plugin: string) => {
        //     viewport[plugin]();
        // });

        return viewport;
    },
    // applyProps(viewport, _oldProps, _newProps) {
    //     const { plugins: oldPlugins, children: oldChildren, ...oldProps } = _oldProps;
    //     const { plugins: newPlugins, children: newChildren, ...newProps } = _newProps;

    //     // Object.keys(newProps).forEach((p) => {
    //     //     if (oldProps[p] !== newProps[p]) {
    //     //         viewport[p] = newProps[p];
    //     //     }
    //     // });
    // },
    didMount() {
        console.log("viewport mounted");
    }
});

// create a component that can be consumed
// that automatically pass down the app
const PixiViewport = forwardRef(function MyPixiViewport(props: any, ref: any) {
    return (
        <PixiViewportComponent ref={ref} app={useApp()} {...props} />
    )
});

// Wiggling bunny
const Bunny = forwardRef(function MyBunny(props: any, ref: any) {

    // abstracted away, see settings>js
    const i = useIteration(0.1);

    return (
        <Sprite
            ref={ref}
            image="https://s3-us-west-2.amazonaws.com/s.cdpn.io/693612/IaUrttj.png"
            anchor={0.5}
            scale={2}
            rotation={Math.cos(i) * 0.98}
            {...props}
        />
    );
});

// Bunny moving in a circle
const BunnyFollowingCircle = forwardRef(function MyBunnyFollowingCircle({ x, y, rad }: any, ref: any) {

    const i = useIteration(0.02);
    return <Bunny ref={ref} x={x + Math.cos(i) * rad} y={y + Math.sin(i) * rad} scale={6} />
});

// 4 squared bunnies
// positioned by its name
const BunniesContainer = ({
    pos,
    ...props
}: {
    pos: AreasArrIndex,
    [x: string]: any;
}) => {
    const [x, y] = areas[pos];
    return (
        <Container x={x} y={y} {...props}>
            <Bunny x={-50} y={-50} />
            <Bunny x={50} y={-50} />
            <Bunny x={-50} y={50} />
            <Bunny x={50} y={50} />
        </Container>
    );
}

// ========================================================
// Pixi app
// ========================================================

// the main app
export const PixiApp = () => {

    // get the actual viewport instance
    const viewportRef = useRef();

    // get ref of the bunny to follow
    const followBunny = useRef();

    // get the current window size
    const [width, height] = useResize();

    // interact with viewport directly
    // move and zoom to specified area
    const focus = useCallback((p: AreasArrIndex) => {
        const viewport = viewportRef.current;
        const [x, y, focusWidth, focusHeight] = areas[p];

        const f = Math.max(focusWidth / width, focusHeight / height);
        const w = width * f;
        const h = height * f;

        // pause following
        //viewport.plugins.pause('follow');

        // and snap to selected
        // viewport.snapZoom({ width: w, height: h, removeOnComplete: true, ease: 'easeInOutExpo' });
        // viewport.snap(x, y, { removeOnComplete: true, ease: 'easeInOutExpo' });
    }, [width, height]);

    const follow = useCallback(() => {
        const viewport = viewportRef.current;

        const focusWidth = 1000;
        const focusHeight = 1000;

        const f = Math.max(focusWidth / width, focusHeight / height);
        const w = width * f;
        const h = height * f;

        // viewport.snapZoom({ width: w, height: h, ease: 'easeInOutExpo' });
        // viewport.follow(followBunny.current, { speed: 20 });
    }, [width, height]);

    return (
        <div className="mapContainer">

            <div className="buttonsGroup">
                <button onClick={() => focus('world')}>Fit</button>
                <button onClick={() => focus('center')}>Center</button>
                <button onClick={() => focus('tl')}>TL</button>
                <button onClick={() => focus('tr')}>TR</button>
                <button onClick={() => focus('bl')}>BL</button>
                <button onClick={() => focus('br')}>BR</button>
                <button onClick={() => follow()}>Follow</button>
            </div>

            <Stage width={width} height={height} options={stageOptions}>

                {/* <PixiViewport
                    ref={viewportRef}
                    // plugins={["drag", "pinch", "wheel", "decelerate"]}
                    screenWidth={width}
                    screenHeight={height}
                    worldWidth={width * 4}
                    worldHeight={height * 4}
                > */}

                    <BunniesContainer pos="tl" />
                    <BunniesContainer pos="tr" />
                    <BunniesContainer pos="bl" />
                    <BunniesContainer pos="br" />
                    <BunniesContainer pos="center" scale={2} />

                    <BunnyFollowingCircle x={1000} y={1000} rad={500} ref={followBunny} />

                {/* </PixiViewport> */}

            </Stage >

        </div>
    );
}

PS: I have tried to set up an example of the above on StackBlitz, but could not get that to work either due to a different error: Error in /turbo_modules/@pixi/[email protected]/lib/Color.js (6:19) Unexpected token 'export'

@ben4d85
Copy link
Author

ben4d85 commented Jul 16, 2023

Update: This thread, davidfig/pixi-viewport#438, provides a partial solution. However, it leads to an error when leaving the page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant