「Flappy Daruma」を作ってみよう!②背景を動かす

今回の内容を整理しよう!

前回の内容を復習しよう

前回は、どんなゲームをどのように作っていくのかを考えて、必要な素材(フォント・画像・音声)データを用意しました。

前回の内容はこちらをご参照ください。

今回進める作業を確認しよう!

今回は、用意した背景と地面の画像を実際にUnityのシーン内に配置して動かしてみます。
まだまだこれだけではゲームとは呼べませんが、背景を無限にスクロールする演出は、さまざまなゲームで応用できるので参考にしてみてください。

 

前提知識を学ぼう!

前回は、バトルシーンに必要な「要素」として、ダルマや地面があることをお話しました。
この「要素」に対応するのが「オブジェクト」です。Unityでは画面上に存在するあらゆるものがオブジェクトとして扱われます。

しかし、オブジェクトは配置されただけでは動きもしなければ見えもしません。
そこで出てくるのが、オブジェクトにさまざまな機能を追加するコンポーネントです。

以下に今回の説明で重要なコンポーネントを紹介します。

画像を扱うSprite Rendererコンポーネント

Sprite Rendererコンポーネントは画像を描画する機能を持ったコンポーネントです。

その他にも、上の動画のように、X軸Y軸で画像を反転させたり、全体の色相・明度・彩度を変更するなどさまざまな機能を持っています。

Sprite Rendererコンポーネントについては、Unityで0から2Dアクションバトルゲームを作ろう!①ゲーム画面の作成の「キャラクターを作成・配置しよう」などでも詳しく説明しているので、そちらも参考にしてください。

位置・角度・拡大率を扱うTransformコンポーネント

Transformコンポーネントは、オブジェクトの位置・角度・拡大率を管理する基本的なコンポーネントです。
このコンポーネントを利用することで、現在のオブジェクトの位置を知ったり、オブジェクトを別の場所へ移動させることができます。

上の動画では、背景1というオブジェクトを左側のシーンビューの中で移動・回転・拡大縮小しています。
この動作と連動して、右側のインスペクターウィンドウのTransformコンポーネントの値が変動していることが確認できます。

背景画像をゲームに配置しよう

画像のインポート

一般的に画像データは.jpgや.pngファイルなどの形式で扱われます。
Unityでこれらの画像ファイルを使うためには、プロジェクトウィンドウ内(プロジェクトのAssetsフォルダ内)に画像ファイルをインポートします。
インポートする際は、エクスプローラーなどで直接Assetsフォルダ以下に画像フォルダをコピー&ペーストします。

また、次のようにプロジェクトウィンドウに直接ドラッグ&ドロップすることでもインポートできます。

ただし、すでに「地面.png」がある状態で、さらに同じファイルをドラッグ&ドロップすると「地面 1.png」のように別ファイルとして新たにインポートされ、上書きはされないことに注意しましょう。

Pixels Per Unitの設定

画像アセットにはPixels Per Unitと呼ばれる値があります。
これは、Unityの画面内で画像をどの程度の大きさで表示するかを指定するもので、1Unit(ワールド座標の単位)がピクセル何個分かを表しています。

Pixels Per Unitは、既定の100の値でも画像を表示できますが、画面解像度やカメラの設定と合っていないと、表示が大きすぎたり小さすぎたりしてしまいます。

今回は、上図の右のように画面解像度(500×700ピクセル)と画像サイズが1:1の比率になるように画像アセットのPixels Per Unitを設定してみましょう。

画面解像度はゲームビューの左上のリストから選択したり、リストの下の[+]ボタンを押して追加することが出来ます。

計算方法としては、カメラのSizeを2倍した値を画面解像度の縦幅で割った値を画像アセットのPixels Per Unitに設定します。
カメラのSizeは、画面に表示する縦幅の半分の長さをUnit単位で表しています。
先ほどカメラのSizeを2倍にしたのは縦幅全体の長さを、画面解像度の縦幅で割り比率(Pixels Per Unit)を得るためです。

例えばカメラのSizeが5で画面解像度が500×700ピクセルの場合、(5×2)/700=70をPixels Per Unitに設定します。
Pixels Per Unitを設定したら、右下の[Apply]ボタンで画像アセットに適用しましょう。

これで、画面解像度と画像サイズの比率と同じ大きさで画像アセットが表示されます。

