import Foundation import simd internal class SpriteTestGame: GameDelegate { private var spriteBatch: SpriteBatch! private var player = TestPlayer(position: .one * 100) private var texture: RendererTexture2D! private var wireShark: RendererTexture2D! private var level = TestLevel() private var frame: Size! var worldMousePosition: SIMD2 { var mpos = Mouse.position if self.spriteBatch.viewport != .init(origin: .zero, size: self.frame) { mpos /= SIMD2(self.frame) mpos *= SIMD2(self.spriteBatch.viewport.size) mpos += SIMD2(self.spriteBatch.viewport.origin) } return mpos } func create(_ renderer: Renderer) { self.spriteBatch = renderer.createSpriteBatch() self.resize(renderer.frame) renderer.clearColor = .init(hue: 301.2, saturation: 0.357, value: 0.488).linear // .magenta.mix(.white, 0.4).mix(.black, 0.8) self.texture = renderer.loadTexture(resourcePath: "test.png") self.wireShark = renderer.loadTexture(resourcePath: "wireshark.png") } func update(_ time: GameTime) { let dt = Float(time.delta) self.player.velocity.x = 0 if let pad = GameController.current?.state { self.player.velocity.x = pad.leftStick.x.axisDeadzone(0.1, 0.8) * 660 if pad.pressed(.start) { self.player.position = .one * 100 self.player.velocity = .zero } if pad.pressed(.east) && player.onGround { self.player.velocity.y = -1000 } else if pad.released(.east) && self.player.velocity.y < -550 { self.player.velocity.y = -550 } } if Keyboard.pressed(.enter) { self.player.position = .one * 100 self.player.velocity = .zero } if Keyboard.down(.left) { self.player.velocity.x = -660 } else if Keyboard.down(.right) { self.player.velocity.x = 660 } if Keyboard.pressed(.up) && player.onGround { self.player.velocity.y = -1000 } else if Keyboard.released(.up) && self.player.velocity.y < -550 { self.player.velocity.y = -550 } let mpos = self.worldMousePosition if Mouse.down(.left) { self.level.set(SIMD2(mpos / TestLevel.cellScale, rounding: .down), true) } else if Mouse.down(.right) { self.level.set(SIMD2(mpos / TestLevel.cellScale, rounding: .down), false) } self.player.update(deltaTime: dt, level: self.level) } func draw(_ renderer: Renderer, _ time: GameTime) { self.spriteBatch.begin(blendMode: .premultiplied) // Draw background self.spriteBatch.draw(self.texture, source: .init( origin: .init(scalar: fmod(Float(time.total), 32)), size: spriteBatch.viewport.size * 0.01), destination: nil, color: .init(renderer.clearColor).setAlpha(0.7)) // Draw level let scale: Float = 64 for y in 0.. 0 { shorkFlip = self.player.velocity.x < 0 ? shorkFlip.counterClockwise : shorkFlip.clockwise } self.spriteBatch.draw(self.wireShark, position: self.player.position, scale: 0.2, origin: .init(250, 275 * 2), flip: shorkFlip) // Sliding door test let doorAngle = max(sin(player.rotate * 2.6) - 0.75, 0) * .pi let doorScale = SIMD2(24, 12) let doorAffine = simd_float2x2( .init( cos(doorAngle) * doorScale.x, sin(doorAngle) * doorScale.y), .init(-sin(doorAngle) * doorScale.x, cos(doorAngle) * doorScale.y)) let doorOrigin = SIMD2(repeating: 1) let doorPosition = SIMD2(704 + 24, 1152 + 12) - doorOrigin * doorAffine self.spriteBatch.draw(self.texture, source: Rect( origin: .init(4 + cos(player.rotate / 1.2) * 4, 0), size: .init(4 + cos(player.rotate / 1.3) * 4, 16)), position: doorPosition, transform: doorAffine, color: .red.mix(.white, 0.3)) // Draw mouse cursor let mpos = self.worldMousePosition let inter = 0.5 + sin(Float(time.total) * 10) * 0.5 let color = Color.green.mix(.white, 0.3) let mesh = Mesh.init(vertices: [ .init(position: mpos, texCoord: .zero, color: .one), .init(position: mpos + .init(50, 0) + .init(-50, 50) * inter, texCoord: .zero, color: SIMD4(color)), .init(position: mpos + .init(0, 50) + .init( 50, -50) * inter, texCoord: .zero, color: SIMD4(color)), .init(position: mpos + .init(80, 80), texCoord: .zero, color: .zero) ], indices: [ 0, 1, 2, 1, 2, 3 ]) if Mouse.down(.left) { self.spriteBatch.draw(self.texture, mesh: mesh) } else { self.spriteBatch.draw(self.texture, vertices: mesh.vertices[..<3]) } self.spriteBatch.end() } func resize(_ frame: Rect) { let viewport = Rect(frame) self.frame = viewport.size let rect = Rect(origin: .zero, size: .init(2560, 1440)) if viewport == rect { self.spriteBatch.viewport = rect return } let viewportRatio = viewport.w / viewport.h let rectRatio = rect.w / rect.h if abs(viewportRatio - rectRatio) <= .ulpOfOne * 10 { self.spriteBatch.viewport = rect } else if viewportRatio > rectRatio { let width = rect.h * viewportRatio self.spriteBatch.viewport = .init( x: (rect.w - width) * 0.5, y: 0, width: width, height: rect.h) } else { let height = rect.w / viewportRatio self.spriteBatch.viewport = .init( x: 0, y: (rect.h - height) * 0.5, width: rect.w, height: height) } } } fileprivate struct TestLevel { public static let levelWidth = 40, levelHeight = 23 public static let cellScale: Float = 64 private var data: [UInt8] init() { self.data = .init(repeating: 0, count: Self.levelWidth * Self.levelHeight) for i in 0.., _ to: Bool) { if p.x >= 0 && p.y >= 0 && p.x < Self.levelWidth && p.y < Self.levelHeight { self.data[p.x + Self.levelWidth * p.y] = to ? 1 : 0 } } func get(_ p: Point) -> Bool { if p.x < 0 || p.y < 0 || p.x >= Self.levelWidth || p.y >= Self.levelHeight { return false } return self.data[p.x + Self.levelWidth * p.y] == 1 } func check(_ p: SIMD2) -> Bool { let p = SIMD2(p / TestLevel.cellScale, rounding: .down) return self.get(Point(p.x, p.y)) } func check(_ rect: Extent) -> Bool { let p = Extent(floor(rect / Self.cellScale)) return self.get(p.topLeft) || self.get(p.topRight) || self.get(p.bottomLeft) || self.get(p.bottomRight) } } fileprivate struct TestPlayer { static private let rect = Extent(left: -30, top: -63, right: 30, bottom: 0) var position: SIMD2 var velocity: SIMD2 = .zero var rotate: Float = 0 private var _onGround = false var onGround: Bool { self._onGround } init(position: SIMD2) { self.position = position } mutating func update(deltaTime dt: Float, level: TestLevel) { self.velocity.y += 1500 * dt if abs(self.velocity.y) > 3000 { self.velocity.y = .init(signOf: self.velocity.y, magnitudeOf: 3000) } if self.velocity.x != 0 { self.position.x += self.velocity.x * dt if level.check(Self.rect + self.position) { let offset = self.velocity.x < 0 ? Self.rect.left : Self.rect.right self.position.x = round((self.position.x + offset) / TestLevel.cellScale) * TestLevel.cellScale - offset self.position.x -= .init(signOf: self.velocity.x, magnitudeOf: .ulpOfOne * 12000); self.velocity.x = 0 } } if self.velocity.y != 0 { self.position.y += self.velocity.y * dt if level.check(Self.rect + (self.position)) { let offset = self.velocity.y < 0 ? Self.rect.top : Self.rect.bottom self.position.y = round((self.position.y + offset) / TestLevel.cellScale) * TestLevel.cellScale - offset self.position.y -= .init(signOf: self.velocity.y, magnitudeOf: .ulpOfOne * 12000); self.velocity.y = 0 } } if self.velocity.x != 0 { self.rotate += abs(self.velocity.x) / 1000 * dt } self._onGround = level.check(Self.rect + self.position + .init(0, .ulpOfOne * 24000)) } } fileprivate extension Color { func setAlpha(_ newAlpha: T) -> Self { return .init(r: r, g: g, b: b, a: newAlpha) } }