Babylon js React change scene with button

Hi, I’m just starting out using Babylon js with React. I want to do a simple thing: press a button to make my cube go up. After several attempts I decided to just post here the simple boilerplate that I started with. First, my App.js:
import React, { useState } from “react”;
import {
FreeCamera,
Vector3,
HemisphericLight,
MeshBuilder,
} from “@babylonjs/core”;
import SceneComponent from “./SceneComponent”;
import “./App.css”;

let box;
const onSceneReady = (scene) => {

  var camera = new FreeCamera("camera1", new Vector3(0, 5, -10), scene);

  camera.setTarget(Vector3.Zero());

  const canvas = scene.getEngine().getRenderingCanvas();

  camera.attachControl(canvas, true);

  var light = new HemisphericLight("light", new Vector3(0, 1, 0), scene);

  light.intensity = 0.7;

  box = MeshBuilder.CreateBox("box", { size: 2 }, scene);

  box.position.y = 0;

};

function App() {

  const [position, setPosition] = useState(0);

  const handleUp = () => {

    setPosition(position + 1);

  };

  return (

    <div>
      <SceneComponent
        antialias
        onSceneReady={onSceneReady}
        id="my-canvas"
      ></SceneComponent>
      <button onClick={handleUp}>up</button>
    </div>

  );
}
export default App;

Then my SceneComponent:

import { Engine, Scene } from "@babylonjs/core";
import React, { useEffect, useRef } from "react";
import "./SceneComponent.css";

export default (props) => {

  const reactCanvas = useRef(null);

  const {
    antialias,
    engineOptions,
    adaptToDeviceRatio,
    sceneOptions,
    onRender,
    onSceneReady,
    ...rest
  } = props;

  useEffect(() => {

    if (reactCanvas.current) {

      const engine = new Engine(

        reactCanvas.current,

        antialias,

        engineOptions,

        adaptToDeviceRatio

      );

      const scene = new Scene(engine, sceneOptions);

      if (scene.isReady()) {

        props.onSceneReady(scene);

      } else {

        scene.onReadyObservable.addOnce((scene) => props.onSceneReady(scene));

      }

      engine.runRenderLoop(() => {

        if (typeof onRender === "function") {

          onRender(scene);

        }

        scene.render();

      });

      const resize = () => {

        scene.getEngine().resize();

      };

      if (window) {

        window.addEventListener("resize", resize);

      }

      return () => {

        scene.getEngine().dispose();

        if (window) {

          window.removeEventListener("resize", resize);

        }

      };

    }

  }, [reactCanvas]);

  return <canvas className="canvas" ref={reactCanvas} {...rest} />;

};

So, how should I pass the position state so that it updates my scene?

You can do it inside handleUp(). But you need to have scene accessible in handleUp.

So first:

let globalScene;
const onSceneReady = (scene) => {
globalScene = scene;

}

And then in handleUp you can call:
globalScene.getMeshByName(box’).position.y = position + 1.

Personally, I like to have a central state store to hold state for the world. I used redux and now moved to mobx. This will simplify maintenance and handle complex states easier.

Then I treat all the updates to the 3D scene as side effects. If I call handleUp(), I trigger an action to update position in central state store. The side effect handling will also receive position update action, and update the box position based on the new Y value carried in the action.

1 Like

Thank you slin this worked!

One additional question: could I also trigger the BABYLON.Animation.CreateAndStartAnimation this way?

Yes. It should also work. I found this example if you need any reference. It listens on scene.onPointerDown. But you can trigger from button click in react as well.

https://www.babylonjs-playground.com/#4ZVTG#0

1 Like

Thanks!