HaxeFlixel/flixel

View on GitHub
flixel/FlxState.hx

Summary

Maintainability
Test Coverage
package flixel;

import flixel.group.FlxContainer;
import flixel.util.FlxColor;
import flixel.util.FlxDestroyUtil;
import flixel.util.FlxSignal;
import flixel.util.typeLimit.NextState;

/**
 * This is the basic game "state" object - e.g. in a simple game you might have a menu state and a play state.
 * It is for all intents and purpose a fancy `FlxContainer`. And really, it's not even that fancy.
 */
@:keepSub // workaround for HaxeFoundation/haxe#3749
#if FLX_NO_UNIT_TEST
@:autoBuild(flixel.system.macros.FlxMacroUtil.deprecateOverride("switchTo", "switchTo is deprecated, use startOutro"))
#end
// show deprecation warning when `switchTo` is overriden in dereived classes
class FlxState extends FlxContainer
{
    /**
     * Determines whether the current state is updated, even when it is not the active state.
     * For example, if you have your game state open first, and then you push a pause state on top of it,
     * if this is set to `true`, the game state would still continue to be updated in the background.
     *
     * By default, this is set to `false`, so the background states will continue to be "paused" when they are not active.
     */
    public var persistentUpdate:Bool = false;

    /**
     * Determines whether the current state is drawn, even when it is not the active state.
     * For example, if you have your game state open first, and then you push a pause state on top of it,
     * if this is set to `true`, the game state would still continue to be drawn behind that pause state.
     *
     * By default, this is set to `true`, so the background states will continue to be "drawn" behind the current state.
     *
     * If you do not want background states to be `visible` when you have a different state on top,
     * then you should set this to `false` for improved performance.
     */
    public var persistentDraw:Bool = true;

    /**
     * If substates get destroyed when they are closed, setting this to
     * `false` might reduce state creation time, at greater memory cost.
     */
    public var destroySubStates:Bool = true;

    /**
     * The natural background color the cameras default to. In `AARRGGBB` format.
     */
    public var bgColor(get, set):FlxColor;
    
    /**
     * The specific argument that was passed into `switchState` or `FlxGame.new`
     */
    @:allow(flixel.FlxGame)
    @:allow(flixel.FlxG)
    var _constructor:NextState;
    
    /**
     * Current substate. Substates also can be nested.
     */
    public var subState(default, null):FlxSubState;

    /**
     * If a state change was requested, the new state object is stored here until we switch to it.
     */
    @:noCompletion
    var _requestedSubState:FlxSubState;

    /**
     * Whether to reset the substate (when it changes, or when it's closed).
     */
    @:noCompletion
    var _requestSubStateReset:Bool = false;

    /**
     * A `FlxSignal` that dispatches when a sub state is opened from this state.
     * @since 4.9.0
     */
    public var subStateOpened(get, never):FlxTypedSignal<FlxSubState->Void>;

    /**
     * A `FlxSignal` that dispatches when a sub state is closed from this state.
     * @since 4.9.0
     */
    public var subStateClosed(get, never):FlxTypedSignal<FlxSubState->Void>;

    /**
     * Internal variables for lazily creating `subStateOpened` and `subStateClosed` signals when needed.
     */
    @:noCompletion
    var _subStateOpened:FlxTypedSignal<FlxSubState->Void>;

    @:noCompletion
    var _subStateClosed:FlxTypedSignal<FlxSubState->Void>;
    
    public function new ()
    {
        super(0);
    }
    
    /**
     * This function is called after the game engine successfully switches states.
     * Override this function, NOT the constructor, to initialize or set up your game state.
     * We do NOT recommend initializing any flixel objects or utilizing flixel features in
     * the constructor, unless you want some crazy unpredictable things to happen!
     */
    public function create():Void {}

    override public function draw():Void
    {
        if (persistentDraw || subState == null)
            super.draw();

        if (subState != null)
            subState.draw();
    }

