アイテムを設置しよう の変更点

Top > アイテムを設置しよう

#contents
*はじめに [#v3a58097]
ステージにアイテム・ギミック・ゴールを設置していきます。
今回は
 ・拾うと移動スピードが変化するりんご
 ・エレベーター
 ・ゴール
の3つを実装します。
#br
*りんごの実装 [#m089a5bb]
まずアイテム用の画像を Assets に追加します。
#br
#download(apple_red.png)
#download(apple_blue.png)
#br
追加できたら Assets からそれぞれの画像を選択し、Inspector から Pixels Per Unit を 32 にそれぞれ変更に変更しておきます。
#br
実装したいリンゴの仕様
 ・ステージの各所に同じ機能のりんごを複数設置する
 ・赤いリンゴを拾うと一定時間速度が上がる
 ・青いリンゴを拾うと一定時間速度が落ちる
 ・速度変化の効果は重ね掛けできる
 ・それぞれの色の速度変化と継続時間は Inspector から調整できるようにする
#br
「拾う」という動作は、
ひつじに当たったときに効果を発動してりんごを消す
という処理で表現しようと思います。
**プレハブについて [#m7a4dc60]
Unityで同じものを複製して使用するときは''プレハブ(Prefab)''が便利です。
&color(,#FBB){プレハブは、1つのゲームオブジェクトを再利用可能なアセットにしたものです。};
プレハブはゲームオブジェクトから生成するので、まずはりんごのゲームオブジェクトを完成させます。
#br
Assets から apple_red.png を Scene にドラッグ・ドロップし、ゲームオブジェクトを作ります。
#br
**コンポーネントの追加 [#tc80cf62]
作ったゲームオブジェクトにコンポーネントを追加します。
必要になりそうなコンポーネントは当たり判定用のColliderです。
りんごの画像がほぼ円に近いので、今回は Circle Collider 2D でよさそうです。
Inspector > Add Component から追加します。
#br
追加できたら Inspector からコンポーネントの設定を少し変更します。
まず、りんごにひつじが反発するなどの物理演算は不要なので、isTrigger にチェックを入れて物理演算を無効にします。
次に、Radius の数値を調整し、当たり判定の大きさをりんごの画像に合わせます。0.4 にします。
#br
**スクリプトを追加 [#v952b5b2]
Assets > Add Component > New Script から新しいスクリプトをりんごに追加します。
スクリプト名は「Apple」としておきます。
毎フレーム行いたい処理はないので Update() は削除して大丈夫です。
まずは必要な変数を作っておきます。
速度の変化率 を float 型で「rate」
継続時間を float 型で「duration」としておきます。
#geshi(csharp,number){{
using System.Collections;
using UnityEngine;

public class Apple : MonoBehaviour
{
    public float duration;
    public float rate;
}
}}
「rate」と「duration」は public にしておけば Inspector から編集できます。
テスト用に「rate」は 1.5 、「duration」は 3.0 にしておきます。
#br
***速度変化の実装 [#wff4b8b3]
次に速度変化を作ります。
当たり判定のイベントを受け取り、当たったオブジェクトがひつじだった場合にひつじの速度を変化させる処理で表現できます。
isTrriger が入った Collider の当たり判定を受け取るイベントには OnTriggerEnter(), OnTriggerStay(), OnTriggerExit() などがありましたが、今回は当たった時に一度だけ呼ばれればよいので OnTriggerEnter() を選択しました。
#geshi(csharp,number){{
using System.Collections;
using UnityEngine;

public class Apple : MonoBehaviour
{
    // 省略

    void OnTriggerEnter2D(Collider2D col)
    {
        if (col.name == "hitsuji_dot") {
            MoveSheep moveSheep = col.GetComponent<MoveSheep>();
            moveSheep.speed *= rate;
        }
    }
}
}}
#br
OnTriggerEnter() の引数 Collider2D 型の中には name という名前で当たったオブジェクトの名前が入っているので、それを使って当たったオブジェクトがひつじかどうかを判定しています。
当たったオブジェクトがひつじだった場合には、そのオブジェクトから GetComponent() で MoveSheep コンポーネント を取得し、
「speed」にりんごの速度変化率「rate」でかけて代入します。
「''*''」が&color(,#FBB){かける命令};になります。
「+=」 と同様、「*=」で右辺の値を左辺に欠けて代入しています。
#br
***継続時間の実装 [#v028d6a9]
次に、継続時間を実装します。
これは、ひつじがりんごに触れて速度を変化させた後、一定時間後に元の速度に戻すことで表現していきます。
Unity で一連の流れの中で数秒後の処理を行いたいときには ''コルーチン (coroutine)'' が便利です。
コルーチンは IEnumerator 型を戻り値とする関数として書き、StartCoroutine() でその関数を呼び出すことができます。
以下はコルーチンの基本的な書き方です。
#geshi(csharp,number){{
public class Apple : MonoBehaviour {
    void Start () {
        // コルーチンを呼び出し  
        StartCoroutine ("Test");
    }

    // コルーチン  
    private IEnumerator Test() {
        // メッセージをconsoleに出力
        Debug.Log("コルーチンの処理が始まりました");
        // 一秒待つ
        yield return new WaitForSeconds (1.0f);
        // メッセージをconsoleに出力
        Debug.Log("コルーチンの処理が終わりました");
    }
}}
このように書くと、Unity の Console では
'''コルーチンの処理が始まりました'''
と出力された1秒後に
'''コルーチンの処理が終わりました'''
と出力されます。
#br
これを使って、
ひつじがリンゴに触れたらコルーチンを呼び、
コルーチンのなかで速度を変化させた後、一定時間後に元の速度に戻すようにスクリプトを改良します。
まずは、コルーチンを作ります。コルーチン名は「ChangeSpeed」としておきます。
コルーチンのなかでひつじの速度を変えたいので、「MoveSheep」を引数で受け取れるようにしておきます。
#geshi(csharp,number){{
public class Apple : MonoBehaviour {

    // 省略

    // コルーチン  
    private IEnumerator ChangeSpeed(MoveSheep moveSheep) {
        moveSheep.speed *= rate;
        yield return new WaitForSeconds(duration);
        moveSheep.speed /= rate;
    }
}
}}
「''/''」は&color(,#FBB){割り算};の演算子です。
「*=」 で変化させたので、 「''/=''」すればもとの速度に戻ります。 
元の速度を変数で保管しておく方法もありそうですが、りんごが複数あった時にスピード変化の効果を重ね掛けできるようにしたいのでこのような処理にしました。
#br
コルーチンができたら、ひつじと接触したときにこのコルーチンを呼び出すようにします。
コルーチンに引数を渡したいときは
#geshi(csharp,number){{
StartCoroutine(コルーチン(引数));
}}
のように書くことができます。

#geshi(csharp,number){{
public class Apple : MonoBehaviour {

    // 省略

    void OnTriggerEnter2D(Collider2D col)
    {
        if (col.name == "hitsuji_dot") {
            MoveSheep moveSheep =  col.GetComponent<MoveSheep>();
            StartCoroutine(ChangeSpeed(moveSheep));
        }
    }

    // 省略

}
}}
#br
***りんごを消す [#y1139f88]
最後にりんごを消す処理を作ります。
&color(,#FBB){オブジェクトの削除は ''Destory()'' で実装できます。};
#geshi(csharp,number){{
Destroy(消したいゲームオブジェクト, 消すまでの時間);
}}
消すまでの時間は省略できます。
#geshi(csharp,number){{
public class Apple : MonoBehaviour {

