SpriteKit
iOS向けの2Dゲーム開発用として、SpriteKit というフレームワークが提供されている。
今回、古典的なピクセルアートのゲーム「パックマン」を作るため、スプライトのキャラクタをもっと簡単に扱えるようにしたい。
キャラクタは基本 16x16ドットで構成されており、テクスチャの切り替えアニメーションも考慮すると、オブジェクトで管理するのは大変そう。
そのため1つの大きなアセット画像を 16x16ドットで切り出して、テクスチャ番号で扱えるようにする。
スプライトもオブジェクト管理ではなく、スプライト管理番号で扱えるようなクラスを作成する。
Sprite Manager class の作成
スプライトを表示する API はシンプルに、
draw(0, x:100, y:50, texture:1)
とする。
また一定間隔(0.1s)でアニメーションする API は、
startAnimation(0, sequence: [1,2,3], timePerFrame: 0.1, repeat: true)
という感じにする。
このような API を持つ、次の CgAssetManager クラスを継承する CgSpriteManager クラスを作成する。
Asset Manager class の作成
テストで使用するアセット画像は下記で、これを 16x16ドットで切り出して、テクスチャ番号を割り当てる CgAssetManager クラスを作成する。
[spriteTest.png]
テクスチャ番号は左下から右上へ、下記のように割り当てる。
4 | 5 | 6 | 7 |
---|---|---|---|
0 | 1 | 2 | 3 |
スプライト描画のプログラム
// Create a sprite manager object.
let sprite = CgSpriteManager(view: self, imageNamed: "spriteTest.png", width: 16, height: 16, maxNumber: 64)
// Draw a #0 sprite with #1 texture at (8,8) position.
sprite.draw(0, x: 8, y: 8, texture: 1)
CgSpriteManagerクラスは、"spriteTest.png" のアセット画像から 16x16ドットでテクスチャを切り出し、同時に最大64個のスプライトを描画するための管理番号を持つ SpriteManager オブジェクトを生成する。
SpriteManagerオブジェクトの draw API で、スプライト管理番号0とし、ピクセル座標(8, 8)にテクスチャ番号1のスプライトを描画する。
Background Manager class の作成
背景となるタイル描画も必要で、8x8ドットのピクセル文字も再現したい。
これらも同様に、大きな背景アセット画像から 8x8ドットのテクスチャを切り出し、テクスチャ番号で扱えるようにする。
背景のタイル描画 API は、
put(0, column: 2, row: 3, texture: 1)
とする。
また、ピクセル文字を表示する API は、
putString(0, column: 2, row: 4, string: "TEST")
という感じにする。
テストで使用する1つのアセット画像は下記で、SpriteManagerと同様に、8x8ドットに切り出してテクスチャ番号を割り当てる CgAssetManager クラスを継承して、CgBackgroundManager クラスを作成する。
[backgroundTest.png]
背景描画のプログラム
// Create a background manager object.
let background = CgBackgroundManager(view: self, imageNamed: "backgroundTest.png", width: 8, height: 8, maxNumber: 2)
// Draw a #0 background of (28x36) size at (14*8,18*8) position.
background.draw(0, x: 14*8, y: 18*8, columnsInWidth: 28, rowsInHeight: 36)
// Put a #1 texture on #0 background at (14,19).
background.put(0, column: 14, row: 19, texture: 1)
// Print text on #0 background at (8,18).
background.putString(0, column: 8, row: 18, string: "SPRITEKIT TEST", offset: -16*2 /* ASCII offset */)
CgBackgroundManagerクラスは、"backgroundTest.png" のアセット画像から 8x8ドットのテクスチャを切り出し、同時に最大2面の背景を描画するための管理番号を持つ BackgroundManager オブジェクトを生成する。
BackgroundManagerオブジェクトの draw API で、背景管理番号0座標(148, 188)に 28x36 のタイル面を生成・描画する。1つのタイルサイズは、テクスチャサイズの 8x8ドットとなる。
put API は、背景管理番号 0 のタイル位置(14, 19)にテクスチャ番号 1 のタイルを描画する。
putString API では、背景管理番号 0 のタイル位置(8, 18)に "SPRITEKIT TEST" のタイルを描画する。先頭文字 "S" の ASCIIコードは 83 となり、offset: -16*2 を引いた 51番のテクスチャを描画する仕組みとなる。
#テスト・プログラム
GitHub に公開しているテスト・プログラムを実行すると、以下のような画面が表示され、赤モンスターがアニメーションしながら移動する。
Asset/Sprite/Background Manager クラスは、SpritekitManager.swift ファイルにコーディングしている。コメント入れて 500行未満。
GameViewController.swift では、GameSceneのサイズを (288, 368)ドットに変更している。
class GameViewController: UIViewController {
private var scene: SKScene!
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
let size = CGSize(width: 28*8, height: 36*8)
scene = GameScene(size: size)
// Set background color to black
scene.backgroundColor = UIColor.black
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFit
// Present the scene
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
GameScene.swift の didMove関数で Managerオブジェクトの生成と描画、update関数で赤モンスターを移動させている。
class GameScene: SKScene {
private var sprite: CgSpriteManager!
private var background: CgCustomBackground!
override func didMove(to view: SKView) {
// Create sprite and background objects.
sprite = CgSpriteManager(view: self, imageNamed: "spriteTest.png", width: 16, height: 16, maxNumber: 64)
background = CgCustomBackground(view: self, imageNamed: "backgroundTest.png", width: 8, height: 8, maxNumber: 2)
// Draw cherries.
sprite.draw(0, x: 8, y: 8, texture: 3)
sprite.draw(1, x: 16*13+8, y: 8, texture: 3)
sprite.draw(2, x: 8, y: 16*17+8, texture: 3)
sprite.draw(3, x: 16*13+8, y: 16*17+8, texture: 3)
// Draw and animate a Pacman.
sprite.setPosition(4, x: 13*8+8, y: 16*12)
sprite.setRotation(4, radians: CGFloat(90.0 * .pi / 180.0))
sprite.startAnimation(4, sequence: [0,1,2], timePerFrame: 0.1, repeat: true)
// Draw grids on #0 background.
background.draw(0, x: 14*8, y: 18*8, columnsInWidth: 28, rowsInHeight: 36)
background.setDepth(0, zPosition: 0)
for y in 0 ..< 18 {
for x in 0 ..< 14 {
background.put(0, column: x*2, row: y*2, columnsInwidth: 2, rowsInHeight: 2, textures: [4,5,6,7])
}
}
// Print text on #1 background.
background.draw(1, x: 14*8, y: 18*8, columnsInWidth: 28, rowsInHeight: 36)
background.setDepth(1, zPosition: 1)
let asciiOffset = -16*2
background.putString(1, column: 8, row: 18, string: "SPRITEKIT TEST", offset: asciiOffset)
// Put a #63 texture on #1 background.
background.put(1, column: 14, row: 19, texture: 128)
}
private var x: CGFloat = 0
private var dx: CGFloat = 0
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Move and animate a Ghost.
if x == 0 {
dx = 1
sprite.startAnimation(5, sequence: [4,5], timePerFrame: 0.1, repeat: true)
} else if x == 28*8 {
dx = -1
sprite.startAnimation(5, sequence: [6,7], timePerFrame: 0.1, repeat: true)
}
x += dx
sprite.setPosition(5, x: x, y: 16*6)
}
}
#参考
#次の記事
[【入門】iOS アプリ開発 #3【Sound を再生する】]
(https://qiita.com/KIKU_CHU/items/01a45a3d4698058bd512)