シューティングゲームのソース(完成版)
import SpriteKit
import GameplayKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate {
var vc: GameViewController!
private var label : SKLabelNode?
private var spinnyNode : SKShapeNode?
var node1 = SKSpriteNode()
var enemyRate : CGFloat = 0.0 // 敵の表示倍率用変数の追加
var enemySize = CGSize(width: 0.0, height: 0.0) // 敵の表示サイズ用変数の追加
var timer: Timer?
let motionMgr = CMMotionManager()
var accelarationX: CGFloat = 0.0
var lifeLabelNode = SKLabelNode() // LIFE表示用ラベル
var scoreLabelNode = SKLabelNode() // SCORE表示用ラベル
// LIFE用プロパティ
var life : Int = 0 {
didSet {
self.lifeLabelNode.text = "LIFE : \(life)"
}
}
// SCORE用プロパティ
var score : Int = 0 {
didSet {
self.scoreLabelNode.text = "SCORE : \(score)"
}
}
// カテゴリビットマスクの定義
let myCategory : UInt32 = 0b0001
let missileCategory : UInt32 = 0b0010
let enemyCategory : UInt32 = 0b0100
override func didMove(to view: SKView) {
var sizeRate : CGFloat = 0.0
var node1Size = CGSize(width: 0.0, height: 0.0)
let offsetY = frame.height / 20
// 画面への重力設定
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
// 以下のコードを追加することでdidBeginメソッドをGameSceneクラス内で呼び出せるようになる
physicsWorld.contactDelegate = self
// 画像ファイルの読み込み
self.node1 = SKSpriteNode(imageNamed: "plane")
// 自機を幅の1/5にするための倍率を求める
sizeRate = (frame.width / 5) / self.node1.size.width
// 自機のサイズを計算する
node1Size = CGSize(width: self.node1.size.width * sizeRate,
height: self.node1.size.height * sizeRate)
// 自機のサイズを設定する
self.node1.scale(to: node1Size)
// 自機の表示位置を設定する
self.node1.position = CGPoint(x: 0, y: (-frame.height / 2) + offsetY + node1Size.height / 2)
// 自機への物理ボディ、カテゴリビットマスク、衝突ビットマスクの設定
self.node1.physicsBody = SKPhysicsBody(rectangleOf: self.node1.size)
self.node1.physicsBody?.categoryBitMask = self.myCategory
self.node1.physicsBody?.collisionBitMask = self.enemyCategory
self.node1.physicsBody?.contactTestBitMask = self.enemyCategory
self.node1.physicsBody?.isDynamic = true
// シーンに自機を追加(表示)する
addChild(self.node1)
// 敵の画像ファイルの読み込み
let tempEnemy = SKSpriteNode(imageNamed: "enemy1")
// 敵を幅の1/5にするための倍率を求める
enemyRate = (frame.width / 10) / tempEnemy.size.width
// 敵のサイズを計算する
enemySize = CGSize(width: tempEnemy.size.width * enemyRate, height: tempEnemy.size.height * enemyRate)
// 敵を表示するメソッドmoveEnemyを1秒ごとに呼び出し
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
self.moveEnemy()
})
// 加速度センサーの取得間隔を設定取得処理
motionMgr.accelerometerUpdateInterval = 0.05
// 加速度センサーの変更値取得
motionMgr.startAccelerometerUpdates(to: OperationQueue.current!) { (val, _) in
guard let unwrapVal = val else {
return
}
let acc = unwrapVal.acceleration
self.accelarationX = CGFloat(acc.x)
}
// ライフの作成
self.life = 3
self.lifeLabelNode.fontName = "HelveticaNeue-Bold"
self.lifeLabelNode.fontColor = UIColor.white
self.lifeLabelNode.fontSize = 30
self.lifeLabelNode.position = CGPoint(
x: frame.width / 2 - (self.lifeLabelNode.frame.width + 20),
y: frame.height / 2 - self.lifeLabelNode.frame.height * 3)
addChild(self.lifeLabelNode)
// スコアの表示
self.score = 0
self.scoreLabelNode.fontName = "HelveticaNeue-Bold"
self.scoreLabelNode.fontColor = UIColor.white
self.scoreLabelNode.fontSize = 30
self.scoreLabelNode.position = CGPoint(
x: -frame.width / 2 + self.scoreLabelNode.frame.width ,
y: frame.height / 2 - self.scoreLabelNode.frame.height * 3)
addChild(self.scoreLabelNode)
}
/// シーンの更新
override func didSimulatePhysics() {
let pos = self.node1.position.x + self.accelarationX * 30
if pos > frame.width / 2 - self.node1.frame.width / 2 { return }
if pos < -frame.width / 2 + self.node1.frame.width / 2 { return }
self.node1.position.x = pos
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 画像ファイルの読み込み
let missile = SKSpriteNode(imageNamed: "missile")
// ミサイルの発射位置の作成
let missilePos = CGPoint(x: self.node1.position.x,
y: self.node1.position.y +
(self.node1.size.height / 2) -
(missile.size.height / 2))
// ミサイル発射位置の設定
missile.position = missilePos
// ミサイルの物理ボディ、カテゴリビットマスク、衝突ビットマスクの設定
missile.physicsBody = SKPhysicsBody(rectangleOf: missile.size)
missile.physicsBody?.categoryBitMask = self.missileCategory
missile.physicsBody?.collisionBitMask = self.enemyCategory
missile.physicsBody?.contactTestBitMask = self.enemyCategory
missile.physicsBody?.isDynamic = true
// シーンにミサイルを表示する
addChild(missile)
// 指定した位置まで0.5秒で移動する
let move = SKAction.moveTo(y: frame.height / 2 - missile.size.height, duration: 0.5)
// 親からノードを削除する
let remove = SKAction.removeFromParent()
// アクションを連続して実行する
missile.run(SKAction.sequence([move, remove]))
}
/// 敵を表示するメソッド
func moveEnemy() {
let enemyNames = ["enemy1", "enemy2", "enemy3"]
let idx = Int.random(in: 0 ..< 3)
let selectedEnemy = enemyNames[idx]
let enemy = SKSpriteNode(imageNamed: selectedEnemy)
// 敵のサイズを設定する
enemy.scale(to: enemySize)
// 敵のx方向の位置を生成する
let xPos = (frame.width / CGFloat.random(in: 1...5)) - frame.width / 2
// 敵の位置を設定する
enemy.position = CGPoint(x: xPos, y: frame.height / 2)
// 敵への物理ボディ、カテゴリビットマスクの設定
enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.size)
enemy.physicsBody?.categoryBitMask = self.enemyCategory
enemy.physicsBody?.isDynamic = true
// シーンに敵を表示する
addChild(enemy)
// 指定した位置まで2.0秒で移動させる
let move = SKAction.moveTo(y: -frame.height / 2, duration: 2.0)
// 親からノードを削除する
let remove = SKAction.removeFromParent()
// アクションを連続して実行する
enemy.run(SKAction.sequence([move, remove]))
}
func didBegin(_ contact: SKPhysicsContact) {
// 炎のパーティクルの読み込みと表示
let explosion = SKEmitterNode(fileNamed: "explosion")
explosion?.position = contact.bodyA.node?.position ?? CGPoint(x: 0, y: 0)
addChild(explosion!)
// 炎のパーティクルアニメーションを0.5秒表示して削除
self.run(SKAction.wait(forDuration: 0.5)) {
explosion?.removeFromParent()
}
// ミサイルが敵に当たった時の処理
if contact.bodyA.categoryBitMask == missileCategory ||
contact.bodyB.categoryBitMask == missileCategory {
self.score += 10
}
// 自機が爆発した時の処理
if contact.bodyA.categoryBitMask == myCategory ||
contact.bodyB.categoryBitMask == myCategory {
// ライフを1つ減らす
self.life -= 1
// 1秒後に restart を実行
self.run(SKAction.wait(forDuration: 1)) {
self.restart()
}
}
// 衝突したノードを削除する
contact.bodyA.node?.removeFromParent()
contact.bodyB.node?.removeFromParent()
}
// リスタート処理
func restart() {
// ライフが0以下の場合
if self.life <= 0 {
for child in self.children {
child.isPaused = true
}
self.timer?.invalidate()
// START画面に戻る
self.run(SKAction.wait(forDuration: 1)) {
self.vc.dismiss(animated: true, completion: nil)
}
}
else {
// ライフが1以上なら自機を再表示
addChild(self.node1)
}
}
}
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
(scene as! GameScene).vc = self
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
return true
}
}