Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from mossprescott/spm
Add support for building separate apps; SpriteGame example
- Loading branch information
Showing
47 changed files
with
1,365 additions
and
115 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
HelloWorld.pdx | ||
Source/pdex.bin | ||
/swift-pd/Sources/CPlaydate/pd_api.h | ||
/swift-pd/Sources/CPlaydate/pd_api | ||
.build | ||
*.pdx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// swift-tools-version:5.5 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "HelloWorld", | ||
products: [ | ||
.library( | ||
name: "HelloWorld", | ||
type: .dynamic, | ||
targets: ["HelloWorld"] | ||
), | ||
], | ||
dependencies: [ | ||
.package(name: "swift-pd", path: "../../swift-pd") | ||
], | ||
targets: [ | ||
.target( | ||
name: "HelloWorld", | ||
dependencies: [ | ||
.product(name: "Playdate", package: "swift-pd") | ||
] | ||
) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import Playdate | ||
|
||
let TEXT_WIDTH = 86 | ||
let TEXT_HEIGHT = 16 | ||
|
||
var x = (400-TEXT_WIDTH)/2 | ||
var y = (240-TEXT_HEIGHT)/2 | ||
var dx = 1 | ||
var dy = 2 | ||
|
||
var font: Graphics.Font! | ||
|
||
class HelloWorld: App { | ||
/// Called once when the app is loaded, before the first call to update(). | ||
/// You can put code to do any one-time setup here. | ||
init() { | ||
do { | ||
font = try Graphics.Font(path: "/System/Fonts/Asheville-Sans-14-Bold.pft") | ||
} catch { | ||
Playdate.System.error("\(error)") | ||
} | ||
} | ||
|
||
/// Called once per frame to consume input and draw to the screen. If anything was drawn and the | ||
/// screen needs to be updated by the sytem, return true. | ||
func update() -> Bool { | ||
Graphics.clear(.white) | ||
|
||
Graphics.setFont(font) | ||
Graphics.drawText("Hello World!", x: x, y: y) | ||
|
||
x += dx | ||
y += dy | ||
|
||
if x < 0 || x > Int(Display.width) - TEXT_WIDTH { | ||
dx = -dx | ||
} | ||
|
||
if y < 0 || y > Int(Display.height) - TEXT_HEIGHT { | ||
dy = -dy | ||
} | ||
|
||
Playdate.System.drawFPS(x: 0, y: 0) | ||
|
||
return true; | ||
} | ||
} | ||
|
||
@_dynamicReplacement(for: makeApp()) | ||
func myApp() throws -> App { | ||
HelloWorld() | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// swift-tools-version:5.5 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "SpriteGame", | ||
products: [ | ||
.library( | ||
name: "SpriteGame", | ||
type: .dynamic, | ||
targets: ["SpriteGame"] | ||
), | ||
], | ||
dependencies: [ | ||
.package(name: "swift-pd", path: "../../swift-pd") | ||
], | ||
targets: [ | ||
.target( | ||
name: "SpriteGame", | ||
dependencies: [ | ||
.product(name: "Playdate", package: "swift-pd") | ||
] | ||
) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Sprite Game Example | ||
|
||
A port of the SpriteGame example from the SDK, which shows off how the Swift types handle | ||
object lifecycles: allocating and freeing Bitmaps and Sprites automatically when they're | ||
no longer referenced. | ||
|
||
There are a few changes compared to the SDK's original version: | ||
- Button B produces continuous fire, because it's fun and because more sprites make a more | ||
interesting test | ||
- The score is displayed in the corner of the screen. Did somebody forget to include that, | ||
after adding a font for it to the project? | ||
- A couple of minor bug fixes | ||
|
||
The code makes no attempt to preload bitmaps or fonts, instead just loading them as needed. That | ||
makes a more interesting test of memory management, and the speed penalty doesn't matter in the | ||
simulator. If we ever get this running on device, might want to fix that for better | ||
performance/efficiency. | ||
|
||
## Notes on the Swift port | ||
|
||
It's not exactly clear how best to break up a moderately involved app like this one. I chose to put | ||
each type of sprite into a separate .swift file, because it makes it easier to see how they compare to one another. | ||
|
||
Even so, the total number of lines, including blanks and comments, is a little less than the | ||
original single-file C version (about 410 versus 490). It might be interesting to compare that | ||
with a Lua version. | ||
|
||
To keep things simple, only the main class [`SpriteGame`](Sources/SpriteGame/SpriteGame.swift) | ||
keeps track of any state. The rest of the files just contain functions to create and update one | ||
type of sprite each. That ended up involving some callbacks, which could be considered overly fancy, | ||
but it also demonstrates how Swift features like closures can be used to help organize even a small | ||
codebase like this. | ||
|
||
Using a Swift `enum` for sprite tags seems natural, but involves some manual mapping back and forth. | ||
In fact, this game takes advantage of the untyped nature of those tags by storing either nothing, | ||
an `enum` value, or a frame number, depending on the sprite. So in Swift you have to know | ||
what the tag means based on the sprite you're looking at, and then you can interpret it into a nicer | ||
type if you want. |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
space 9 | ||
! 9 | ||
" 9 | ||
# 9 | ||
$ 9 | ||
% 9 | ||
& 9 | ||
' 9 | ||
( 9 | ||
) 9 | ||
* 9 | ||
+ 9 | ||
, 9 | ||
- 9 | ||
. 9 | ||
/ 9 | ||
0 9 | ||
1 9 | ||
2 9 | ||
3 9 | ||
4 9 | ||
5 9 | ||
6 9 | ||
7 9 | ||
8 9 | ||
9 9 | ||
: 9 | ||
; 9 | ||
< 9 | ||
= 9 | ||
> 9 | ||
? 9 | ||
@ 9 | ||
A 9 | ||
B 9 | ||
C 9 | ||
D 9 | ||
E 9 | ||
F 9 | ||
G 9 | ||
H 9 | ||
I 9 | ||
J 9 | ||
K 9 | ||
L 9 | ||
M 9 | ||
N 9 | ||
O 9 | ||
P 9 | ||
Q 9 | ||
R 9 | ||
S 9 | ||
T 9 | ||
U 9 | ||
V 9 | ||
W 9 | ||
X 9 | ||
Y 9 | ||
Z 9 | ||
[ 9 | ||
\ 9 | ||
] 9 | ||
_ 9 | ||
` 9 | ||
a 9 | ||
b 9 | ||
c 9 | ||
d 9 | ||
e 9 | ||
f 9 | ||
g 9 | ||
h 9 | ||
i 9 | ||
j 9 | ||
k 9 | ||
l 9 | ||
m 9 | ||
n 9 | ||
o 9 | ||
p 9 | ||
q 9 | ||
r 9 | ||
s 9 | ||
t 9 | ||
u 9 | ||
v 9 | ||
w 9 | ||
x 9 | ||
y 9 | ||
z 9 | ||
| 9 | ||
~ 9 |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
name=Sprite Game | ||
author=Moss Prescott | ||
description=It's not much of a game! (originally by Panic, Inc) | ||
bundleID=com.example.sprite | ||
imagePath= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import Playdate | ||
|
||
enum Background { | ||
static var y = 0 | ||
static var image: Graphics.Bitmap! | ||
static var sprite: Sprite! | ||
|
||
static func setup() throws { | ||
image = try Graphics.Bitmap(path: "images/background") | ||
sprite = Sprite() | ||
|
||
sprite.setUpdateFunction(update) | ||
sprite.setDrawFunction { _, _ in draw() } | ||
|
||
sprite.bounds = Rect(x: 0, y: 0, width: 400, height: 240) | ||
sprite.zIndex = 0 | ||
|
||
Sprite.add(sprite) | ||
} | ||
|
||
private static func update() { | ||
y += 1 | ||
if y > image.height { | ||
y = 0 | ||
} | ||
|
||
// Note: this sprite moves every frame and draws the entire screen, | ||
// So marking it dirty causes a full redraw every time. That means none | ||
// of the other sprites actually need to call it. | ||
// In fact, if each sprite tries to dirty itself, the wrong parts of the | ||
// the screen get drawn. Hmm. | ||
sprite.markDirty() | ||
} | ||
|
||
private static func draw() { | ||
image.draw(x: 0, y: y) | ||
image.draw(x: 0, y: y-image.height) | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
Examples/SpriteGame/Sources/SpriteGame/BackgroundPlane.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Playdate | ||
|
||
/// Manage the small planes that fly harmlessly by underneath the action. Note, there's no state | ||
/// aside from the actual Sprite, so no instances of BackgroundPlane. | ||
enum BackgroundPlane { | ||
/// "createBackgroundPlane" | ||
static func spawn(departedCallback: @escaping () -> Void) { | ||
let plane = Sprite() | ||
|
||
plane.setUpdateFunction { sprite in update(plane: sprite, departedCallback: departedCallback) } | ||
|
||
// TODO: preload | ||
guard let planeImage = try? Graphics.Bitmap(path: "images/plane2") else { | ||
fatalError() | ||
} | ||
|
||
let w = planeImage.width | ||
let planeHeight = planeImage.height | ||
|
||
plane.setImage(planeImage) | ||
|
||
plane.moveTo(x: Float.random(in: 0 ..< 400) - Float(w)/2, y: -Float(planeHeight)) | ||
plane.zIndex = 100 | ||
Sprite.add(plane) | ||
} | ||
|
||
/// "updateBackgroundPlane" | ||
private static func update(plane: Sprite, departedCallback: @escaping () -> Void) { | ||
let (x, y) = plane.position | ||
let newY = y + 2 | ||
|
||
if let h = plane.getImage()?.height, newY > 400 + Float(h) { | ||
Sprite.remove(plane) | ||
departedCallback() | ||
} | ||
else { | ||
plane.moveTo(x: x, y: newY) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import Playdate | ||
|
||
/// Manage bullets fired by the player's plane. Note, there's no state aside from the actual | ||
/// Sprite, so no instances of Bullet. | ||
enum Bullet { | ||
/// "playerFire" | ||
static func spawn(x: Float, y: Float, hitEnemyCallback: @escaping () -> Void) { | ||
let bullet = Sprite() | ||
|
||
bullet.setUpdateFunction { sprite in update(sprite, hitEnemyCallback) } | ||
|
||
// TODO: preload | ||
guard let bulletImage = try? Graphics.Bitmap(path: "images/doubleBullet") else { | ||
fatalError() | ||
} | ||
|
||
let w = bulletImage.width | ||
let bulletHeight = bulletImage.height | ||
|
||
bullet.setImage(bulletImage) | ||
|
||
bullet.collideRect = Rect(x: 0, y: 0, width: Float(w), height: Float(bulletHeight)) | ||
bullet.setCollisionResponseFunction { _, _ in .overlap } | ||
|
||
bullet.moveTo(x: x, y: y) // Note: the original example has a bug here that makes the bullets misaligned | ||
bullet.zIndex = 999 | ||
Sprite.add(bullet) | ||
|
||
bullet.tag = SpriteType.bullet.rawValue | ||
} | ||
|
||
/// "updatePlayerFire" | ||
private static func update(_ bullet: Sprite, _ hitEnemyCallback: @escaping () -> Void) { | ||
let (x, y) = bullet.position | ||
let newY = y - 20 | ||
|
||
if let h = bullet.getImage()?.height, newY < Float(-h) { // bullet is offscreen, remove it | ||
Sprite.remove(bullet) | ||
} | ||
else { | ||
let (_, _, collisions) = bullet.moveWithCollisions(goalX: x, goalY: newY) | ||
|
||
var hit = false | ||
for c in collisions { | ||
let type = SpriteType(rawValue: c.other.tag) | ||
if type == .enemy { | ||
EnemyPlane.destroy(c.other) | ||
hit = true | ||
hitEnemyCallback() | ||
} | ||
} | ||
|
||
if hit { | ||
Sprite.remove(bullet) | ||
} | ||
|
||
// Note: no need to free the collision info; Swift takes care of it | ||
} | ||
} | ||
} |
Oops, something went wrong.