画像の配置

簡単に画像をゲーム内に表示したい場合は、プロジェクトウィンドウの画像アセットをシーンビューにドラッグ&ドロップするだけで完了です。

これだけで、その画像(今回は「背景」)を表示するSprite Rendererコンポーネントが追加されたオブジェクトが新規作成されます。

これで、画像を表示するオブジェクトがシーンに追加されました。
あとは画面左上の操作ツールや、オブジェクトを選択後インスペクターウィンドウに表示されるTransformコンポーネントの値によって位置などを調整しましょう。

絵柄を反復して大きな画像を表示する

続いて、地面の画像を配置します。
前回、地面は障害物となると説明しましたが、障害物としての機能は次回に実装し、今回は背景画像と同じくスクロールさせるだけです。

地面の画像は背景画像と比べて小さいです。
これを1つ1つ横に並べていくのは大変です。

そんなときは、Sprite RendererコンポーネントのDrawModeをTiledにします。
このとき警告に従い、画像アセットのMesh TypeをFull Rectにしておきます。

このようにすると、通常は大きさを拡大すると絵が伸び縮みしますが、DrawModeがTiledの場合はサイズ内で絵柄を反復して表示できます。


↑Draw ModeがSimpleの場合の拡大縮小


↑Draw ModeがTiledの場合の拡大縮小

 

背景画像を無限にスクロールさせよう

続いて、シーンに配置した背景と地面の画像をスクロールさせてみましょう。

横に並べるとつながる画像の用意

今回の無限スクロール処理では、画像自体にも工夫をする必要があります。
それは、同じ画像を2つ横に並べたときに違和感なくつながっていることと、表示する画面(プレイ画面)の横幅より大きくすることです。

画像に工夫する理由は、今回のスクロールの処理方法が理由になります。

無限にスクロールさせる方法はいくつかありますが、今回は、2つの画像を何度も往復させる方法を用いました。
2つのつながった画像のうち、先頭の画像が左端までいくと、最初の位置に戻り順番を入れ替えて移動し続けます。

動画を見るとわかるように、2枚の画像は同じ速さで左に移動し、ゴールの位置まで移動すると、右のスタートの位置に戻ります。
ゴールの位置は、背景画像が画面の左端を完全に通過して、プレイヤーから見えなくなったタイミングになるように調整しています。

仕組みとしては非常に直感的で単純なので実装も行いやすいのですが、そのかわり画像に前述した工夫をする必要があります。
そのため、この方法で無限に横スクロールさせる場合は、右端と左端の模様が重なるように意識して画像を作成しておきましょう。

大きさについては画面サイズより小さくても、拡大して対応できますが画質が荒くなるかもしれないので注意しましょう。

背景画像を動かすスクリプトの作成

Unityでは、標準で用意されているSpriteコンポーネントやUnityアセットストアにあるスクリプトを利用する他に、自作したスクリプトを新たなコンポーネントとして追加して利用することもできます。

