猫でもわかるUnity入門(第14回 玉転がし作成 その6)

f:id:enia:20210228230354p:plain

えにあです。猫でもわかるUnity入門 第14回を進めていきます。
前回はピックアップオブジェクト作成し、ゲームエリアに配置しました。 今回は配置したオブジェクトにプレイヤーが衝突したら、そのオブジェクトを取得できるようにしていきます。

目次

アクティブ/非アクティブ

まず、前回非アクティブにしたプレイヤーオブジェクトをアクティブに戻しましょう。 インスペクターでチェックボックスにチェックをいれればOKです。

ここで、アイテムを集めるためには何ができれば良いかを考えてみましょう。 プレイヤーがアイテムを取得したら、取得したアイテムはフィールド上から消す必要がありますよね。 この消す処理に非アクティブ化が使えそうです。

つまり、プレイヤーがピックアップオブジェクトに衝突したら、スクリプトの中でピックアップオブジェクトを非表示にするのです。 スクリプトはこの後作成しますので、ここではアクティブ/非アクティブについて覚えておきましょう。

衝突を検出しよう

ピックアップオブジェクトを非アクティブにする処理は、どこに書けばよいでしょうか。 Updateメソッドの中に書いてしまっては、ゲーム開始後すぐに非アクティブになってしまいます。

実は、オブジェクトとオブジェクトが衝突した時に実行される便利なメソッドがあります。 OnTriggerEnterメソッドです。

PlayerControllerスクリプトを以下のように修正します。 最初なので、今までの修正も含めてすべてのコードを記載していますが、追加したのはOnTriggerEnterメソッドだけです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float speed;
    private Rigidbody rb;

    void Start ()
    {
        rb = GetComponent<Rigidbody>();
    }

    void FixedUpdate ()
    {
    float moveHorizontal = Input.GetAxis("Horizontal");
    float moveVertical= Input.GetAxis("Vertical");

        Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
        rb.AddForce(movement * speed);
    }

    void OnTriggerEnter(Collider other)
    {
        other.gameObject.SetActive(false);
    }
}

OnTriggerEnterメソッドはCollider型のotherを引数として受け取ります。 この引数にはプレイヤーオブジェクトに衝突したオブジェクトが入っています。

メソッドの中身を見てください。
other.gameObject.setActive(false)と書かれていますね。 other.gameObjectで衝突したオブジェクトを取得し、そのgameObjectをsetActive(false)で非アクティブにすることで非表示にすることができるのです。

しかし、これだけでは大事な要素が抜けています。 次の手順で修正しましょう。

何と衝突したか判定する

先ほどのスクリプトでは、プレイヤーオブジェクトがピックアップオブジェクトと衝突した時に、ピックアップオブジェクトを非表示にするという要件を満たしていません。 そう、衝突したものが何かを判定できていないのです。

衝突したものが何かを判定するにはどうすればよいでしょう。
その答えはタグです。
オブジェクトにタグ文字列をつけて、衝突したオブジェクトのタグが期待したものであるかどうかを判定するのです。 Twitterハッシュタグで検索するのと同じようなものですね。

Unityに戻って、Pick Upオブジェクトにタグをつけます。 Pick Upオブジェクトは12個ありますが、プレイヤーオブジェクトと衝突した時の動作はどれも変わらないはずです。 つまり、12個のピックアップオブジェクトはすべて同じタグ名でよいはずです。 一度に設定するにはどうすればよいでしょう。 プレハブの出番です。

まず、プロジェクトビューでプレハブを選択します。 ※前回1列レイアウトに変更した続きなので、そのままにしています。
f:id:enia:20210302130955p:plain

次に、インスペクターでタグを追加します。現状はUntaggedになっていますね。
f:id:enia:20210302131106p:plain

遷移後の画面で+ボタンを押してタグを追加します。名前をPick UpにしてSaveボタンを押しましょう。
f:id:enia:20210302131243p:plain

さて、もう一度プロジェクトビューでPick Upプレハブを選択して、インスペクターを再表示してみてください。
f:id:enia:20210302131521p:plain

おっと。まだUntaggedになっていますね。 変更を反映させるために、プルダウンからPick Upを選択してください。
f:id:enia:20210302131605p:plain

今度はヒエラルキービューでPick Upオブジェクトを一つ選んでインスペクターで見てみましょう。 私はPick Up(5)を見てみましたがタグに「Pick Up」が設定されています。 プレハブの変更が全てのPick Upオブジェクトにちゃんと反映されていますね。
f:id:enia:20210302131702p:plain

では次に、このタグを使って、PlayerControlelrスクリプトを修正していきましょう。 修正後のOnTriggerEnterメソッドは以下のようになります。

    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Pick Up")) {
            other.gameObject.SetActive(false);
        }
    }

タグを判定するにはgameObjectのCompareTagメソッドを使います。 これで衝突したオブジェクトに”Pick Up"というタグが設定されている場合だけ、非アクティブにすることができます。

プレイモードにして試してみましょう。 あれ?ピックアップオブジェクトにぶつかると、壁にぶつかったときと同じようにボールが止まってしまいますね。

トリガーに設定する

