diff --git a/flixel/input/gamepad/FlxGamepad.hx b/flixel/input/gamepad/FlxGamepad.hx index a6dfdd2726..62c342b732 100644 --- a/flixel/input/gamepad/FlxGamepad.hx +++ b/flixel/input/gamepad/FlxGamepad.hx @@ -1,5 +1,8 @@ package flixel.input.gamepad; +import flixel.system.replay.IntegerFloatPair; +import flixel.system.replay.GamepadRecord; +import flixel.system.replay.CodeValuePair; import flixel.input.FlxInput.FlxInputState; import flixel.input.gamepad.FlxGamepadMappedInput; import flixel.input.gamepad.lists.FlxGamepadAnalogList; @@ -120,6 +123,11 @@ class FlxGamepad implements IFlxDestroyable */ public var pointer(default, null):FlxGamepadPointerValueList; + /** + * Returns axis values from the `axis` list, as if gamepads were turned off. + **/ + public var recording:Bool = false; + #if FLX_JOYSTICK_API public var hat(default, null):FlxPoint = FlxPoint.get(); public var ball(default, null):FlxPoint = FlxPoint.get(); @@ -702,6 +710,15 @@ class FlxGamepad implements IFlxDestroyable function getAxisValue(AxisID:Int):Float { + if (recording) + { + if (AxisID < 0 || AxisID >= axis.length) + { + return 0; + } + return axis[AxisID]; + } + var axisValue:Float = 0; #if FLX_GAMEINPUT_API @@ -722,6 +739,7 @@ class FlxGamepad implements IFlxDestroyable axisValue = axis[AxisID]; #end + if (isAxisForAnalogStick(AxisID)) { axisValue = applyAxisFlip(axisValue, AxisID); @@ -908,6 +926,76 @@ class FlxGamepad implements IFlxDestroyable LabelValuePair.weak("deadZone", deadZone) ]); } + /** + * If any buttons are not "released", + * this function will return an array indicating + * which buttons are pressed and what state they are in. + * + * @return An array of button state data. Null if there is no data. + */ + @:allow(flixel.system.replay.FlxReplay) + function record():GamepadRecord + { + var data:Array<CodeValuePair> = null; + + for (button in buttons) + { + if (button == null || button.released) + { + continue; + } + + if (data == null) + { + data = new Array<CodeValuePair>(); + } + + data.push(new CodeValuePair(button.ID, button.current)); + } + + var analogData:Array<IntegerFloatPair> = new Array<IntegerFloatPair>(); + + for (i in 0...axis.length) + { + var axisValue = getAxisValue(i); + if (axisValue != 0) + analogData.push(new IntegerFloatPair(i, axisValue)); + + } + + return new GamepadRecord(id, data, analogData); + } + + /** + * Part of the gamepad recording system. + * Takes data about analog and buttons and sets it into array. + * + * @param Record Array of data about key states. + */ + @:allow(flixel.system.replay.FlxReplay) + function playback(record:GamepadRecord):Void + { + var i = 0; + final len = record.buttons.length; + + while (i < len) + { + final keyRecord = record.buttons[i++]; + final id = getButton(keyRecord.code); + id.current = keyRecord.value; + } + i = 0; + final len = record.analog.length; + for (i in 0...axis.length) + { + axis[i] = 0; + } + while (i < len) + { + final keyRecord = record.analog[i++]; + axis[keyRecord.code] = keyRecord.value; + } + } } enum FlxGamepadDeadZoneMode diff --git a/flixel/input/gamepad/FlxGamepadManager.hx b/flixel/input/gamepad/FlxGamepadManager.hx index 0477304e5c..b73088aa54 100644 --- a/flixel/input/gamepad/FlxGamepadManager.hx +++ b/flixel/input/gamepad/FlxGamepadManager.hx @@ -371,6 +371,12 @@ class FlxGamepadManager implements IFlxInputManager return -1; } + public function addPlaybackGamepad(gamepad:FlxGamepad) + { + _gamepads[gamepad.id] = gamepad; + _activeGamepads.push(gamepad); + } + function addGamepad(Device:GameInputDevice):Void { if (Device == null) diff --git a/flixel/system/replay/FlxReplay.hx b/flixel/system/replay/FlxReplay.hx index 74d0f5ba5d..3ada38f233 100644 --- a/flixel/system/replay/FlxReplay.hx +++ b/flixel/system/replay/FlxReplay.hx @@ -1,5 +1,6 @@ package flixel.system.replay; +import flixel.input.gamepad.FlxGamepad; import flixel.FlxG; import flixel.util.FlxArrayUtil; @@ -165,6 +166,19 @@ class FlxReplay continueFrame = false; #end + #if FLX_GAMEPAD + var gamepadRecords:Array<GamepadRecord> = new Array(); + for (gamepad in FlxG.gamepads.getActiveGamepads()) + { + var gamepadRecord:GamepadRecord = gamepad.record(); + if (gamepadRecord != null) + { + gamepadRecords.push(gamepadRecord); + continueFrame = false; + } + } + #end + if (continueFrame) { frame++; @@ -178,6 +192,9 @@ class FlxReplay #if FLX_KEYBOARD frameRecorded.keys = keysRecord; #end + #if FLX_GAMEPAD + frameRecorded.gamepad = gamepadRecords; + #end _frames[frameCount++] = frameRecorded; @@ -188,6 +205,8 @@ class FlxReplay } } + var fakeGamepads:Map<Int, FlxGamepad> = []; + /** * Get the current frame record data and load it into the input managers. */ @@ -220,6 +239,28 @@ class FlxReplay FlxG.mouse.playback(fr.mouse); } #end + #if FLX_GAMEPAD + if (fr.gamepad != null) + { + for (record in fr.gamepad) + { + var gamepad = null; + if (fakeGamepads.exists(record.gamepadID)) + { + gamepad = fakeGamepads.get(record.gamepadID); + } + else + { + gamepad = new FlxGamepad(-record.gamepadID, FlxG.gamepads, FlxGamepadModel.UNKNOWN, null); + FlxG.gamepads.addPlaybackGamepad(gamepad); + gamepad.recording = true; + fakeGamepads.set(record.gamepadID, gamepad); + } + + gamepad.playback(record); + } + } + #end } /** diff --git a/flixel/system/replay/FrameRecord.hx b/flixel/system/replay/FrameRecord.hx index 438e62ce01..5035eeafe5 100644 --- a/flixel/system/replay/FrameRecord.hx +++ b/flixel/system/replay/FrameRecord.hx @@ -20,6 +20,11 @@ class FrameRecord */ public var mouse:MouseRecord; + /** + * An array containing all the gamepad inputs. + */ + public var gamepad:Array<GamepadRecord>; + /** * Instantiate array new frame record. */ @@ -28,6 +33,7 @@ class FrameRecord frame = 0; keys = null; mouse = null; + gamepad = null; } /** @@ -37,11 +43,12 @@ class FrameRecord * @param Mouse Mouse data from the mouse manager. * @return A reference to this FrameRecord object. */ - public function create(Frame:Float, ?Keys:Array<CodeValuePair>, ?Mouse:MouseRecord):FrameRecord + public function create(Frame:Float, ?Keys:Array<CodeValuePair>, ?Mouse:MouseRecord, ?Gamepad:Array<GamepadRecord>):FrameRecord { frame = Math.floor(Frame); keys = Keys; mouse = Mouse; + gamepad = Gamepad; return this; } @@ -53,6 +60,7 @@ class FrameRecord { keys = null; mouse = null; + gamepad = null; } /** @@ -85,6 +93,44 @@ class FrameRecord output += mouse.x + "," + mouse.y + "," + mouse.button + "," + mouse.wheel; } + if (gamepad != null) + { + for (record in gamepad) + { + output += "g"; + if (record != null) + { + output += record.gamepadID + ","; + var object:CodeValuePair; + var i:Int = 0; + var l:Int = record.buttons.length; + while (i < l) + { + if (i > 0) + { + output += ","; + } + object = record.buttons[i++]; + output += object.code + ":" + object.value; + } + output += "/"; + var object:IntegerFloatPair; + var i:Int = 0; + var l:Int = record.analog.length; + while (i < l) + { + if (i > 0) + { + output += ","; + } + object = record.analog[i++]; + output += object.code + ":" + object.value; + } + } + } + } + + return output; } @@ -103,8 +149,10 @@ class FrameRecord // split up keyboard and mouse data array = array[1].split("m"); + var gamepadArray:Array<String> = array[1].split("g"); + var keyData:String = array[0]; - var mouseData:String = array[1]; + var mouseData:String = gamepadArray.splice(0, 1)[0]; // parse keyboard data if (keyData.length > 0) @@ -140,6 +188,63 @@ class FrameRecord } } + if (gamepadArray.length > 0) + { + for (gamepadString in gamepadArray) + { + array = gamepadString.split(","); + var currentGamepad:GamepadRecord = new GamepadRecord(Std.parseInt(array[0]), [], []); + + var inputsArray:Array<String> = gamepadString.substring(array[0].length + 1).split("/"); + var buttonsArray:Array<String> = inputsArray[0].split(","); + var analogArray:Array<String> = inputsArray[1].split(","); + + // go through each data pair and enter it into this frame's button state + var buttonPair:Array<String>; + i = 0; + l = inputsArray[0].length; + while (i < l) + { + var pairString = buttonsArray[i++]; + if (pairString != null) + { + buttonPair = pairString.split(":"); + if (buttonPair.length == 2) + { + if (gamepad == null) + { + gamepad = new Array<GamepadRecord>(); + } + currentGamepad.buttons.push(new CodeValuePair(Std.parseInt(buttonPair[0]), Std.parseInt(buttonPair[1]))); + } + } + } + + // go through each data pair and enter it into this frame's analog state + var analogPair:Array<String>; + i = 0; + l = inputsArray[0].length; + while (i < l) + { + var pairString = analogArray[i++]; + if (pairString != null) + { + analogPair = pairString.split(":"); + if (analogPair.length == 2) + { + if (gamepad == null) + { + gamepad = new Array<GamepadRecord>(); + } + currentGamepad.analog.push(new IntegerFloatPair(Std.parseInt(analogPair[0]), Std.parseFloat(analogPair[1]))); + } + } + } + + this.gamepad.push(currentGamepad); + } + } + return this; } } diff --git a/flixel/system/replay/GamepadRecord.hx b/flixel/system/replay/GamepadRecord.hx new file mode 100644 index 0000000000..18578445da --- /dev/null +++ b/flixel/system/replay/GamepadRecord.hx @@ -0,0 +1,36 @@ +package flixel.system.replay; + +import flixel.input.FlxInput.FlxInputState; + +/** + * A helper class for the frame records, part of the replay/demo/recording system. + */ +class GamepadRecord +{ + /** + * The ID of the gamepad being recorded. + */ + public var gamepadID(default, null):Int; + + /** + * An array referring to digital gamepad buttons and it's state. + */ + public var buttons(default, null):Array<CodeValuePair>; + /** + * An array referring to analog gamepad inputs and their state. + */ + public var analog(default, null):Array<IntegerFloatPair>; + + /** + * Instantiate a new gamepad input record. + * + * @param GamepadID The ID of the gamepad being recorded + * @param Buttons An array referring to digital gamepad buttons and it's state. + */ + public function new(gamepadID:Int, buttons:Array<CodeValuePair>, analog:Array<IntegerFloatPair>) + { + this.gamepadID = gamepadID; + this.buttons = buttons; + this.analog = analog; + } +} diff --git a/flixel/system/replay/IntegerFloatPair.hx b/flixel/system/replay/IntegerFloatPair.hx new file mode 100644 index 0000000000..6b35c97c24 --- /dev/null +++ b/flixel/system/replay/IntegerFloatPair.hx @@ -0,0 +1,13 @@ +package flixel.system.replay; + +class IntegerFloatPair +{ + public var code:Int; + public var value:Float; + + public function new(code:Int, value:Float) + { + this.code = code; + this.value = value; + } +}