Swift

Swiftでシューティングゲームを作ろう

はじめに

こんにちは、東京大学 工学部 電気電子工学科というところに通っている学部3年生です。
インターンなどの機会を利用させていただきながらプログラミングの学習に励んでいます。
大学でのプログラミングに関する授業はアルゴリズムや計算量、CPUなどの低レイヤーな分野を中心的に扱うことが多いです。
そのため、Webサービスを利用しているユーザに近い部分を扱うことが少なかったりします。
この記事がそのあたりに興味を持つきっかけになればとても嬉しいです。

この記事の目的

次の理由から、SwiftによるiOSゲーム開発を行うチュートリアルを作りました。
よりユーザに近い部分を開発することに興味を持っていただければ嬉しいです。

  • 自分が持っているiPhoneで作ったものが動くと楽しい
  • Macを持っていればXcodeをダウンロードするだけで開発構築が完了する
  • メモアプリとかを作るよりはゲームを作ったほうが楽しいんじゃね?

この記事の内容

  • Swiftというプログラミング言語を使ってiPhoneのシューティングゲームを作ります
  • フレームワークにはSpriteKitというものを使います
  • iOSなのでMacを持っている人が対象です
  • 当方、Swiftでのサービス開発の経験はありますが、ゲーム開発の経験はないので何かおかしいところがあるかもしれません
  • Xcode 9.2(Swift 4.0.3)を利用するのでここから「Xcode 9.2」をダウンロードしておいてください
  • Swiftの文法(変数定義やif文、for文など)について軽く知っておくと理解がスムーズになると思います(軽く検索すると出てくるのでこの記事では割愛します)
  • Xcodeのダウンロードには時間がかかるので、その間にSwiftの文法をちょっと見てみるのはいかがでしょうか!

シューティングゲームを作ろう

完成品

本チュートリアルで作成するゲームの完成品コードはここに置いています。
リンク先に今回作るゲームのGIF画像が置いてあるので、どんなものを作るか知りたい人は見てみてください。

プロジェクトの作成

ゲームアプリを開発するためにプロジェクトを作成します。

  1. Xcodeを開く → Create a new Xcode projectを選択 → Gameを選択してNext
  2. Product Nameには「shooting-game」と入力
  3. Teamは「None」のままでOK(なんでもOK)
  4. Organization Nameは自分の名前とかにする(なんでもOK)
  5. Organization Identifierには「com.自分の名前」とかにする(なんでもOK)
  6. Languageは「Swift」に
  7. Game Technologyは「SpriteKit」に
  8. チェックボックスは全部外しておいてOK

スクリーンショット 2017-12-16 23.19.51.png

ビルドできるか確認

プロジェクトが作成できたら次のような画面が表示されているはずです。
まずは何もせずに左上の「三角ボタン」を押してください。
デフォルトで生成されているコードがビルドされて「Hello, World」と表示されたiPhoneシミュレータが立ち上がるはずです。
試しに画面をタップしたりスワイプしたりしてみましょう。なんか四角のリングみたいなのがフワフワ出てくるゲーム(?)で遊べます。
ちなみに、三角ボタンの横の「iPhone 8 Plus」を変更すると好きなiPhoneでビルドすることができます。

スクリーンショット 2017-12-16 23.38.25.png

コーディングの準備

コーディングを始める前に、デフォルトで記載されているコードを削除して、ゲームに使用する素材画像をインポートします。

  1. GameScene.sksというファイルを開いて「Hello, World」というラベルを選択してDelete
  2. GameScene.swiftを開いていらないコードを消す
    • ここを参考にコードを削除してください
    • この記事ではこんな感じでGitHubの「Pull Request」を参考に実装を進めていきます
    • GitHubもPull Requestもよく分からなくても大丈夫です。背景が赤い部分は消すコード、緑の部分は追加するコードということを覚えておけば問題ありません。ファイル名も書いてあるのでどのファイルのコードを触ればいいかも分かります。
  3. ここの画像をすべてダウンロードする(すべてフリー素材です)
  4. Assets.xcassetsを開いて、先程ダウンロードした画像をドラッグ&ドロップする

メニュー画面とゲーム画面

