phaserjs / phaser

Phaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.
https://phaser.io
MIT License
37.22k stars 7.1k forks source link

New State Manager draft #1268

Closed photonstorm closed 9 years ago

photonstorm commented 10 years ago

Dumping my thoughts here for discussion.

Note: This is a Phaser 2.2 level change, not part of 2.1.x.

At the moment Phaser states work but are too limited in their nature. There is also one core game loop that is responsible for updating all of the key systems, such as the Tween manager, Sound manager, etc.

The proposal being drafted involves two changes:

1) The State now owns the systems, not the core Game. So a State has its own Tween Manager, Sound Manager, etc that belong exclusively to it. Each Manager is modified to allow multiple instances of it to exist.

2) States will be able to spawn Child States. A Child State is defined in two ways:

2a) It runs in parallel with the parent State and any other Child States. 2b) It optionally shares the same system managers as its parent, or it has its own.

Child States will be defined in a similar way that States are currently. The difference being that you can stipulate which managers are created for them. Equally a Child State will be able to be timesliced: that is you can define its rate of update. They can also be paused, resumed and destroyed.

To put this into a game context:

A Game State could have several Child States that handle different, but specific tasks, that you may require to happen during your game without closing the Game State itself.

For example an in-game shop, a settings panel, an inventory selection screen, a cut-scene, a level selection screen, a game world map. These are game-level events that need to occur without the Game being destroyed. In some cases the game will need to pause when they are active. In others the game can carry on running in the background.

Potential Issues

Some managers aren't good at sharing resources/events, for example the Input Manager.

It's a big rewrite.

pnstickne commented 10 years ago

This proposal is approximately what came up with when I was playing around with the concept of State/Game separation. The ability to be able to "push" a state (and then "pop") back over to the parent state is very good idea; a generalization wouldn't hurt. This would really help in trivial tasks such as popup/modal windows. States should have a way of submitting a value back to their parent when they are closed/popped, and parents should be able to supply values to children states.

My partial implementation created custom per-State managers, which seemed fairly viable. The biggest issue was that the existing code uses "game" to resolve any managers/support they themselves needed (and these in turn bound to..), so "game" became not-the-real-game. I liked the separation, but not [still] referring to it as "game"; rather it is a context (and there may be many contexts within a single game)

It would be worthwhile to try and detwine the state and managers when possible. Having the managers also added directly to the state made it more complicated than it needed to be. (Those properties can also be eliminated as they are available through the game/context.) - That is, a State has a state-context and a context includes per-state managers.

Unifying both parent/normal states and child states should be possible without fragmentation as there are relatively few differences, mainly lifecycle events. The addition of an increased of "on suspend/on resume" as per Android appeared to work well and it provided the pushed/popped states with more flexibility in deciding what to do.

photonstorm commented 10 years ago

It's important to keep the game.* manager references, as they are part and parcel of how most devs use Phaser. However I don't believe there would be an issue with having them mapped to the current parent State managers.

Lots of devs don't realise you can do this.add for example within a State to access the GameObjectFactory and always use game.add instead. Then again, there are plenty who do use it this way too. Neither is right/wrong. Phaser is built around convenience over configuration, a mantra that has contributed significantly to its popularity, so not something I'm going to significantly change.

However when it comes to child states we can afford to be a little different, and this will be an interesting way to test how devs respond to it.

ThaisRobba commented 10 years ago

Rich, I really like this! :+1:

Could be nice to add Phasery way of pausing the game state without pausing certain animations on a child state change. Stuff like opening a pause screen and the background clouds keep moving.

Maybe a new flag for animations associated with a boolean? Probably should be false by default.

I also agree that some level of interaction between children and parent states is a must - would make stuff like an rpg inventory really easy (and greatly reduce clutter).

On the subject of context, I don't think the game.* is confusing even if it doesn't refer to the exact 'game' that was first defined. I too see it as a convenience. One could also always do a

  state = this;

And work with state.add and whatnot when using a child state. Personally, I'd rather stick with game.*

photonstorm commented 10 years ago

The way I see the managers working is that game.* will always point to the parent State. If the child state has elected to use the parent managers, then it can use this.* or game.* and they'll be mapped to the same thing. If it has chosen to start its own managers then this.* will point to those (and game.* will always point to the parents).

pnstickne commented 10 years ago

I don't believe keeping keeping these concepts together is appropriate - it was leading into obscure issues with my test implementation, which is why I ultimately abandoned it without a "larger core change".

The minimum change should be the re-threading of a ("game-like") context. Contexts have parents contexts (associated with states), and perhaps have access the core game; but they are not states, and are not games. (Effectively a context becomes a scoped DI service-locator, as opposed to the current 'singleton' game.)