    // 省略

    void OnTriggerEnter2D(Collider2D col)
    {
        if (col.name == "hitsuji_dot") {
            MoveSheep moveSheep =  col.GetComponent<MoveSheep>();
            StartCoroutine(ChangeSpeed(moveSheep));
            Destroy(this.gameObject);
        }
    }

    // 省略

}
}}
これで動きそうですが、これを実行してみるとひつじの速度が変わらなくなります。
原因は、Destroy() が実行中のコルーチンもオブジェクトと一緒に消してしまうためです。
そこで、オブジェクト自体は消さずに、画像を見えなくして当たり判定を無効にする処理に変えようと思います。
オブジェクトの画像を Unity で表示している SpriteRenderer と CircleCollider2D をそれぞれ無効にすれば再現できそうです。
#geshi(csharp,number){{
public class Apple : MonoBehaviour {

    // 省略

    void OnTriggerEnter2D(Collider2D col)
    {
        if (col.name == "hitsuji_dot") {
            MoveSheep moveSheep =  col.GetComponent<MoveSheep>();
            spriteRenderer = this.GetComponent<SpriteRenderer>();
            circleCollider2D = this.GetComponent<CircleCollider2D>();

            StartCoroutine(ChangeSpeed(moveSheep));
            spriteRenderer.enabled = false;
            circleCollider2D.enabled = false;
        }
    }

