Open4

pixi-reactに触ってみての注意点や感想とか

pentamaniapentamania

初めに

pixi-reactのパッケージは@pixi/react@inlet/react-pixiが混在しているが、前者の方を使うようにすること

  • @inlet~ のほうはpixijs公式に取り込まれる前の個人プロジェクトだった頃のものと思われる、_https://reactpixi.org/ ではまだこちらの方をインストールするよう指定されるので注意 (2024-01-17更新)ドメイン失効?で消えてた、念のためリンク回避
  • 特にv7未満のパッケージはreact 18に対応していないが、@inlet~のほうはv6.8.0までしかない
  • (pixiにreactを組み合わせたプロジェクトはreact-pixi-fiberなど他にもあるが、特に理由が無ければ@pixi/reactで事足りる?)

検証モジュールのバージョン

  • pixi.js v7
  • react v18
  • @pixi/react v7

検証サンドボックス
https://codesandbox.io/s/react-pixi-test-site-0lodwm?file=/src/App.tsx


気を付けたほうがいいポイントなど

拡張コンポーネントでのprops引継ぎを忘れない

既存コンポーネントを拡張する際は以下のようにする。

import { Sprite } from "@pixi/react";

const RedBunny = (props) => {
  return <Sprite image={"./うさぎ画像.png"} tint={0xff0000} {...props} />;
};

この時{...props}のところを忘れないこと。
そうしないと(childrenも含め)拡張元のSpriteへpropsが引き継がれない

GraphicsはPixiComponentで定義するのが良さげ

Graphicsを使ったコンポーネントは一旦PixiComponentで定義するとスマート

// Good
import { PixiComponent } from "@pixi/react";
import { Graphics as PixiGraphics } from "pixi.js";

export const CircleSprite = PixiComponent("CircleSprite", {
  create: () => {
    const g = new PixiGraphics();
    g.lineStyle(2, 0xffffff)
      .beginFill(0xff0000, 1)
      .drawCircle(0, 0, 7, 14)
      .endFill();
    return g;
  }
});

というのも以下のようにdraw propsで直接定義することもできるが、処理が激重になったため
(draw処理が事あるごとに走るから?)

// NG!
  return (
    <Graphics
      preventRedraw={true}
      draw={(g) => {
        g.lineStyle(2, 0xffffff)
          .beginFill(0x35cc5a, 1)
          .drawStar(0, 0, 7, 14)
          .endFill();
      }}
      x={x}
      y={y}
      angle={angle}
    >
      {props.children}
    </Graphics>
  );

参考リンク

https://blog.logrocket.com/getting-started-pixijs-react-create-canvas/

pentamaniapentamania

Graphics処理についてはuseCallbackのhookを使ってdraw処理を定義するのも良いらしい

 const draw = useCallback(g => {
    g.beginFill(0x0033cc, 1)
    g.lineStyle(4,0xff0000,1) 
    g.drawRect(250, 150, 150, 120)
    g.endFill()
  },[]);
<Graphics draw={draw} />
pentamaniapentamania

更新処理のあれこれ

useTickが基本だが…

コンポーネントの毎フレーム状態変更はuseTickが便利
(UnityなどでいうところのOnUpdate的な処理が可能)

const [angle, setAngle] = useState(0);
  useTick((delta) => {
    setAngle(angle + delta);
  });

※deltaパラメータは複雑なので割愛するが通常は1が入る

ただ状態更新(useTickの実行)タイミングはデフォルトではPIXI.Ticker(=requestAnimationFrame)に依存していることに留意(例えば高Hzモニターでは秒間144回useTickが走ることもあり得る?)
https://blog.oimo.io/2021/06/06/adjust-fps/

Tickerの設定

PIXI.Tickerには最大FPSを制限する機能があるが、基本的にTickerはデフォルト(制限なし)のまま使っている?のでuseTickの実行頻度が端末によって変わることを考慮する必要がある。

(特に何も設定してない場合、素のPIXI.Applicationクラスに付随するTickerをそのまま使われ、このTicker(Ticker.shared)はほぼデフォルト値のまま)
https://github.com/pixijs/pixi-react/blob/8ac74789a2af5c6eecc5fd1f51ae64594aed2957/packages/react/src/stage/index.js#L133

options propを経由して自分で設定したTickerを使うことも可能

const myTicker = new Ticker();
myTicker.maxFPS = 15;

// 省略

    <Stage
      options={{
        ticker: myTicker 
      }}
    >/* 省略 */
    </Stage>

独自のループ処理を組み込んでみる

描画(renderer.renderの実行)と状態更新(useTickの実行)ループのタイミングは独自の処理に変更することもできる
https://reactpixi.org/stage#custom-updates

以下はとりあえずの方法(一例であり、要改善)

  • <Stage> propのrafをfalseにする(必要に応じてrenderOnComponentChangeもオフ)
  • refやuseEffectを駆使して初回呼び出しだけループ開始する処理を実行する
const loopFrameinterval = 1000 / 15; // 約15FPSにする

export default function App() {
  // 独自ループ処理仕込み:初回呼び出しだけ
  const appRef = useRef<import("pixi.js").Application>();
  {
    const once = useRef(true);
    useEffect(() => {
      if (!once.current) return;
      const loop = () => {
        if (appRef.current) {
          const app = appRef.current;
          app.ticker.update(); // 更新処理
          app.renderer.render(app.stage); // 描画処理
        }
        setTimeout(loop, loopFrameinterval); // 次のループを仕込む
      };
      loop(); // ループ開始
      once.current = false; // 次回呼び出し以降にループ開始処理を行わないようにする
    }, []);
  }

    <Stage
      onMount={(app) => {
        // app参照を保持
        appRef.current = app;
      }}
      raf={false}
      renderOnComponentChange={false}
    >/* 省略 */
    </Stage>
}

ゲームで更新頻度は60回に固定したいときなどに有効

その他

ちなみに「コンポーネントの状態が変更したときだけ再レンダリングしたい」というだけならraf propをfalse、renderOnComponentChangeをtrueにするだけで良い

pentamaniapentamania

良いと思ったところ・その他など(WIP)

useApp Hookを使うとrenderer参照やスクリーンサイズを取ってくる的なことが自然にできてよい

const { renderer, screen } = useApp();

これはRenderTextureのレンダリングにも便利

その他

  • 元のpixijsクラスをimportするときはPixiXxxとリネームすると良い(公式もそうしている)