アプリ起動時に表示されるメニュー画面からゲーム画面に遷移するところまでを作ります。
ちょっとこのあたりはXcodeの使い方を知らないと分かりにくいところなので、画像多めで解説していきます。

  1. Main.storyboardを開くと次のような画面が表示される スクリーンショット 2017-12-17 0.15.53.png
  2. 黒い画面の横あたりにに右下の「View Controller」をドラッグ&ドロップする スクリーンショット 2017-12-17 0.16.24.png
  3. 黒い画面の左側に付いている矢印マークを先程追加した白い画面に持っていく スクリーンショット 2017-12-17 0.17.13.png
  4. 右下の検索ボックスに「button」と入力して出てくる「Button」を白い画面の中央に持ってきて、そのボタンをダブルクリックして「Start」というテキストに変更する スクリーンショット 2017-12-17 0.17.55.png
  5. 先程追加したボタンを「Ctrlキー」を押しながら右側の黒い画面にドラッグ&ドロップする。2つの画面の間に矢印が表示されれば成功です。 スクリーンショット 2017-12-17 0.28.52.png
  6. 左上の三角ボタンを押してRunしてみましょう。Startボタンをタップすると黒い画面に遷移するようになっているはずです。(ちなみに、Cmd + RでRunすることもできます)

地球の追加

ゲーム画面の下の方にずっと表示される地球を追加します。
ここを参考にGameSceneクラスのdidMove(to:)メソッドにコードを追加しましょう。

  • didMove(to:)メソッドはゲーム画面に遷移したときに自動的に実行される
  • 地球を表示する処理はこのメソッド内に書けば良い
  • SKSpriteNodeはゲーム画面に表示するオブジェクトのクラスです
  • CGPointは画面内の座標(原点は画面中央)を表すクラスで、そのインスタンスをearthpositionプロパティに代入します
  • earthのスケールや位置を調整して、最後にaddChild(_:)を呼んで画面に地球を表示します

宇宙船の追加

ゲーム画面に「端末の傾きで横方向に移動し、タップでミサイルが発射される宇宙船」を追加します。

  1. ここを参考に宇宙船を画面に追加する
  2. ここを参考に、端末の傾きによって宇宙船が動くようにする
    • CoreMotionというフレームワークをimportし、CMMotionManagerというクラスを利用すると端末の傾きを取得できます
    • ここからは端末の傾きを取得する必要があるため、iPhone Simulatorではなく実機でビルドする必要があります。ここなどを参考にお持ちのiPhoneでビルドしてみてください。iPhoneを持っていないなら、少し難しいかもしれませんが、画面タップで宇宙船が左右に移動するように変更してみましょう。
  3. ここを参考に、画面をタップすると宇宙船からミサイルが発射されるようにする
    • SKActionクラスの機能を使ってSKSpriteNodeを動かすことができます
    • SKSpriteNodeクラスのメソッドrun(_:)を利用して実際に指定したSKActionを実行させることができます

小惑星を追加

ゲーム画面に「1秒ごとにランダムな位置に出現して迫ってくる小惑星」を追加します。
ここを参考に一定時間ごとに小惑星を出現させましょう。

  • TimerクラスのscheduledTimer(withTimeInterval:repeats:block:)メソッドを使うと一定時間後にblockに渡した関数を実行してくれます
  • repeatstrueにすると一定時間ごとに同じ処理を行ってくれます
  • arc4random_uniform(_:)という0から引数の間のランダム値を生成してくれる関数を利用して出現する小惑星を3種類から選択しています

衝突処理

「ミサイルと小惑星」、「小惑星と宇宙船」、「小惑星と地球」の衝突処理を実装します。

  1. FileNewFileSpriteKit Particle Fileを選択してNext
  2. Particle temprateがFireになっていることを確認してNext
  3. Explosion.sksという名前にして保存
  4. ここを参考に衝突処理を行うための準備を行う
    • SKSpriteNodeにはphysicsBodyというプロパティがある
    • そこにSKPhysicsBodyのインスタンスを代入するとSKSpriteNodeが当たり判定を持つようになる
    • SKPhysicsBodyにはcategoryBitMaskcontactTestBitMaskcollisionBitMaskという3つの衝突に関するプロパティがある
    • このあたりは説明すると少し長くなるので、今回はcategoryBitMaskにはそのノードのIDに相当するもの(2進数)、contactTestBitMaskにはそのノードが衝突する相手のID(2進数)を代入すると良いということだけ覚えておけば十分です。
  5. ここを参考に衝突時に小惑星が爆発するようにする
    • 衝突するとdidBegin(_:)が自動的に呼ばれる
    • didBegin(_:)に渡ってくるcontactbodyAbodyBを持っていて、それぞれ衝突した2つのノードのSKPhysicsBodyになっている
    • しかし、例えば小惑星と宇宙船が衝突したとき、bodyAbodyBのどちらが小惑星でどちらが宇宙船かどうかは分からないため、categoryBitMaskを利用して自分で判別してあげる必要がある
    • 爆発のエフェクトにはSKEmitterNodeを使用する。特にSKEmitterNodeに特有の処理は行わないのであまり気にする必要はない