The object-graph and context should be an all-other-nothing. Otherwise a manager accessed by a different method (state.x or game.x) will potentially result in a different object - ick. This change will be marginally breaking to do well, and such should be accepted.

If a child ever wants to access the parent context it should be explicit - e.g. context.tweens, context.parentContext.tweens, context.game.tweens. Mixing the contexts up and down will be problematic. I am not opposed to provided such shortcuts (eg. manager properties copied from the correct state-context to a state, preferably by explicit request or a flag) for user code. However, using a "state" as the manager risks [additional] collisions with client code and does not fit well enough the current loose-state model.

However, to rely on or use or even hint at the viability of multiple context contains/paths is a can of worms (managers copied to a state are an end-point/convenience only). Break me off a piece of the context-bar.

jounii commented 10 years ago

Rich's suggestion sounds rather nice solution.

Places I've so far would have needed multiple parallel states is state transition, but that can be archived otherwise. My solution was custom state change function which can respond to signal triggered to "onExit" type of event from current state before starting the new state. Not exactly parallel solution, but more of "do something synchronous before state change is triggered".

Another place was music manager which needed state persisting timers. I though simple patch solution would work too (Time would not remove "flagged" timers on state change) but I had structure ready to restart timers on state change, so I opted to not patch Phaser for such functionality as I read about the state manager rewrite.

I suppose both would be directly possible with Rich's new state manager.

pnstickne commented 10 years ago

The next states/state-context/contexts should probably also be updated to encompass Physics and anything else that might be useful per-state/context. (ref. https://github.com/photonstorm/phaser/issues/1289).

The "worst" case would simply be aliasing the same objects.

I think this would be a good time to separate and thread some abstract context (not and not just a modified game) through game, whenever it is used. For 99% (well, most) of the code the context should be structurally-equivalent to the game, even though the "name" would be incorrect - but this would still be pulling the rug out from someone in a few cases.

Internal code would then look like this:

function X (game, ...) {
    this.context = game;  // future
    this.game = this.context.game;  // "migration"

Care would need to be taken to ensure that it is the context and not game that is passed down to any dependents.

amadeus commented 10 years ago

Forgive me, I am a bit late to this and I only recently started experimenting with Phaser. I am very keen on these parallel states for a project I am working on so I am quite interested.

I assume based on what I could gather around the impending 2.2 release this didn't make it in, but I do have some thoughts on the matter.

My past experience has been with ImpactJS which in many ways is simpler. I ended up writing a plugin for it called Impact Layers that accomplished a similar idea to this.

ImpactJS overall was quite a bit simpler in scope, but through this layers (the kinda Phaser equivelent of states) api you could create various layers, and then pick which ones would be added to the stack. Being added to the stack meant that update/draw methods would fire in both the specified stack order, but you also had some configuration in that you could optionally stop update or draw cycles on any given layer at any time. This for example made it super simple to create a pause menu that appears over the rendered game. It also enabled you to easily manipulate various game modes, etc.

While I agree with most of the initial post, I wonder if something perhaps a bit simpler would also work. I should make it clear that I don't know Phaser very well yet, so I could be speaking from ignorance, so this is all from what I've been able to gather in the last day or so of playing around with it (and I should say I quite like it a lot!) Also, I am not attached to these ideas and given I have so little experience with Phaser I won't be offended if you think I am completely off base.

I think the concept of parent and child states is perhaps a bit more complicated than it needs to be? If there are just states, a state manager and the game. The APIs available on states could be reworked a bit (although this would be a breaking change, I don't know how you feel about that), so only the ones that are state specific can be available on the state itself [add, make, cache, math, load, time, tweens, particles, physics] and then 'global' apis, should be done through game [keyboard, stage, world, camera, and all the other aforementioned APIs that have always been there]. The benefit of this would be that you are absolutely clear where things are being hooked up too - the state, or the game.

I did realize that by throwing cache in there, it creates a new wrench, but I would like to have state specific cache that I could just blow away when I destroy a state.

The new state manager would also need some additional functionality, any number of states could be added/removed and run from the stack. This means there would need to be some way to initialize/destroy states and also an API to push/pop from the state stack. And finally, states could have the following APIs - state.pause (disable's the update loop, but still renders), and state.visible (which controls rendering).

Anyways, this is a bit long winded, but I hope it at least adds something to this discussion.

photonstorm commented 9 years ago

I'm closing this off in order to tidy up the issues list. This will form part of Phaser 3 now.

hackergrrl commented 9 years ago

Are there docs/proposals somewhere for the new Phaser 3 state manager?

photonstorm commented 9 years ago

Not yet, we're still discussing internally - will open up soon, likely on the phaser3 repo.