diff --git a/src/CameraController.hx b/src/CameraController.hx index cfcfaa39..7d6d3030 100644 --- a/src/CameraController.hx +++ b/src/CameraController.hx @@ -26,6 +26,7 @@ import h3d.Camera; import h3d.Vector; import hxsl.Types.Matrix; import h3d.scene.Scene; +import src.Gamepad; enum CameraMode { FreeOrbit; @@ -160,18 +161,18 @@ class CameraController extends Object { var lerpt = hxd.Math.min(1, 1 - Math.pow(0.6, dt * 600)); - if (Key.isDown(Settings.controlsSettings.camForward)) { - nextCameraPitch += 0.75 * 5 * dt; - } - if (Key.isDown(Settings.controlsSettings.camBackward)) { - nextCameraPitch -= 0.75 * 5 * dt; - } - if (Key.isDown(Settings.controlsSettings.camLeft)) { - nextCameraYaw -= 0.75 * 5 * dt; - } - if (Key.isDown(Settings.controlsSettings.camRight)) { - nextCameraYaw += 0.75 * 5 * dt; - } + var cameraPitchDelta = (Key.isDown(Settings.controlsSettings.camBackward) ? 1 : 0) + - (Key.isDown(Settings.controlsSettings.camForward) ? 1 : 0) + + Gamepad.getAxis(Settings.gamepadSettings.cameraYAxis); + if (Settings.gamepadSettings.invertYAxis) + cameraPitchDelta = -cameraPitchDelta; + nextCameraPitch += 0.75 * 5 * cameraPitchDelta * dt * Settings.gamepadSettings.cameraSensitivity; + var cameraYawDelta = (Key.isDown(Settings.controlsSettings.camRight) ? 1 : 0) + - (Key.isDown(Settings.controlsSettings.camLeft) ? 1 : 0) + + Gamepad.getAxis(Settings.gamepadSettings.cameraXAxis); + if (Settings.gamepadSettings.invertXAxis) + cameraYawDelta = -cameraYawDelta; + nextCameraYaw += 0.75 * 5 * cameraYawDelta * dt * Settings.gamepadSettings.cameraSensitivity; nextCameraPitch = Math.max(-Math.PI / 2 + Math.PI / 4, Math.min(Math.PI / 2 - 0.0001, nextCameraPitch)); diff --git a/src/Gamepad.hx b/src/Gamepad.hx new file mode 100644 index 00000000..43150775 --- /dev/null +++ b/src/Gamepad.hx @@ -0,0 +1,116 @@ +package src; + +import hxd.Pad; +import src.Console; +import src.Settings; + +class Gamepad { + public static var gamepad:Pad = Pad.createDummy(); + + public static function init() { + Pad.wait(onPad); + } + + public static function onPad(pad:Pad) { + Console.log("Gamepad found"); + pad.axisDeadZone = Settings.gamepadSettings.axisDeadzone; + pad.onDisconnect = function() { + Console.log("Gamepad disconnected"); + gamepad = Pad.createDummy(); + } + gamepad = pad; + } + + public static function getId(name:String) { + switch (name) { + case "start": + return gamepad.config.start; + case "ranalogY": + return gamepad.config.ranalogY; + case "ranalogX": + return gamepad.config.ranalogX; + case "ranalogClick": + return gamepad.config.ranalogClick; + case "dpadUp": + return gamepad.config.dpadUp; + case "dpadRight": + return gamepad.config.dpadRight; + case "dpadLeft": + return gamepad.config.dpadLeft; + case "dpadDown": + return gamepad.config.dpadDown; + case "back": + return gamepad.config.back; + case "analogY": + return gamepad.config.analogY; + case "analogX": + return gamepad.config.analogX; + case "analogClick": + return gamepad.config.analogClick; + case "Y": + return gamepad.config.Y; + case "X": + return gamepad.config.X; + case "RT": + return gamepad.config.RT; + case "RB": + return gamepad.config.RB; + case "LT": + return gamepad.config.LT; + case "LB": + return gamepad.config.LB; + case "B": + return gamepad.config.B; + case "A": + return gamepad.config.A; + } + return -1; + } + + public static function isDown(buttons:Array) { + for (button in buttons) { + var buttonId = getId(button); + if (buttonId < 0 || buttonId > gamepad.buttons.length) + continue; + if (gamepad.isDown(buttonId)) + return true; + } + return false; + } + + public static function isPressed(buttons:Array) { + for (button in buttons) { + var buttonId = getId(button); + if (buttonId < 0 || buttonId > gamepad.buttons.length) + continue; + if (gamepad.isPressed(buttonId)) + return true; + } + return false; + } + + public static function isReleased(buttons:Array) { + for (button in buttons) { + var buttonId = getId(button); + if (buttonId < 0 || buttonId > gamepad.buttons.length) + continue; + if (gamepad.isReleased(buttonId)) + return true; + } + return false; + } + + public static function getAxis(axis:String) { + switch (axis) { + case "analogX": + return gamepad.xAxis; + case "analogY": + return gamepad.yAxis; + case "ranalogX": + return gamepad.rxAxis; + case "ranalogY": + return gamepad.ryAxis; + } + return 0.0; + } +} diff --git a/src/Main.hx b/src/Main.hx index 9ea4e95f..ca62dbc7 100644 --- a/src/Main.hx +++ b/src/Main.hx @@ -18,6 +18,7 @@ import hxd.res.DefaultFont; import h2d.Text; import h3d.Vector; import src.ProfilerUI; +import src.Gamepad; class Main extends hxd.App { var marbleGame:MarbleGame; @@ -67,6 +68,7 @@ class Main extends hxd.App { try { Settings.init(); + Gamepad.init(); ResourceLoader.init(s2d, () -> { AudioManager.init(); AudioManager.playShell(); diff --git a/src/Marble.hx b/src/Marble.hx index 6141779d..6fa9500c 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -61,6 +61,7 @@ import collision.CCDCollision.TraceInfo; import src.ResourceLoaderWorker; import src.InteriorObject; import src.Console; +import src.Gamepad; class Move { public var d:Vector; @@ -1686,6 +1687,8 @@ class Marble extends GameObject { var move = new Move(); move.d = new Vector(); if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching) { + move.d.x = Gamepad.getAxis(Settings.gamepadSettings.moveYAxis); + move.d.y = -Gamepad.getAxis(Settings.gamepadSettings.moveXAxis); if (Key.isDown(Settings.controlsSettings.forward)) { move.d.x -= 1; } @@ -1698,10 +1701,14 @@ class Marble extends GameObject { if (Key.isDown(Settings.controlsSettings.right)) { move.d.y -= 1; } - if (Key.isDown(Settings.controlsSettings.jump) || MarbleGame.instance.touchInput.jumpButton.pressed) { + if (Key.isDown(Settings.controlsSettings.jump) + || MarbleGame.instance.touchInput.jumpButton.pressed + || Gamepad.isDown(Settings.gamepadSettings.jump)) { move.jump = true; } - if (Util.isTouchDevice() ? MarbleGame.instance.touchInput.powerupButton.pressed : Key.isDown(Settings.controlsSettings.powerup)) { + if (Key.isDown(Settings.controlsSettings.powerup) + || (Util.isTouchDevice() && MarbleGame.instance.touchInput.powerupButton.pressed) + || Gamepad.isDown(Settings.gamepadSettings.powerup)) { move.powerup = true; } if (MarbleGame.instance.touchInput.movementInput.pressed) { diff --git a/src/MarbleGame.hx b/src/MarbleGame.hx index 15d118f8..6dec62c9 100644 --- a/src/MarbleGame.hx +++ b/src/MarbleGame.hx @@ -25,6 +25,7 @@ import src.ProfilerUI; import src.Settings; import src.Console; import src.Debug; +import src.Gamepad; @:publicFields class MarbleGame { @@ -182,17 +183,10 @@ class MarbleGame { if (!paused) { world.update(dt * Debug.timeScale); } - if (Key.isPressed(Key.ESCAPE) && world.finishTime == null && world._ready) { - #if hl + if (((Key.isPressed(Key.ESCAPE) #if js && paused #end) || Gamepad.isPressed(["start"])) + && world.finishTime == null && world._ready) { paused = !paused; handlePauseGame(); - #end - #if js - if (paused) { - paused = false; - } - handlePauseGame(); - #end } } if (canvas != null) { diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 2ef1b552..ffc469e6 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -94,6 +94,7 @@ import src.ProfilerUI; import src.ResourceLoaderWorker; import haxe.io.Path; import src.Console; +import src.Gamepad; class MarbleWorld extends Scheduler { public var collisionWorld:CollisionWorld; @@ -1183,12 +1184,15 @@ class MarbleWorld extends Scheduler { ProfilerUI.measure("updateTimer"); this.updateTimer(dt); - if ((Key.isPressed(Settings.controlsSettings.respawn)) && this.finishTime == null) { + if ((Key.isPressed(Settings.controlsSettings.respawn) || Gamepad.isPressed(Settings.gamepadSettings.respawn)) + && this.finishTime == null) { performRestart(); return; } - if ((Key.isDown(Settings.controlsSettings.respawn) || MarbleGame.instance.touchInput.restartButton.pressed) + if ((Key.isDown(Settings.controlsSettings.respawn) + || MarbleGame.instance.touchInput.restartButton.pressed + || Gamepad.isDown(Settings.gamepadSettings.respawn)) && !this.isWatching && this.finishTime == null) { if (timeState.timeSinceLoad - this.respawnPressedTime > 1.5) { @@ -1202,6 +1206,7 @@ class MarbleWorld extends Scheduler { if (Key.isPressed(Settings.controlsSettings.blast) || (MarbleGame.instance.touchInput.blastbutton.pressed) + || Gamepad.isPressed(Settings.gamepadSettings.blast) && !this.isWatching && this.game == "ultra") { this.marble.useBlast(); @@ -1245,7 +1250,10 @@ class MarbleWorld extends Scheduler { ProfilerUI.measure("updateAudio"); AudioManager.update(this.scene); - if (this.outOfBounds && this.finishTime == null && Key.isDown(Settings.controlsSettings.powerup) && !this.isWatching) { + if (this.outOfBounds + && this.finishTime == null + && (Key.isDown(Settings.controlsSettings.powerup) || Gamepad.isDown(Settings.gamepadSettings.powerup)) + && !this.isWatching) { this.restart(); return; } diff --git a/src/Settings.hx b/src/Settings.hx index 4e68c950..fcbd2e45 100644 --- a/src/Settings.hx +++ b/src/Settings.hx @@ -75,6 +75,21 @@ typedef TouchSettings = { var buttonJoystickMultiplier:Float; } +typedef GamepadSettings = { + var moveXAxis:String; + var moveYAxis:String; + var cameraXAxis:String; + var cameraYAxis:String; + var jump:Array; + var powerup:Array; + var cameraSensitivity:Float; + var invertXAxis:Bool; + var invertYAxis:Bool; + var axisDeadzone:Float; + var respawn:Array; + var blast:Array; +} + typedef PlayStatistics = { var oobs:Int; var respawns:Int; @@ -141,6 +156,21 @@ class Settings { buttonJoystickMultiplier: 2.5 } + public static var gamepadSettings:GamepadSettings = { + moveXAxis: "analogX", + moveYAxis: "analogY", + cameraXAxis: "ranalogX", + cameraYAxis: "ranalogY", + jump: ["A", "LT"], + powerup: ["B", "RT"], + cameraSensitivity: 1.0, + invertXAxis: false, + invertYAxis: false, + axisDeadzone: 0.15, + respawn: ["back"], + blast: ["X", "LB", "RB"] + } + public static var playStatistics:PlayStatistics = { oobs: 0, respawns: 0, @@ -200,6 +230,7 @@ class Settings { options: optionsSettings, controls: controlsSettings, touch: touchSettings, + gamepad: gamepadSettings, stats: playStatistics, highscoreName: highscoreName, marbleIndex: optionsSettings.marbleIndex, @@ -310,6 +341,9 @@ class Settings { touchSettings.blastButtonPos = [300, 240]; touchSettings.blastButtonSize = 60; } + if (json.gamepad != null) { + gamepadSettings = json.gamepad; + } if (json.stats != null) { playStatistics = json.stats; } diff --git a/src/gui/EndGameGui.hx b/src/gui/EndGameGui.hx index 67591c51..e5b5bdf3 100644 --- a/src/gui/EndGameGui.hx +++ b/src/gui/EndGameGui.hx @@ -45,6 +45,7 @@ class EndGameGui extends GuiControl { continueButton.position = new Vector(460, 307); continueButton.extent = new Vector(104, 54); continueButton.accelerator = hxd.Key.ENTER; + continueButton.gamepadAccelerator = ["A"]; continueButton.pressedAction = (e) -> continueFunc(continueButton); var restartButton = new GuiButton(loadButtonImages("data/ui/endgame/replay")); @@ -52,6 +53,7 @@ class EndGameGui extends GuiControl { restartButton.vertSizing = Bottom; restartButton.position = new Vector(460, 363); restartButton.extent = new Vector(104, 54); + restartButton.gamepadAccelerator = ["B"]; restartButton.pressedAction = (e) -> restartFunc(restartButton); var nextLevel = new GuiControl(); @@ -77,6 +79,7 @@ class EndGameGui extends GuiControl { nextLevelBtn.vertSizing = Height; nextLevelBtn.position = new Vector(0, 0); nextLevelBtn.extent = new Vector(130, 110); + nextLevelBtn.gamepadAccelerator = ["X"]; nextLevelBtn.pressedAction = (e) -> nextLevelFunc(nextLevelBtn); nextLevel.addChild(nextLevelBtn); diff --git a/src/gui/EnterNameDlg.hx b/src/gui/EnterNameDlg.hx index 48a6326c..d82836c1 100644 --- a/src/gui/EnterNameDlg.hx +++ b/src/gui/EnterNameDlg.hx @@ -70,6 +70,7 @@ class EnterNameDlg extends GuiControl { okbutton.position = new Vector(151, 184); okbutton.extent = new Vector(110, 55); okbutton.accelerator = hxd.Key.ENTER; + okbutton.gamepadAccelerator = ["A"]; okbutton.pressedAction = (sender) -> { MarbleGame.canvas.popDialog(this); Settings.highscoreName = enterNameEdit.text.text; diff --git a/src/gui/ExitGameDlg.hx b/src/gui/ExitGameDlg.hx index 06226e71..0488bd35 100644 --- a/src/gui/ExitGameDlg.hx +++ b/src/gui/ExitGameDlg.hx @@ -41,6 +41,7 @@ class ExitGameDlg extends GuiControl { yesButton.vertSizing = Top; yesButton.horizSizing = Right; yesButton.accelerator = hxd.Key.ENTER; + yesButton.gamepadAccelerator = ["A"]; yesButton.pressedAction = (sender) -> yesFunc(yesButton); var noButton = new GuiButton(loadButtonImages("data/ui/common/no")); @@ -48,6 +49,7 @@ class ExitGameDlg extends GuiControl { noButton.extent = new Vector(86, 40); noButton.vertSizing = Top; noButton.horizSizing = Right; + noButton.gamepadAccelerator = ["B"]; noButton.pressedAction = (sender) -> noFunc(noButton); var restartButton = new GuiButton(loadButtonImages("data/ui/common/restart")); @@ -55,6 +57,7 @@ class ExitGameDlg extends GuiControl { restartButton.extent = new Vector(86, 40); restartButton.vertSizing = Top; restartButton.horizSizing = Right; + restartButton.gamepadAccelerator = ["X"]; restartButton.pressedAction = (sender) -> restartFunc(restartButton); dialogImg.addChild(overlay); diff --git a/src/gui/GuiButton.hx b/src/gui/GuiButton.hx index 6a092db4..4d838aaf 100644 --- a/src/gui/GuiButton.hx +++ b/src/gui/GuiButton.hx @@ -6,6 +6,7 @@ import gui.GuiControl.MouseState; import hxd.Window; import h2d.Tile; import src.ResourceLoader; +import src.Gamepad; enum ButtonType { Normal; @@ -28,6 +29,8 @@ class GuiButton extends GuiAnim { public var buttonSounds:Bool = true; public var accelerator:Int = 0; + public var gamepadAccelerator:Array = []; + public var acceleratorWasPressed = false; public function new(anim:Array) { super(anim); @@ -69,11 +72,20 @@ class GuiButton extends GuiAnim { } } } - if (!disabled && accelerator != 0 && hxd.Key.isReleased(accelerator)) { - if (this.pressedAction != null) { - this.pressedAction(new GuiEvent(this)); + if (!disabled) { + if (acceleratorWasPressed && + (accelerator != 0 && hxd.Key.isReleased(accelerator)) || Gamepad.isReleased(gamepadAccelerator)) { + if (this.pressedAction != null) { + this.pressedAction(new GuiEvent(this)); + } + } else if ((accelerator != 0 && hxd.Key.isPressed(accelerator)) || Gamepad.isPressed(gamepadAccelerator)) { + acceleratorWasPressed = true; } } + if (acceleratorWasPressed) { + if ((accelerator != 0 && hxd.Key.isReleased(accelerator)) || Gamepad.isReleased(gamepadAccelerator)) + acceleratorWasPressed = false; + } super.update(dt, mouseState); } diff --git a/src/gui/HelpCreditsGui.hx b/src/gui/HelpCreditsGui.hx index 0d8833a1..277f346f 100644 --- a/src/gui/HelpCreditsGui.hx +++ b/src/gui/HelpCreditsGui.hx @@ -48,6 +48,7 @@ class HelpCreditsGui extends GuiImage { homeButton.position = new Vector(274, 385); homeButton.extent = new Vector(94, 46); homeButton.accelerator = hxd.Key.ESCAPE; + homeButton.gamepadAccelerator = ["B"]; homeButton.pressedAction = (sender) -> { MarbleGame.canvas.setContent(new MainMenuGui()); } diff --git a/src/gui/MainMenuGui.hx b/src/gui/MainMenuGui.hx index f45dca53..3cac97fa 100644 --- a/src/gui/MainMenuGui.hx +++ b/src/gui/MainMenuGui.hx @@ -62,6 +62,7 @@ class MainMenuGui extends GuiImage { var playButton = new GuiButton(loadButtonImages("data/ui/menu/play")); playButton.position = new Vector(-5, -2); playButton.extent = new Vector(247, 164); + playButton.gamepadAccelerator = ["A"]; playButton.pressedAction = (sender) -> { cast(this.parent, Canvas).setContent(new PlayMissionGui()); } diff --git a/src/gui/MessageBoxOkDlg.hx b/src/gui/MessageBoxOkDlg.hx index 7b50a2da..69217aaa 100644 --- a/src/gui/MessageBoxOkDlg.hx +++ b/src/gui/MessageBoxOkDlg.hx @@ -47,6 +47,7 @@ class MessageBoxOkDlg extends GuiControl { okButton.extent = new Vector(88, 41); okButton.vertSizing = Top; okButton.accelerator = hxd.Key.ENTER; + okButton.gamepadAccelerator = ["A"]; okButton.pressedAction = (sender) -> { MarbleGame.canvas.popDialog(this); } diff --git a/src/gui/MessageBoxYesNoDlg.hx b/src/gui/MessageBoxYesNoDlg.hx index 7472146f..f85e98c0 100644 --- a/src/gui/MessageBoxYesNoDlg.hx +++ b/src/gui/MessageBoxYesNoDlg.hx @@ -47,6 +47,7 @@ class MessageBoxYesNoDlg extends GuiControl { yesButton.extent = new Vector(82, 35); yesButton.vertSizing = Top; yesButton.accelerator = hxd.Key.ENTER; + yesButton.gamepadAccelerator = ["A"]; yesButton.pressedAction = (sender) -> { MarbleGame.canvas.popDialog(this); yesFunc(); @@ -58,6 +59,7 @@ class MessageBoxYesNoDlg extends GuiControl { noButton.extent = new Vector(75, 35); noButton.vertSizing = Top; noButton.accelerator = hxd.Key.ESCAPE; + noButton.gamepadAccelerator = ["B"]; noButton.pressedAction = (sender) -> { MarbleGame.canvas.popDialog(this); noFunc(); diff --git a/src/gui/PlayMissionGui.hx b/src/gui/PlayMissionGui.hx index 1bee2026..177c091f 100644 --- a/src/gui/PlayMissionGui.hx +++ b/src/gui/PlayMissionGui.hx @@ -176,6 +176,7 @@ class PlayMissionGui extends GuiImage { pmMenuButton.position = new Vector(119, 325); pmMenuButton.extent = new Vector(92, 43); pmMenuButton.accelerator = hxd.Key.ESCAPE; + pmMenuButton.gamepadAccelerator = ["B"]; pmMenuButton.pressedAction = (sender) -> { cast(this.parent, Canvas).setContent(new MainMenuGui()); }; @@ -202,6 +203,7 @@ class PlayMissionGui extends GuiImage { var pmPrev = new GuiButton(loadButtonImages("data/ui/play/prev")); pmPrev.position = new Vector(436, 325); pmPrev.extent = new Vector(72, 43); + pmPrev.gamepadAccelerator = ["dpadLeft"]; pmPrev.pressedAction = (sender) -> { setSelectedFunc(currentSelection - 1); } @@ -210,6 +212,7 @@ class PlayMissionGui extends GuiImage { var pmPlay = new GuiButton(loadButtonImages("data/ui/play/play")); pmPlay.position = new Vector(510, 325); pmPlay.extent = new Vector(92, 43); + pmPlay.gamepadAccelerator = ["A"]; pmPlay.pressedAction = (sender) -> { // Wacky hacks currentList[currentSelection].index = currentSelection; @@ -223,6 +226,7 @@ class PlayMissionGui extends GuiImage { var pmNext = new GuiButton(loadButtonImages("data/ui/play/next")); pmNext.position = new Vector(604, 325); pmNext.extent = new Vector(72, 43); + pmNext.gamepadAccelerator = ["dpadRight"]; pmNext.pressedAction = (sender) -> { setSelectedFunc(currentSelection + 1); } diff --git a/src/gui/ReplayNameDlg.hx b/src/gui/ReplayNameDlg.hx index 84beea3b..15ef02bb 100644 --- a/src/gui/ReplayNameDlg.hx +++ b/src/gui/ReplayNameDlg.hx @@ -71,6 +71,7 @@ class ReplayNameDlg extends GuiControl { yesButton.extent = new Vector(95, 45); yesButton.vertSizing = Top; yesButton.accelerator = hxd.Key.ENTER; + yesButton.gamepadAccelerator = ["A"]; yesButton.pressedAction = (sender) -> { if (StringTools.trim(textInput.text.text) != "") { MarbleGame.instance.recordingName = textInput.text.text; @@ -86,6 +87,7 @@ class ReplayNameDlg extends GuiControl { noButton.extent = new Vector(88, 41); noButton.vertSizing = Top; noButton.accelerator = hxd.Key.ESCAPE; + noButton.gamepadAccelerator = ["B"]; noButton.pressedAction = (sender) -> { MarbleGame.canvas.popDialog(this); callback();