【GameMaker:Studio2】フラッピーバードもどきを作るチュートリアル

# 始めに
今回はフラッピーバードもどきを作ってみます

## 素材データ
今回使用する素材データは以下のURLからダウンロードできます
http://syun777.sakura.ne.jp/tmp/gms2/gms2_FlappyBird.zip


## プロジェクトの作成

新規プロジェクトを作成します。
プロジェクト名は「FlappyBird」としておきます。
![094.png](https://qiita-image-store.s3.amazonaws.com/0/43772/5cb8be37-ab62-7860-a31b-647b2882be6e.png)

## 基本設定の変更
今回のゲームは フレームレートを 60 にします。
メニューバーの歯車アイコンをクリックして、GameOptions を開き、Main Options > General > Game frames per second を 60 にします。
![105.png](https://qiita-image-store.s3.amazonaws.com/0/43772/08feb02a-7c5b-f67a-c252-9775f4330ac3.png)

デフォルトの30はアクションゲームではガクガク動くように見えるので、60がおすすめです。
ただ、そのぶん描画コストが上がり、処理が重たくなってしまうので、描画負荷が少なくて済むように作る必要があります。

今回のゲームはとてもシンプルなですので、そのあたりの心配は必要ありません。

## プレイヤーオブジェクトの作成
Workspaceの何もないところを右クリックして、Resources > Create Objectを選び、オブジェクトを作成します。
![095.png](https://qiita-image-store.s3.amazonaws.com/0/43772/ce9ca195-7f05-1f64-d2e7-4aaea2453497.png)

オブジェクト名は objPlayerとし、New Spriteをクリックしてスプライトを追加します。
![096.png](https://qiita-image-store.s3.amazonaws.com/0/43772/bbd4ae7a-d66b-4309-74da-e3775d8cfa8f.png)

スプライト名は、sprPlayerとし、Importをクリックして画像を取り込みます。
![097.png](https://qiita-image-store.s3.amazonaws.com/0/43772/8bf1458d-9235-3d82-e90d-a3975c7d0697.png)

あらかじめダウンロードした素材、```player_strip2.png``` をインポートすると、
スプライト内に2つのフレームへ画像が分割された状態となります。
このように、ファイル名の末尾に "strip#" という文字があると、その画像はアニメーションするために分割されたデータとみなされます。
![098.png](https://qiita-image-store.s3.amazonaws.com/0/43772/d8a7892d-3c12-9ec8-05a8-e46270673208.png)

三角ボタン(▷) をクリックしてアニメーションを再生すると、高速で画像が切り替わってしまいます。
これは Speedの部分が「15」となっていて速すぎるので、「3」に減らします。
![099.png](https://qiita-image-store.s3.amazonaws.com/0/43772/341d27f9-e50a-f63b-861b-3e23494dfc29.png)

再び再生すると、よい感じにアニメーションするようになりました。
次にobjPlayerの動きを定義します。
objPlayerの Add Event > Create を選び、Createイベントを作成します。
![103.png](https://qiita-image-store.s3.amazonaws.com/0/43772/5f86fffc-dbd3-001a-d0ed-c7d92bd9a50b.png)

Toolbox に "move" と入力し、「Set Gravity Force」アクションを配置します。
設定は以下の通りです。
* Force: 2 (Relativeにチェックは入れない)
![104.png](https://qiita-image-store.s3.amazonaws.com/0/43772/b78ee222-0118-689f-e2c8-a72061e76be5.png)

続いて、Spaceキーを押したら、ジャンプするようにします。
objPlayerの Add Event から、Key Pressed > Space イベントを作成します。
![106.png](https://qiita-image-store.s3.amazonaws.com/0/43772/93970b82-d3b9-23fe-e3d0-dcaef3cf7043.png)

Toolbox に "move" と入力して、「Set Speed」アクションを配置します。
設定は以下の通りです。
* Type: Vertical
* Speed: -20 (Relativeにチェックは入れない)
![107.png](https://qiita-image-store.s3.amazonaws.com/0/43772/a22b64d6-b3f4-f857-3e55-1a0df5358ab7.png)

これは垂直方向の上向きに 20 の速度を設定するものとなります。

## プレイヤーをルームに配置する

プレイヤーの動きができたので、ルームに配置していきます。
右側にある Resources タブから、Rooms > room0 をダブルクリックして、room0 を開きます。
![100.png](https://qiita-image-store.s3.amazonaws.com/0/43772/3868571f-23aa-3189-b3e4-585599b2e44d.png)

GameMaker:Studio2 のルームサイズのデフォルトは 1024 x 720 のなのですが、これは大きすぎるので、800 x 480 に減らします。
左下にある Room Properties から、Room Settings > Width を 800、Height を 480 に変更します。
![101.png](https://qiita-image-store.s3.amazonaws.com/0/43772/a0f9438a-71ee-9ca5-6235-3a921cfc0e7a.png)

もし、Room Properties が表示されていない場合は、room0 を開いた後に、メニューバーから、Room > Room Properties を選んで表示させてください。
![122.png](https://qiita-image-store.s3.amazonaws.com/0/43772/67e59ada-502e-aecc-78de-bb8edb89f33f.png)

では、objPlayer を ルーム内に配置します。
右にスクロールするゲームを想定しているので、左側にプレイヤーを配置します。
![102.png](https://qiita-image-store.s3.amazonaws.com/0/43772/74306409-085b-6c27-86d3-173be1da3292.png)

実行すると、プレイヤーが落下します。
Spaceキーを押すと、少しだけ浮上しますので、Spaceキーを連打して、落ちないようする挙動が確認できました。

## 障害物オブジェクトの実装
Resourcesタブから、Objectsを右クリックして、Create Object を選びます。
![108.png](https://qiita-image-store.s3.amazonaws.com/0/43772/acfc3bcf-be40-ff76-4d5f-ae8a17167bcd.png)

オブジェクトの Name は objBlock を指定し、New Spriteを選びます。
![109.png](https://qiita-image-store.s3.amazonaws.com/0/43772/bf9792cf-5605-a1bb-9b6d-aabe63c08a6c.png)

スプライトの Name は sprBlock を指定し、Import をクリックします。
![110.png](https://qiita-image-store.s3.amazonaws.com/0/43772/1a963833-a1e3-1e9c-9b23-48cd6c052fbe.png)

```5box.png``` を選び、開くをクリックして画像をインポートします。
![111.png](https://qiita-image-store.s3.amazonaws.com/0/43772/87a31835-e4d1-7504-3e20-1b5d23def04a.png)

objBlock に戻り、Createイベントを作成します。
Add Event > Create を選びます。
![118.png](https://qiita-image-store.s3.amazonaws.com/0/43772/e0ea86cc-14a0-5575-b2be-47fb8e238ef2.png)

アクションは「Set Speed」を選びます。
設定は以下のようにします。
* Type: Horizontal
* Speed: -5

![123.png](https://qiita-image-store.s3.amazonaws.com/0/43772/cae94b39-8fd7-d88b-2f18-29b29ce634d5.png)

## プレイヤーの死亡判定を実装する
objPlayer を開いて、Add Event > Other > Outside Room イベントを作成します。
これは、オブジェクトが画面外に出た時に発生するイベントとなります。
![112.png](https://qiita-image-store.s3.amazonaws.com/0/43772/0ce564ac-3ce3-4bc5-0c56-9173cd7a5499.png)

アクションを設定します。
Toolbox に "destroy" と入力して、「Destroy Instance」アクションを配置します。
![113.png](https://qiita-image-store.s3.amazonaws.com/0/43772/2c606e90-664a-4278-ca7a-d7ca5792d7a8.png)

死亡演出はここに定義せずに、オブジェクトが消えるときに発生する「Destroy」イベントに定義することにします。
Add Event > Destroy を選びます。
![114.png](https://qiita-image-store.s3.amazonaws.com/0/43772/6ebb3fa9-e7ed-1e54-75a5-bec0810c5671.png)

Toolbox に "do effect" と入力して、☆のアイコンを配置します。
設定は以下の通りです。
* Type: Firework
* Where: Below Objects
* Size: Large
* X: 32 (Relativeにチェックを入れる)
* Y: 32 (Relativeにチェックを入れる)
![115.png](https://qiita-image-store.s3.amazonaws.com/0/43772/b0ca7d40-29fd-6152-3126-7fe2f5a66d05.png)

※Relativeにチェックを入れ忘れると、おかしな場所にエフェクトが出てしまうので付け忘れないように注意してください
※(X, Y)に(32, 32)を指定しているのは、スプライトの原点を左上としているためです

少しややこしいですが、以下のようなイベントの数珠つなぎにより、消滅エフェクトが発生するようになります

1. プレイヤーが画面外に出た → Outside Roomイベントが発生
2. Outside Roomイベント内で、消滅アクション(Destroy Instance)を呼び出す → Destroyイベントが発生
3. Destroyイベント内で、消滅エフェクトが発生
![125.png](https://qiita-image-store.s3.amazonaws.com/0/43772/c12274e0-aa78-f4ea-db27-44ba5c7e1048.png)

なぜこのような面倒なことをするのかというと、プレイヤーの死亡ルールが複数ある場合に、死亡時の消滅エフェクトの設定を1つの場所に定義するだけでよいからです。

もう１つの死亡判定である、ブロックとの衝突を実装します。
Add Event > Collision > objBlock イベントを選びます。
![116.png](https://qiita-image-store.s3.amazonaws.com/0/43772/0196d42d-ad1b-8edf-1fec-ac53f9ce3e46.png)

アクションは「Destroy Instance」を配置します。

これにより、
1. 画面外に出た場合
2. objBlockに衝突した場合
どちらの死亡判定が発生しても、最終的には Destroy イベントが呼び出されることとなります。

![126.png](https://qiita-image-store.s3.amazonaws.com/0/43772/04fd7cfd-9484-8fc4-641b-9d4ee8ab76c6.png)

もし、Destroyイベントで処理を共通化しないと、それぞれにイベントに同じ設定のアクションを配置しなければならなくなり、少し面倒になります。

## 障害物をルームに配置する
Resourcesタブから、room0 をダブルクリックしてルームエディタを開き、objBlock を配置します。
![120.png](https://qiita-image-store.s3.amazonaws.com/0/43772/bef16dac-bc2d-eeed-efdc-092d8cf0861c.png)

右側から迫ってくるので、右端に配置した方が良いと思います。
配置できたら、動作を確認します。
障害物が右から迫ってきて、プレイヤーが障害物に接触したり、画面外に出たりすると、死亡します。
![121.png](https://qiita-image-store.s3.amazonaws.com/0/43772/d33d7528-05a6-ed8e-01b4-844275adc771.png)

## 当たり判定の調整
プレイヤーが障害物に少しでもかすめただけでも死亡となってしまうのは厳しすぎるので、当たり判定を少し甘くします。
sprPlayerを開いて、「Collision Mask」の三角アイコン「▷」をクリックして当たり判定を開きます。
![127.png](https://qiita-image-store.s3.amazonaws.com/0/43772/e95088dc-85d9-4670-b5e3-e339b538808b.png)

Modeを「Manual」に変更して、右側の画像を覆っている半透明の黒い四角をマウス操作でサイズを小さくしていきます。画像の例では、
* Left: 21
* Top: 19
* Right: 46
* Bottom: 43
としましたが、おおよその値で構いません。

では、実行して、かすめるだけでは死亡しないことを確認します。

## 障害物を自動生成する
障害物をランダムで出現するようにします。
Resourcesタブ > Objects を右クリックして、Create Object を選ぶ、などをして、新規にオブジェクトを作成します。
オブジェクト名は、"objGame" とします。
ゲームの管理をするだけなので、スプライトには何も設定しません。
![129.png](https://qiita-image-store.s3.amazonaws.com/0/43772/1be9cb04-17d4-9413-d677-c993b9939694.png)

続いて、Add Event > Create を選び、Createイベントを作成します。
![130.png](https://qiita-image-store.s3.amazonaws.com/0/43772/3fee3cf3-cf1a-518a-ceff-1beefe5b9ac4.png)

Toolbox に "alarm" と入力して、「Set Alarm Countdown」アクションを配置します。
設定値は以下のようにします。
* Alarm: 0
* Countdown: 20
![131.png](https://qiita-image-store.s3.amazonaws.com/0/43772/14feba02-f811-3405-32c7-6ce436d78c5e.png)

Alarm アクションは一定時間経過後にアラームイベントを発生させるものとなります。上記設定の場合は、20フレーム (=1/3秒間) 経過後に、アラーム０イベントを発生させます。そして、アラームイベント内で、さらにアラームイベントを呼び出すということをすると、一定間隔で発生するイベントを作成することが可能となります。
![132_2.png](https://qiita-image-store.s3.amazonaws.com/0/43772/d6f14682-c4cf-2beb-f996-93fcfb3f4e2d.png)

Stepイベントでも同じことはできますが、その場合は毎フレーム処理されてしまうので、特別に処理を入れて毎フレーム処理しないようにする必要があります。
アラームイベントであれば、そういった余計な設定なしで手軽に、一定のタイミングで繰り返し行う処理を簡単に実装できるようになります。

では、Alarm 0 イベントを実装します。
Add Event > Alarm > Alarm 0 を選びます。
![133.png](https://qiita-image-store.s3.amazonaws.com/0/43772/42d03163-4e81-e218-b496-2d53666ac502.png)

Toolboxに "alarm" と入力して、「Set Alarm Countdown」アクションを配置します。
* Alarm: 0
* Countdown: 20
![134.png](https://qiita-image-store.s3.amazonaws.com/0/43772/e09ec73d-05e4-40d8-4c5d-8fa45007308f.png)

これで、Alarm 0 イベントが繰り返し呼び出されることになります。

続けて、Toolboxに "instance" と入力して、「Create Instance」アクションを配置します。
設定は以下のとおりです。
* Object: objBlock
* X: room_width
* Y: 64 * random(7)
* Layer: "instances"
* Target: 空欄
![136.png](https://qiita-image-store.s3.amazonaws.com/0/43772/b0033076-86ca-b1cb-0298-31f6f4704bc6.png)

では room0 を開いて、objGameをルーム内に配置します
![137.png](https://qiita-image-store.s3.amazonaws.com/0/43772/22113541-4a82-89e6-7e76-193c8c329825.png)

実行すると、画面の右側から障害物が出現し続けます。

ただ、このままだと生成した障害物が画面から消えても残り続けている（見た目上、消えているだけで実体は存在し続けています）ため、いずれメモリが枯渇した場合にゲームがクラッシュする恐れがあります。
（今回のミニゲームであれば、メモリ使用量が少ないためクラッシュすることはないですが、今後ゲームを作っていく上で、使わなくなったオブジェクトを消す、という考え方は、安定して動作するゲームを作るために重要な考えとなります）

ですので、objBlockを消す処理を入れます。
Add Event > Other > Outside Room を選び、Outside Roomイベントを作成します。
![138.png](https://qiita-image-store.s3.amazonaws.com/0/43772/9fd0cd13-135a-d53a-80a5-e19e2884acf2.png)

Outside Roomイベントには、Destroy Instanceアクションを配置します。
![139.png](https://qiita-image-store.s3.amazonaws.com/0/43772/ed2765dc-0614-f1c8-3e7c-3121de01314e.png)

続けて、Add Event > Destroy を選び、Destroyイベントを作成します。
![140.png](https://qiita-image-store.s3.amazonaws.com/0/43772/49d2d784-f93c-d74a-8f66-cc142c15f60f.png)

Toolbox に "do effect" と入力して、Do Effectアクションを配置します。
設定は以下のとおりです。
* Type: Explosion
* Where: Below Objects
* Size: Medium
* X: 32 (Relativeにチェックを入れる)
* Y: 32 (Relativeにチェックを入れる)
* Colour: $FFFFFFFF
![141.png](https://qiita-image-store.s3.amazonaws.com/0/43772/e6effc9a-7e84-c042-bebc-d6fa04ddf77d.png)

実行すると障害物が画面端に消えると、エフェクトを発生させます。

## ゲームオーバー判定を入れる
このままだとゲームオーバー時にやり直しができないので、ゲームオーバー時にやり直しができるようにします。

objGameを開いて、Add Event > Key Pressed > Shiftイベントを追加します。
![146.png](https://qiita-image-store.s3.amazonaws.com/0/43772/552f7967-11b2-1dbd-0315-dc2f355d9252.png)

Toolboxに"if"と入力して、If Instance Existsアクションを配置します。
![143.png](https://qiita-image-store.s3.amazonaws.com/0/43772/7cf18f83-9577-c4d7-d663-f8c75be3dd58.png)

「If」アクションはゲームのルールを設定するものです。例えば、
* 「スコアを1000点取得したら」ゲームクリア
* 「残機が０になったら」ゲームオーバー
* 「パワーアップアイテムを取ったら」攻撃力アップ
といったように「～したら」「～になったら」という条件を定義するアクションです。
「If Instance Exists」は、特定のオブジェクトが存在したら、もしくは存在しなくなったら、というルールを指定する「If」アクションとなります。

「If Instance Exists」アクションのObjectの設定には、objPlayerを指定します。
アクション内の右側にあるアイコンをクリックすると、対象となるオブジェクトを選択するダイアログが表示されるので、そこから 「objPlayer」を選びます。
![144.png](https://qiita-image-store.s3.amazonaws.com/0/43772/13a9565f-5b48-f30a-adb1-95f2a4359840.png)

また、"Not" にはチェックを入れておきます。
![145.png](https://qiita-image-store.s3.amazonaws.com/0/43772/b6f77969-c9da-bfdb-501c-9595362b6bdd.png)

この設定により、「objPlayerが存在しなかったら」というルールを指定することができます。（Notにチェックを入れると反対の条件になります）

では、この条件により発生するアクションを設定します。
Toolboxに "game" と入力して、「Restart Game」アイコンを 赤い文字で Empty と書いてある部分にドラッグ＆ドロップします。
![147.png](https://qiita-image-store.s3.amazonaws.com/0/43772/261db7d8-05b5-f326-2baa-7b54efd3ff91.png)

このように接続されればOKです。
![148.png](https://qiita-image-store.s3.amazonaws.com/0/43772/9d0e718d-f9ee-be65-aa9c-71d9cd2f059f.png)

では実行して動作を確認します。
プレイヤーが消滅すると、Shiftキーを押すことでゲームを再スタートすることができます。

続けて、リスタートできる場合には、リスタートできることをテキスト表示する機能を作ります。
objGameを開いて、Add Event > Draw > Draw GUI イベントを作成します。
![149.png](https://qiita-image-store.s3.amazonaws.com/0/43772/7c52eb1c-a078-fbab-0f89-cf8bc0fcc79f.png)

「If Instance Exists」アクションを配置して、先ほどと同じ値を設定します。
* Object: objPlayer
* Not: チェックを入れる
![145.png](https://qiita-image-store.s3.amazonaws.com/0/43772/8f5ab947-f3e0-d672-4b02-7793cd1742f1.png)

この条件により発生するアクションを設定します。
Toolboxに "text" と入力して、「Draw Transformed Value」アクションを配置します。
![150.png](https://qiita-image-store.s3.amazonaws.com/0/43772/11bccb69-5d64-2d9a-5106-bb285cf2e46f.png)

ドラッグ＆ドロップする場所は、Empty の文字の右側あたりです。
![151.png](https://qiita-image-store.s3.amazonaws.com/0/43772/406523fe-440e-cc59-856f-2f106a43e390.png)

「Draw Transformed Value」には以下の値を設定します。
* Caption: "SHIFT to Restart"
* Value
 * X: 0
 * Y: 0
 * Scale X: 3
 * Scale Y: 3
 * Rotation: 0
![153.png](https://qiita-image-store.s3.amazonaws.com/0/43772/2687d75f-3f40-1e3e-7f42-a99269c0b9f0.png)

Scale X / ScaleY というのは、それぞれ横方向と縦方向の文字の拡大率となります。
3なので、テキストを3倍に拡大して描画します。

なお、配置アクションが画面内に入っておらず入力しにくい場合は、"Draw Transformed Value" と書いてあるウィンドウバーの部分をダブルクリックすると画面内に入ってくれます。

実行すると、プレイヤー死亡時にテキストが表示されます。
![152.png](https://qiita-image-store.s3.amazonaws.com/0/43772/5a7ff9b0-1ce4-0f00-a649-216f419779a9.png)

## 少しずつゲームを難しくする
今のままだと同じ速度で障害物が出るだけなので、ゲームに変化がなく面白くありません。
障害物を少しずつ速くすることでゲームを難しくしてみます。

objGameを開いて、Variable Definitionsをクリックします。
![154.png](https://qiita-image-store.s3.amazonaws.com/0/43772/4ec10b15-0dd5-4a44-bf59-6127c9690338.png)

すると、Variable Definitionsウィンドウが開きます。
これは、オブジェクトの変数を定義するものとなります。
オブジェクトには、速度を表す vspeed や hspeed 変数がありますが、それ以外の変数を持たせることができます。

Add をクリックして以下のように設定します
* Name: block_speed
* Default: 5
* Type: Real
![155.png](https://qiita-image-store.s3.amazonaws.com/0/43772/f88b6fbc-cc4c-f204-9631-9ee43d54f375.png)

変数「block_speed」を初期値「5」で作成する、という設定になります。

続けて、objGameの 「Alarm 0」 イベントを開きます。
配置済みの「Create Instance」アクションの「Target」の値を「inst」にして、Tempにチェックを入れます。
![156.png](https://qiita-image-store.s3.amazonaws.com/0/43772/9646f98f-a5c6-8d5e-afef-36266d033adb.png)

これは、生成したオブジェクトを "inst" というローカル変数に代入する指定となります。
ローカル変数というのは、先ほど定義した "block_speed" 変数と異なり、このイベント内でしか使用できない変数となります。

* オブジェクト変数：obj_block内からであればいつでも使える
* ローカル変数：定義したイベント内でしか使えない (この場合は Alarm 0 イベント内でのみ有効)

続けて、Toolboxに"apply" と入力して、「Apply To」アクションを一番下に配置します。
![157.png](https://qiita-image-store.s3.amazonaws.com/0/43772/63e193e2-3071-c5fe-a853-aca33f63787a.png)

そして、▼の部分をクリックします。
![158.png](https://qiita-image-store.s3.amazonaws.com/0/43772/c80662fb-544b-452c-987e-1767796d51bc.png)


すると適用するオブジェクトを指定するダイアログが表示されるので、Expressionの部分に「inst」と入力します。
![159.png](https://qiita-image-store.s3.amazonaws.com/0/43772/49818299-df2d-544c-70a1-1dcd05f96ec5.png)

この「Apply To」は指定のオブジェクトに何らかのアクションを適用するために使用します。
「inst」を指定したことで、ローカル変数「inst」に対して操作を行うことができるようになります。
つまり、このイベントで生成したオブジェクトに操作を行うことができます。

では、この「Apply To」につながるアクションを配置します。
Toolboxに"move"と入力して、「Set Speed」アクションを「Apply To」の右側にドラッグ＆ドロップします。
![160.png](https://qiita-image-store.s3.amazonaws.com/0/43772/9715efa4-cc3b-cb23-546a-d5ca9141993d.png)

Typeに「Horizontal」、Speedは「-other.block_speed」を指定します。
![161.png](https://qiita-image-store.s3.amazonaws.com/0/43772/0bf32928-be3e-4bc7-d53f-fc0459e214db.png)

Speedが「-other.block_speed」となっているのは、「Apply To」により現在操作する対象のオブジェクトが「inst」となっているためです。
otherキーワードをつけると、もともとのオブジェクトであった objGame の値をみることができるようになります。
ではこれで、生成した障害物の速度を「block_speed」変数の値で設定できました。

最後にToolboxに"var"と入力して、「Assign Variable」アクションを配置します。
![162.png](https://qiita-image-store.s3.amazonaws.com/0/43772/9c73dabd-2369-c4ce-f80c-ec340831c26d.png)

「Assign Variable」アクションは「Apply To」に下あたりにドラッグ＆ドロップします。
そうすると以下のような配置になります。
![163.png](https://qiita-image-store.s3.amazonaws.com/0/43772/0d144376-a7eb-64ee-b2f9-dec3df164f96.png)

設定値は、
* Name: block_speed
* Value: 1 (Relativeにチェックを入れる)
となります。

これは、"block_speed"変数に1を足し込むアクションとなります。
これにより、障害物が出現するたびに、速度が1上昇していくことになります。

では実行して動作を確認します。
障害物がどんどん速度が上がっていくのが確認できます。


これでゲームは完成となります。