スコアとライフ

「小惑星をミサイルで破壊したときにスコアが増える処理」と「宇宙船と地球が小惑星に衝突したときにライフが減る処理」を実装します。

  1. ここを参考にライフを導入する
    • didMove(to:)の中でSKSpriteNodeの配列であるheartsを初期化する
    • 小惑星が宇宙船か地球と衝突したときにheartsを1つ減らす処理を行う
  2. ここを参考にスコアを導入する
    • スコアの表示にはテキストを表示するためのノードであるSKLabelNodeを利用する
    • スコアを管理するscoreプロパティにdidSetを持たせることで、scoreに代入する度にscoreLabeltextプロパティを変更する処理を行うことができる
    • ミサイルが小惑星と衝突したときにスコアを加算するのを忘れずに

ゲームオーバー

ライフが0になるとゲームが終了するようにします。

  1. ここを参考に、ライフが0になったときにゲームを一時停止するようにする
    • isPausedプロパティにtrueを代入するとゲームを一時停止できる
    • そのときにtimerプロパティのinvalidate()を呼んでおく
  2. ここを参考に一時停止してから1秒後にメニュー画面に戻るようにする
    • GameViewControllerクラスのdismiss(animated:completion:)メソッドを呼ぶと最初のメニュー画面に戻ることができるが、GameSceneからGameViewControllerのメソッドを直接呼ぶことができない
    • よってGameViewControllerからGameSceneのインスタンスを作るときにselfGameSceneに渡すようにして、gameOver()メソッドの中で渡ってきたgameVC(型はGameViewController)のdismiss(animated:completion:)を呼ぶ

ベストスコアの保存と表示

ここを参考にゲーム画面にベストスコアを表示するようにしましょう。

  • 値を保存するのには様々な方法がありますが、今回は一番簡単なUserDefaultsを使う方法で行う
  • AppDelegate.swiftapplication(_:didFinishLaunchWithOptions:)に書かれた処理は初めてアプリが起動されたときにのみ実行される
  • UserDefaultsでは辞書型で値を保持していて、キーに対する値をAppDelegateで登録する
  • ベストスコアが更新されていたらUserDefaultsに保持している値を更新する

難易度を上げていく

このままでは無限にプレイできてしまうので、小惑星が迫ってくるスピードを時間が経つにつれて早くなるように変更します。
ここを参考に、時間とともに小惑星のスピードを上げていくようにします。
ここは今までの知識を使えばできるので答えを見ずに実装してみてもいいかもしれません。

完成です!

これでとりあえずは完成です!
お疲れ様でした!
ちなみにこのゲームの僕のベストスコアは390点です。
皆さんはどうでしたか?

アレンジする

しかし、このアプリにはまだまだ改善点があります。
例えば以下のようなものがあるのでできそうなものから改善に挑戦してみましょう。
もちろん自分でオリジナルの機能を考えて実装してみるのも素晴らしいです!

  • 一時停止機能を追加する
  • 最初から始める機能を追加する
  • 途中でやめる機能を追加する
  • 難易度の上げ方(今は単純に小惑星のスピードを上げているだけ)を工夫する
  • 左右だけでなく上下にも動けるようにする
  • ときどきアイテムが落ちてきてミサイルが強くなったりする
  • didMove(to:)にいっぱいコードを書いていて可読性が悪いのでコードを整理(リファクタリング)する

おわりに

最後まで読んでいただきありがとうございました!

今回はSwiftでiOSゲームを開発しましたが、ゲームに限らずユーザに近い部分の開発に興味を持っていただければ嬉しいです。
もちろんSwiftを使ったiOSアプリ開発もオススメです。
まずは今回作ったゲームを改良してもいいですし、オリジナルのアプリを作ってみてもいいですね。
ゲーム以外のアプリを作るなら、ちょっと内容が古い記事になってしまっていますが、僕が以前書いた記事が参考になるかもしれません。