Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rudimentary Gamepad Recording and Loading Support for FlxReplay #3330

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions flixel/input/gamepad/FlxGamepad.hx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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];
}

Comment on lines +713 to +721
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you talk about why this is needed?

Copy link
Author

@agmass agmass Jan 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm editing the axis list upon playback (i'm not entirely sure if there's a better way to edit the axises of a gamepad, that's just what i was able to find) but if i don't specify the gamepad has to find it's axis from there, the gamepad will pull it directly from the input device. Along with this, the recording could double-up the axis flip as getAxisValue() flips it once during playback and could flip it again if called raw when recording. I wasn't sure if making a new variable inside FlxGamepad or making a new class overriding this was the better choice

var axisValue:Float = 0;

#if FLX_GAMEINPUT_API
Expand All @@ -722,6 +739,7 @@ class FlxGamepad implements IFlxDestroyable
axisValue = axis[AxisID];
#end


if (isAxisForAnalogStick(AxisID))
{
axisValue = applyAxisFlip(axisValue, AxisID);
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions flixel/input/gamepad/FlxGamepadManager.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
41 changes: 41 additions & 0 deletions flixel/system/replay/FlxReplay.hx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package flixel.system.replay;

import flixel.input.gamepad.FlxGamepad;
import flixel.FlxG;
import flixel.util.FlxArrayUtil;

Expand Down Expand Up @@ -165,6 +166,19 @@ class FlxReplay
continueFrame = false;
#end

#if FLX_GAMEINPUT_API
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
Geokureli marked this conversation as resolved.
Show resolved Hide resolved

if (continueFrame)
{
frame++;
Expand All @@ -178,6 +192,9 @@ class FlxReplay
#if FLX_KEYBOARD
frameRecorded.keys = keysRecord;
#end
#if FLX_GAMEINPUT_API
Geokureli marked this conversation as resolved.
Show resolved Hide resolved
frameRecorded.gamepad = gamepadRecords;
#end

_frames[frameCount++] = frameRecorded;

Expand All @@ -188,6 +205,8 @@ class FlxReplay
}
}

var fakeGamepads:Map<Int, FlxGamepad> = [];

/**
* Get the current frame record data and load it into the input managers.
*/
Expand Down Expand Up @@ -220,6 +239,28 @@ class FlxReplay
FlxG.mouse.playback(fr.mouse);
}
#end
#if FLX_GAMEINPUT_API
Geokureli marked this conversation as resolved.
Show resolved Hide resolved
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
}

/**
Expand Down
109 changes: 107 additions & 2 deletions flixel/system/replay/FrameRecord.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -28,6 +33,7 @@ class FrameRecord
frame = 0;
keys = null;
mouse = null;
gamepad = null;
}

/**
Expand All @@ -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;
}
Expand All @@ -53,6 +60,7 @@ class FrameRecord
{
keys = null;
mouse = null;
gamepad = null;
}

/**
Expand Down Expand Up @@ -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;
}

Expand All @@ -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)
Expand Down Expand Up @@ -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;
}
}
Loading
Loading