flixel/input/actions/FlxAction.hx
package flixel.input.actions;
import flixel.input.FlxInput.FlxInputState;
import flixel.input.IFlxInput;
import flixel.input.actions.FlxActionInput.FlxInputDeviceID;
import flixel.input.actions.FlxActionInput.FlxInputType;
import flixel.input.actions.FlxActionInputAnalog.FlxAnalogAxis;
import flixel.input.actions.FlxActionInputAnalog.FlxAnalogState;
import flixel.input.actions.FlxActionInputAnalog.FlxActionInputAnalogClickAndDragMouseMotion;
import flixel.input.actions.FlxActionInputAnalog.FlxActionInputAnalogGamepad;
import flixel.input.actions.FlxActionInputAnalog.FlxActionInputAnalogMouseMotion;
import flixel.input.actions.FlxActionInputAnalog.FlxActionInputAnalogMousePosition;
import flixel.input.actions.FlxActionInputDigital.FlxActionInputDigitalIFlxInput;
import flixel.input.actions.FlxActionInputDigital.FlxActionInputDigitalGamepad;
import flixel.input.actions.FlxActionInputDigital.FlxActionInputDigitalKeyboard;
import flixel.input.actions.FlxActionInputDigital.FlxActionInputDigitalMouse;
import flixel.input.actions.FlxActionInputDigital.FlxActionInputDigitalMouseWheel;
#if android
import flixel.input.actions.FlxActionInputDigital.FlxActionInputDigitalAndroid;
#end
import flixel.input.keyboard.FlxKey;
import flixel.input.mouse.FlxMouseButton.FlxMouseButtonID;
import flixel.input.android.FlxAndroidKey;
import flixel.input.gamepad.FlxGamepadInputID;
import flixel.util.FlxDestroyUtil;
import flixel.util.FlxDestroyUtil.IFlxDestroyable;
#if FLX_STEAMWRAP
import steamwrap.api.Controller.EControllerActionOrigin;
#end
using flixel.util.FlxArrayUtil;
/**
* A digital action is a binary on/off event like "jump" or "fire".
* FlxActions let you attach multiple inputs to a single in-game action,
* so "jump" could be performed by a keyboard press, a mouse click,
* or a gamepad button press.
*
* @since 4.6.0
*/
class FlxActionDigital extends FlxAction
{
/**
* Function to call when this action occurs
*/
public var callback:FlxActionDigital->Void;
/**
* Create a new digital action
* @param Name name of the action
* @param Callback function to call when this action occurs
*/
public function new(?Name:String = "", ?Callback:FlxActionDigital->Void)
{
super(FlxInputType.DIGITAL, Name);
callback = Callback;
}
/**
* Add a digital input (any kind) that will trigger this action
* @param input
* @return This action
*/
public function add(input:FlxActionInputDigital):FlxActionDigital
{
addGenericInput(input);
return this;
}
/**
* Add a generic IFlxInput action input
*
* WARNING: IFlxInput objects are often member variables of some other
* object that is often destructed at the end of a state. If you don't
* destroy() this input (or the action you assign it to), the IFlxInput
* reference will persist forever even after its parent object has been
* destroyed!
*
* @param Input A generic IFlxInput object (ex: FlxButton.input)
* @param Trigger Trigger What state triggers this action (PRESSED, JUST_PRESSED, RELEASED, JUST_RELEASED)
* @return This action
*/
public function addInput(Input:IFlxInput, Trigger:FlxInputState):FlxActionDigital
{
return add(new FlxActionInputDigitalIFlxInput(Input, Trigger));
}
/**
* Add a gamepad action input for digital (button-like) events
* @param InputID "universal" gamepad input ID (A, X, DPAD_LEFT, etc)
* @param Trigger What state triggers this action (PRESSED, JUST_PRESSED, RELEASED, JUST_RELEASED)
* @param GamepadID specific gamepad ID, or FlxInputDeviceID.ALL / FIRST_ACTIVE
* @return This action
*/
public function addGamepad(InputID:FlxGamepadInputID, Trigger:FlxInputState, GamepadID:Int = FlxInputDeviceID.FIRST_ACTIVE):FlxActionDigital
{
return add(new FlxActionInputDigitalGamepad(InputID, Trigger, GamepadID));
}
/**
* Add a keyboard action input
* @param Key Key identifier (FlxKey.SPACE, FlxKey.Z, etc)
* @param Trigger What state triggers this action (PRESSED, JUST_PRESSED, RELEASED, JUST_RELEASED)
* @return This action
*/
public function addKey(Key:FlxKey, Trigger:FlxInputState):FlxActionDigital
{
return add(new FlxActionInputDigitalKeyboard(Key, Trigger));
}
/**
* Mouse button action input
* @param ButtonID Button identifier (FlxMouseButtonID.LEFT / MIDDLE / RIGHT)
* @param Trigger What state triggers this action (PRESSED, JUST_PRESSED, RELEASED, JUST_RELEASED)
* @return This action
*/
public function addMouse(ButtonID:FlxMouseButtonID, Trigger:FlxInputState):FlxActionDigital
{
return add(new FlxActionInputDigitalMouse(ButtonID, Trigger));
}
/**
* Action for mouse wheel events
* @param Positive True: respond to mouse wheel values > 0; False: respond to mouse wheel values < 0
* @param Trigger What state triggers this action (PRESSED, JUST_PRESSED, RELEASED, JUST_RELEASED)
* @return This action
*/
public function addMouseWheel(Positive:Bool, Trigger:FlxInputState):FlxActionDigital
{
return add(new FlxActionInputDigitalMouseWheel(Positive, Trigger));
}
#if android
/**
* Android buttons action inputs
* @param Key Android button key, BACK, or MENU probably (might need to set FlxG.android.preventDefaultKeys to disable the default behaviour and allow proper use!)
* @param Trigger What state triggers this action (PRESSED, JUST_PRESSED, RELEASED, JUST_RELEASED)
* @return This action
*
* @since 4.10.0
*/
public function addAndroidKey(Key:FlxAndroidKey, Trigger:FlxInputState):FlxActionDigital
{
return add(new FlxActionInputDigitalAndroid(Key, Trigger));
}
#end
override public function destroy():Void
{
callback = null;
super.destroy();
}
override public function check():Bool
{
var val = super.check();
if (val && callback != null)
{
callback(this);
}
return val;
}
}
/**
* Analog actions are events with continuous (floating-point) values, and up
* to two axes (x,y). This is for events like "move" and "accelerate" where the
* event is not simply on or off.
*
* FlxActions let you attach multiple inputs to a single in-game action,
* so "move" could be performed by a gamepad joystick, a mouse movement, etc.
*
* @since 4.6.0
*/
class FlxActionAnalog extends FlxAction
{
/**
* Function to call when this action occurs
*/
public var callback:FlxActionAnalog->Void;
/**
* X axis value, or the value of a single-axis analog input.
*/
public var x(get, never):Float;
/**
* Y axis value. (If action only has single-axis input this is always == 0)
*/
public var y(get, never):Float;
/**
* Create a new analog action
* @param Name name of the action
* @param Callback function to call when this action occurs
*/
public function new(?Name:String = "", ?Callback:FlxActionAnalog->Void)
{
super(FlxInputType.ANALOG, Name);
callback = Callback;
}
/**
* Add an analog input that will trigger this action
*/
public function add(input:FlxActionInputAnalog):FlxActionAnalog
{
addGenericInput(input);
return this;
}
/**
* Add mouse input -- same as mouse motion, but requires a particular mouse button to be PRESSED
* Very useful for e.g. panning a map or canvas around
* @param ButtonID Button identifier (FlxMouseButtonID.LEFT / MIDDLE / RIGHT)
* @param Trigger What state triggers this action (MOVED, JUST_MOVED, STOPPED, JUST_STOPPED)
* @param Axis which axes to monitor for triggering: X, Y, EITHER, or BOTH
* @param PixelsPerUnit How many pixels of movement = 1.0 in analog motion (lower: more sensitive, higher: less sensitive)
* @param DeadZone Minimum analog value before motion will be reported
* @param InvertY Invert the Y axis
* @param InvertX Invert the X axis
* @return This action
*/
public function addMouseClickAndDragMotion(ButtonID:FlxMouseButtonID, Trigger:FlxAnalogState, Axis:FlxAnalogAxis = FlxAnalogAxis.EITHER,
PixelsPerUnit:Int = 10, DeadZone:Float = 0.1, InvertY:Bool = false, InvertX:Bool = false):FlxActionAnalog
{
return add(new FlxActionInputAnalogClickAndDragMouseMotion(ButtonID, Trigger, Axis, PixelsPerUnit, DeadZone, InvertY, InvertX));
}
/**
* Add mouse input -- X/Y is the RELATIVE motion of the mouse since the last frame
* @param Trigger What state triggers this action (MOVED, JUST_MOVED, STOPPED, JUST_STOPPED)
* @param Axis which axes to monitor for triggering: X, Y, EITHER, or BOTH
* @param PixelsPerUnit How many pixels of movement = 1.0 in analog motion (lower: more sensitive, higher: less sensitive)
* @param DeadZone Minimum analog value before motion will be reported
* @param InvertY Invert the Y axis
* @param InvertX Invert the X axis
* @return This action
*/
public function addMouseMotion(Trigger:FlxAnalogState, Axis:FlxAnalogAxis = EITHER, PixelsPerUnit:Int = 10, DeadZone:Float = 0.1, InvertY:Bool = false,
InvertX:Bool = false):FlxActionAnalog
{
return add(new FlxActionInputAnalogMouseMotion(Trigger, Axis, PixelsPerUnit, DeadZone, InvertY, InvertX));
}
/**
* Add mouse input -- X/Y is the mouse's absolute screen position
* @param Trigger What state triggers this action (MOVED, JUST_MOVED, STOPPED, JUST_STOPPED)
* @param Axis which axes to monitor for triggering: X, Y, EITHER, or BOTH
* @return This action
*/
public function addMousePosition(Trigger:FlxAnalogState, Axis:FlxAnalogAxis = EITHER):FlxActionAnalog
{
return add(new FlxActionInputAnalogMousePosition(Trigger, Axis));
}
/**
* Add gamepad action input for analog (trigger, joystick, touchpad, etc) events
* @param InputID "universal" gamepad input ID (LEFT_TRIGGER, RIGHT_ANALOG_STICK, TILT_PITCH, etc)
* @param Trigger What state triggers this action (MOVED, JUST_MOVED, STOPPED, JUST_STOPPED)
* @param Axis which axes to monitor for triggering: X, Y, EITHER, or BOTH
* @param GamepadID specific gamepad ID, or FlxInputDeviceID.FIRST_ACTIVE / ALL
* @return This action
*/
public function addGamepad(InputID:FlxGamepadInputID, Trigger:FlxAnalogState, Axis:FlxAnalogAxis = EITHER,
GamepadID:Int = FlxInputDeviceID.FIRST_ACTIVE):FlxActionAnalog
{
return add(new FlxActionInputAnalogGamepad(InputID, Trigger, Axis, GamepadID));
}
override public function update():Void
{
_x = null;
_y = null;
super.update();
}
override public function destroy():Void
{
callback = null;
super.destroy();
}
override public function toString():String
{
return "FlxAction(" + type + ") name:" + name + " x/y:" + _x + "," + _y;
}
override public function check():Bool
{
var val = super.check();
if (val && callback != null)
{
callback(this);
}
return val;
}
function get_x():Float
{
return (_x != null) ? _x : 0;
}
function get_y():Float
{
return (_y != null) ? _y : 0;
}
}
/**
* @since 4.6.0
*/
@:allow(flixel.input.actions.FlxActionDigital, flixel.input.actions.FlxActionAnalog, flixel.input.actions.FlxActionSet)
class FlxAction implements IFlxDestroyable
{
/**
* Digital or Analog
*/
public var type(default, null):FlxInputType;
/**
* The name of the action, "jump", "fire", "move", etc.
*/
public var name(default, null):String;
/**
* This action's numeric handle for the Steam API (ignored if not using Steam)
*/
var steamHandle(default, null):Int = -1;
/**
* If true, this action has just been triggered
*/
public var triggered(default, null):Bool = false;
/**
* The inputs attached to this action
*/
public var inputs:Array<FlxActionInput>;
var _x:Null<Float> = null;
var _y:Null<Float> = null;
var _timestamp:Int = 0;
var _checked:Bool = false;
/**
* Whether the steam controller inputs for this action have changed since the last time origins were polled. Always false if steam isn't active
*/
public var steamOriginsChanged(default, null):Bool = false;
#if FLX_STEAMWRAP
var _steamOriginsChecksum:Int = 0;
var _steamOrigins:Array<EControllerActionOrigin>;
#end
function new(InputType:FlxInputType, Name:String)
{
type = InputType;
name = Name;
inputs = [];
#if FLX_STEAMWRAP
_steamOrigins = [];
for (i in 0...FlxSteamController.MAX_ORIGINS)
{
_steamOrigins.push(cast 0);
}
#end
}
public function getFirstSteamOrigin():Int
{
#if FLX_STEAMWRAP
if (_steamOrigins == null)
return 0;
for (i in 0..._steamOrigins.length)
{
if (_steamOrigins[i] != EControllerActionOrigin.NONE)
{
return cast _steamOrigins[i];
}
}
#end
return 0;
}
public function getSteamOrigins(?origins:Array<Int>):Array<Int>
{
#if FLX_STEAMWRAP
if (origins == null)
{
origins = [];
}
if (_steamOrigins != null)
{
for (i in 0..._steamOrigins.length)
{
origins[i] = cast _steamOrigins[i];
}
}
#end
return origins;
}
public function removeAll(Destroy:Bool = true):Void
{
var len = inputs.length;
for (i in 0...len)
{
var j = len - i - 1;
var input = inputs[j];
remove(input, Destroy);
inputs.splice(j, 1);
}
}
public function remove(Input:FlxActionInput, Destroy:Bool = false):Void
{
if (Input == null)
return;
inputs.remove(Input);
if (Destroy)
{
Input.destroy();
}
}
public function toString():String
{
return "FlxAction(" + type + ") name:" + name;
}
/**
* See if this action has just been triggered
*/
public function check():Bool
{
_x = null;
_y = null;
if (_timestamp == FlxG.game.ticks)
{
triggered = _checked;
return _checked; // run no more than once per frame
}
_timestamp = FlxG.game.ticks;
_checked = false;
var len = inputs != null ? inputs.length : 0;
for (i in 0...len)
{
var j = len - i - 1;
var input = inputs[j];
if (input.destroyed)
{
inputs.splice(j, 1);
continue;
}
input.update();
if (input.check(this))
{
_checked = true;
}
}
triggered = _checked;
return _checked;
}
/**
* Check input states & fire callbacks if anything is triggered
*/
public function update():Void
{
check();
}
public function destroy():Void
{
FlxDestroyUtil.destroyArray(inputs);
inputs = null;
#if FLX_STEAMWRAP
FlxArrayUtil.clearArray(_steamOrigins);
_steamOrigins = null;
#end
}
public function match(other:FlxAction):Bool
{
return name == other.name && steamHandle == other.steamHandle;
}
function addGenericInput(input:FlxActionInput):FlxAction
{
if (inputs == null)
{
inputs = [];
}
if (!checkExists(input))
inputs.push(input);
return this;
}
function checkExists(input:FlxActionInput):Bool
{
if (inputs == null)
return false;
return inputs.contains(input);
}
}