Author Topic: "Sub-FlxState"  (Read 2609 times)

IQAndreas

  • Member
  • **
  • Posts: 35
  • Karma: +0/-0
    • View Profile
    • IQAndreas.com
"Sub-FlxState"
« on: Sun, Dec 18, 2011 »
Noo! I was on autopilot, and while thinking I hit the "Reply to Topic", it turns out that button said "Remove Topic" ???

Anyway, this is just reposting that old topic. There were two replies already as well, so I'll just copy and paste them in (luckily the topic was cached, so the back button brought it up again)



Take for instance MineCraft, you are still in the "Play state", but when you open a chest, a GUI appears on top of the current state, and all user input (keyboard, mouse, the works) go to this new state.

However, the background play state still keeps all variables and is essentially "paused" in the background until you exit the current "sub-state".


Is there already a built in way of creating these "sub states" in Flixel, or should I "take matters into my own hands"?

Quote from: test84
I think you can have a boolean for being in a menu and in your update, check and if you are in menu, skip everything and draw and handle menu. But I'm sure there are better ways to do it.

Quote from: auriplane
I didn't use Flixel's state system at all, and implemented maybe 3 global state machines for my game.  Of course, my code is hardly a shining example ;-)  But implementing state machines is easy, so I recommend not getting too hung up on specifics, and just pick a solution and keep your forward momentum.  If it's ever a problem down the road, it shouldn't be a big deal to refactor.

IQAndreas

  • Member
  • **
  • Posts: 35
  • Karma: +0/-0
    • View Profile
    • IQAndreas.com
Re: "Sub-FlxState"
« Reply #1 on: Sun, Dec 18, 2011 »
As for the reply I was going to make before I removed the thread...

Thanks for the input. (Also thanks to Arkeus on IRC for letting me glean his code)

I ended up adding in my own system into the Flixel framework and can be found on this branch:
https://github.com/IQAndreas/flixel/tree/dev_FlxSubState
(I could have used separate classes, but by modifying the core classes, I kept my code simpler and avoided a few potential complications)

In a nutshell, you can extend "FlxSubState" which works just like a "FlxState" but adds a "isBlocking" option which if set to true prevents the parent FlxState from running it's "update" method. (Note that the parent state's "draw" method is always run, even if the subState is blocking the update command)

So for instance, the "Pause Menu" in MineCraft would have "isBlocking" set to true, so the "game state" stops.
The "Opening Chest" menu in MineCraft would not be blocking, since the world keeps playing in the background.

 - When opening a SubState, you can add a callback for when the state closes.
 - When the SubState closes, you can pass a string to the parent state with information (for instance, why the state closed).
 - The SubState does not have a public reference to it's parent. This is to adhere to proper encapsulation so the SubState doesn't need to know anything about it's parent state. You can however pass parameters to the constructor of the SubState if needed.
 - The SubState will ALWAYS be rendered on top of the parent state.

Code: [Select]
class PauseMenu extends FlxSubState
{

    public static const QUIT:String = "PauseMenu::quit";
    public static const RESUME:String = "PauseMenu::resume";
   
    public function PauseMenu()
    {
        // 'true' here means the state is blocking the parent state
        super(true);
    }
   
    public override function create():void
    {
        // ... display buttons and add listeners
    }
   
    //Sample handlers
    public function onQuitButtonClicked(event:Event):void
    {
        this.close(PauseMenu.QUIT);
    }
    public function onResumeButtonClicked(event:Event):void
    {
        this.close(PauseMenu.Resume);
    }
}

Code: [Select]
class PlayState extends FlxState
{
    // ...
   
    //No need to add anything to "update()" as it will pause automatically
   
    function openPauseMenu():void
    {
        this.setState(new PauseMenu(), onMenuClosed);
    }
   
    function onMenuClosed(state:FlxSubState, result:String):void
    {
        if (result == PauseMenu.QUIT)
        {
            trace("Exit to main menu");
        }
        //else - keep playing
    }
}



I haven't actually applied it to any project yet, but it should work in theory.
« Last Edit: Sun, Dec 18, 2011 by IQAndreas »

test84

  • Key Contributor
  • *****
  • Posts: 1328
  • Karma: +0/-0
  • ت
    • View Profile
    • My personal site.
Re: "Sub-FlxState"
« Reply #2 on: Mon, Dec 19, 2011 »
So do we have to "transit" to this state? If so, how do we keep everything from the previous state? And how to keep the game in the background?
blog, twitter, Check out my award winning game, Rot Gut:

