Trying to reason about what state be reactive and what should not

I am setting up a very basic game and learning how to think in terms of R3F/ three – which is providing difficult after a decade of practicing React philosophy almost exclusively.

My game has two main concerns: Player and WorldItem.

// A world item can be selected from the
// shop to be placed into the player's world.
type WorldItemT = {
  id: string
  modelId: string
  modelName: string
  position: THREE.Vector3
  rotation: THREE.Euler
  scale: THREE.Vector3
  isGlowing: boolean
  glowColor: THREE.Color
  glowIntensity: number
}

// The player can run / jump around their
// world, but thats about it for v1.
type PlayerT = {
  modelId: string
  position: THREE.Vector3
  rotation: THREE.Euler
  scale: THREE.Vector3
  isVisible: boolean
}

I’ve learned through trial and error that I can not expect to manage WorldItem positioning changes to be fluid and consistently linear if they are dictated by reactive state. So I got them set up with useFrame and it works smoothly. And then I added handling for rotation in useFrame – all good.

Then I moved on to Player movement, attempting to handle it the same way with the difference being (mainly) the camera following the Player. I set up a reactive store of key names to booleans so when the user pressed W, for example, the Player began moving upwards smoothly and the camera followed by using camera.lookAt(player.position). It was all good until the user released the key and caused the React component to re-render. The camera suddenly reset back to 0 0 0. I don’t yet fully understand why this is the case, but I do understand that it has something to do with components re-rendering with their own values, rather than what is actually present on the mesh / whatever in three’s world.

Now I am working to mentally grasp what should be / can be reactive state and what basically just needs to be held inside of an object that I mutate to signal different functions to be called in useFrame.

I feel a little bit crazy about this train of thought, but…

I am under the impression that I need to depend purely on React for declarative initial rendering of objects in my 3D world, and from there forward handle everything with conditions invoked every frame with useFrame + regularly querying the state of all the items in the world from three and syncing that for auto-save.

I’ve been considering using Mobx to handle the few React re-renders I need to trigger (for example, a WorldItem is added/removed from the world, WorldItemManager component need’s to re-render to render the new array of WorldItem.)

But I am struggling with… position and rotation can signal to useFrame that they need to update based on something like { isPressedW: true }, so it knows to start moving a selected WorldItem / Player / whatever. But if the user, for example selects a WorldItem and toggles an option to turn on its isGlowing… oh, right… that wouldn’t need to be in useFrame because it is not a repeated condition based update that needs to happen…

So I should just handle something like that in a function that takes the selected item ref and modifies it using the three API it exposes, rather than updating Reactive state and causing the component to re-render, right?

Am I on the right path or am I missing something?

  1. For any game-related state, be sure to not use component state (ie. avoid useState.) Use either zustand or context - that’ll prevent components from forcing incorrect / desynchronised state.

  2. Think in “targets” instead of positions / rotations. State should hold the “next desired position / rotation” for each entity in the world. Then component reads that state and interprets it in any way it desires - if you want entities to move smoothly, use .lerp in useFrame to tween Object3D ref to the next target state. If you want the entities to follow up instantly - just copy the position and rotation directly to the ref.

1 Like

I don’t think there is any general answer to your questions. You should show some code and ask targeted questions for each snippet.

The reason why your camera position resets to 0 when the player releases a key is probably about how you wrote the code for handling player inputs.

We don’t know neither how you implement glowing for items.

I don’t get the reasoning here. Ain’t Context also using component state? Won’t Zustand make bound components to re-render as well?

It’s not really about React / components / re-rendering. Zustand / top-level context lets you maintain a single source of truth - which is very important for the 3D game worlds, where components should not control the world, they should just render it. Parts like 2D UI can have their own separate or local states, since they are not really parts of the 3D game world.

It’s generally good to divide state into client- and server-side, even for single-player games.

Client-side state does not directly control the state of the game and state of the game does not directly control the UI and can use useState / local useContext - ex. weather changes in the 3D game world would usually not close menu modals or click buttons. UI can indirectly send queries to the server-side to modify state, but that’s all the power they should have.

Server-side state controls the game, and should feed it to the frontend as a JSON (or initially a full JSON, then deltas as updates.) That way each component can use Zustand to pick only the specific part of the state they are interested in and, without flickering or considering any kind of local state, adjust their rendering to fit the active game state - and that’s the part where lack of that local state comes in. 3D world should only listen and render, and neither directly or indirectly modify the server-side state.

1 Like

I like this view. But there’s also a chance that Hanah’s game uses physics, in which case the physics world would hold its own state, which means that the single source of truth could not be established, and some state syncronization have to occur between the physics world and the game rules.

There’s also a chance that Hannah’s game doesn’t have any game rules per se, and leans more towards the “toybox” side. In which case the utility of a global state / single source of truth could be debated.

1 Like