Managing Application and Game State in A-Frame
The current solution now is the A-Frame State Component, which is sort of based off Redux patterns, but without all the memory issues, simpler API, and tailored for A-Frame performance.
Larger VR applications need a way of cleanly managing application or game state. Managing lots of client-side state in a non-tangled manner has been a large topic in 2D web development over the past couple of years which has given rise to libraries such as Redux. Having built several non-trivial WebVR applications, we’ll go over how we started to bring these ideas of state management into A-Frame’s DOM-based entity-component architecture.
Initial Approach with Redux
Image credit to makeitopen.com
My initial approach was to use Redux straight up with A-Frame. Redux is a library to predictably manage and modify state similar to a state management machine. Redux has become extremely popular over the last couple of years when used with React. I personally started using it the day it was released. Redux is an agnostic library that can be integrated into any framework.
Redux’s three principles: single source of truth, read-only state, and changes are made with pure functions. The implementation of Redux involves a single global state, making it easy to debug and inspect. That single global state is modified through pure functions (reducers) that takes the previous state and action and returns next state, making it easy to unit test. Then we can only modify the state by dispatching actions (i.e., events), providing a single entry point into modifying the state.
For a basic example, say we have a state {value: 1}
. I could have a reducer
that modifies the state; on the INCREMENT
action, the reducer would do return
a new state with value++;
, and on the DECREMENT
action, the reducer would
return a new state with value--;
. An action dispatcher would send the
INCREMENT
or DECREMENT
actions, triggering a state change.
I created an A-Frame Redux component to integrate Redux into A-Frame. The Redux component lets us declaratively set up a Redux store, and pass in reducers:
<a-scene redux="reducers: counter"></a-scene> |
And to register a reducer:
AFRAME.registerReducer('counter', { |
To dispatch actions, grab a handler to the Redux store and dispatch:
scene.systems.redux.store.dispatch({type: 'COUNTER__INCREASE'}); |
The Redux component then provided a data-binding component to bind state to A-Frame entities:
<a-entity redux="counter.value: text.value"></a-entity> |
The Redux component seems very usable with A-Frame. If I were to build a larger application where I needed to compose multiple reducers and split up the application state into sub-states, I would personally start reaching for Redux.
But to people had that have not used Redux, all of Redux’s terms (e.g., reducers, stores, dispatchers, actions) are foreign. A lot of A-Frame developers are coming from graphics, games, or 3D industries, and they don’t care about Redux. For me, it took a couple of weeks to fully grasp. So I wanted to see if we could create something simpler and more tailored to A-Frame’s architecture and VR.
Game State Implementation in A-Blast
The implementation below is the one I’m currently using on my personal projects, but it will evolve. Note we’ll be calling it game state as that’s a common term for 3D-related applications, but game state can apply for any application in general.
A-Blast is a cute VR shooter game. We wanted to build an application in one of the most common and popular genres to prove A-Frame as a 3D and VR framework. A-Blast let us see how common concepts such as game state would be implemented alongside A-Frame. For A-Blast, we needed to keep track of things such as the wave, player health, score, phase (e.g., Intro, Game Over), and time left.
The game state we implemented took ideas of Redux but tailored it for A-Frame:
- A single global game state A-Frame system which is registered on the
<a-scene>
element. - Initial state is specified in the system’s schema.
- State can be accessed via the system’s data (e.g.,
sceneEl.getAttribute('gamestate')
). - Actions are dispatched using events
- Handlers in the game state are responsible for transitioning the state
- The game state system publishes the game state and notifies via an event
- Related to the game state system is a component to declaratively bind game state to A-Frame component properties in HTML
Below is a basic skeleton for game state system implementation that mirrors A-Blast’s. Fewer than 50 lines of code that does the basics of Redux:
AFRAME.registerSystem('gamestate', { |
Then to dispatch an action, any A-Frame entity can emit an event, and it’ll bubble up to the scene, where the game state can trigger its handlers. No need to grab a handle on the game state; entities don’t even have to know about the game state.
A-Blast also has a gamestatebind
component that updates entities if necessary
on every state change. If we wanted to bind game state’s value
to A-Frame’s
text component’s value
property, we can use the component declaratively:
<a-entity gamestatebind="value: text.value"></a-entity> |
Similar to the concepts in React, A-Frame components in this model of game state could either be “smart components”, meaning aware of the game state, or “dumb components”, meaning they’re agnostic and purely responsible for pieces such as 3D appearance, behavior, logic.
We are also free to choose what state is able to be handled within a single component, local state, and when to hoist a piece of state up to the global level if multiple entities or components need to interface with it.
State specific to an entity can either be kept in a component, and if we need to make it global or more closely manage it, we can hoist that up to the global game state. Say we had a collection of data in the game state each representing an entity. To allow each entity having its own state while managing it at the global level, we could “select” or bind each piece of data to its respective entity; similar to how you might possibly fetch a list of data and bind it to list items in HTML.
Game State Implementation in A Saturday Night
A Saturday Night is a demo for motion capture where people can get into their headset, record a dance, and share it with their friends. Like a “dancegram”. Diego Marcos implemented the game state for A Saturday Night and took a different approach from scratch, without any bias from Redux. This implementation is less generalized but optimized for A Saturday Night.
- A single global game state component which is registered on the
<a-scene>
element - The application can be in a single game state at a time
- All possible game states are statically defined up front in an array
- A-Frame Components are registered, each one handling a different state. The
state components define an
init
handler on what’s run when the application transitions to its state, and aremove
handler on what’s run when the application transitions away from its state - States are changed using a
setState
method that swaps to a different state - The game state component publishes its state to the application by toggling the state components
Below is a basic skeleton for game state system implementation that mirrors A Saturday Night’s:
AFRAME.registerComponent('game-state', { |
Components can update the game state, and the game state will decide whether conditions have been fulfilled to transition to the next state.
Then we have components that represent each distinct state:
AFRAME.registerComponent('bar', { |
Comparing Implementations Between Redux Approach, A-Blast, A Saturday Night
A-Blast’s game state was something I came up with, basing it on Redux. The advantage is that components don’t have to know about the game state; they just emit events, and the game state listens to them. A-Blast’s game state is also more flexible since there’s no single state represented as a string; any change to pieces of the game state represents an entirely new game state. We send actions, and the game state controls what needs to change in the state. This model from this state management has form of state management has been battle-tested through the web community’s adoption of Redux.
A Saturday Night’s game state was implemented by Diego Marcos. This model of game state is well suited for simpler applications such as A Saturday Night where there are only very distinct states (avatar selection, dancing, sharing states). All the code that reacts to state changes are contained within components that each manage a single state; this could be good or bad since there is lots of code touching lots of parts of the application contained within a single component. The nice part is that changing states and reacting to changed states are synchronous rather than through events. However, it would be possible to change A-Blast’s game state to be synchronous.
However, as an application grows very large and contains lots of state, I would give Redux a serious look up. By composing reducers, Redux is able to split an application up into various sub-states or subtrees making each piece of the application more manageable and predictable. The only obstacle would be the learning curve, but the benefits would be proven state management with an excellent community of state debugging tools.
In-VR State Debugging Tools and Time Travel
Since A-Frame is about VR, it’s useful to view the entire game state while in VR for debugging purposes. In A-Blast, we had a component that printed out the entire game state on a panel with text within VR. Every time the game state changed, we’d update the panel.
Eventually, it’d also be nice to have 2D state debugging tools just as Redux has developer tools to inspect every single state change, or even time travel. We’d be able to see every action that’s dispatched and what it causes. With A-Blast’s game state component, it’d be easy since the system emits an event on each game state change, providing the action that triggered the state change, the diff between the old and next states, and the current new state.
In the future, I can imagine creating a time-travel
component that could
rewind and pan through the different states. We could turn the Vive trackers
into a dial that we could physically turn to reverse time, and see the world go
backwards in VR!
Conclusion
These have been our initial attempts in implementing game state. I have no doubt that our ideas and implementations will change and evolve for the better as we build more and more complex WebVR applications. Coincidentally, Don McCurdy was brainstorming about state machines at the exact time of writing of this post! Check out his ideas as well.
More recently, I have been building the beginnings of a fairly complex application where I am often interacting with game state. You can watch my recorded developer logs or catch me working on game state live on Twitch as I continue to develop it.
If you want to see various source code for implementations of game states:
Whatever becomes the ultimate approach to game state will become the Oracle of the Metaverse!