    public function openSubState(SubState:FlxSubState):Void
    {
        _requestSubStateReset = true;
        _requestedSubState = SubState;
    }

    /**
     * Closes the substate of this state, if one exists.
     */
    public function closeSubState():Void
    {
        _requestSubStateReset = true;
    }

    /**
     * Load substate for this state
     */
    public function resetSubState():Void
    {
        // Close the old state (if there is an old state)
        if (subState != null)
        {
            if (subState.closeCallback != null)
                subState.closeCallback();
            if (_subStateClosed != null)
                _subStateClosed.dispatch(subState);

            if (destroySubStates)
                subState.destroy();
        }

        // Assign the requested state (or set it to null)
        subState = _requestedSubState;
        _requestedSubState = null;

        if (subState != null)
        {
            // Reset the input so things like "justPressed" won't interfere
            if (!persistentUpdate)
                FlxG.inputs.onStateSwitch();

            subState._parentState = this;

            if (!subState._created)
            {
                subState._created = true;
                subState.create();
            }
            if (subState.openCallback != null)
                subState.openCallback();
            if (_subStateOpened != null)
                _subStateOpened.dispatch(subState);
        }
    }

    override function destroy():Void
    {
        _constructor = function():FlxState
        {
            throw "Attempting to resetState while the current state is destroyed";
        };
        FlxDestroyUtil.destroy(_subStateOpened);
        FlxDestroyUtil.destroy(_subStateClosed);
        
        if (subState != null)
        {
            subState.destroy();
            subState = null;
        }
        super.destroy();
    }

    /**
     * Called from `FlxG.switchState()`. If `false` is returned, the state
     * switch is cancelled - the default implementation returns `true`.
     *
     * Useful for customizing state switches, e.g. for transition effects.
     */
    @:deprecated("switchTo is deprecated, use startOutro")
    public function switchTo(nextState:FlxState):Bool
    {
        return true;
    }
    
    /**
     * Called from `FlxG.switchState()`, when `onOutroComplete` is called, the actual state
     * switching will happen.
     * 
     * Note: Calling `super.startOutro(onOutroComplete)` will call `onOutroComplete`.
     * 
     * @param   onOutroComplete  Called when the outro is complete.
     * @since 5.3.0
     */
    public function startOutro(onOutroComplete:()->Void)
    {
        onOutroComplete();
    }

    /**
     * This method is called after the game loses focus.
     * Can be useful for third party libraries, such as tweening engines.
     */
    public function onFocusLost():Void {}

    /**
     * This method is called after the game receives focus.
     * Can be useful for third party libraries, such as tweening engines.
     */
    public function onFocus():Void {}

    /**
     * This function is called whenever the window size has been changed.
     *
     * @param   Width    The new window width
     * @param   Height   The new window Height
     */
    public function onResize(Width:Int, Height:Int):Void {}

    @:allow(flixel.FlxGame)
    function tryUpdate(elapsed:Float):Void
    {
        if (persistentUpdate || subState == null)
            update(elapsed);

        if (_requestSubStateReset)
        {
            _requestSubStateReset = false;
            resetSubState();
        }
        if (subState != null)
        {
            subState.tryUpdate(elapsed);
        }
    }

    @:noCompletion
    function get_bgColor():FlxColor
    {
        return FlxG.cameras.bgColor;
    }

    @:noCompletion
    function set_bgColor(Value:FlxColor):FlxColor
    {
        return FlxG.cameras.bgColor = Value;
    }
    
    @:noCompletion
    function get_subStateOpened():FlxTypedSignal<FlxSubState->Void>
    {
        if (_subStateOpened == null)
            _subStateOpened = new FlxTypedSignal<FlxSubState->Void>();

        return _subStateOpened;
    }

    @:noCompletion
    function get_subStateClosed():FlxTypedSignal<FlxSubState->Void>
    {
        if (_subStateClosed == null)
            _subStateClosed = new FlxTypedSignal<FlxSubState->Void>();

        return _subStateClosed;
    }
}