【Unity】チュートリアルの「Roll a Ball」をやってみる話 #6

記事をご覧いただき、誠に有難うございます。

投稿主の無能です。

今回は、Wallにぶつかった時のスクリプトを追加することと、ここまでに(前回までに)書いたスクリプトを少し修正していきたいと思います。


Wallにぶつかった時のスクリプトを追加する

今のWallだと、Playerの速度が速いと乗り越えることが出来て落下してしまいます。

壁を乗り越えて落下してしまう

ですので、Wallにぶつかったら現実のように勢いがなくなるようにしてみたいと思います。

WallとPlayerがぶつかった時に勢いが無くなるという事は、ぶつかった時に進む力がゼロになる、という処理を書けば良さそうです。

という事は、壁にぶつかったという判定ができるようにすることと、Playerがぶつかったもの、即ちぶつかったオブジェクトがWallであると判別できることが条件になります。

方向性が見えてきたのでやってみましょう。

PrefabでWallを作る

Wallと判別できるようにする作業の前に、これから度々ゲームオブジェクトの変更があって、その変更するオブジェクトが複数個あるものだと、その度に複数個を変更する必要が出てきます。

複数個の変更になると、変更する箇所が違っていたり、そもそも変更漏れが起こる可能性もあります。

このような事が起こらないように、ゲームオブジェクトの仕様が一律であり、かつ再利用が可能なものを用意します。

これがPrefab(プレハブ)と呼ばれるものです。

では早速やってみます。

Prefabの作成

Prefabの作成は非常に簡単です。
HierarchyビューからProjectビューへドラッグ&ドロップするだけです。

…説明が1行で済みました。
簡単ですね。

その前に、Assetフォルダ内にPrefab専用のフォルダを作ります。
名前を「Prefabs」として、フォルダを作成します。

「Prefabs」フォルダを新規作成

これからPrefabを作る際は、このフォルダに入れていきます。

では、どこでも良いのでWallを一つドラッグ&ドロップしてみましょう。

Wallの一つをPrefabsフォルダへドラッグ&ドロップする

すると操作したWallが複製されて、他のWallと異なりアイコンが青くなりました。
この青いアイコンのものがPrefabです。

これから変更をする時は、Prefabsフォルダ内のオブジェクトを操作すると、変更が瞬時に反映されます。

このPrefabsフォルダのオブジェクトは、名前に方角は必要無いので「Wall」に変更します。

名前を「Wall」に変更

では、他の3つのWallは削除して、Prefabsフォルダ内にあるWallをドラッグ&ドロップして複製して作り直します。

作り直す際に、WallのPositionとRotationをメモしておくと楽です。

先程Prefab化したWall以外の3つのWallを削除

Prefabsフォルダ内のWallをHierarchyビューへドラッグ&ドロップする

作業を繰り返してPrefabのWallに作り直す

作り直したら、Wallを判別できる設定をしていきます。

WallにTagを付ける

オブジェクトを判別する方法の一つとして「Tag」(タグ)があります。
荷物に付いているTagそのものと考えて問題ないと思います。

今回はTagを使ってWallであるかそうでないかを判別できるようにします。

PrefabsフォルダのWallを選択し、Inspectorビューを見てみます。

PrefabsフォルダのWallのInspectorビュー

名前の下にTagの項目があり、ここでTagが設定できます。

ここでTagが設定できる

今回はTagを「Wall」に設定します。
プルダウンを開きます。

Tagに「Wall」が無いので、Add Tagを選んで新しくTagを追加します。

プルダウンを開きAdd Tagで新しくTagを追加する

Inspectorビューが切り替わって、Tagの編集が出来るようになります。

Inspectorビューが切り替わりTagの編集が可能になる

Tagsの中にある+ボタンを押してTagを追加します。

+ボタンを押してTagを追加

ダイアログが出るので、Tagの名前を入れてSaveを押します。

+ボタンを押すとダイアログが出る

追加したいTagの名前を入力してSaveを押下

新しく「Wall」というTagが追加されました。

Tagが新たに追加された

PrefabsフォルダのWallに戻って、Tagを追加します。

