-
Notifications
You must be signed in to change notification settings - Fork 4
Use Elmish for game state management (basic sample) #28
base: master
Are you sure you want to change the base?
Conversation
First of all thanks for taking time to make these searches on that difficult Elmish topic @alfonsogarciacaro 👍 |
Hi! State machinesThrough the years I've always been using state machines. let mutable gameState = Init
// our render loop, every frame
app.ticker.add (fun delta ->
gameState<-
match screen with
| Init -> // prepare model then move to start
let model = ...
NextScreen (Start model)
| Start model -> // launch start anim, often some title tween
let model, reachedEnd = Start.Update model
if reachedEnd then
Start.Cleanup() // one call to remove all unused sprites
NextScreen (Game model)
| NextScreen nextScreen -> nextScreen // wait one frame to go to the next state
| GameOver model ->
let model, done = GameOverState.Update model
if done then
GameOver.Cleanup()
NextScreen Init
| Game model ->
let model, reachedEnd = GameState.Update model
if reachedEnd then
GameState.Cleanup() // one call to remove all unused sprites
NextScreen GameOver
) |> ignore
This sample could easily be refactored to have central cleanup system and So we would end with
EventsEvents plague all game developments with callbacks... that may call other callbacks to bubble up to an event management system wtht will tell what to do. Here, the But then the information dispatched needs to be analysed thanks to a context, which would often be our local model. For instance, if sprite A collided with Sprite B, we would dispatch a Then, we would let the main loop or an upper management system handle this END_OF_STATE message. For instance, it would lead to the cleanup of the current state, local model and start of a new Game Over state to display a neat GameOver animation. ElmishNow regarding Elmish, I think that dispatching messages is the most important thing. But as you said, we are working in an always mutable land that does not need to wait for user input or model input to update its rendering. I mean, unlike your previous Canvas sample, here with pixi (and also with paperjs and maybe any game lib) I think it may be best to let the rendering happen and just control the logic and analyse the dispatched messages. StrengtheningBut like OpenFrameworks or Processing (setup, draw) I think we should enforce some mechanism to make our FSM more robust. It whould not be complicated because what we always end up using:
Sprite licecycleAlso a sprite needs to be taken care of by removing it from its ancestor. There are two ways to do that:
or
Conclusionif I read you well, and if I recollect my memories from previous experiences, I think using Elmish is not a good fit unless we animate everything using event based animation systems like Animatejs which allows us to have some I do have this kind of projects. I think Secret of Monkey Island could fit well in an Elmish Architecture. But to make a Bullet Hell shmup like like the Touhou series... well, I think we may just find try to dig further into a GloriousStateMachine 😉 I don't think I have said all. But I'm a little bit short on time so let's say here are my starters 😉 |
Thanks a lot for your feedback @whitetigle! So I guess the best option is to build our own framework, inspired by Elmish but more adapted to a 60-FPS game 👍 State MachinesI absolutely agree with this. Actually one of the drawbacks of Elmish for me is that, because everything works by composition you cannot define isolated state machines. I'm a big fan of the proposal of Tomas Petricek to model state machines with async. This would allow us to represent the top flow very nicely. For example: type Result = Win | Lose
type MenuOption = StartGame | ShowCredits
type Screen = Menu | GamePlay of int | GameOver | GameCompleted | Credits
let rec startGame(): Async<unit> = async {
let! option = showScreen Menu
match option with
| StartGame ->
let mutable level = 1
while level > 0 && level < MAX_LEVEL do
let! res = showScreen (GamePlay level)
match res with
| Win -> level <- level + 1
| Lose -> level <- 0
do! showScreen GameOver
if level = MAX_LEVEL then
do! showScreen GameCompleted
| ShowCredits ->
do! showScreen Credits
return! startGame()
} EventsI also agree here that a dispatch model à la Elmish is easier than a subscriber model. My only doubt is if the events have to be dealt with immediately or one by one, or rather just collected and be dealt with later in the StateBesides different levels of State (global and local) I wonder if the state should be coupled with the renderer or not. Basically, whether Pixi sprites, etc, should go into the state or not. Having only simple types in the state can have advantages: it's easier to serialize and thus makes time travel debugging and exchanges with a worker also easier, separates rendering from logic. On the other hand, it can be more complicated to the dev, who will have to update the Pixi objects in the render function (unless we can create something like Virtual DOM for Pixi). |
Thanks @alfonsogarciacaro 👏 State Machines: ok for Async. Could you just prepare some sample so that I can better see what it involves? Events: I think you're right. So how could we apply that to a Drag and drop model with pixi Interaction Events like in this sample? State: Like I said, I think the state should be decoupled from the renderer. It should be somehow agnostic. |
@whitetigle I've updated the dragging sample. The result is much more verbose than the original and probably many things can be improved but I hope it helps you get a better idea. It's a very simple state machine with two states: dragging and waiting for dragging to start, each one with a different update function. The logic (State.fs) is the decoupled from the view (Renderer.fs) module. The main function is an asynchronous loop which sends a |
Hey that's very interesting! |
Ok. I started a very secret ㊙️ project involving your proposal. It's here It's my little contribution to the advent calendar. So there's no real events like your take on the drag and drop sample. There's just a render function that dispatch messages. But still it's clearly interesting to dispatch messages. It's running at 60fps on my laptop and there are some particles 😉 |
I think @alfonsogarciacaro is creating a bank of image and gif since several months and is now using them :) Other than that, great work to both or you on this Elmish + Pixi related stuff. Don't have much time to help on this subject ATM. |
😆😆😆
No problem at all. I also have zounds of things to do. Anyway thanks @MangelMaxime . In 2018 we'll Hink the world 😉 🍷 at Strasbourg 😁 |
Finally I got some time to have a look at this :) First I tried to update #27 (actually this PR is branched from that) but I realized that #27, while sharing the state between the React and Pixi parts (so, for example, dragons can be added by clicking the button or the sprite), Elmish is used to manage the React part and it's complicated to share the update function with Pixi so at the end I opted for integrating Elmish with the basic sample.
Please have a look at
src/basic/App.fs
, it should be structured more or less as a typical Elmish application. For that I use thesrc/basic/Elmish.Animation.fs
helper, which is an adaptation of the Elmish.Worker in the FableConf game workshop. Note that I've used an additionaltick
function to solve the issue mentioned here. Actually, I noticed with this the PIXI ticker is not actually necessary so I removed it.Advantages of this approach:
But there also disadvantages:
dispatch
only collect the messages and then maketick
the actualupdate
function, which would receive the collection of messages (aka events) happened in this frame together with the delta. However, this was not possible the way Elmish works. We would have to modify it for this.update
andtick
functions can only be enforced by the programmer discipline, not the compiler.So basically we need to decide if we want to a) adapt Elmish as is for games, b) create something similar but more fit to games, or c) leave the Elmish way definitely. What do you think? @whitetigle @MangelMaxime