I'm using redux
as a state container in a simple shooter game. State is completely deterministic, the only input the system receives is user input (eg. a weapon was fired, etc).
My problem is that I have to track (and process) certain events, that happen during the game (eg. something was destroyed, etc), and I'm not quite sure how to do that.
My current solution is that the reducer maintains an events
array in the current state, and every reducer just appends events to it.
FIRE_WEAPON+-+ FIRE_WEAPON+-+
| |
| |
+-v--------+--------------v------------->
|
|
+->PLAYER_DESTROYED
Here the reduces receives two FIRE_WEAPON
action, and should "emit" a PLAYER_DESTROYED
event (right now, it is used to render an explosion there).
The project is open source, the reducer looks something like this (it's just pseudocode, but here is the relevant game logic):
// combine is just (f, g) => ((s, a) => g(f(s, a), a))
const reducer = combine(
// simulate world
(state, action) => {
while (state.time < action.time) {
state = evolve(state, delta); // evolve appends the happened in-game events to the state
}
return state;
},
// handle actual user input
(state, action) => {
return handleUserInput(state, action);
}
);
const evolve = (state, delta) => {
const events = [];
// some game logic that does some `events.push(...)`
return {
...state,
time: state.time + delta,
events: state.events.concat(events),
};
}
We can assume, that handleUserInput
is a simple x => x
identity function (it doesn't touch the events
array).
During evolve
, I'd like to "emit" events, but since that would make evolve
impure, I cannot do that.
As I said, right now I'm doing this by storing the happened events in the state, but there might be a better way. Any suggestions?
These events
are used during rendering, which looks likes this:
let sprites = [];
// `subscribe`d to store
function onStateChange() {
// `obsolete` removes old sprites that should not be displayed anymore
// `toSprite` converts events to sprites, you can assume that they are just simple objects
sprites = sprites.filter(obsolete).concat(store.getState().events.map(toSprite));
}
function render(state) {
renderState(state);
renderSprites(sprites);
}
But later on I'd like use events
on the server (the reducer described above runs on the server too), for calculating various stats (eg. enemies destroyed, etc.).
Ps.: these "emitted" events have no influence on the state (they are totally unrelated), so I'm sure that they shouldn't be actions (because they would leave the state unchanged). They are processed after the reducer has finished, after that they can be dropped (the reducer always receives an empty events
array).
I'm pretty sure that you can divide it in three parts:
-Actions:
const fireWeapon = ()=>({
type: FIRE_WEAPON
})
You can launch actions like fireWeapon okey, as you said reducers are pure functions so you can store in the state how much times you have launched that action.
-Reducer Fires
initialState: { fireWeapon: 0, fireShotgun:0}
CASE FIRE_WEAPON:
return {...state, fireWeapon: state.fireWeapon+1}
And finally, the key part, a libary called redux-observable, it's based on rxjs, reactive programming. You can suscribe to stream of actions and emit a new ones.
A really easy example is :
export const clearDeletedSiteEpic = (action$,state$) =>
action$.pipe(
ofType(FIRE_WEAPON),
map(() => {
if (state$.value.fires.fireWeapon % 2 === 0){
playerDestroyed() // action is launched
}
}
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With