From 39d46da7f741543db10798a7743f78155df4f442 Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Thu, 22 Aug 2024 17:08:28 +1000 Subject: [PATCH] allow camera movement with the keyboard --- Sources/Voxelotl/Application.swift | 7 +- Sources/Voxelotl/CMakeLists.txt | 1 + Sources/Voxelotl/Game.swift | 20 +++-- Sources/Voxelotl/Keyboard.swift | 137 +++++++++++++++++++++++++++++ Sources/Voxelotl/Player.swift | 106 +++++++++++++++------- 5 files changed, 231 insertions(+), 40 deletions(-) create mode 100644 Sources/Voxelotl/Keyboard.swift diff --git a/Sources/Voxelotl/Application.swift b/Sources/Voxelotl/Application.swift index 05e5faa..2eb604c 100644 --- a/Sources/Voxelotl/Application.swift +++ b/Sources/Voxelotl/Application.swift @@ -69,6 +69,7 @@ public class Application { } private func beginHandleEvents() { + Keyboard.instance.newFrame() GameController.instance.newFrame() } @@ -82,10 +83,14 @@ public class Application { case SDLK_ESCAPE: return .exitSuccess default: - break + Keyboard.instance.keyDownEvent(scan: event.key.scancode, repeat: event.key.repeat != 0) } return .running + case SDL_EVENT_KEY_UP: + Keyboard.instance.keyUpEvent(scan: event.key.scancode) + return .running + case SDL_EVENT_GAMEPAD_ADDED: if SDL_IsGamepad(event.gdevice.which) != SDL_FALSE { GameController.instance.connectedEvent(id: event.gdevice.which) diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index 1dd7bca..3e8a53a 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable(Voxelotl MACOSX_BUNDLE Color.swift Camera.swift Renderer.swift + Keyboard.swift GameController.swift FPSCalculator.swift GameDelegate.swift diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index dfd16c1..ba3a8f5 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -27,11 +27,16 @@ class Game: GameDelegate { var chunk = Chunk(position: .zero) init() { - self.player.position = SIMD3(0.5, Float(Chunk.chunkSize) + 0.5, 0.5) - self.player.rotation = .init(.pi, 0) + self.resetPlayer() self.generateWorld() } + private func resetPlayer() { + self.player.position = .init(repeating: 0.5) + .init(0, Float(Chunk.chunkSize), 0) + self.player.velocity = .zero + self.player.rotation = .init(.pi, 0) + } + private func generateWorld() { #if true let newSeed = UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32 @@ -70,9 +75,7 @@ class Game: GameDelegate { // Player reset if pad.pressed(.back) { - self.player.position = .init(repeating: 0.5) + .init(0, Float(Chunk.chunkSize), 0) - self.player.velocity = .zero - self.player.rotation = .init(.pi, 0) + self.resetPlayer() } // Regenerate @@ -81,6 +84,13 @@ class Game: GameDelegate { } } + if Keyboard.pressed(.r) { + self.resetPlayer() + } + if Keyboard.pressed(.g) { + self.generateWorld() + } + self.player.update(deltaTime: deltaTime, chunk: chunk) self.camera.position = player.eyePosition self.camera.rotation = player.eyeRotation diff --git a/Sources/Voxelotl/Keyboard.swift b/Sources/Voxelotl/Keyboard.swift new file mode 100644 index 0000000..0671dac --- /dev/null +++ b/Sources/Voxelotl/Keyboard.swift @@ -0,0 +1,137 @@ +import SDL3 + +public class Keyboard { + public enum Keys { + case a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z + case right, left, up, down + case space + } + + public static func down(_ key: Keys) -> Bool { + keyState(key) & Self._DOWN == Self._DOWN + } + + public static func pressed(_ key: Keys, repeat rep: Bool = false) -> Bool { + var state = keyState(key) + if rep { + state &= ~Self._REPEAT + } + return state == Self._PRESS + } + + public static func released(_ key: Keys) -> Bool { + keyState(key) == Self._RELEASE + } + + //MARK: - Private + + private static let _instance = Keyboard() + public static var instance: Keyboard { _instance } + + @inline(__always) private static func keyState(_ key: Keys) -> UInt8 { + self._instance._state[Int(key.sdlScancode.rawValue)] + } + + private static let _UP = UInt8(0b000), _DOWN = UInt8(0b010), _IMPULSE = UInt8(0b001) + private static let _REPEAT = UInt8(0b100) + private static let _PRESS: UInt8 = _DOWN | _IMPULSE + private static let _RELEASE: UInt8 = _UP | _IMPULSE + + private var _state = [UInt8](repeating: _UP, count: Int(SDL_NUM_SCANCODES.rawValue)) + + internal func keyDownEvent(scan: SDL_Scancode, repeat rep: Bool) { + var newState = Self._PRESS + if rep { + newState |= Self._REPEAT + } + self._state[Int(scan.rawValue)] = newState + } + + internal func keyUpEvent(scan: SDL_Scancode) { + self._state[Int(scan.rawValue)] = Self._RELEASE + } + + internal func newFrame() { + self._state = self._state.map({ $0 & ~(Self._IMPULSE | Self._REPEAT) }) + } +} + +internal extension Keyboard.Keys { + var sdlKeycode: SDL_Keycode { + switch self { + case .a: SDLK_A + case .b: SDLK_B + case .c: SDLK_C + case .d: SDLK_D + case .e: SDLK_E + case .f: SDLK_F + case .g: SDLK_G + case .h: SDLK_H + case .i: SDLK_I + case .j: SDLK_J + case .k: SDLK_K + case .l: SDLK_L + case .m: SDLK_M + case .n: SDLK_N + case .o: SDLK_O + case .p: SDLK_P + case .q: SDLK_Q + case .r: SDLK_R + case .s: SDLK_S + case .t: SDLK_T + case .u: SDLK_U + case .v: SDLK_V + case .w: SDLK_W + case .x: SDLK_X + case .y: SDLK_Y + case .z: SDLK_Z + case .left: SDLK_LEFT + case .right: SDLK_RIGHT + case .up: SDLK_UP + case .down: SDLK_DOWN + case .space: SDLK_SPACE + } + } + + var sdlScancode: SDL_Scancode { + switch self { + case .a: SDL_SCANCODE_A + case .b: SDL_SCANCODE_B + case .c: SDL_SCANCODE_C + case .d: SDL_SCANCODE_D + case .e: SDL_SCANCODE_E + case .f: SDL_SCANCODE_F + case .g: SDL_SCANCODE_G + case .h: SDL_SCANCODE_H + case .i: SDL_SCANCODE_I + case .j: SDL_SCANCODE_J + case .k: SDL_SCANCODE_K + case .l: SDL_SCANCODE_L + case .m: SDL_SCANCODE_M + case .n: SDL_SCANCODE_N + case .o: SDL_SCANCODE_O + case .p: SDL_SCANCODE_P + case .q: SDL_SCANCODE_Q + case .r: SDL_SCANCODE_R + case .s: SDL_SCANCODE_S + case .t: SDL_SCANCODE_T + case .u: SDL_SCANCODE_U + case .v: SDL_SCANCODE_V + case .w: SDL_SCANCODE_W + case .x: SDL_SCANCODE_X + case .y: SDL_SCANCODE_Y + case .z: SDL_SCANCODE_Z + case .left: SDL_SCANCODE_LEFT + case .right: SDL_SCANCODE_RIGHT + case .up: SDL_SCANCODE_UP + case .down: SDL_SCANCODE_DOWN + case .space: SDL_SCANCODE_SPACE + } + } +} + +fileprivate extension SDL_Keycode { + init(scancode: SDL_Scancode) { + self.init(scancode.rawValue | UInt32(SDLK_SCANCODE_MASK)) + } +} diff --git a/Sources/Voxelotl/Player.swift b/Sources/Voxelotl/Player.swift index 9c39015..8f782d6 100644 --- a/Sources/Voxelotl/Player.swift +++ b/Sources/Voxelotl/Player.swift @@ -14,6 +14,7 @@ struct Player { static let airAccelCoeff: Float = 3 static let gravityCoeff: Float = 20 static let frictionCoeff: Float = 22 + static let flySpeedCoeff: Float = 36 static let jumpVelocity: Float = 7 static let maxVelocity: Float = 160 @@ -34,47 +35,84 @@ struct Player { .init(angle: self._rotation.x, axis: .up) } - mutating func update(deltaTime: Float, chunk: Chunk) { - if let pad = GameController.current?.state { - // Turning input - let turning = pad.rightStick.radialDeadzone(min: 0.1, max: 1) - _rotation += turning * deltaTime * 3.0 - if self._rotation.x < 0.0 { - self._rotation.x += .pi * 2 - } else if _rotation.x > .pi * 2 { - self._rotation.x -= .pi * 2 - } - self._rotation.y = self._rotation.y.clamp(-.pi * 0.5, .pi * 0.5) + enum JumpInput { case off, press, held } - // Jumping + mutating func update(deltaTime: Float, chunk: Chunk) { + var turning: SIMD2 = .zero + var movement: SIMD2 = .zero + var flying: Float = .zero + var jumpInput: JumpInput = .off + + // Read controller input (if one is plugged in) + if let pad = GameController.current?.state { + turning += pad.rightStick.radialDeadzone(min: 0.1, max: 1) + movement += pad.leftStick.cardinalDeadzone(min: 0.1, max: 1) + flying += pad.rightTrigger.axisDeadzone(0.01, 1) - pad.leftTrigger.axisDeadzone(0.01, 1) if pad.pressed(.east) { - self._shouldJump = 0.3 - } else if self._shouldJump != .none { - if pad.down(.east) { - self._shouldJump! -= deltaTime - if self._shouldJump! <= 0.0 { - self._shouldJump = .none - } - } else { + jumpInput = .press + } else if jumpInput != .press && pad.down(.east) { + jumpInput = .held + } + } + + // Read keyboard input + if Keyboard.down(.w) { movement.y -= 1 } + if Keyboard.down(.s) { movement.y += 1 } + if Keyboard.down(.a) { movement.x -= 1 } + if Keyboard.down(.d) { movement.x += 1 } + if Keyboard.down(.up) { turning.y -= 1 } + if Keyboard.down(.down) { turning.y += 1 } + if Keyboard.down(.left) { turning.x -= 1 } + if Keyboard.down(.right) { turning.x += 1 } + if Keyboard.down(.q) { flying += 1 } + if Keyboard.down(.e) { flying -= 1 } + if Keyboard.pressed(.space) { + jumpInput = .press + } else if jumpInput != .press && Keyboard.down(.space) { + jumpInput = .held + } + + // Turning input + self._rotation += turning * deltaTime * 3.0 + if self._rotation.x < 0.0 { + self._rotation.x += .pi * 2 + } else if _rotation.x > .pi * 2 { + self._rotation.x -= .pi * 2 + } + self._rotation.y = self._rotation.y.clamp(-.pi * 0.5, .pi * 0.5) + + // Jumping + if jumpInput == .press { + self._shouldJump = 0.3 + } else if self._shouldJump != .none { + if jumpInput == .held { + self._shouldJump! -= deltaTime + if self._shouldJump! <= 0.0 { self._shouldJump = .none } - } - if self._onGround && self._shouldJump != .none { - self._velocity.y = Self.jumpVelocity - self._onGround = false + } else { self._shouldJump = .none } - - // Movement (slower in air than on ground) - let movement = pad.leftStick.cardinalDeadzone(min: 0.1, max: 1) - let rotc = cos(self._rotation.x), rots = sin(self._rotation.x) - let coeff = self._onGround ? Self.accelerationCoeff : Self.airAccelCoeff - self._velocity.x += (movement.x * rotc - movement.y * rots) * coeff * deltaTime - self._velocity.z += (movement.y * rotc + movement.x * rots) * coeff * deltaTime - - // Flying and unflying - self._velocity.y += (pad.rightTrigger - pad.leftTrigger) * 36 * deltaTime } + if self._onGround && self._shouldJump != .none { + self._velocity.y = Self.jumpVelocity + self._onGround = false + self._shouldJump = .none + } + + // Movement (slower in air than on ground) + let movementMagnitude = simd_length(movement) + if movementMagnitude > 1.0 { + movement /= movementMagnitude + } + let rotc = cos(self._rotation.x), rots = sin(self._rotation.x) + let coeff = self._onGround ? Self.accelerationCoeff : Self.airAccelCoeff + self._velocity.x += (movement.x * rotc - movement.y * rots) * coeff * deltaTime + self._velocity.z += (movement.y * rotc + movement.x * rots) * coeff * deltaTime + + // Flying and unflying + flying = flying.clamp(-1, 1) + self._velocity.y += flying * Self.flySpeedCoeff * deltaTime // Apply gravity self._velocity.y -= Self.gravityCoeff * deltaTime