mirror of
https://github.com/GayPizzaSpecifications/voxelotl-engine.git
synced 2025-08-02 13:00:53 +00:00
allow camera movement with the keyboard
This commit is contained in:
parent
83fc86d2a5
commit
39d46da7f7
@ -69,6 +69,7 @@ public class Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func beginHandleEvents() {
|
private func beginHandleEvents() {
|
||||||
|
Keyboard.instance.newFrame()
|
||||||
GameController.instance.newFrame()
|
GameController.instance.newFrame()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,10 +83,14 @@ public class Application {
|
|||||||
case SDLK_ESCAPE:
|
case SDLK_ESCAPE:
|
||||||
return .exitSuccess
|
return .exitSuccess
|
||||||
default:
|
default:
|
||||||
break
|
Keyboard.instance.keyDownEvent(scan: event.key.scancode, repeat: event.key.repeat != 0)
|
||||||
}
|
}
|
||||||
return .running
|
return .running
|
||||||
|
|
||||||
|
case SDL_EVENT_KEY_UP:
|
||||||
|
Keyboard.instance.keyUpEvent(scan: event.key.scancode)
|
||||||
|
return .running
|
||||||
|
|
||||||
case SDL_EVENT_GAMEPAD_ADDED:
|
case SDL_EVENT_GAMEPAD_ADDED:
|
||||||
if SDL_IsGamepad(event.gdevice.which) != SDL_FALSE {
|
if SDL_IsGamepad(event.gdevice.which) != SDL_FALSE {
|
||||||
GameController.instance.connectedEvent(id: event.gdevice.which)
|
GameController.instance.connectedEvent(id: event.gdevice.which)
|
||||||
|
@ -28,6 +28,7 @@ add_executable(Voxelotl MACOSX_BUNDLE
|
|||||||
Color.swift
|
Color.swift
|
||||||
Camera.swift
|
Camera.swift
|
||||||
Renderer.swift
|
Renderer.swift
|
||||||
|
Keyboard.swift
|
||||||
GameController.swift
|
GameController.swift
|
||||||
FPSCalculator.swift
|
FPSCalculator.swift
|
||||||
GameDelegate.swift
|
GameDelegate.swift
|
||||||
|
@ -27,11 +27,16 @@ class Game: GameDelegate {
|
|||||||
var chunk = Chunk(position: .zero)
|
var chunk = Chunk(position: .zero)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.player.position = SIMD3(0.5, Float(Chunk.chunkSize) + 0.5, 0.5)
|
self.resetPlayer()
|
||||||
self.player.rotation = .init(.pi, 0)
|
|
||||||
self.generateWorld()
|
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() {
|
private func generateWorld() {
|
||||||
#if true
|
#if true
|
||||||
let newSeed = UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32
|
let newSeed = UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32
|
||||||
@ -70,9 +75,7 @@ class Game: GameDelegate {
|
|||||||
|
|
||||||
// Player reset
|
// Player reset
|
||||||
if pad.pressed(.back) {
|
if pad.pressed(.back) {
|
||||||
self.player.position = .init(repeating: 0.5) + .init(0, Float(Chunk.chunkSize), 0)
|
self.resetPlayer()
|
||||||
self.player.velocity = .zero
|
|
||||||
self.player.rotation = .init(.pi, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regenerate
|
// 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.player.update(deltaTime: deltaTime, chunk: chunk)
|
||||||
self.camera.position = player.eyePosition
|
self.camera.position = player.eyePosition
|
||||||
self.camera.rotation = player.eyeRotation
|
self.camera.rotation = player.eyeRotation
|
||||||
|
137
Sources/Voxelotl/Keyboard.swift
Normal file
137
Sources/Voxelotl/Keyboard.swift
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ struct Player {
|
|||||||
static let airAccelCoeff: Float = 3
|
static let airAccelCoeff: Float = 3
|
||||||
static let gravityCoeff: Float = 20
|
static let gravityCoeff: Float = 20
|
||||||
static let frictionCoeff: Float = 22
|
static let frictionCoeff: Float = 22
|
||||||
|
static let flySpeedCoeff: Float = 36
|
||||||
static let jumpVelocity: Float = 7
|
static let jumpVelocity: Float = 7
|
||||||
static let maxVelocity: Float = 160
|
static let maxVelocity: Float = 160
|
||||||
|
|
||||||
@ -34,47 +35,84 @@ struct Player {
|
|||||||
.init(angle: self._rotation.x, axis: .up)
|
.init(angle: self._rotation.x, axis: .up)
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func update(deltaTime: Float, chunk: Chunk) {
|
enum JumpInput { case off, press, held }
|
||||||
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)
|
|
||||||
|
|
||||||
// Jumping
|
mutating func update(deltaTime: Float, chunk: Chunk) {
|
||||||
|
var turning: SIMD2<Float> = .zero
|
||||||
|
var movement: SIMD2<Float> = .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) {
|
if pad.pressed(.east) {
|
||||||
self._shouldJump = 0.3
|
jumpInput = .press
|
||||||
} else if self._shouldJump != .none {
|
} else if jumpInput != .press && pad.down(.east) {
|
||||||
if pad.down(.east) {
|
jumpInput = .held
|
||||||
self._shouldJump! -= deltaTime
|
}
|
||||||
if self._shouldJump! <= 0.0 {
|
}
|
||||||
self._shouldJump = .none
|
|
||||||
}
|
// Read keyboard input
|
||||||
} else {
|
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
|
self._shouldJump = .none
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if self._onGround && self._shouldJump != .none {
|
|
||||||
self._velocity.y = Self.jumpVelocity
|
|
||||||
self._onGround = false
|
|
||||||
self._shouldJump = .none
|
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
|
// Apply gravity
|
||||||
self._velocity.y -= Self.gravityCoeff * deltaTime
|
self._velocity.y -= Self.gravityCoeff * deltaTime
|
||||||
|
Loading…
Reference in New Issue
Block a user