IQAndreas

  • Member
  • **
  • Posts: 35
  • Karma: +0/-0
    • View Profile
    • IQAndreas.com
Re: "Sub-FlxState"
« Reply #3 on: Mon, Dec 19, 2011 »
So do we have to "transit" to this state? If so, how do we keep everything from the previous state? And how to keep the game in the background?
For this example, let's assume your current, plain old FlxState is an instance of your own class "GameState". You switch to the new state by calling this inside of your GameState:
Quote
this.setState(new PauseMenu(), <optional callback when the menu closes>);

That will draw the new state right on top of EVERYTHING in the current state (but if the top state is only a little "Alert" window etc, you can still see the "game state" behind it.)

Also, if the new state is blocking (like the standard "pause menu" usually is), the "update" command won't run on the GameState until the menu has closed.

The current "GameState" is still running in the background and still in memory. The new PauseMenu sub-state does not replace "FlxG.currentState", but is in a sense a "child" of the current GameState instance. (but it won't show up in the list of the GameState members)


Since FlxSubState extends FlxState, it too has a "subState" property, so you can keep stacking on these states, such as having a "ControlsMenu" or "AboutMenu" on top of the current "PauseMenu" sub-state.


Would it be clearer if I modified a project to use this system?

test84

  • Key Contributor
  • *****
  • Posts: 1328
  • Karma: +0/-0
  • ت
    • View Profile
    • My personal site.
Re: "Sub-FlxState"
« Reply #4 on: Mon, Dec 19, 2011 »
I like it and sure seems handy, sure it's nice to have a minimal project to see it in action.
blog, twitter, Check out my award winning game, Rot Gut:

IQAndreas

  • Member
  • **
  • Posts: 35
  • Karma: +0/-0
    • View Profile
    • IQAndreas.com
Re: "Sub-FlxState"
« Reply #5 on: Wed, Dec 21, 2011 »
And done! (note, not my best work, but it looks nice at least :-\

The example game (a branch of AdamAtomic's EZPlatformer) can be found here:
https://github.com/IQAndreas/EZPlatformer

The SWF is in the "bin" folder, and I'm too tired at the moment to wrap it in a HTML file and put it up on a server somewhere. ;)

I still haven't merged the changes to "my" master flixel, but instead still have everything stored in a separate branch:
https://github.com/IQAndreas/flixel/tree/dev_FlxSubState
« Last Edit: Wed, Dec 21, 2011 by IQAndreas »

xhunterko

  • Contributor
  • ****
  • Posts: 449
  • Karma: +0/-0
    • View Profile
Re: "Sub-FlxState"
« Reply #6 on: Wed, Dec 21, 2011 »
Actually, this might be done perhaps a lot simpler with a modified FlxDialog object.

//FlxDialog
Code: [Select]

package org.flixel {


import org.flixel.*;


public class FlxDialog extends FlxGroup{

/**
* Use this to tell if dialog is showing on the screen or not.
*/
public var showing:Boolean;

/**
* The text field used to display the text
*/
private var _field:FlxText;

/**
* Called when dialog is finished (optional)
*/
private var _finishCallback:Function;

/**
* Stores all of the text to be displayed. Each "page" is a string in the array
*/
private var _dialogArray:Array;

/**
* Background rect for the text
*/
private var _bg:FlxSprite;


internal var _pageIndex:int;
internal var _charIndex:int;
internal var _displaying:Boolean;
internal var _displaySpeed:int
internal var _elapsed:Number;
internal var _endPage:Boolean;

public function FlxDialog()
{

_bg = new FlxSprite(88,56).makeGraphic(420, 72, 0xff808080);
_bg.scrollFactor.x = _bg.scrollFactor.y = 0;
add(_bg);

_field = new FlxText(_bg.x,_bg.y+16, 410, " ");
_field.setFormat(null, 12, 0xff00FFFF, "center");
_field.scrollFactor.x = _field.scrollFactor.y = 0;
add(_field);

_elapsed = 0;

_displaySpeed = 0.45;
_bg.alpha = 0;
}

/**
* Call this from your code to display some dialog
*/
public function showDialog(pages:Array):void
{
_pageIndex = 0;
_charIndex = 0;
_field.text = pages[0].charAt(0);
_dialogArray = pages;
_displaying = true;
_bg.alpha = 1;
showing = true;
}

/**
* The meat of the class. Used to display text over time as well
* as control which page is 'active'
*/
override public function update():void
{

if(_displaying)
{
_elapsed += FlxG.elapsed;
if(_elapsed > _displaySpeed)
{
_elapsed = 0;
_charIndex++;

if(_charIndex > _dialogArray[_pageIndex].length)
{
_endPage = true;
_displaying = false;
}

_field.text = _dialogArray[_pageIndex].substr(0, _charIndex);
}
}

if(FlxG.mouse.justPressed())
{

if(_displaying)
{
_displaying = false;
_endPage = true;
_field.text = _dialogArray[_pageIndex].substr(0, _charIndex);
_elapsed = 0;
_charIndex = 0;

}
else if(_endPage&& showing)
{
if(_pageIndex == _dialogArray.length - 1)
{
//we're at the end of the pages

_pageIndex = 0;
_field.text = " ";
_bg.alpha = 0;

if(_finishCallback != null)
_finishCallback();
showing = false;
}
else
{
_pageIndex++;
_displaying = true;
}
}
}

if(FlxG.keys.SPACE)
{
this.kill();
_finishCallback();
showing = false;
}

super.update();


}

/**
* Called when the dialog is completely finished
*/
public function set finishCallback(val:Function):void
{
_finishCallback = val;
}

}
}


Because FlxDialogue is an extended FlxGroup, that means you can put FlxSprites in there too.  Now, gettting the state to "pause" while this is accessed is easy too:

Code: [Select]
override public function update():void
{

//Hard wired pausing for the message to pop up
if (!dialog.showing)
{
///etc

super.update();

//Something to trigger the message
if (startMessage == 1)
{
dialog.showDialog(page);
dialog.finishCallback = dialogKill;
add(dialog);
}
//etc

}

 else
{
dialog.update();
}

public function dialogKill():void
{
startMessage = 2;
header.kill();
headerTxt.kill();
}



Now, since your menu won't run on messages, you'd have to figure out some way to close it instead. Which, can just be as easy as a key press! Hope this helped too!
Now on twitter: http://twitter.com/xhunterko I made a game that's in alpha you can buy here: http://xhunterko.itch.io/wave-miner-alpha

auriplane

  • Snails!!
  • Contributor
  • ****
  • Posts: 497
  • Karma: +1/-0
  • Snails!!
    • View Profile
Re: "Sub-FlxState"
« Reply #7 on: Wed, Dec 21, 2011 »
I think you could post this sort of thing in the Releases forum, which is lower traffic, and it's less likely it'll be overlooked.

IQAndreas

  • Member
  • **
  • Posts: 35
  • Karma: +0/-0
    • View Profile
    • IQAndreas.com
Re: "Sub-FlxState"
« Reply #8 on: Thu, Dec 22, 2011 »
Actually, this might be done perhaps a lot simpler with a modified FlxDialog object.
Thanks for the code example!

But that's still a bit of code you have to re-write each time. By just adding a "setSubState" function and a "FlxSubState" class, you can reduce the amount of work needed to create such a dialog. It's not a huge save, but anything that makes rapid development easier is a step in the right direction in my opinion. :)

I think you could post this sort of thing in the Releases forum, which is lower traffic, and it's less likely it'll be overlooked.
Good idea! I created a new thread for the changes in that section, so just let this one die (or better yet, if a moderator can lock it)

http://forums.flixel.org/index.php/topic,5663.0.html

If you guys have any thoughts or ideas for the changes, let me know.

pixelomatic

  • Active Member
  • ***
  • Posts: 131
  • Karma: +0/-0
    • View Profile
Re: "Sub-FlxState"
« Reply #9 on: Thu, Dec 22, 2011 »
There's no need to alter flixel's code. Create a FlxGroup: inventoryGroup and add the inventory gui to that group. When you want to go into inventory just enable the inventoryGroup(using alive flag etc.) and set a flag that indicates to lock controls. Check that flag in player's control code and disable keyboard/mouse controls if that flag is set to true. This way, you'll be able to use inventory screen while game is still going on but without interfering with the actual game controls. If want to pause the game at the same time, you can:
a) Add another group and add the game objects to that group. When the inventory is open, don't update that group but just draw that group.
b) Just go to another state, but copy the previous state's bitmapData before going there, then draw that bitmap data and draw anything else on top of it, so there will be an illusion that you are still in game.