mirror of
				https://github.com/GayPizzaSpecifications/voxelotl-engine.git
				synced 2025-11-04 02:59:37 +00:00 
			
		
		
		
	implement on screen virtual controller for iOS
This commit is contained in:
		@ -2,6 +2,10 @@ import Foundation
 | 
			
		||||
import SDL3
 | 
			
		||||
import QuartzCore.CAMetalLayer
 | 
			
		||||
 | 
			
		||||
#if canImport(GameController)
 | 
			
		||||
import GameController
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
public class Application {
 | 
			
		||||
  private let cfg: ApplicationConfiguration
 | 
			
		||||
  private var del: GameDelegate!
 | 
			
		||||
@ -12,12 +16,24 @@ public class Application {
 | 
			
		||||
  private var lastCounter: UInt64 = 0
 | 
			
		||||
  private var time: Duration = .zero
 | 
			
		||||
 | 
			
		||||
#if os(iOS)
 | 
			
		||||
  private var onScreenVirtualController: GCVirtualController? = nil
 | 
			
		||||
  private var onScreenVirtualControllerShown: Bool = false
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  public init(delegate: GameDelegate, configuration: ApplicationConfiguration) {
 | 
			
		||||
    self.cfg = configuration
 | 
			
		||||
    self.del = delegate
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private func initialize() -> ApplicationExecutionState {
 | 
			
		||||
#if os(iOS)
 | 
			
		||||
    if cfg.flags.contains(.onScreenVirtualController) {
 | 
			
		||||
      onScreenVirtualController = initializeOnScreenVirtualController()
 | 
			
		||||
      self.showVirtualGameController(true)
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    guard SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD) else {
 | 
			
		||||
      printErr("SDL_Init() error: \(String(cString: SDL_GetError()))")
 | 
			
		||||
      return .exitFailure
 | 
			
		||||
@ -31,6 +47,12 @@ public class Application {
 | 
			
		||||
    if cfg.flags.contains(.highDPI) {
 | 
			
		||||
      windowFlags |= SDL_WindowFlags(SDL_WINDOW_HIGH_PIXEL_DENSITY)
 | 
			
		||||
    }
 | 
			
		||||
    if cfg.flags.contains(.borderless) {
 | 
			
		||||
      windowFlags |= SDL_WindowFlags(SDL_WINDOW_BORDERLESS)
 | 
			
		||||
    }
 | 
			
		||||
    if cfg.flags.contains(.fullscreen) {
 | 
			
		||||
      windowFlags |= SDL_WindowFlags(SDL_WINDOW_FULLSCREEN)
 | 
			
		||||
    }
 | 
			
		||||
    window = SDL_CreateWindow(cfg.title, cfg.frame.w, cfg.frame.h, windowFlags)
 | 
			
		||||
    guard window != nil else {
 | 
			
		||||
      printErr("SDL_CreateWindow() error: \(String(cString: SDL_GetError()))")
 | 
			
		||||
@ -70,6 +92,22 @@ public class Application {
 | 
			
		||||
    return .running
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#if os(iOS)
 | 
			
		||||
  private func initializeOnScreenVirtualController() -> GCVirtualController {
 | 
			
		||||
    let configuration = GCVirtualController.Configuration()
 | 
			
		||||
    configuration.elements = [
 | 
			
		||||
      GCInputLeftThumbstick,
 | 
			
		||||
      GCInputRightThumbstick,
 | 
			
		||||
      GCInputLeftTrigger,
 | 
			
		||||
      GCInputRightTrigger,
 | 
			
		||||
      GCInputButtonA,
 | 
			
		||||
      GCInputButtonB,
 | 
			
		||||
    ]
 | 
			
		||||
    let controller = GCVirtualController(configuration: configuration)
 | 
			
		||||
    return controller
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  private func deinitialize() {
 | 
			
		||||
    self.del = nil
 | 
			
		||||
    self.renderer = nil
 | 
			
		||||
@ -141,6 +179,33 @@ public class Application {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private func showVirtualGameController(_ shown: Bool) {
 | 
			
		||||
#if os(iOS)
 | 
			
		||||
    guard let onScreenVirtualController = self.onScreenVirtualController else {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if shown {
 | 
			
		||||
      if !onScreenVirtualControllerShown {
 | 
			
		||||
        let semaphore = DispatchSemaphore(value: 1)
 | 
			
		||||
        DispatchQueue.global().async {
 | 
			
		||||
          Task.detached {
 | 
			
		||||
            try? await onScreenVirtualController.connect()
 | 
			
		||||
            semaphore.signal()
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        semaphore.wait()
 | 
			
		||||
        onScreenVirtualControllerShown = true
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      if onScreenVirtualControllerShown {
 | 
			
		||||
        onScreenVirtualController.disconnect()
 | 
			
		||||
        onScreenVirtualControllerShown = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private func update() -> ApplicationExecutionState {
 | 
			
		||||
    let deltaTime = getDeltaTime()
 | 
			
		||||
    time += deltaTime
 | 
			
		||||
@ -201,6 +266,9 @@ public struct ApplicationConfiguration {
 | 
			
		||||
 | 
			
		||||
    static let resizable = Flags(rawValue: 1 << 0)
 | 
			
		||||
    static let highDPI = Flags(rawValue: 1 << 1)
 | 
			
		||||
    static let borderless = Flags(rawValue: 1 << 2)
 | 
			
		||||
    static let fullscreen = Flags(rawValue: 1 << 3)
 | 
			
		||||
    static let onScreenVirtualController = Flags(rawValue: 1 << 4)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public enum VSyncMode: Equatable {
 | 
			
		||||
 | 
			
		||||
@ -36,5 +36,7 @@
 | 
			
		||||
	<false/>
 | 
			
		||||
	<key>UIApplicationSupportsIndirectInputEvents</key>
 | 
			
		||||
	<true/>
 | 
			
		||||
	<key>UIRequiresFullScreen</key>
 | 
			
		||||
	<true/>
 | 
			
		||||
</dict>
 | 
			
		||||
</plist>
 | 
			
		||||
 | 
			
		||||
@ -4,14 +4,33 @@ import Foundation
 | 
			
		||||
  @objc public static func run() -> Int32 {
 | 
			
		||||
    Thread.current.qualityOfService = .userInteractive
 | 
			
		||||
 | 
			
		||||
    var flags: ApplicationConfiguration.Flags = [ .resizable, .highDPI, .onScreenVirtualController ]
 | 
			
		||||
    if enableFullscreenWindow() {
 | 
			
		||||
      flags = flags.union(.fullscreen)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let app = Application(
 | 
			
		||||
      delegate: Game(),
 | 
			
		||||
      configuration: ApplicationConfiguration(
 | 
			
		||||
      frame: Size(1280, 720),
 | 
			
		||||
      title: "Voxelotl Demo",
 | 
			
		||||
      flags: [ .resizable, .highDPI ],
 | 
			
		||||
      flags: flags,
 | 
			
		||||
      vsyncMode: .on(interval: 1)))
 | 
			
		||||
 | 
			
		||||
    return app.run()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static func enableFullscreenWindow() -> Bool {
 | 
			
		||||
    return Program.isFrontAndCenterGamingDevice()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static func isFrontAndCenterGamingDevice() -> Bool {
 | 
			
		||||
#if os(iOS)
 | 
			
		||||
    return !(ProcessInfo.processInfo.isiOSAppOnMac || ProcessInfo.processInfo.isMacCatalystApp)
 | 
			
		||||
#elseif os(tvOS)
 | 
			
		||||
    return true
 | 
			
		||||
#else
 | 
			
		||||
    return false
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user