    // 省略

}
}}
enebled を false にすることでコンポーネントを簡単に無効化できます。
これでりんごが消えたように見えます。
#br
**りんごのプレハブ化 [#ef9f3281]
完成したリンゴをプレハブにします。
その前に、プレハブ保管用のフォルダを Assets のなかに作っておきます。
フォルダ名は「Prefabs」としておきます。
フォルダを作ったら Hierarchy から先ほど作ったりんごをフォルダにドラッグ・ドロップします。
これだけでプレハブの完成です。
プレハブは Hierarchy 上で水色のキューブとして表示されるようになります。
#br
**りんごをステージの各所に設置 [#n7fd35e9]
作ったプレハブを Scene にドラッグ・ドロップすると簡単にプレハブの''インスタンス''を生成できます。
ソフトウェアの分野でインスタンスとは「実体」を指し、&color(,#FBB){設計図をもとにして実際に作った物}などとよく表現されています。
ソフトウェアの分野でインスタンスとは「実体」を指し、&color(,#FBB){設計図をもとにして実際に作った物};などとよく表現されています。
ここではプレハブが「設計図」になっているわけです。
純粋な「コピー」とは少し異なる概念になります。
#br
ステージの適当な位置にりんごのインスタンスを作って設置しておきます。
#br
**青りんごの実装 [#b35ea27a]
青リンゴも赤リンゴと同じように作ります。スクリプトは「Apple.cs」を流用し、「rate」を 1/1.5 にするだけです。
長くなるので省略します。
#br
*エレベーターの実装 [#p15c1530]
エレベーターの画像を Assets に追加します。
#download(elevator.png);
例によって画像の Pixels Per Unit は 32 に変更します。
#br
エレベーターの仕様
 ・一定時間おきに定位置から出現する
 ・一定距離登ったら消滅する
 ・地面のように上でジャンプできる
 ・上昇距離、上昇スピード、エレベーターの生成間隔は Inspector から調整できるようにする
#br
地面のようにエレベーター上でジャンプできるようにするには、エレベーターに「ground」タグをつけるのが簡単です。
プレハブのインスタンス生成はプログラムから行うことができます。
一定時間おきに定位置からインスタンスを生成するのが簡単です。
Assets から elevator.png を Scene にドラッグ・ドロップし、プレハブになるゲームオブジェクトを先に完成させます。
#br
**コンポーネントの追加 [#hef01867]
作ったゲームオブジェクトにコンポーネントを追加します。
物理演算が有効な当たり判定を持ったオブジェクトが移動する場合、Collider に加え Rigidbody が必要です。
よって必要になるコンポーネントは
 ・物理演算用の RigidBody
 ・当たり判定用の Collider
になります。
#br
Rigidbody は2Dゲーム用の Rigidbody2D を使います。
Collider はエレベーターの画像に合った形にしたいので、今回は PolygonCollider2D を使います。
PolygonCollider2Dは画像に合った形のコライダーを自動で作ってくれます。
Inspector > AddComponent から追加します。
#br
追加できたら Inspector からコンポーネントの設定を少し変更します。
今回は物理演算を使ってひつじと反発してほしいので Collider の isTrigger はいじりません。
Rigidbody は Body Type を Kinematic に変更しておきます。
RigidBody の Kinematic モードは物理演算で摩擦、重力などを無視したい場合に使います。
物理法則に反する動きを作りたいときに便利です。また、Kinematic モードでは transform による移動ができます。
#br
**スクリプトを追加 [#g80ac75f]
Assets > AddComponent > New Script から新しいスクリプトをエレベーターに追加します。
スクリプト名は「Elevator」としておきます。
先に必要な変数を作っておきます。
上昇速度 を float 型で「speed」
上昇距離 を float 型で「height」としておきます。
#geshi(csharp,number){{
using UnityEngine;

public class Elevator : MonoBehaviour
{
    public float speed;
    public float height;

    void Start()
    {
    }

    void Update()
    {
    }
}
}}
#br
Inspector から「speed」を 2 、「height」を 6 に設定しておきます。
#br
***上昇の実装 [#zd0eed21]
Update() を使って毎フレーム上に移動させます。
[[ひつじを動かそう]] と同じやり方でできます。
#geshi(csharp,number){{
using UnityEngine;

public class Elevator : MonoBehaviour
{

    // 省略

    void Update()
    {
        Vector3 pos = this.transform.position;
        pos.y += speed * Time.deltaTime;
        this.transform.position = pos;
    }
}
}}
#br
***削除の実装 [#n2f74772]
上昇開始位置を変数で保存し、その現在位置 - 位置 が「height」を超えたときに Destroy() すれば再現できそうです。
private な 変数「initPosition」を追加し, Start() で開始位置を記録するようにします。
#geshi(csharp,number){{
public class Elevator : MonoBehaviour
{
    // 省略

    private Vector3 initPosition;

    void Start()
    {
        initPosition = this.transform.position;
    }

    void Update()
    {
        Vector3 pos = this.transform.position;
        pos.y += speed * Time.deltaTime;
        this.transform.position = pos;
        if (this.transform.position.y - initPosition.y > height)
        {
            Destroy(this.gameObject);
        }
    }
}
}}
#br
**エレベーターのプレハブ化 [#xa53860d]
Hierarchy から完成したエレベーターを「Prefabs」フォルダにドラッグ・ドロップします。
Hierarchy に残っているインスタンスは右クリック > Delete で削除します。
#br
**インスタンスの自動生成 [#k88c1e21]
りんごはあらかじめステージに設置しましたが、エレベーターはスクリプトで一定時間おきに同じ場所から出現するようにします。
***ElevatorGeneratorの作成 [#v3c09cd5]
まずは、エレベーターを生成するオブジェクトを新しく作ります。
Hierarchy > Create Empty
で空オブジェクトを作り、名前を「ElevatorGenerator」としておきます。
***スクリプトの追加 [#c04b7115]
作成したオブジェクトにスクリプトを追加します。
スクリプト名は「ElevatorGenerator」としておきます。
&color(,#FBB){プレハブの生成は ''Instantiate()'' で書くことができます。};
#geshi(csharp,number){{
Instantiate(プレハブ);
}}
Instantiate() は引数で生成する場所や向きを指定できます。
自分と同じ位置に生成したい場合は、Prefab の座標を (0,0) にして
#geshi(csharp,number){{
Instantiate(プレハブ, this.transform);
}}
のようにするだけでOKです
以下細かいこと
 Instantiate() の引数の挙動に注意
 ・引数が1つの場合、生成されるプレハブの transform は プレハブの original の transform と同じになる。
 ・引数が2つの場合、第二引数はプレハブが参照する transform を指定する。
 ・引数が3つの場合は、引数の型に応じて
  第二:参照する transform (transform)、第三:ワールド座標 (false) or 第二引数の transform 基準のローカル座標 (true) の切り替え 指定しない場合(引数2つの場合)true
  第二:参照する position (Vector3)  、第三:参照する rotation (Quaternion)
  を切り替える
 よってプレハブを生成する座標を指定したい場合、position に加え rotation まで rotation まで指定する必要がある
 親オブジェクトと同じ位置に生成したい場合、Instantiate(elevator, this.transform.position, this.transform.rotation)
 とする方法もあるが, あらかじめ Prefab の original の座標を (0,0) に設定しておくことで、
 Instantiate(elevator, this.transform) のように書いて実現できる

#br
プレハブは Assets 内から検索するのが難しいので、public で代入用の変数を作り、Inspector から設定します。
#geshi(csharp,number){{
using UnityEngine;

public class ElevatorGenerator : MonoBehaviour
{
    public Elevator elevator;

    void Start()
    {
        Instantiate(elevator, this.transform);
    }
}
}}
Assets > Prefabs > Elevatorを選択し、transform > Position の x ,y をそれぞれ (0, 0) にし、
ElevatorGenerator を Inspector で選択し、Elevator 変数に Assets > Prefabs > Elevator をドラッグ・ドロップしておきます。
この状態でゲームを起動すると、「ElevatorGenerator」の場所に「Elevator」が生成されます。
#br
次に、エレベーターを一定時間おきに生成するようにスクリプトを改良します。
&color(,#FBB){一定時間おきに同じ処理を行う場合には ''InvokeRepeating()'' を使うのが簡単です。};
#geshi(csharp,number){{
InvokeRepeating("関数名", 開始時間, 繰り返し間隔);
}}
これを使って「ElevatorGenerator」を改良します。
InvokeRepeating() に渡す関数にはそのままでは引数を指定できないので、Instantiate() をそのまま呼ぶのはあきらめ、Instantiate() に引数入れて呼ぶ関数を別で作成します。
関数名は「GenerateElevator」としておきます。
生成間隔は public で変数を作り、Inspector から調整できるようにしておきます。
#geshi(csharp,number){{
public class ElevatorGenerator : MonoBehaviour
{
    public Elevator elevator;
    public float repeatRate;

    void Start()
    {
        InvokeRepeating("GenerateElevator", 3f, repeatRate);
    }

    void GenerateElevator()
    {
        Instantiate(elevator, this.transform);
    }
}
}}
これでスクリプトは完成です。
「repeatRate」 は 1 に設定しておきます。
ElevatorGenerator を Scene 上で移動すれば、好きな位置からエレベーターを生やすことができます。
**エレベーターを地面判定にする [#h6cbd989]
地面の判定は 「ground」タグで行ったので、エレベーターに「ground」タグをつければエレベーターを地面扱いにできます。
Assets > Prefabs > Elevator を選択し、Inspector から Tag を「ground」に設定します。
これでひつじがエレベーター上でもジャンプできるようになります。

*ゴールの実装 [#x539a60a]
最後にゴールを実装します。
ゴールの仕様は「接触したらクリア(スコア)画面に飛ぶ」というものになります。
 1.ゴールとの当たり判定を見る
 2.当たった対象がプレイヤーかどうか判定を取る
 3.クリア(スコア)画面に飛ぶ
という処理で仕様を再現しようと思います。
**ゲームオブジェクトの作成 [#o8356dcc]
まず、ゴールの画像を挿入しましょう。
#download(goal.png)
これまでと同様に、上の画像からダウンロードして Assets の Images にいれて
画像の PixePerUnit を 32 にします。
そうしたら、Hierarchy にドラッグ&ドロップし、名前を「Goal」とします。
これで、「Goal」という名前のゲームオブジェクトができました。
#br
次に、ゴールの設置したいところに作った「Goal」を移動させましょう。
Scene ビューでからドラッグして移動するのが簡単です。
#br
**新しいシーンの追加 [#rd0e1d6b]
次は「ゴールについたらクリア(スコア)画面に飛ぶ」という機能を追加します。
そのためにはまず、新しいシーンを作る必要があります。
新しいシーンは、Assets を右クリックし、Create > Scene で作ります。
名前は「Score」などとしておきます。
次に、「ゴールについたら」つまり「『Goal』オブジェクトに触れたら」今のシーンから「Score」シーンに移動する、
という機能を作ります。
**シーンセッティング [#d443b7fd]
次は、ゲームのプロジェクトに作ったシーンを追加します
Inspector  > BuildSetting から作ったシーンを追加します。
#br
**当たり判定の追加 [#h6bd4345]
前準備として、 「Goal」に当たり判定を作ります。
「Goal」の Inspector 下部の AddComportnemt から、
Physics2D > PolygonCollider2D を押してコンポーネントを追加します。
「Goal」の当たり判定は円や四角形で表現するのは難しいため、
今回は PolygonCollider2D という多角形の当たり判定を作れるものを利用します。
Inspector を開き、PolygonCollider2D > isTrigger にチェックを入れ、
PolygonCollider2D > EditCollider から画像の縁に合うように当たり判定の線を調節します。
#br
**スクリプトの追加 [#k681a75c]
当たり判定ができたら、C#スクリプトを作っていきます。
これまでと同じように、「Goal」のインスペクター下部の AddComportnemt から、
NewScriptを押して、名前を「Goal」として新しいスクリプトを「Goal」オブジェクトのコンポーネントととして追加します。
次に、テキストエディターを開いて「Goal」スクリプトを編集していきます。
#br
#geshi(csharp,number){{
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; //<=新たに追加

public class Goal : MonoBehaviour
{
    // 省略

        // 当たり判定を貫通したときの命令を記述する
    void OnTriggerEnter2D(Collider2D col)
    {
        // 当たった対象の名前が"hitsuji_dot"だったら、「Score」シーンに移動する
        if (col.name == "hitsuji_dot")
        {
            SceneManager.LoadScene("Score");
        }
    }
}
}}
#br
ここで初めて、これまでさんざん無視してきた「using ○○;」に触れようと思います。
ここでの○○は「''名前空間(ネームスペース)''」と呼ばれるもので、&color(,#FBB){○○の中で定義されるものを、このスクリプトでも扱えるようにしてくれます。};
C#では定義されたオブジェクトしか使うことができません。
例えば、「using UnityEngine」がないと、これまで何気なく使ってきた GameObject クラスや Transform クラス、今回も使っている Collider2D クラスなどが使えません。
逆を言えば、「using UnityEngine」をいれることで、 UnityEngine の名前空間で定義される様々なクラスやメゾットを自分で定義せずに使うことができます。
今回も、シーンに関する設定・動きなどを扱っている ''UnityEngine.SceneManagment'' という名前空間を使うことで、
SceneManager クラスおよび LoadScene() (引数に指定した名前のシーンに移動する関数)を自作せずに利用できます。
#br
これでスクリプトは完成しました。
動作確認をしてみましょう。
ゴールに触れて真っ青な画面に飛んだら成功です。