Tagのプルダウンを開くと、先程追加したWallがあるので、そのままTagをWallにします。

先程追加したWallが追加されている

TagをWallにする

先程置き換えた4つのPrefabのTagを見てみます。

4つ共Tagに変わっています。





このようにPrefabは大元に変更があると、その変更がSceneにある該当のPrefabに適用されます。
Materialなどのコンポーネントも同様なので、色を変えたりコンポーネントの追加・削除も反映されます。
大きいのはスクリプトのアタッチで、大元のPrefabの変更がSceneのPrefabに適用されるので、アタッチ忘れが起きにくくなるなる点です。

これからどんどんPrefabを使っていきましょう。

ではWallに判別用のTagを付けることができたので、スクリプトを書いていきましょう。

PlayerControllerスクリプトに壁にぶつかった時の処理を追加する

PlayerControllerスクリプトに、WallというTagが付いたオブジェクトを判別して処理を行うスクリプトを追加していきます。

壁にぶつかった時というのはどのような事が起こっているのか?
そのあたりを説明します。

まず、重力が分からなかったPlayerというSphereが、なぜGroundに接地できているのかを考えてみます。

これは「collider」(コライダー)という当たり判定があります。

Player、Ground、WallのそれぞれのInspectorビューを見てみます。
それぞれコンポーネントの形はことなりますが、各種colliderが付いています。

Player

Ground

Wall

それぞれのオブジェクトに適した形のcolliderが付いています。

それではPlayerのSphere colliderを非アクティブにして、ゲームを実行してみます。
非アクティブ化するには、名前の横のチェックを外します。

PlayerのSphere colliderを非アクティブにする

Groundをすり抜けて落下していきました。

PlayerのSphere colliderを非アクティブにするとGroundをすり抜けて落下した

このように、colliderによってオブジェクト同士の当たり判定を判別しています。

Playerのcolliderをアクティブに戻して、PlayerControllerスクリプトを開きます。

今回は、新たに関数を作ります。
Update関数の下に、このように記述します。

    // Playerと接触したとき
    private void OnCollisionEnter(Collision collision)
    {
        
    }
こんな感じです。

Update関数の下にOnCollisionEnter関数を追加する

このOnCollisionEnter関数はUnityで用意されていて、collider同士が接触した瞬間に呼ばれる関数です。

このOnCollisionEnter関数の中に、冒頭で話した「Wallにぶつかったら勢いを無くす」という処理を書きます。

それで「Wallにぶつかった」になりますが、もう少し詳しく状況を言い換えると「WallというTagを持っているオブジェクトに(Playerが)接触したら」という風に言い換えることが出来ます。

あわせて「接触したら」と言う条件が入っています。

このように条件にあわせて処理が分かれることを「条件分岐」と呼び、「if」(イフ)を使います。

まずVSに戻って、OnCollisionEnter関数の中に「if」と入力してTabキーを2回押します。
するとこのようになります。

    // Playerと接触したとき
    private void OnCollisionEnter(Collision collision)
    {
        if (true)
        {

        }
    }
こんな感じです。


このifの隣にある(true)のカッコ内に条件を記述します。

この条件がtrue(トゥルー)だった場合、その下の「{}」(波かっこ/中かっこ)の中を処理します。
ifの条件に合致しない場合(trueではない場合)は{}内は実行されません。

今回はifの条件に「WallというTagを持っているとき」という条件にします。

このように記述します。

        // Wallタグを持っているとき
        if (collision.gameObject.CompareTag("Wall"))
        {

        }
こんな感じです。


順を追っていくと、collisionはOnCollisionEnter関数の右隣のカッコ内に「(Collision collision)」となっている部分があります。

これは「引き数」(ひきすう)と呼ばれ、このOnCollisionEnter関数が呼ばれると同時に、このカッコ内で指定した値が入るようになっています。

そのOnCollisionEnter関数が呼ばれた際にcollisionというものを使って、条件を記載しています。

collision.gameObjectですが、接触したcollisionのゲームオブジェクト、という形になります。

