Godux helps Godot developers consolidate game state into a single store. Communicating between nodes in your project becomes increasingly difficult the more complex your project becomes. Instead of littering all component nodes with game state, which can be unruly and confusing for larger projects, you can use a single source of information in order to easily access all data in your game from any node.
This is a continuation of Kenny Au's godot-redux.
Using the redux architecture allows for some interesting features:
- Saving and loading saved games becomes trivial.
- Undo/redoing actions.
- Event notifications.
- Quest tracking.
- Add the files from the src folder to your project.
- Make
Store.gd
a singleton using "Scene > Project Settings > AutoLoad".
A useful implementation is to create the following scripts as singletons:
ActionTypes.gd
Actions.gd
Reducers.gd
For a demo, see the examples folder.
The store is your main entry point for using Godux. You can create a store with store.create()
.
The global state of your game is a Dictionary
object in the store. It can be accessed with store.get_state()
.
{
'game': {
'paused': false
}
'players': {
'player1': {
'id': 'player1',
'name': 'Jane',
'position_x': 0,
'position_y': 0,
'moving': true
}
},
}
The way to change the state is to create an action, an Dictionary
object describing the changes you want to make, and dispatch it to the store. To dispatch an action, you use store.dispatch()
.
Actions must have a type
property, and are deployed with an action creator, a function which returns the action.
func game_update_paused(paused):
return {
'type': 'GAME_UPDATE_PAUSED',
'paused': paused
}
To dispatch an action to our store, use store.dispatch()
from a non-singleton script. This is due to singletons being processed last in the tree order.
onready var actions := get_node('/root/actions')
onready var reducers := get_node('/root/reducers')
onready var store := get_node('/root/store')
func _ready():
store.create([
{'name': 'game', 'instance': reducers},
{'name': 'players', 'instance': reducers}
])
# game_update_paused() is called, an action creator that returns
# an action dictionary that can be dispatched.
var new_action = actions.game_update_paused(true)
store.dispatch(new_action)
Reducers consume dispatched actions and create a new state object to be applied to the store. Reducers are pure functions that take 2 parameters: the last known state and an action.
When you create a reducer, it is important that it is a pure function. Specifically:
- The state is read-only, so the reducer must construct a new
state
object or return the unchangedstate
argument. - The return value must be the same given the same arguments. Impure functions cannot be used within a reducer.
onready var store := get_node('/root/store')
func game(state, action):
if action['type'] == 'GAME_UPDATE_PAUSED':
var new_state = state.duplicate()
new_state['paused'] = action['paused']
return new_state
return state
Subscriber functions are called whenever the state is changed. These are useful for listening to changes to the global state from any file in your game.
Functions can be subscribed to the state by calling store.subscribe()
, passing the node where the function is located, and the name of the function. The subscribed function only begins listening to state changes after store.subscribe() is called, so be aware of the tree order when making state changes when the scene tree is first initialized.
The subscribe() function returns a Closure
which can be used to unsubscribe the same function. To do so, you can call call_funcv()
on the return value of subscribe(). You can also call store.unsubscribe()
directly with the node where the function is located and the function name as arguments.
func _ready():
# This will subscribe print_pause_state() to the store.
var unsubscribe = store.subscribe(self, "state_updated")
# This will unsubscribe print_pause_state() from the store.
unsubscribe.call_funcv()
func state_updated():
var new_state = store.get_state()
print(new_state)
The base subscribe method notifies listeners when the store updates. It does not specify which part of the store it listens to. Subscribe Enhancers can be used to specify what updates the subscriber function listens to. Godux provides the Watch
subscribe enhancer. This enhancer lets you know what changed with an object_path
argument. To use this class, autoload Watch.gd
.
onready var watch := get_node('/root/watch')
func _ready():
# Pass the object path as the third argument. The first key in
# the object path is the reducer, and each additional key
# is accessed with dot notation.
watch.subscribe(self, 'coins_updated', ['game.coins'])
# This will unsubscribe print_pause_state() from the store.
unsubscribe.call_funcv()
func coins_updated(params: Dictionary):
var path = params['path']
if path != 'game.coins':
return
var prev_value = params['prev_value']
var next_value = params['next_value']
print('%s changed from %s to %s', [path, prev_value, next_value])
Subscribe Enhancers receive similar arguments to store.subscribe()
. Additional parameters are passed as a single array. Subscriber functions receive all arguments as a Dictionary
with argument names as the keys of the dictionary.
Parameter | Required | Description | Example |
---|---|---|---|
reducer |
No | String of the reducer name. | 'reducer' |
Returns: Dictionary containing entire state, or the state of the specified reducer.
Parameter | Required | Description | Example |
---|---|---|---|
reducers |
Yes | An array of dictionaries, each with name and instance keys. |
[{ 'name': 'function_name', 'instance': obj }] |
callbacks |
No | An array of dictionaries, each with name and instance keys. |
[{ 'name': 'function_name', 'instance': obj }] |
Returns: Nothing.
Parameter | Required | Description | Example |
---|---|---|---|
action |
Yes | A dictionary containing type key. |
{ 'type': 'ACTION_TYPE' } |
Returns: Nothing
Parameter | Required | Description | Example |
---|---|---|---|
target |
Yes | Object containing the callback function. | self |
method |
Yes | String of the callback function name. | 'callback_function' |
Returns: Closure object. Calling call_funcv()
on this object will unsubscribe the subscribed method.
Parameter | Required | Description | Example |
---|---|---|---|
target |
Yes | Object containing the callback function. | self |
method |
Yes | String of the callback function name. | 'callback_function' |
Returns: Nothing
- Nathaniel Adams <nathaniel.adams@berkeley.edu>
- Kenny Au - original author - <glumpyfish@gmail.com>
This project is licensed under the MIT License - see the LICENSE.txt file for details.