Pick Upプレハブを選択し、インスペクターでBox Colliderのトリガに設定するにチェックを入れてみましょう。 f:id:enia:20210302133409p:plain

これを有効にするとコライダーはトリガーイベントに反応し、物理エンジンからは無視されます。

というポップアップが出ていますね。

先ほどまではUnityの物理エンジンの計算に従って処理が行われていたため、動くオブジェクトが、止まっているオブジェクトにぶつかると弾かれる、というデフォルトの動作になっていました。 このチェックを入れることで物理エンジンから無視されるため、弾かれることはなくなります。

さぁ、もう一度プレイモードにして試してみましょう。

おぉー。アイテムに触れたら消えるようになりましたね! f:id:enia:20210302133920p:plain

NOTE:
OnTriggerEnterと似たようなメソッドに、OnCollisionEnterがあります。 OnTriggerEnterはオブジェクトとオブジェクトが重なり、すり抜けたときに呼ばれるメソッドなのに対し、OnCollisionEnterはオブジェクトとオブジェクトがぶつかり、反発するときに呼ばれるメソッドです。 「トリガーにする」にチェックを入れることで物理エンジンから無視され、OnTriggerEnterメソッドが呼ばれるようになったわけですね。 ※こちらの記事を参考にしました。 nullkun0803.hateblo.jp

パフォーマンスを改善する

ここまでで目的の動作を実現できましたが、最後にもう少しだけ改善しましょう。

Unityはパフォーマンスを最適化するために、シーン内のすべての静的なオブジェクトの領域を計算し、キャッシュとして保存しています。 静的なオブジェクトとは、簡単に言えば基本的にはじっとしていて動かないオブジェクトです。 フレームごとに衝突の計算をする場合、動かないオブジェクトであれば今どの範囲にオブジェクトが存在するか計算する必要はありません。動かないのですからキャッシュの値をそのまま使えば良いわけです。

しかし、動かないオブジェクトを動かした場合はどうでしょう。 今回で言えば、ピックアップオブジェクトは元々はただのキューブでしたが、スクリプトで回転させていますね。 このように、オブジェクトの位置や角度が変更された場合、Unityは変更されるたびにキャッシュを更新していきます。これは負荷のかかる作業です。

一方で、動的なオブジェクトの場合、キャッシュの計算は行われません。 動的なオブジェクトとは、簡単に言えばプレイヤーオブジェクトのような動くオブジェクトです。動くことを前提としているのでキャッシュを保存する必要はありません。(※必要な都度計算するのだと思います)


では、ピックアップオブジェクトをキャッシュさせないようにするにはどうしたら良いでしょう。 動的なオブジェクトに変更すればよいのです。

Unityにおいて、静的か動的かに判定はどのように判定されるのでしょうか。 実はRegitbodyとColliderを持っているオブジェクトは動的と判定されます。 プレイヤーオブジェクトは、元々Sphere Colliderを持っており、以前の手順でRegidbodyを追加しているので動的オブジェクトとして判定されています。 一方、ピックアップオブジェクトをインスペクターで見ると、BoxColliderは持っていますが、Regidbodyはありません。そのため、静的オブジェクトとして判定されています。

動的にオブジェクトに変更するために、Regidbodyをアタッチしてみましょう。 リジッドボディのアタッチは、プレイヤーオブジェクトを作成する時に一度やりましたね。 ただし、複数のピックアップオブジェクトを一度に更新するため、プレハブの方に設定していきます。 インスペクターから「コンポーネントを追加」->「リジットボディ」で追加しましょう。
f:id:enia:20210302140317p:plain

これで、ピックアップオブジェクトはRegidBodyとColliderを持つ動的オブジェクトになりました。 セーブして、プレイモードにして実行してみましょう。
あれ?」スタートするとすぐに、ピックアップオブジェクトが地面に吸い込まれてしまいました。

リジットボディをアタッチしたことで、重力の影響を受けるようになり落下してしまったのです。 さらに、前の手順でトリガーにしたことで床に衝突することもなくすり抜けてしまいます。

ピックアップオブジェクトは物理的な力の影響を受けないようにしたいので、RegidBodyの設定を変えましょう。 インスペクターで「RegidBody」 -> 「isKinematic」にチェックを入れましょう。 isKinematicにチェックをつけると物理演算が無効になり、 チェックを外すと他の物体が衝突した時にその影響を受けるようになります。 f:id:enia:20210302140858p:plain

NOTE:
UseGravityというチェックボックスもあります。このチェックボックスにチェックを入れると重力の影響を受け、外すと重力の影響をうけません。
UseGravityにチェックをいれ、isKinematicのチェックを外した場合はどうなるでしょうか?重力の影響は受けませんが他の物理演算は行われます。つまり、プレイヤーとピックアップオブジェクトは衝突してしまいます。
逆に、isKinematicだけにチェックを入れた場合、全ての物理演算が行われなくなるため、重力の影響も含めてすべての物理的な影響を受けなくなるようです。

これで、動作は変えずにキャッシュ計算を減らし、パフォーマンスを改善することができました。

ゲームらしさがどんどん増してきましたね! 今回はここまで!