2013 年 8 月 27 日
教程:游戏控制器入门
现在是移动应用开发者激动人心的时代。借助移动设备和蓝牙支持的神奇功能,现在可以使用 HID(人机接口设备)控制器来玩游戏,除了标准的触摸屏和加速计之外。虽然这还不是“乌托邦”,但您可以使用无线设备和现代技术做一些非常酷的事情。
按键事件框架
对于 HID 控制器,我们决定基于现有的 按键事件框架构建。因此,控制器的编程不会有太大的不同——HID 兼容的控制器不需要插件,也不需要 require()
任何特殊的东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
local function onKeyEvent( event ) local phase = event.phase local keyName = event.keyName if ( phase == "down" ) then if ( keyName == "buttonA" ) then return true elseif ( keyName == "buttonB" ) then return true elseif ( keyName == "buttonX" ) then return true elseif ( keyName == "buttonY" ) then return true elseif ( keyName == "up" ) then -- 方向键向上 return true elseif ( keyName == "down" ) then -- 方向键向下 return true elseif ( keyName == "left" ) then -- 方向键向左 return true elseif ( keyName == "right" ) then -- 方向键向右 return true elseif ( keyName == "buttonSelect" ) then -- 选择/返回按钮 return true elseif ( keyName == "buttonStart" ) then -- 开始/主页按钮 return true elseif ( keyName == "buttonMode" ) then -- 电源开/关按钮 return true elseif ( keyName == "leftShoulderButton1" ) then -- 左上按钮 return true elseif ( keyName == "rightShoulderButton1" ) then -- 右上按钮 return true elseif ( keyName == "leftShoulderButton2" ) then -- 左下按钮 return true elseif ( keyName == "rightShoulderButton2" ) then -- 右下按钮 return true elseif ( keyName == "leftJoyStickButton" ) then -- 按下左摇杆按钮 return true elseif ( keyName == "rightJoystickButton" ) then -- 按下右摇杆按钮 return true -- 现在处理 Android 标准按钮 elseif ( keyName == "back" ) then -- 硬件返回按钮 return true elseif ( keyName == "volumeUp" ) then return true elseif ( keyName == "volumeDown" ) then return true end print( "Done with keys" ) end -- 重要!返回 false 表示此应用不覆盖接收到的按键 -- 这允许操作系统执行其默认的按键处理 return false end Runtime:addEventListener( "key", onKeyEvent ) |
当然,除了返回 true
之外,我们没有提供任何代码,因为它完全取决于您想要做什么。按钮名称也可能因控制器而异——例如,它们可能并不总是被称为 "buttonA"
或 "buttonX"
,因此,如果您正在使用新的控制器,最好检查按下的按钮的值。
按钮阶段
请注意,按钮/键会产生两个阶段,很像触摸处理程序。这两个阶段是 "up"
和 "down"
,其中 "down"
表示用户已按下按钮,而 "up"
表示他们已释放按钮。与触摸处理程序类似,您不会“连续”获取事件——一旦您按下按钮并获取 "down"
阶段,在按钮释放之前您不会收到另一个事件,从而触发 "up"
阶段。
与触摸事件类似,通常最好使用 "down"
状态来执行时间敏感的操作,例如移动或发射武器。但是,对于通常像鼠标点击一样响应的 UI 元素,或者在触摸完成时触发的触摸事件,可能需要 "up"
状态。
整合
让我们看一个使用基于事件的按钮的 movePlayer()
函数的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
local player1 = display.newRect( 0, 0, 10, 30 ) player1.x = 30 player1.y = display.contentCenterY player1:setFillColor( 1, 0, 0 ) player1.isMoving = false player1.direction = "up" local player2 = display.newRect( 0, 0, 10, 30 ) player2.x = display.contentWidth-30 player2.y = display.contentCenterY player2:setFillColor( 0, 0, 1 ) player2.isMoving = false player2.direction = "up" local moveSpeed = 2 local function movePlayer( player, event ) if ( player == nil ) then return end if ( player.isMoving ) then if ( player.direction == "down" ) then player.y = player.y + moveSpeed if ( player.y > (display.contentHeight-20) ) then player.y = display.contentHeight-20 end else player.y = player.y - moveSpeed if ( player.y < 20 ) then player.y = 20 end end end end local function onKeyEvent( event ) local thisPlayer if ( event.device.descriptor == "Joystick 1" ) then thisPlayer = player1 else thisPlayer = player2 end if ( event.keyName == "up" ) then thisPlayer.direction = "up" if ( event.phase == "down" ) then thisPlayer.isMoving = true else thisPlayer.isMoving = false end return true elseif ( event.keyName == "down" ) then thisPlayer.direction = "down" if ( event.phase == "down" ) then thisPlayer.isMoving = true else thisPlayer.isMoving = false end return true end return false end Runtime:addEventListener( "key", onKeyEvent ) local function onFrame() movePlayer( player1 ) movePlayer( player2 ) end Runtime:addEventListener( "enterFrame", onFrame ) |
检测控制器
大多数情况下,你还需要知道控制器是否已连接,以及何时断开连接,或者是否有新的控制器接入。这可以通过使用 inputDevice 类型来处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
-- 获取当前连接到系统的所有输入设备 local inputDevices = system.getInputDevices() local controllersActive = {} -- 遍历所有输入设备 for deviceIndex = 1,#inputDevices do -- 获取输入设备的信息 print( deviceIndex, "canVibrate", inputDevices[deviceIndex].canVibrate ) print( deviceIndex, "connectionState", inputDevices[deviceIndex].connectionState ) print( deviceIndex, "descriptor", inputDevices[deviceIndex].descriptor ) print( deviceIndex, "displayName", inputDevices[deviceIndex].displayName ) print( deviceIndex, "isConnected", inputDevices[deviceIndex].isConnected ) print( deviceIndex, "type", inputDevices[deviceIndex].type ) print( deviceIndex, "permenantid", tostring(inputDevices[deviceIndex].permanentId) ) print( deviceIndex, "andoridDeviceid", inputDevices[deviceIndex].androidDeviceId ) local index if ( event.device.descriptor == "Joystick 1" ) then index = 1 elseif ( event.device.descriptor == "Joystick 2" ) then index = 2 elseif ( event.device.descriptor == "Joystick 3" ) then index = 3 elseif ( event.device.descriptor == "Joystick 4" ) then index = 4 end controllersActive[index] = inputDevices[deviceIndex].isConnected end |
以下是关于控制器检测的一些注意事项
- 对于多人游戏应用,
event.descriptor
(例如"Joystick 1"
,"Joystick 2"
)可以用作将特定控制器绑定到玩家的唯一键。这将扫描活动设备并设置一个当前已连接设备的表格。 - 您应该在游戏过程中注意连接和断开连接,并处理“丢失”控制器或添加控制器的情况。如果控制器断开,您可能还需要暂停游戏。
- 您可能需要隐藏特定于某个控制器的 UI 元素,或者在存在控制器时隐藏“触摸屏”功能。
下面是一些处理此问题的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
local function onInputDeviceStatusChanged( event ) -- 处理输入设备更改 if ( event.connectionStateChanged ) then print( event.device.displayName .. ": " .. event.device.connectionState ) local index if ( event.device.descriptor == "Joystick 1" ) then index = 1 elseif ( event.device.descriptor == "Joystick 2" ) then index = 2 elseif ( event.device.descriptor == "Joystick 3" ) then index = 3 elseif ( event.device.descriptor == "Joystick 4" ) then index = 4 end controllersActive[index] = inputDevices[deviceIndex].isConnected end end -- 添加输入设备状态事件侦听器 Runtime:addEventListener( "inputDeviceStatus", onInputDeviceStatusChanged ) |
请注意,如果应用处于暂停状态,这些状态更改将不会注册。 如果您恢复到游戏状态,您有责任轮询控制器。
结论
大多数游戏控制器都提供数字输入(按钮)和称为摇杆或操纵杆的模拟输入。 由于典型的操纵杆可以上下左右移动,因此我们根据它移动的“轴”来测量运动。 模拟摇杆上下移动将在其 Y 轴上测量,而左右移动将在 X 轴上测量。
轴输入将在以后的博客文章中介绍,但重要的是要知道,许多游戏控制器会将左模拟摇杆映射到 D-Pad。 因此,移动左摇杆会频繁生成 "up"
、"down"
、"left"
和 "right"
事件。 此外,底部肩键被视为“模拟”输入,但当它们被挤压超过特定阈值时,会生成“按钮”事件。 如果您的应用程序需要准确知道这些触发器产生的值,您可以使用轴方法来测量事物,以进行微调控制。
Kohan Ikin
发布于 16:04, 8 月 27 日这是个很棒的消息,但是 HID 控制器支持也会很快进入模拟器吗?(例如,这样我们就可以将游戏手柄插入我们的 Mac,并在模拟器中测试我们的游戏,而无需每次进行更改都为设备构建?)
Chris
发布于 07:33, 8 月 28 日听起来太棒了。
那模拟摇杆呢?
我们有没有办法返回 x/y(以便我们可以计算出它所朝向的角度)?
Dave Cheng
发布于 10:00, 8 月 28 日也对模拟摇杆支持感到好奇。这对我正在开发的游戏来说太棒了。
Rob Miracle
发布于 16:43, 8 月 28 日正如我们上面提到的,Chris 和 Dave,我们将在以后的文章中介绍轴支持(模拟摇杆、x、y 等)。 这次已经很长了,而且我的 OUYA 才用了几天,所以我必须在写出来之前整理好一些东西。 这些功能在每日构建 API 文档中存在
https://docs.solar2d.cn/daily/api/
这些功能应该很快就会进入公共版本,因此常规文档将更新为这些信息。 如果您想在我们发布博客文章之前先睹为快,您可以在 Event 部分下查找“axis”。
Andrew
发布于 05:53, 8 月 30 日这真是个好消息! 一直在期待 Corona 何时可以构建到 OUYA。 Rob – 这是否意味着我们将期待您的终极配置和设备检测代码的更新,以包括 OUYA 的配置等。 您知道下一个公共版本何时发布吗?
Rob Miracle
发布于 19:11, 8 月 30 日实际上,现有的 Ultimate config.lua 应该可以很好地处理 OUYA 和 GameStick。
Andrew
发布于 19:22, 9 月 5 日今天下载了稳定版本,并尝试将我的 PS3 控制器连接到我的 Mac 以进行测试,看起来我的控制器已成功连接到 Mac,但不知道如何使其与 Corona 一起工作。 有其他人成功做到这一点吗? 如果是,您是如何让它工作的?
Rob Miracle
发布于 20:31, 9 月 5 日按键/轴支持在 Mac 模拟器以及 iOS 上不可用。 您必须在支持控制器的 Android 设备或 Windows 模拟器上对其进行测试。
https://docs.solar2d.cn/api/event/key/index.html
Andrew
发布于 05:41, 9 月 6 日谢谢 Rob! 这在一定程度上降低了在 Mac/Windows 的模拟器中使用控制器进行测试的兴奋感。 您知道这是否计划在未来的版本中支持吗? 如果没有,您可以将我引导至功能请求页面,我会提交请求。
Rob Miracle
发布于 14:18, 9 月 6 日Windows 模拟器确实支持它。 这不是 iOS 支持的功能,我不知道为什么 Mac 不支持。 我对 Mac OS-X SDK 以及它可能或不可能实现什么或添加到其中的难度一无所知。
Anton
发布于 03:51, 9 月 25 日什么时候为 mac 模拟器添加键盘控制支持? 现在为 android、ouya 开发应用程序非常不方便。
在 iOS 7 中出现了维护操纵杆,您是否计划添加支持?
Philip Dijkstra
发布于 05:03, 11 月 20 日我如何让它与 iCade (Core) 一起工作?
Rob Miracle
发布于 17:08, 11 月 20 日Anton,我们现在确实在 OS-X 模拟器上支持有限的键盘功能。 事实上,上周二有一篇关于使用 Mac App 将 HID 控制器输入转换为与您为 OUYA 编写的代码兼容的击键的博客文章。
Philip,我查了一下 iCade,它似乎只需要键盘控制。 我不知道我们是否已经支持 iOS 的键盘。
Rob
Nick
发布于 20:59, 2 月 18 日当大量使用 OnKeyEvent 时,还有其他人遇到非常严重的帧率下降吗? 我正在构建一个 OUYA 游戏,每次按下按钮时,即使它没有任何作用,帧率也会下降到 10 左右。 🙁