CompareTag("Wall")の部分ですが、まずCompareTagがカッコ内の文字列と比較する関数です。

この場合はカッコ内で記述されたWallというタグであるかどうか比較しています。

文字列を記載する場合は「"」(ダブルクォーテーション)で文字列を括ります。

条件をそのまま続けて読むと
collisionのゲームオブジェクトのタグがWallかどうか
という判別が出来るようになります。

もっと簡単に言い換えると
ぶつかったオブジェクトに付いているタグがWallであるか
と言う風になります。

それでifの条件に合致するかの判別をします。
判別は真偽で判別され、合致する(真である)場合はtrueを、合致しない(偽である)場合はfalse(フォルス/フォールス)を返します。

ではifの条件に合致した場合の、ifの波カッコ内を書いていきます。

このように記述します。

        // Wallタグを持っているとき
        if (collision.gameObject.CompareTag("Wall"))
        {
            rb.velocity = Vector3.zero;
        }
記述すると、VSからエラーが発生していることを教えてくれます。

エラー表示

このエラーの内容は、「rb」というものはありません、という内容です。

え?でもrbって、Update関数の中で使ってるよ?となると思います。

これはC#の決まりで、関数内で作成した変数はその作成された関数内でしか使うことができないようになっています。

このような決まりは、プログラミングにおいて非常に重要で、アクセスできる範囲が明確に決まっています。
前回覚えた公開の範囲と同様です。覚えておきましょう。

ではどうすれば良いのかと言うと、このrbをこのPlayerControllerスクリプト内で共通して使えるようにします。

変数speedの上に、rbの行を切り取ってきます。
このようになります。

変数speedの上にrbの行を切り取ってくる

するとエラーの内容が変わりました。

エラーの内容が変わった

このエラーは、ゲームオブジェクトのコンポーネントを取得するなどの処理は、ここでは出来ないよ、というエラーになります。

rbに取得したコンポーネントを代入しようとしているのでエラーになっています。

ですので、rbの後ろを切り取ってこのように直します。


すると、エラーが消えました。

エラーが消えた

ただこのままだと、rbが何を指しているのか分からなくなっている状態なので、Start関数に先程切り取ったものを記述します。

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<rigidbody>();
    }

このようになります。


これでエラーが解消されました。

説明に戻ります。
rb.velocity = Vector3.zero;
を=で分けて考えます。

左辺のrb.velocityですが、velocityは前回Vector3でやった「一般的なベクトル演算を行うための関数が含まれています。」の部分にあたります。

velocityはRigidbodyの速度ベクトルになります。
ですので左辺はrbの速度ベクトル、という事になります。

右辺のVector3.zeroは、Vector3(0, 0, 0)と同じ意味になります。

するとこの行はrbの速度ベクトルをゼロにする、と言う意味になります。

これで目標の壁にぶつかったら勢いをなくすことができました。

スクリプトを保存して、Unityで実行してみます。

Gameビュー

壁にぶつかった際に、壁を乗り越えなくなりました!

スクリプトの修正

前回までのスクリプトを修正します。
と言っても、PlayerControllerスクリプトをちょっと変えるだけです。

場所はUpdate関数で、Updateの前にFixedを付けるだけです。

このようになります。

Updateの前にFixedを付けた

これでどのように変わったかは、ゲームを実行してもほぼ分からないと思います。

Update関数が毎フレーム呼ばれるのに対し、FixedUpdate関数は一定間隔毎に呼ばれる、という違いがあります。

FixedUpdate関数には、なるべく現実の時間にあわせて、ずれを無くしている(ように見せている)ものになります。

物理的な処理をする場合にFixedUpdate関数を使うんだな、くらいで覚えておいてください。

今回は物理的な処理はPlayerなので、PlayerControllerスクリプトをFixedUpdate関数にしました。

今回はここまで。


まとめ

本記事では
  • WallをPrefabに置き換えた
  • PlayerControllerスクリプトに、壁にぶつかった時の処理を追加した
という事を行いました。

次は、収集するアイテムをステージに追加したいと思います。

本記事もご覧頂き、誠に有難うございます。
ではまた。


コメント