From 37a3cb678f804b3110f6c026149ff74b958355ec Mon Sep 17 00:00:00 2001 From: Finn Voorhees Date: Thu, 28 Mar 2024 17:44:05 +0000 Subject: [PATCH] Move update/draw/collisionResponse callback to open method --- Sources/CPlaydate/include/CPlaydate.apinotes | 8 + Sources/PlaydateKit/Core/Sprite.swift | 206 ++++++++++++------- 2 files changed, 143 insertions(+), 71 deletions(-) diff --git a/Sources/CPlaydate/include/CPlaydate.apinotes b/Sources/CPlaydate/include/CPlaydate.apinotes index 1c78f606..aa3d89a4 100644 --- a/Sources/CPlaydate/include/CPlaydate.apinotes +++ b/Sources/CPlaydate/include/CPlaydate.apinotes @@ -217,3 +217,11 @@ Enumerators: SwiftName: nxor - Name: kDrawModeInverted SwiftName: inverted +- Name: kCollisionTypeSlide + SwiftName: slide +- Name: kCollisionTypeFreeze + SwiftName: freeze +- Name: kCollisionTypeOverlap + SwiftName: overlap +- Name: kCollisionTypeBounce + SwiftName: bounce diff --git a/Sources/PlaydateKit/Core/Sprite.swift b/Sources/PlaydateKit/Core/Sprite.swift index 1046d541..adbb361d 100644 --- a/Sources/PlaydateKit/Core/Sprite.swift +++ b/Sources/PlaydateKit/Core/Sprite.swift @@ -6,56 +6,78 @@ public import CPlaydate /// or the enemies that chase after your player. Sprites animate efficiently, and offer collision detection and /// a host of other built-in functionality. public enum Sprite { - // MARK: Public - - public typealias CollisionResponseType = SpriteCollisionResponseType + // MARK: Open - public class CollisionInfo { - // MARK: Lifecycle - - init(collisions: UnsafeBufferPointer, actual: Point) { - self.collisions = collisions - self.actual = actual - } - - deinit { collisions.deallocate() } - - // MARK: Public - - public let collisions: UnsafeBufferPointer - public let actual: Point - } - - public class QueryInfo { - // MARK: Lifecycle - - init(info: UnsafeBufferPointer) { - self.info = info - } - - deinit { - info.deallocate() - } - - // MARK: Public - - public let info: UnsafeBufferPointer - } - - public class Sprite { + open class Sprite { // MARK: Lifecycle /// Allocates and returns a new Sprite. public init() { pointer = sprite.newSprite.unsafelyUnwrapped().unsafelyUnwrapped + userdata = unsafeBitCast(self, to: UnsafeMutableRawPointer.self) + setUpdateFunction { sprite in + let userdata = PlaydateKit.Sprite.getUserdata(sprite.unsafelyUnwrapped).unsafelyUnwrapped + let sprite = unsafeBitCast(userdata, to: Sprite.self) + sprite.update() + } +// setDrawFunction { sprite, bounds, drawRect in +// let userdata = PlaydateKit.Sprite.getUserdata(sprite.unsafelyUnwrapped).unsafelyUnwrapped +// let sprite = unsafeBitCast(userdata, to: Sprite.self) +// sprite.draw(bounds: Rect(bounds), drawRect: Rect(drawRect)) +// } + setCollisionResponseFunction { sprite, other in + let spriteUserdata = PlaydateKit.Sprite.getUserdata(sprite.unsafelyUnwrapped) + let sprite = unsafeBitCast(spriteUserdata, to: Sprite.self) + let otherUserdata = PlaydateKit.Sprite.getUserdata(other.unsafelyUnwrapped) + let other = unsafeBitCast(otherUserdata, to: Sprite.self) + return sprite.collisionResponse(other: other) + } } init(pointer: OpaquePointer) { self.pointer = pointer + userdata = unsafeBitCast(self, to: UnsafeMutableRawPointer.self) + setUpdateFunction { sprite in + let userdata = PlaydateKit.Sprite.getUserdata(sprite.unsafelyUnwrapped).unsafelyUnwrapped + let sprite = unsafeBitCast(userdata, to: Sprite.self) + sprite.update() + } +// setDrawFunction { sprite, bounds, drawRect in +// let userdata = PlaydateKit.Sprite.getUserdata(sprite.unsafelyUnwrapped).unsafelyUnwrapped +// let sprite = unsafeBitCast(userdata, to: Sprite.self) +// sprite.draw(bounds: Rect(bounds), drawRect: Rect(drawRect)) +// } + setCollisionResponseFunction { sprite, other in + let spriteUserdata = PlaydateKit.Sprite.getUserdata(sprite.unsafelyUnwrapped) + let sprite = unsafeBitCast(spriteUserdata, to: Sprite.self) + let otherUserdata = PlaydateKit.Sprite.getUserdata(other.unsafelyUnwrapped) + let other = unsafeBitCast(otherUserdata, to: Sprite.self) + return sprite.collisionResponse(other: other) + } } deinit { sprite.freeSprite.unsafelyUnwrapped(pointer) } + // MARK: Open + + /// Called by ``Sprite.updateAndDrawDisplayListSprites()`` before sprites are drawn. Overriding this method + /// gives you the opportunity to perform some code upon every frame. + open func update() {} + + /// If the sprite doesn’t have an image, the sprite’s draw function is called as needed to update the display. Note that this method + /// is only called when the sprite is on screen and has a size specified via ``setSize(width:height:)`` or ``bounds``. + /// > Warning: This currently does not work due to [apple/swift/issues/72626](https://github.com/apple/swift/issues/72626) + open func draw(bounds _: Rect, drawRect _: Rect) {} + + /// Override to control the type of collision response that should happen when a collision with other occurs. + /// + /// This method should not attempt to modify the sprites in any way. While it might be tempting to deal with + /// collisions here, doing so will have unexpected and undesirable results. Instead, this function should return + /// one of the collision response values as quickly as possible. If sprites need to be modified as the result of a + /// collision, do so elsewhere, such as by inspecting the list of collisions returned by ``moveWithCollisions(goal:)``. + /// The default collision response is freeze. + open func collisionResponse(other _: Sprite) -> CollisionResponseType { .freeze } + // MARK: Public /// The sprite's stencil bitmap, if set. @@ -125,12 +147,6 @@ public enum Sprite { set { sprite.setVisible.unsafelyUnwrapped(pointer, newValue ? 1 : 0) } } - /// Gets/sets the sprite’s userdata, an arbitrary pointer used for associating the sprite with other data. - public var userdata: UnsafeMutableRawPointer? { - get { sprite.getUserdata.unsafelyUnwrapped(pointer) } - set { sprite.setUserdata.unsafelyUnwrapped(pointer, newValue) } - } - /// Get/set the `collisionsEnabled` flag of the sprite (along with the `collideRect`, this /// determines whether the sprite participates in collisions). Set to true by default. public var collisionsEnabled: Bool { @@ -237,25 +253,6 @@ public enum Sprite { sprite.setIgnoresDrawOffset.unsafelyUnwrapped(pointer, ignoresDrawOffset ? 1 : 0) } - /// Sets the update function for the sprite. - public func setUpdateFunction( - _ updateFunction: (@convention(c) (_ sprite: OpaquePointer?) -> Void)? - ) { - sprite.setUpdateFunction.unsafelyUnwrapped(pointer, updateFunction) - } - - /// Sets the draw function for the sprite. Note that the callback is only called when the - /// sprite is on screen and has a size specified via ``setSize(width:height:)`` or ``bounds``. - public func setDrawFunction( - drawFunction: (@convention(c) ( - _ sprite: OpaquePointer?, - _ bounds: PDRect, - _ drawRect: PDRect - ) -> Void)? - ) { - sprite.setDrawFunction.unsafelyUnwrapped(pointer, drawFunction) - } - /// Adds the sprite to the display list, so that it is drawn in the current scene. public func addToDisplayList() { sprite.addSprite.unsafelyUnwrapped(pointer) @@ -266,16 +263,6 @@ public enum Sprite { sprite.removeSprite.unsafelyUnwrapped(pointer) } - /// Set a callback that returns a `SpriteCollisionResponseType` for a collision between `sprite` and other. - public func setCollisionResponseFunction( - function: (@convention(c) ( - _ sprite: OpaquePointer?, - _ other: OpaquePointer? - ) -> CollisionResponseType)? - ) { - sprite.setCollisionResponseFunction.unsafelyUnwrapped(pointer, function) - } - /// Returns the same values as `moveWithCollisions()` but does not actually move the sprite. public func checkCollisions(goalX: Float, goalY: Float) -> CollisionInfo { var actualX: Float = 0, actualY: Float = 0 @@ -317,6 +304,77 @@ public enum Sprite { // MARK: Internal let pointer: OpaquePointer + + /// Gets/sets the sprite’s userdata, an arbitrary pointer used for associating the sprite with other data. + var userdata: UnsafeMutableRawPointer? { + get { sprite.getUserdata.unsafelyUnwrapped(pointer) } + set { sprite.setUserdata.unsafelyUnwrapped(pointer, newValue) } + } + + /// Set a callback that returns a `SpriteCollisionResponseType` for a collision between `sprite` and other. + func setCollisionResponseFunction( + function: (@convention(c) ( + _ sprite: OpaquePointer?, + _ other: OpaquePointer? + ) -> CollisionResponseType)? + ) { + sprite.setCollisionResponseFunction.unsafelyUnwrapped(pointer, function) + } + + /// Sets the draw function for the sprite. Note that the callback is only called when the + /// sprite is on screen and has a size specified via ``setSize(width:height:)`` or ``bounds``. + func setDrawFunction( + drawFunction: (@convention(c) ( + _ sprite: OpaquePointer?, + _ bounds: PDRect, + _ drawRect: PDRect + ) -> Void)? + ) { + sprite.setDrawFunction.unsafelyUnwrapped(pointer, drawFunction) + } + + /// Sets the update function for the sprite. + func setUpdateFunction( + _ updateFunction: (@convention(c) (_ sprite: OpaquePointer?) -> Void)? + ) { + sprite.setUpdateFunction.unsafelyUnwrapped(pointer, updateFunction) + } + } + + // MARK: Public + + public typealias CollisionResponseType = SpriteCollisionResponseType + + public class CollisionInfo { + // MARK: Lifecycle + + init(collisions: UnsafeBufferPointer, actual: Point) { + self.collisions = collisions + self.actual = actual + } + + deinit { collisions.deallocate() } + + // MARK: Public + + public let collisions: UnsafeBufferPointer + public let actual: Point + } + + public class QueryInfo { + // MARK: Lifecycle + + init(info: UnsafeBufferPointer) { + self.info = info + } + + deinit { + info.deallocate() + } + + // MARK: Public + + public let info: UnsafeBufferPointer } // MARK: - Properties @@ -438,6 +496,12 @@ public enum Sprite { return UnsafeBufferPointer(start: sprites, count: Int(length)) } + // MARK: Internal + + static func getUserdata(_ sprite: OpaquePointer) -> UnsafeMutableRawPointer? { + Self.sprite.getUserdata(sprite) + } + // MARK: Private private static var sprite: playdate_sprite { Playdate.playdateAPI.sprite.pointee }