今回はその一例として、背景と地面の画像を無限に横スクロールさせる独自のスクリプトScrollAndBack.csを作って動作させてみましょう。
スクリプトはプロジェクトウィンドウで右クリックをしてポップアップの[Create]→[C# Script]から作成できます。任意の名前を指定してください。(ファイル名のあとの「.cs」はC#のスクリプトであることを表す拡張子になります)

もしも、後でファイル名を変える場合は、C#のプログラムのMonoBehaviourを継承したclass名も同じ名前に変更してください。下記スクリプトの一行目のpublic class以降になります。

以下が、横に無限スクロールさせる部分を抜粋したC#のプログラムになります。
FixedUpdate関数はUnityのイベント関数で、物理演算を行うために1秒の間に何回も呼び出されます。
スクロール処理はFixedUpdateイベント関数内だけで行われています。

public class ScrollAndBack : MonoBehaviour {

    public float speed_ = 0.1f;
    public float startPositionX_ = 7.85f;
    public float endPositionX_ = -7.85f;

    private void FixedUpdate()
    {
        // 左にスクロールします。
        this.transform.Translate(-speed_ * Time.fixedDeltaTime, 0, 0);

        // 目標の x 座標を通過した場合
        if (transform.position.x <= endPositionX_)
        {
            // 開始の x 座標に戻します。
            transform.position = new Vector3(startPositionX_, transform.position.y, transform.position.z);
        }
    }
}

スクロールさせる処理の説明

はじめに左に移動します。
さきほど紹介したTransformコンポーネントのTranslate関数に移動する量を指定して、その分だけ移動します。

speed_は1秒間で移動する距離を表します。
しかし、FixedUpdateイベント関数は1秒間に何度も呼び出されるので、毎回speed_の分だけ移動すると指定した速度よりも何倍も早く移動してしまいます。

そこで、標準で提供されるTime.fixedDeltaTime変数を乗算します。
この変数は、FixedUpdateイベント関数が呼び出される時間の間隔を表しているので、これを乗算することで指定したspeed_の秒速でスクロールすることができます。

最初の位置に戻す処理の説明

最初の位置に戻す処理では、startPositionX_とendPositionX_メンバ変数が参照されます。
startPositionX_は最初の位置(2つの画像のうち、右側の画像の位置)、endPositionX_は最初の位置に戻す条件となる位置(画像がスクロールする一番左側の位置)です。

スクロールして少しずつ左に進んでいき、もしもオブジェクトの位置がendPositionX_と同じか、もしくは超えた場合、startPositionX_の位置に戻します。
このときも先ほどと同じくTransformコンポーネントを使います。

Transformコンポーネントの位置を管理するpositionプロパティに、最初の位置を代入して瞬間的に位置を移動させます。
今回移動させるのはX座標だけなので、他のY,Z座標についてはもとの値を設定しています。

スクリプトをコンポーネントとして追加

それでは作ったスクリプトを背景や地面のオブジェクトに追加しましょう。
方法は簡単で、追加したい背景や地面のオブジェクトを選択した後、インスペクターウィンドウに.csファイルをドラッグ&ドロップするだけです。
この方法のほかに、インスペクターウィンドウにある[Add Component]ボタンから選択して追加することもできます。

追加したScrollAndBackコンポーネントのSpeed_などの設定は、インスペクターウィンドウから簡単に変更できます。
背景1と背景2で同じ設定をする場合は2つのオブジェクトを選択して設定すると簡単に同じ値を設定できるので便利です。

すきまの原因と対策

自作のコンポーネントを追加し実行してみるとわかりますが、さきほどのプログラムには1つミスがあります。
スクロールの途中で、わずかに隙間が発生し縦線がノイズのように出てしまうのです。
これはspeed_の値をインスペクター上で2にすると顕著に現れました。

この原因は、最初の位置に戻す際の位置設定にあります。
今回の無限スクロールでは、2つの画像がぴったりくっついて移動する必要がありました。

オブジェクトの位置が、endPositionX_とぴったり同じときに、最初の位置に戻る場合はさきほどのように位置をstartPositionX_で上書きするだけで問題ありません。
しかし、endPositionX_をわずかでも超えた状態でstartPositionX_に戻すと、左側の画像の位置がわずかに進んだ状態になるため隙間ができてしまうのです。

この対策として、下記のプログラムのように、戻す位置をstartPositionX_だけではなく、わずかにendPosiontX_を超えた分を補正してあげると隙間ができなくなります。

public class ScrollAndBack : MonoBehaviour {

    public float speed_ = 0.1f;
    public float startPositionX_ = 7.85f;
    public float endPositionX_ = -7.85f;

    private void FixedUpdate()
    {
        // 左にスクロールします。
        this.transform.Translate(-speed_ * Time.fixedDeltaTime, 0, 0);

        // 目標の x 座標を通過した場合
        if (transform.position.x <= endPositionX_)
        {
            // 開始の x 座標に戻します(補正付き)。
            transform.position = new Vector3(startPositionX_ - (endPositionX_ - transform.position.x), transform.position.y, transform.position.z);
        }
    }
}

これで隙間が発生しない無限スクロールができました。

初めてのプログラムの作成時には予期しないエラーはつきものです。ネットで調べたり、それでも解決できない場合は人に聞くなどして、焦らずにどこに原因があるかを見極めるようにしましょう。

まとめ

今回は、画像の配置の仕方と、配置した背景や地面の画像を無限に横スクロールさせる方法を紹介しました。
縦スクロールさせたい場合も、処理の対象をX座標からY座標へ置き換えるだけですので、簡単に応用ができると思います。

オススメアプリ!渋谷で気軽に友達を作れるアプリが登場!