Files
playdate-kit/Sources/PlaydateKit/Sprite.swift
T
javier 2e7ee12ec1
CI / Build & test (macOS) (push) Has been cancelled
CI / Embedded Swift cross-compile (push) Has been cancelled
Documentation / deploy (push) Has been cancelled
Adjusted the naming of the library in the Package file.
2026-07-25 12:02:55 +02:00

656 lines
26 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Sprite.swift
// Wraps `playdate->sprite` (pd_api_sprite.h).
//
// The binding stores a back-reference to each `Sprite` wrapper in the
// underlying `LCDSprite`'s userdata slot, so callbacks and queries can
// recover the wrapper. Do not mix these wrappers with C code that sets its
// own sprite userdata; use `Sprite.userdata` for per-sprite storage instead.
//
internal import CPlaydate
private var spriteAPI: UnsafePointer<playdate_sprite> { Playdate.spriteAPI.unsafelyUnwrapped }
/// A floating-point rectangle mirroring `PDRect`.
public struct Rect: Sendable {
public var x: Float
public var y: Float
public var width: Float
public var height: Float
public init(x: Float, y: Float, width: Float, height: Float) {
self.x = x
self.y = y
self.width = width
self.height = height
}
init(_ rect: PDRect) {
self.init(x: rect.x, y: rect.y, width: rect.width, height: rect.height)
}
var cValue: PDRect { PDRect(x: x, y: y, width: width, height: height) }
}
/// A sprite: a drawable object with position, z-order, and collision
/// support. Wraps `LCDSprite`. Static members wrap the global sprite
/// system functions.
public final class Sprite {
let pointer: OpaquePointer
let isOwned: Bool
/// Position in the static `displayList`, or -1 when not in it; makes
/// `add()`/`remove()` O(1) instead of scanning the list.
private var displayListIndex = -1
/// Per-sprite callbacks and retained resources.
var updateFunction: ((Sprite) -> Void)?
var drawFunction: ((Sprite, _ bounds: Rect, _ drawRect: Rect) -> Void)?
var collisionResponseFunction: ((Sprite, _ other: Sprite) -> CollisionResponse)?
private var retainedImage: Graphics.Bitmap?
private var retainedStencil: Graphics.Bitmap?
private var retainedTilemap: Graphics.TileMap?
/// Free-form storage for game use (the C userdata slot is reserved
/// by the binding).
public var userdata: AnyObject?
init(pointer: OpaquePointer, isOwned: Bool) {
self.pointer = pointer
self.isOwned = isOwned
// Transient wrappers for sprites created outside the binding must not
// store a back-reference: it would dangle once the wrapper is
// deallocated, and only owned wrappers clear it in `deinit`.
if isOwned {
spriteAPI.pointee.setUserdata.unsafelyUnwrapped(pointer, Unmanaged.passUnretained(self).toOpaque())
}
}
/// Allocates a new sprite.
public convenience init() {
self.init(pointer: spriteAPI.pointee.newSprite.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true)
}
deinit {
if isOwned {
spriteAPI.pointee.setUserdata.unsafelyUnwrapped(pointer, nil)
spriteAPI.pointee.freeSprite.unsafelyUnwrapped(pointer)
}
}
/// Returns the Swift wrapper stored in the sprite's userdata, or a
/// transient unowned wrapper for sprites created outside the binding.
static func wrapper(for pointer: OpaquePointer) -> Sprite {
if let userdata = spriteAPI.pointee.getUserdata.unsafelyUnwrapped(pointer) {
return Unmanaged<Sprite>.fromOpaque(userdata).takeUnretainedValue()
}
return Sprite(pointer: pointer, isOwned: false)
}
/// Copies the sprite. Callbacks and retained resources are carried
/// over to the copy.
public func copy() -> Sprite {
let copy = Sprite(pointer: spriteAPI.pointee.copy.unsafelyUnwrapped(pointer).unsafelyUnwrapped,
isOwned: true)
copy.updateFunction = updateFunction
copy.drawFunction = drawFunction
copy.collisionResponseFunction = collisionResponseFunction
copy.retainedImage = retainedImage
copy.retainedStencil = retainedStencil
copy.retainedTilemap = retainedTilemap
return copy
}
// MARK: - Types
/// How a sprite reacts when a collision occurs.
public enum CollisionResponse: UInt32, Sendable {
case slide = 0
case freeze = 1
case overlap = 2
case bounce = 3
init(_ response: SpriteCollisionResponseType) {
self = CollisionResponse(rawValue: UInt32(response.rawValue)) ?? .freeze
}
var cValue: SpriteCollisionResponseType { SpriteCollisionResponseType(SpriteCollisionResponseType.RawValue(rawValue)) }
}
/// Information about a single collision, mirroring `SpriteCollisionInfo`.
public struct CollisionInfo {
/// The sprite being moved.
public let sprite: Sprite
/// The sprite it collided with.
public let other: Sprite
/// The collision response used.
public let response: CollisionResponse
/// `true` if the sprites were overlapping when the collision
/// started; `false` if the sprite tunneled through.
public let overlaps: Bool
/// How far along the movement (0...1) the collision occurred.
public let ti: Float
/// The difference between the requested and actual positions.
public let move: (x: Float, y: Float)
/// The collision normal (each component -1, 0, or 1).
public let normal: (x: Int, y: Int)
/// Where the sprite started touching `other`.
public let touch: (x: Float, y: Float)
/// The sprite's rect at the moment of the touch.
public let spriteRect: Rect
/// `other`'s rect at the moment of the touch.
public let otherRect: Rect
init(_ info: SpriteCollisionInfo) {
sprite = Sprite.wrapper(for: info.sprite)
other = Sprite.wrapper(for: info.other)
response = CollisionResponse(info.responseType)
overlaps = info.overlaps != 0
ti = info.ti
move = (info.move.x, info.move.y)
normal = (Int(info.normal.x), Int(info.normal.y))
touch = (info.touch.x, info.touch.y)
spriteRect = Rect(info.spriteRect)
otherRect = Rect(info.otherRect)
}
}
/// Information about a sprite intersected by a line segment,
/// mirroring `SpriteQueryInfo`.
public struct QueryInfo {
public let sprite: Sprite
/// How far along the segment (0...1) the segment enters the sprite.
public let ti1: Float
/// How far along the segment (0...1) the segment exits the sprite.
public let ti2: Float
public let entryPoint: (x: Float, y: Float)
public let exitPoint: (x: Float, y: Float)
init(_ info: SpriteQueryInfo) {
sprite = Sprite.wrapper(for: info.sprite)
ti1 = info.ti1
ti2 = info.ti2
entryPoint = (info.entryPoint.x, info.entryPoint.y)
exitPoint = (info.exitPoint.x, info.exitPoint.y)
}
}
// MARK: - Display list
/// Sprites currently added to the display list, kept alive here.
nonisolated(unsafe) private static var displayList: [Sprite] = []
/// When `true`, all sprites redraw every frame instead of only when
/// marked dirty.
public static func setAlwaysRedraw(_ flag: Bool) {
spriteAPI.pointee.setAlwaysRedraw.unsafelyUnwrapped(flag ? 1 : 0)
}
/// Marks the given screen region as needing a redraw.
public static func addDirtyRect(_ rect: Graphics.Rect) {
spriteAPI.pointee.addDirtyRect.unsafelyUnwrapped(rect.cValue)
}
/// Draws every sprite in the display list.
public static func drawAll() {
spriteAPI.pointee.drawSprites.unsafelyUnwrapped()
}
/// Updates and then draws every sprite in the display list.
public static func updateAndDrawAll() {
spriteAPI.pointee.updateAndDrawSprites.unsafelyUnwrapped()
}
/// The number of sprites in the display list.
public static var count: Int {
Int(spriteAPI.pointee.getSpriteCount.unsafelyUnwrapped())
}
/// Adds the sprite to the display list.
public func add() {
spriteAPI.pointee.addSprite.unsafelyUnwrapped(pointer)
if displayListIndex < 0 {
displayListIndex = Sprite.displayList.count
Sprite.displayList.append(self)
}
}
/// Removes the sprite from the display list.
public func remove() {
spriteAPI.pointee.removeSprite.unsafelyUnwrapped(pointer)
guard displayListIndex >= 0 else { return }
// Swap-remove: the keep-alive list is unordered (the OS keeps the
// draw order), so the last sprite can take the vacated slot.
let index = displayListIndex
let last = Sprite.displayList.removeLast()
if last !== self {
Sprite.displayList[index] = last
last.displayListIndex = index
}
displayListIndex = -1
}
/// Removes the given sprites from the display list.
public static func remove(_ sprites: [Sprite]) {
for sprite in sprites { sprite.remove() }
}
/// Removes every sprite from the display list.
public static func removeAll() {
spriteAPI.pointee.removeAllSprites.unsafelyUnwrapped()
for sprite in displayList { sprite.displayListIndex = -1 }
displayList = []
}
// MARK: - Geometry
/// The sprite's bounds. Setting this positions and sizes the sprite.
public var bounds: Rect {
get { Rect(spriteAPI.pointee.getBounds.unsafelyUnwrapped(pointer)) }
set { spriteAPI.pointee.setBounds.unsafelyUnwrapped(pointer, newValue.cValue) }
}
/// Moves the sprite so its anchor point is at (x, y).
public func moveTo(x: Float, y: Float) {
spriteAPI.pointee.moveTo.unsafelyUnwrapped(pointer, x, y)
}
/// Moves the sprite by (dx, dy).
public func moveBy(dx: Float, dy: Float) {
spriteAPI.pointee.moveBy.unsafelyUnwrapped(pointer, dx, dy)
}
/// The sprite's anchor position.
public var position: (x: Float, y: Float) {
var x: Float = 0, y: Float = 0
spriteAPI.pointee.getPosition.unsafelyUnwrapped(pointer, &x, &y)
return (x, y)
}
/// Sets the sprite's size without changing its image.
public func setSize(width: Float, height: Float) {
spriteAPI.pointee.setSize.unsafelyUnwrapped(pointer, width, height)
}
/// The anchor point used for positioning, where (0, 0) is the top
/// left and (1, 1) the bottom right. Defaults to (0.5, 0.5).
public var center: (x: Float, y: Float) {
get {
var x: Float = 0, y: Float = 0
spriteAPI.pointee.getCenter.unsafelyUnwrapped(pointer, &x, &y)
return (x, y)
}
set { spriteAPI.pointee.setCenter.unsafelyUnwrapped(pointer, newValue.x, newValue.y) }
}
/// Draw order: higher values draw on top.
public var zIndex: Int16 {
get { spriteAPI.pointee.getZIndex.unsafelyUnwrapped(pointer) }
set { spriteAPI.pointee.setZIndex.unsafelyUnwrapped(pointer, newValue) }
}
// MARK: - Appearance
/// Sets the sprite's image, resizing its bounds to match.
public func setImage(_ image: Graphics.Bitmap?, flip: Graphics.BitmapFlip = .unflipped) {
retainedImage = image
spriteAPI.pointee.setImage.unsafelyUnwrapped(pointer, image?.pointer, flip.cValue)
}
/// The sprite's image.
public var image: Graphics.Bitmap? {
if let retainedImage { return retainedImage }
guard let image = spriteAPI.pointee.getImage.unsafelyUnwrapped(pointer) else { return nil }
return Graphics.Bitmap(pointer: image, isOwned: false)
}
/// Sets the sprite's tilemap, resizing its bounds to match.
public var tilemap: Graphics.TileMap? {
get { retainedTilemap }
set {
retainedTilemap = newValue
spriteAPI.pointee.setTilemap.unsafelyUnwrapped(pointer, newValue?.pointer)
}
}
/// The mode used to draw the sprite's image.
public func setDrawMode(_ mode: Graphics.DrawMode) {
spriteAPI.pointee.setDrawMode.unsafelyUnwrapped(pointer, mode.cValue)
}
/// How the sprite's image is mirrored when drawn.
public var imageFlip: Graphics.BitmapFlip {
get { Graphics.BitmapFlip(spriteAPI.pointee.getImageFlip.unsafelyUnwrapped(pointer)) }
set { spriteAPI.pointee.setImageFlip.unsafelyUnwrapped(pointer, newValue.cValue) }
}
/// Sets the stencil applied when drawing the sprite. If `tile` is
/// `true` the image width must be a multiple of 32.
public func setStencil(_ stencil: Graphics.Bitmap?, tile: Bool = false) {
retainedStencil = stencil
spriteAPI.pointee.setStencilImage.unsafelyUnwrapped(pointer, stencil?.pointer, tile ? 1 : 0)
}
/// Sets an 8×8 stencil pattern (8 rows of image data).
public func setStencilPattern(_ rows: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) {
// The tuple is already 8 contiguous bytes; the C side copies the
// pattern, so passing the stack storage directly is safe.
withUnsafeBytes(of: rows) { buffer in
let pattern = UnsafeMutablePointer(
mutating: buffer.baseAddress.unsafelyUnwrapped.assumingMemoryBound(to: UInt8.self))
spriteAPI.pointee.setStencilPattern.unsafelyUnwrapped(pointer, pattern)
}
}
public func clearStencil() {
retainedStencil = nil
spriteAPI.pointee.clearStencil.unsafelyUnwrapped(pointer)
}
/// Clips the sprite's drawing to `rect` (screen coordinates).
public func setClipRect(_ rect: Graphics.Rect) {
spriteAPI.pointee.setClipRect.unsafelyUnwrapped(pointer, rect.cValue)
}
public func clearClipRect() {
spriteAPI.pointee.clearClipRect.unsafelyUnwrapped(pointer)
}
/// Clips all sprites with z-index in `startZ...endZ` to `rect`.
public static func setClipRectsInRange(_ rect: Graphics.Rect, startZ: Int, endZ: Int) {
spriteAPI.pointee.setClipRectsInRange.unsafelyUnwrapped(rect.cValue, Int32(startZ), Int32(endZ))
}
public static func clearClipRectsInRange(startZ: Int, endZ: Int) {
spriteAPI.pointee.clearClipRectsInRange.unsafelyUnwrapped(Int32(startZ), Int32(endZ))
}
// MARK: - Behavior flags
/// Whether the sprite's update function is called by `updateAndDrawAll()`.
public var updatesEnabled: Bool {
get { spriteAPI.pointee.updatesEnabled.unsafelyUnwrapped(pointer) != 0 }
set { spriteAPI.pointee.setUpdatesEnabled.unsafelyUnwrapped(pointer, newValue ? 1 : 0) }
}
public var collisionsEnabled: Bool {
get { spriteAPI.pointee.collisionsEnabled.unsafelyUnwrapped(pointer) != 0 }
set { spriteAPI.pointee.setCollisionsEnabled.unsafelyUnwrapped(pointer, newValue ? 1 : 0) }
}
public var isVisible: Bool {
get { spriteAPI.pointee.isVisible.unsafelyUnwrapped(pointer) != 0 }
set { spriteAPI.pointee.setVisible.unsafelyUnwrapped(pointer, newValue ? 1 : 0) }
}
/// Marking a sprite opaque tells the system it does not need to redraw
/// anything behind it.
public func setOpaque(_ flag: Bool) {
spriteAPI.pointee.setOpaque.unsafelyUnwrapped(pointer, flag ? 1 : 0)
}
/// Forces the sprite to redraw this frame.
public func markDirty() {
spriteAPI.pointee.markDirty.unsafelyUnwrapped(pointer)
}
/// Marks part of the sprite (in sprite-local coordinates) as needing
/// a redraw.
public func markDirty(rect: Rect) {
spriteAPI.pointee.markDirtyRect.unsafelyUnwrapped(pointer, rect.cValue)
}
/// An integer tag for identifying sprites (e.g. in collisions).
public var tag: UInt8 {
get { spriteAPI.pointee.getTag.unsafelyUnwrapped(pointer) }
set { spriteAPI.pointee.setTag.unsafelyUnwrapped(pointer, newValue) }
}
/// When `true`, the sprite draws in screen coordinates, ignoring the
/// global draw offset.
public func setIgnoresDrawOffset(_ flag: Bool) {
spriteAPI.pointee.setIgnoresDrawOffset.unsafelyUnwrapped(pointer, flag ? 1 : 0)
}
// MARK: - Callbacks
/// Sets the function called by `updateAndDrawAll()` for this sprite.
public func setUpdateFunction(_ update: ((Sprite) -> Void)?) {
updateFunction = update
if update != nil {
spriteAPI.pointee.setUpdateFunction.unsafelyUnwrapped(pointer, { spritePointer in
guard let spritePointer else { return }
let sprite = Sprite.wrapper(for: spritePointer)
sprite.updateFunction?(sprite)
})
} else {
spriteAPI.pointee.setUpdateFunction.unsafelyUnwrapped(pointer, nil)
}
}
/// Sets a custom draw function, called when the sprite needs to draw.
/// `bounds` is the sprite's bounds; `drawRect` is the region that
/// needs redrawing.
public func setDrawFunction(_ draw: ((Sprite, _ bounds: Rect, _ drawRect: Rect) -> Void)?) {
drawFunction = draw
if draw != nil {
spriteAPI.pointee.setDrawFunction.unsafelyUnwrapped(pointer, { spritePointer, bounds, drawRect in
guard let spritePointer else { return }
let sprite = Sprite.wrapper(for: spritePointer)
sprite.drawFunction?(sprite, Rect(bounds), Rect(drawRect))
})
} else {
spriteAPI.pointee.setDrawFunction.unsafelyUnwrapped(pointer, nil)
}
}
// MARK: - Collisions
/// Clears the collision world. Call when changing scenes.
public static func resetCollisionWorld() {
spriteAPI.pointee.resetCollisionWorld.unsafelyUnwrapped()
}
/// The rect (in sprite-local coordinates) used for collisions.
public var collideRect: Rect {
get { Rect(spriteAPI.pointee.getCollideRect.unsafelyUnwrapped(pointer)) }
set { spriteAPI.pointee.setCollideRect.unsafelyUnwrapped(pointer, newValue.cValue) }
}
public func clearCollideRect() {
spriteAPI.pointee.clearCollideRect.unsafelyUnwrapped(pointer)
}
/// Sets the function deciding how this sprite responds when it
/// collides with `other`.
public func setCollisionResponseFunction(_ filter: ((Sprite, _ other: Sprite) -> CollisionResponse)?) {
collisionResponseFunction = filter
if filter != nil {
spriteAPI.pointee.setCollisionResponseFunction.unsafelyUnwrapped(pointer, { spritePointer, otherPointer in
guard let spritePointer, let otherPointer else { return kCollisionTypeFreeze }
let sprite = Sprite.wrapper(for: spritePointer)
let other = Sprite.wrapper(for: otherPointer)
return sprite.collisionResponseFunction?(sprite, other).cValue ?? kCollisionTypeFreeze
})
} else {
spriteAPI.pointee.setCollisionResponseFunction.unsafelyUnwrapped(pointer, nil)
}
}
/// Visits and frees a C collision info array.
private static func visitCollisions(_ pointer: UnsafeMutablePointer<SpriteCollisionInfo>?,
count: Int32, _ visit: (CollisionInfo) -> Void) {
guard let pointer else { return }
for index in 0..<Int(count) {
visit(CollisionInfo(pointer[index]))
}
System.systemFree(pointer)
}
/// Converts and frees a C collision info array.
private static func collisionInfos(_ pointer: UnsafeMutablePointer<SpriteCollisionInfo>?,
count: Int32) -> [CollisionInfo] {
var infos = [CollisionInfo]()
infos.reserveCapacity(Int(count))
visitCollisions(pointer, count: count) { infos.append($0) }
return infos
}
/// Returns the collisions that would occur if the sprite moved toward
/// (goalX, goalY), without moving it.
public func checkCollisions(goalX: Float, goalY: Float)
-> (actual: (x: Float, y: Float), collisions: [CollisionInfo]) {
var actualX: Float = 0, actualY: Float = 0, count: Int32 = 0
let result = spriteAPI.pointee.checkCollisions.unsafelyUnwrapped(
pointer, goalX, goalY, &actualX, &actualY, &count)
return ((actualX, actualY), Sprite.collisionInfos(result, count: count))
}
/// Like `checkCollisions(goalX:goalY:)`, but visits each collision
/// instead of building an array, avoiding per-call allocations.
public func checkCollisions(goalX: Float, goalY: Float,
_ visit: (CollisionInfo) -> Void) -> (x: Float, y: Float) {
var actualX: Float = 0, actualY: Float = 0, count: Int32 = 0
let result = spriteAPI.pointee.checkCollisions.unsafelyUnwrapped(
pointer, goalX, goalY, &actualX, &actualY, &count)
Sprite.visitCollisions(result, count: count, visit)
return (actualX, actualY)
}
/// Moves the sprite toward (goalX, goalY), resolving collisions, and
/// returns where it ended up and what it hit.
@discardableResult
public func moveWithCollisions(goalX: Float, goalY: Float)
-> (actual: (x: Float, y: Float), collisions: [CollisionInfo]) {
var actualX: Float = 0, actualY: Float = 0, count: Int32 = 0
let result = spriteAPI.pointee.moveWithCollisions.unsafelyUnwrapped(
pointer, goalX, goalY, &actualX, &actualY, &count)
return ((actualX, actualY), Sprite.collisionInfos(result, count: count))
}
/// Like `moveWithCollisions(goalX:goalY:)`, but visits each collision
/// instead of building an array, avoiding per-call allocations.
@discardableResult
public func moveWithCollisions(goalX: Float, goalY: Float,
_ visit: (CollisionInfo) -> Void) -> (x: Float, y: Float) {
var actualX: Float = 0, actualY: Float = 0, count: Int32 = 0
let result = spriteAPI.pointee.moveWithCollisions.unsafelyUnwrapped(
pointer, goalX, goalY, &actualX, &actualY, &count)
Sprite.visitCollisions(result, count: count, visit)
return (actualX, actualY)
}
/// Visits and frees a C sprite pointer array.
private static func visitSprites(_ pointer: UnsafeMutablePointer<OpaquePointer?>?,
count: Int32, _ visit: (Sprite) -> Void) {
guard let pointer else { return }
for index in 0..<Int(count) {
if let spritePointer = pointer[index] {
visit(wrapper(for: spritePointer))
}
}
System.systemFree(pointer)
}
/// Converts and frees a C sprite pointer array.
private static func sprites(_ pointer: UnsafeMutablePointer<OpaquePointer?>?,
count: Int32) -> [Sprite] {
var sprites = [Sprite]()
sprites.reserveCapacity(Int(count))
visitSprites(pointer, count: count) { sprites.append($0) }
return sprites
}
/// Sprites with collision rects containing the point.
public static func query(atPoint x: Float, _ y: Float) -> [Sprite] {
var count: Int32 = 0
let result = spriteAPI.pointee.querySpritesAtPoint.unsafelyUnwrapped(x, y, &count)
return sprites(result, count: count)
}
/// Like `query(atPoint:_:)`, visiting each sprite without building an
/// array.
public static func query(atPoint x: Float, _ y: Float, _ visit: (Sprite) -> Void) {
var count: Int32 = 0
let result = spriteAPI.pointee.querySpritesAtPoint.unsafelyUnwrapped(x, y, &count)
visitSprites(result, count: count, visit)
}
/// Sprites with collision rects intersecting the rect.
public static func query(inRect x: Float, _ y: Float, width: Float, height: Float) -> [Sprite] {
var count: Int32 = 0
let result = spriteAPI.pointee.querySpritesInRect.unsafelyUnwrapped(x, y, width, height, &count)
return sprites(result, count: count)
}
/// Like `query(inRect:_:width:height:)`, visiting each sprite without
/// building an array.
public static func query(inRect x: Float, _ y: Float, width: Float, height: Float,
_ visit: (Sprite) -> Void) {
var count: Int32 = 0
let result = spriteAPI.pointee.querySpritesInRect.unsafelyUnwrapped(x, y, width, height, &count)
visitSprites(result, count: count, visit)
}
/// Sprites with collision rects intersecting the line segment.
public static func query(alongLine x1: Float, _ y1: Float, _ x2: Float, _ y2: Float) -> [Sprite] {
var count: Int32 = 0
let result = spriteAPI.pointee.querySpritesAlongLine.unsafelyUnwrapped(x1, y1, x2, y2, &count)
return sprites(result, count: count)
}
/// Like `query(alongLine:_:_:_:)`, visiting each sprite without building
/// an array.
public static func query(alongLine x1: Float, _ y1: Float, _ x2: Float, _ y2: Float,
_ visit: (Sprite) -> Void) {
var count: Int32 = 0
let result = spriteAPI.pointee.querySpritesAlongLine.unsafelyUnwrapped(x1, y1, x2, y2, &count)
visitSprites(result, count: count, visit)
}
/// Like `query(alongLine:)`, with entry/exit information for each sprite.
public static func queryInfo(alongLine x1: Float, _ y1: Float,
_ x2: Float, _ y2: Float) -> [QueryInfo] {
var count: Int32 = 0
guard let result = spriteAPI.pointee.querySpriteInfoAlongLine.unsafelyUnwrapped(
x1, y1, x2, y2, &count) else { return [] }
var infos = [QueryInfo]()
infos.reserveCapacity(Int(count))
for index in 0..<Int(count) {
infos.append(QueryInfo(result[index]))
}
System.systemFree(result)
return infos
}
/// Sprites whose collision rects overlap this sprite's.
public var overlappingSprites: [Sprite] {
var count: Int32 = 0
let result = spriteAPI.pointee.overlappingSprites.unsafelyUnwrapped(pointer, &count)
return Sprite.sprites(result, count: count)
}
/// Like `overlappingSprites`, visiting each sprite without building an
/// array.
public func overlappingSprites(_ visit: (Sprite) -> Void) {
var count: Int32 = 0
let result = spriteAPI.pointee.overlappingSprites.unsafelyUnwrapped(pointer, &count)
Sprite.visitSprites(result, count: count, visit)
}
/// All sprites in the display list that overlap another sprite.
public static var allOverlappingSprites: [Sprite] {
var count: Int32 = 0
let result = spriteAPI.pointee.allOverlappingSprites.unsafelyUnwrapped(&count)
return sprites(result, count: count)
}
/// Like `allOverlappingSprites`, visiting each sprite without building
/// an array.
public static func allOverlappingSprites(_ visit: (Sprite) -> Void) {
var count: Int32 = 0
let result = spriteAPI.pointee.allOverlappingSprites.unsafelyUnwrapped(&count)
visitSprites(result, count: count, visit)
}
}