はじめに
前回の続きです。
今回はついにゴール作成です
実装
ゴール判定は迷ったのですが指定のブロックに接触したらゴールしたことにします。
横に 50 個ブロックをならべているので最後の3つのブロックを判定に使います。
クリア画面作成
クリア画面を作成します。
final class ClearScene: SKScene {
private let jumpTextures: [SKTexture] = [
.init(imageNamed: "j1"),
.init(imageNamed: "j2"),
]
override func didMove(to view: SKView) {
backgroundColor = .init(red: 51/255, green: 51/255, blue: 51/255, alpha: 1.0)
let label = SKLabelNode(text: "Clear")
label.fontName = "PixelMplus10-Bold"
label.fontColor = .white
label.position = .init(x: frame.midX, y: frame.midY)
addChild(label)
let floor = SKShapeNode(rectOf: .init(width: 1000, height: 10))
floor.position = .init(x: frame.midX, y: 0)
floor.fillColor = .white
floor.physicsBody = .init(rectangleOf: .init(width: 1000, height: 10))
floor.physicsBody?.isDynamic = false
addChild(floor)
let player = SKSpriteNode(texture: .init(imageNamed: "c1"), size: .init(width: 32, height: 32))
player.position = .init(x: frame.midX, y: 20)
player.physicsBody = .init(texture: player.texture!, size: player.size)
player.physicsBody?.allowsRotation = false
addChild(player)
let action = SKAction.group([
.animate(with: jumpTextures, timePerFrame: 0.2),
.sequence([
.wait(forDuration: 0.2),
.applyImpulse(.init(dx: 0, dy: 13), duration: 0.2)
])
])
player.run(.repeatForever(.sequence([
action,
.wait(forDuration: 0.5)
])))
if let snow = SKEmitterNode(fileNamed: "Snow") {
snow.position = .init(x: size.width/2, y: size.height)
addChild(snow)
}
}
}
こんな感じです。
ゴール判定
GameScene
の didMove
のタイルの physicsBody
設定処理を下記のように修正します。
tilePositions.forEach {
let node = SKSpriteNode()
node.name = "floor"
node.position = .init(x: CGFloat($0.column) * tileSize.width + tileSize.width/2,
y: CGFloat($0.row) * tileSize.height + tileSize.height/2)
node.physicsBody = .init(rectangleOf: tileSize)
node.physicsBody?.isDynamic = false
// ここ追加
if $0.column > tilePositions.last!.column - 3 {
node.physicsBody?.categoryBitMask = 0b0011
node.name = "goal"
} else {
node.physicsBody?.categoryBitMask = 0b0100
}
tileMap.addChild(node)
}
あとは SKPhysicsContactDelegate
の衝突判定で下記の遷移処理を追加すれば完成です。
if nodeA.name == "goal" || nodeB.name == "goal" {
view?.presentScene(ClearScene(size: size), transition: .fade(withDuration: 2.0))
}
微調整
敵の数など微調整していきます。
敵が1体はさみしいので3体表示。
let enemyPositions: [CGFloat] = [400, 800, 950]
enemyPositions.forEach { x in
let enemy = makeEnemy()
enemy.position = .init(x: x, y: frame.midY)
addChild(enemy)
enemy.run(makeEnemyAction())
}
ブロック数修正。
private let tilePositions: [(column: Int, row: Int)] = [
(0, 0), (0, 1),
(1, 0), (1, 1),
(2, 0), (2, 1),
(3, 0), (3, 1),
(4, 0), (4, 1),
(5, 0), (5, 1),
(6, 0), (6, 1),
(7, 0), (7, 1),
(8, 0), (8, 1),
(9, 0), (9, 1),
(10, 0), (10, 1),
(11, 0), (11, 1),
(12, 0), (12, 1),
(13, 0), (13, 1), (13, 2),
(14, 0), (14, 1), (14, 2), (14, 3),
(15, 0), (15, 1),
(16, 0), (16, 1),
(17, 0), (17, 1),
(18, 0), (18, 1),
(19, 0), (19, 1),
(20, 0), (20, 1),
(21, 0), (21, 1),
(22, 0), (22, 1),
(23, 0), (23, 1),
(24, 0), (24, 1),
(25, 0), (25, 1),
(26, 0), (26, 1),
(27, 0), (27, 1),
(28, 0), (28, 1),
(29, 0), (29, 1),
(30, 0), (30, 1),
(31, 0), (31, 1),
(32, 0), (32, 1),
(33, 0), (33, 1),
(34, 0), (34, 1),
(35, 0), (35, 1),
(36, 0), (36, 1),
(37, 0), (37, 1),
(38, 0), (38, 1),
(39, 0), (39, 1),
(40, 0), (40, 1),
(41, 0), (41, 1),
(42, 0), (42, 1),
(43, 0), (43, 1), (43, 2),
(44, 0), (44, 1), (44, 2), (44, 3),
(45, 0), (45, 1), (45, 2), (45, 3), (45, 4),
(46, 0), (46, 1), (46, 2), (46, 3), (46, 4), (46, 5),
(47, 0), (47, 1),
(48, 0), (48, 1),
(49, 0), (49, 1),
]
// ここのrowsも修正
let tileMap = SKTileMapNode(tileSet: tileSet, columns: tilePositions.last!.column + 1, rows: 6, tileSize: tileSize)
プレイヤーの開始位置修正。
player.position = .init(x: frame.midX, y: frame.midY)
完成品はこんな感じです。
おわりに
これで SpriteKit を使った 2D 横スクロールゲームは完成です
あとは SE とか付けるともっとそれっぽくなると思います。SKAudioNode 使うとできそう。SKNode
はその他にも色々あるので下記記事を参考にどうぞ。
SpriteKit あまりさわったことないのでもっといい方法あればぜひ教えて下さい