SwiftUIGamepad
可以轻松地将 Gamepad 支持添加到任何 SwiftUI View
。
如果您觉得 SwiftUIGamepad
有用,并希望帮助支持其持续开发和维护,请考虑进行小额捐赠,尤其是在您将其用于商业产品时。
通过像您这样的贡献者的支持,我可以继续免费构建、发布和维护高质量、文档完善的 Swift 包,例如 SwiftUIGamepad
。
Swift Package Manager (Xcode 11 及以上)
https://github.com/Appracatappra/SwiftUIGamepad.git
。为什么不选择 CocoaPods、Carthage 或其他?
支持多个依赖管理器会使库的维护呈指数级复杂和耗时。
由于 Swift Package Manager 与 Xcode 11 (及更高版本) 集成,因此它是进一步支持的最简单选择。
使用 SwiftUIGamepad
,您可以轻松地将 Gamepad 支持添加到任何基于 SwiftUI
的应用程序。 SwiftUIGamepad
提供了一组内置的 Gamepad 图像,并支持基于 Gamepad 在任何 View
中使用方式显示帮助叠加层。 SwiftUIGamepad
还支持 Gamepad 连接/断开与设备的连接,并在应用程序需要 Gamepad 才能工作时显示叠加层。
在 Swift 应用程序中使用 Gamepad 之前,您需要启用支持。 在 Xcode 中,选择您的应用程序的Project > Signing & Capabilities > + Capability 并添加 Game Controllers
启用后,从复选框列表中选择您要支持的 Gamepad 类型。
如果您启用了 Micro Gamepad,它可能会阻止 Apple TV 识别 Extended Gamepad 已连接。 如果您在 tvOS 应用程序中使用此包,我建议禁用它。
通过将 SwiftUIGamepad
导入 SwiftUI View
定义中,它将获得几个新的事件,您可以响应这些事件,因为用户与游戏手柄交互并将其连接到或断开与运行该应用程序的设备的连接。
为了使
SwiftUIGamepad
正常工作,您必须将GamepadManager
事件添加到应用程序的.onChange
事件中。 有关完整详细信息,请参见下面的 在哪里设置样式更改和连接 Gamepad 支持 文档。
例如,让我们看一下 About View 的 body
@State var showGamepadHelp:Bool = false
@State var isGamepadConnected:Bool = false
var body: some View {
mainContents()
.onAppear {
connectGamepad(viewID: "About", handler: { controller, gamepadInfo in
isGamepadConnected = true
buttonMenuUsage(viewID: "About", "Return to the **Cover Page Menu**.")
buttonAUsage(viewID: "About", "Show or hide **Gamepad Help**.")
})
}
.onDisappear {
disconnectGamepad(viewID: "About")
}
.onRotate { newOrientation in
Execute.onMain {
orientation = newOrientation
}
}
.onGampadAppBecomingActive(viewID: "About") {
reconnectGamepad()
}
.onGamepadDisconnected(viewID: "About") { controller, gamepadInfo in
isGamepadConnected = false
}
.onGamepadButtonMenu(viewID: "About") { isPressed in
if isPressed {
// Return to main menu ...
}
}
.onGamepadButtonA(viewID: "About") { isPressed in
if isPressed {
showGamepadHelp = !showGamepadHelp
}
}
.onGamepadLeftShoulder(viewID: "About") { isPressed, value in
if isPressed {
// Return to main menu ...
}
}
}
接下来的章节将详细介绍此代码。
首先要注意的是,当 View
出现时,我们要求它请求访问连接到运行该应用程序的设备的任何游戏手柄
.onAppear {
connectGamepad(viewID: "About", handler: { controller, gamepadInfo in
isGamepadConnected = true
buttonMenuUsage(viewID: "About", "Return to the **Cover Page Menu**.")
buttonAUsage(viewID: "About", "Show or hide **Gamepad Help**.")
})
}
在所有
SwiftUIGamepad
控件和事件中使用的viewID
属性非常重要,并且对于应用程序中的每个视图必须是唯一的。 未能提供唯一的viewID
将导致应用程序中出现不可预测的行为,并且可能导致错误的View
响应错误的事件或根本无法响应游戏手柄事件。此外,同一
View
上的所有SwiftUIGamepad
控件必须使用相同的viewID
属性。
如果建立了游戏手柄访问权限,我们将设置一个状态变量,并为 View
响应的游戏手柄控件提供用户帮助描述。
您可以使用 GamepadHelpOverlay
视图在您的应用程序中显示标准化帮助,如下所示
if showGamepadHelp {
GamepadHelpOverlay()
}
您使用 buttonMenuUsage
或 buttonAUsage
等函数添加的条目将在此处显示。
此外,如果您的应用程序需要游戏手柄才能工作,则可以使用标准化的 GamepadRequiredOverlay
来请求用户在继续之前连接游戏手柄
if !isGamepadConnected {
GamepadRequiredOverlay()
}
您的 View
应随时响应游戏手柄连接或断开与运行该应用程序的设备的连接。 使用 onGampadAppBecomingActive
事件
.onGampadAppBecomingActive(viewID: "About") {
reconnectGamepad()
}
调用 reconnectGamepad
函数可确保在应用程序从后台唤醒时再次调用 connectGamepad
事件。
要处理游戏手柄与应用程序断开连接,请使用
.onGamepadDisconnected(viewID: "About") { controller, gamepadInfo in
isGamepadConnected = false
}
当用户与游戏手柄的控件(例如按下 A 按钮或拉动右侧扳机)交互时,View
可以响应几种不同类型的事件。 例如
.onGamepadButtonMenu(viewID: "About") { isPressed in
if isPressed {
// Return to main menu ...
}
}
.onGamepadButtonA(viewID: "About") { isPressed in
if isPressed {
showGamepadHelp = !showGamepadHelp
}
}
.onGamepadLeftShoulder(viewID: "About") { isPressed, value in
if isPressed {
// Return to main menu ...
}
}
在我们的示例 About View 中,按下游戏手柄的左肩按钮或菜单按钮会将用户返回到应用程序的主菜单。 按下 A 按钮 将显示或隐藏 Gamepad Help Overlay。
有关游戏手柄
View Events
及其使用的完整列表,请参阅 Extended Modules > SwiftUI > Extended Protocols > View in the DocC Documentation。
GamepadControlTip
控件可用于向应用程序用户显示快速帮助。 例如
GamepadControlTip(iconName: GamepadManager.gamepadOne.gampadInfo.buttonAImage, title: "Help", scale: ScreenMetrics.controlButtonScale, enabledColor: Color("HUDForeground"))
在此示例中,GamepadManager.gamepadOne.gampadInfo.buttonAImage
将根据用户连接到设备的 Gamepad 类型(PS4、PS5、Xbox 等)获取 A 按钮 的正确图像。
有关您可以获得的有关连接的游戏手柄的完整信息,请参阅
GamePadInfo
类。
当 View
完成访问游戏手柄时,它需要使用以下代码释放其访问权限
.onDisappear {
disconnectGamepad(viewID: "About")
}
在 SwiftUIGamepad
中嵌入了两个默认声音,可在 GamepadMenuView
控件中使用
两种声音均来自 Freeound.org,采用 Creative Commons 0 许可。
SwiftUIGamepad
中嵌入了几个 Gamepad 图像,可用于显示用户的帮助。 为以下控制器提供图像
所有图像均在 Creative Commons 0 许可 下发布。
SwiftUIGamepad
提供了一些辅助实用程序,可让您轻松访问存储在 Swift 包中的资源(例如上面的图像)。
例如,以下代码将返回 xxx.png
文件的路径
let path = SwiftUIGamepad.pathTo(resource:"xxx.png")
SwiftUIGamepad
中定义的几个控件具有一组静态属性来控制在不指定这些属性的情况下创建的任何控件的所有实例。
例如,GamepadMenu
定义了
/// Defines the default card font for the `GamepadMenu` view.
public static var gameMenuCardFontName:String = "Arial"
/// Defines the default card font size for the `GamepadMenu` view.
public static var gameMenuCardFontSize:Float = 24
/// Defines the default card font color for the `GamepadMenu` view.
public static var gameMenuCardFontColor:Color = .white
/// Defines the default card background color for the `GamepadMenu` view.
public static var gameMenuCardBackground:Color = .blue
如果您想统一设置应用程序中使用的所有 GamepadMenuCardView
实例的样式,只需调整 SwiftUIGamepad
的 gameMenuCardBackground
SwiftUIGamepad.gameMenuCardBackground = .red
现在,所有新创建的 GamepadMenuCardView
项都将具有 red
背景。
为了使样式更改生效,您需要在绘制任何 Views
之前进行更改。 您可以在您的主应用程序中使用以下代码
import SwiftUI
import SwiftletUtilities
import LogManager
import SwiftUIKit
import SwiftUIGamepad
@main
struct PackageTesterApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
@Environment(\.scenePhase) private var scenePhase
@Environment(\.colorScheme) var colorScheme
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { oldScenePhase, newScenePhase in
switch newScenePhase {
case .active:
Debug.info(subsystem: "PackageTesterApp", category: "Scene Phase", "App is active")
// Start watching for Gamepads being connected
GamepadManager.startWatchingForGamepads()
// Inform any gamepad views that the app is becoming active
GamepadManager.gamepadOne.appIsBecomingActive()
case .inactive:
Debug.info(subsystem: "PackageTesterApp", category: "Scene Phase", "App is inactive")
// Inform any gamepad views that the app is becoming inactive
GamepadManager.gamepadOne.appIsBecomingInactive()
case .background:
Debug.info(subsystem: "PackageTesterApp", category: "Scene Phase", "App is in background")
// Stop watching for gamepads
GamepadManager.stopWatchingForGamepads()
// Inform any gamepad views that the app is entering the background
GamepadManager.gamepadOne.appIsEnteringBackground()
@unknown default:
Debug.notice(subsystem: "PackageTesterApp", category: "Scene Phase", "App has entered an unexpected scene: \(oldScenePhase), \(newScenePhase)")
}
}
}
}
/// Class the handle the event that would typically be handled by the Application Delegate so they can be handled in SwiftUI.
class AppDelegate: NSObject, UIApplicationDelegate {
/// Handles the app finishing launching
/// - Parameter application: The app that has started.
func applicationDidFinishLaunching(_ application: UIApplication) {
// Register to receive remote notifications
UIApplication.shared.registerForRemoteNotifications()
}
/// Handle the application getting ready to launch
/// - Parameters:
/// - application: The application that is going to launch.
/// - launchOptions: Any options being passed to the application at launch time.
/// - Returns: Returns `True` if the application can launch.
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// Set any `SwiftUIGamepad` global style defaults here before any `Views` are drawn.
// Set style defaults
SwiftUIGamepad.gameMenuCardBackground = .red
return true
}
/// Handles the app receiving a remote notification
/// - Parameters:
/// - application: The app receiving the notifications.
/// - userInfo: The info that has been sent to the App.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
}
}
有了这段代码,在 func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool
中进行任何样式更改,它们将应用于之后构建的所有视图。
为了使
SwiftUIGamepad
正常工作,您必须将GamepadManager
事件添加到应用程序的.onChange
事件中(如上所列)。 目前,SwiftUIGamepad
仅支持一次连接一个游戏手柄,它将在GamepadManager.gamepadOne
属性中可用。
Package 包含其所有功能的完整 DocC Documentation。