レッスン 8

目次

交差するStateエリアのシステムをつくる(Creating a System for Intersecting State Areas)

Triggerに入った場合のStateを設定しておくことで、プレイヤーが自分で決めた行き先によって音楽をダイナミックに変化させることができます。この方式は、リージョンに分けられたゲームで、範囲がかぶっていなければ、うまくいきますが、交差すると問題がでてきます。下図は、VillageとWoodlandsの2つのTriggerを表した概念図です。

ここでは、Woodlands Triggerが、少しだけVillage Triggerとかぶっています。プレイヤーがVillageからWoodlandsの方に向かい、Woodlands Triggerに入ると、StateがWoodlandsに設定されます。ところが、前セクションで実装したロジックがあるので、プレイヤーがWoodlandsのさらに奥に進み、Villageをあとにすると、OnTriggerExitState()ファンクションのせいで、グローバルのMusic_RegionsのStateがNowhereに設定されます。そうするとプレイヤーは、Woodlandsリージョンに立っていながらも、Ambient Musicを聞いているような状態になってしまいます。

この問題の1つの解決策は、Stateのリストでプレイヤーが入ったTriggerをすべてトラッキングし、音楽のテーマの優先順位をまとめて管理するシステムをつくることです。

そうすればプレイヤーがTriggerに入るたびにStateリストのカウントに入れるので、今入っているTriggerの数が正確に分かります。プレイヤーが1つのTriggerを出ると、このシステムは、プレイヤーが別のTriggerに入っているかを確認します。入っていなければ、StateをNowhereに戻します。これから、前のセクションで作成したSetMusicStateスクリプトを使い、それを拡張して、交差するTriggerに対応する方法を説明します。Stateのリストを作成し、PlayerがリージョンのTriggerを出たり入ったりした時点でダイナミックにアップデートするのです。

  1. Unityのメニューで、 Audiokinetic > Certification > 301 > Lesson 8 を選択し、 Creating a System for Intersecting State Areas を選択します。

    この演習は、VillageとWoodlandsの2つのミュージックリージョンが対象です。両方とも、前の演習で作成したSetMusicStateスクリプトを使って設定する必要があります。

  2. Hierarchyで Village Music TriggerWoodlands Music Trigger の2つのゲームオブジェクトを選択します。

    [ヒント]

    ShiftまたはCtrlを押しながら選択すれば、両方のゲームオブジェクトを選択できます。

    複数のゲームオブジェクトを選択した場合は、Inspectorで追加したコンポーネントが、どちらのゲームオブジェクトにも追加されます。

  3. Inspectorで Add Component をクリックし、 SetMusicState を検索して選択します。

    スクリプトの中に入る前に、Wwise-TypeのStateをアサインしてください。2つのMusic Triggerがまだ選択されていることを確認してください。

  4. VillageWoodlands の2つのMusic Triggerゲームオブジェクトを選択した状態で、 On Trigger Exit State プロパティを開き、 MusicStates > Music_Regions を選択して Nowhere を選択します。

    TriggerのStateがそれぞれ違うかもしれないので、個別に編集する必要があります。

  5. Hierarchyで Village Music Trigger だけを選択します。

  6. Inspectorで OnTriggerEnterState を開き、 MusicStates > Music_Regions を選択して Village を選択します。

  7. Hierarchyで Woodlands Music Trigger だけを選択します。

  8. Inspectorで OnTriggerEnterState を開き、 MusicStates > Music_Regions を選択し、 Woodlands を選択します。

    それでは、スクリプトを開いて必要な修正を行います。

  9. Inspectorで SetMusicState スクリプトをダブルクリックします。

    次にゲームプレイのPlayerとTriggerの交差で引き起こされるStateの変化をトラッキングするためのリストをつくります。リストをつくるには、以下を書きます。

                                List< >  
     

    山括弧の中に、リストに格納するプロパティのタイプを宣言します。ここでは、Wwiseクラスタイプの1つであるAK.Wwise.Stateを、プロパティをアサインするときと同じように書きます。

  10. SetMusicStateスクリプトを開き、 SetMusicState クラスの一番上に新しい行を追加し、 List<AK.Wwise.State> と入力します。

         
        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
    
        public class SetMusicState : MonoBehaviour {
            List<AK.Wwise.State>
            public AK.Wwise.State OnTriggerEnterState;
            public AK.Wwise.State OnTriggerExitState;
            private void OnTriggerEnter(Collider other){
                if(other.CompareTag("Player")){
                    OnTriggerEnterState.SetValue();
                }
             }
            private void OnTriggerExit(Collider other){
                if(other.CompareTag("Player")){
                    OnTriggerExitState.SetValue();
                }
             }

    これでWwise-TypeのStateのリストを宣言できましたが、名前を付ける必要があります。

  11. List<AK.Wwise.State> のあとに、 ListOfStates という名前を入れます。

    public class SetMusicState : MonoBehaviour {
        List<AK.Wwise.State> ListOfStates

    Listを作成できましたが、ほかのスクリプトからこれを見て、Stateを追加できることを確認するには、少し修正する必要があります。まずpublic修飾子を追加し、このクラスの外からも見えるようにします。

  12. リストの前に public を挿入します。

    public class SetMusicState : MonoBehaviour {
        public List<AK.Wwise.State> ListOfStates

    次に、買い物リストを書くところが必要なのと同じで、まずリストをインスタンス化してから、項目を追加し始めます。リストをインスタンス化するには、修飾子'new'を使います。

  13. public List<AK.Wwise.State> ListOfStates のあとに = new List<AK.W、wise.State>(); を追加します。

     public class SetMusicState : MonoBehaviour {
        public List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>();

    前の演習で、OnTriggerEnter()とOnTriggerExit()の2つのファンクションを作成しました。Village TriggerやWoodlands Triggerでも同じスクリプトを使っていますが、そのファンクションの範囲が private となっていたため、どちらのTriggerも、OnTriggerEnter()ファンクションと、OnTriggerExit()ファンクションの、自分用のインスタンスをコールします。そうすれば、SetMusicStateスクリプトのある各Music Triggerが、これから出てくるStateリストに、それぞれ独立して追加できます。ただしスクリプトはゲームオブジェクトごとにインスタンス化されるので、どうすれば、すべてのスクリプトが同じリストを参照するようにできますか?その答えが、'static'(静的)修飾子です。リストをstaticにすることで、常に1つしか存在しないようにできます。そうすれば、このリストをどのスクリプトから参照しても、同じリストを見ることになります。

  14. static を、 public 修飾子のあとに挿入します。

     public class SetMusicState : MonoBehaviour {
        public static List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>();

    次に、OnTriggerEnter() と OnTriggerExit()のファンクションを編集し、リストのStateを追加・削除できるようにします。次に進む前に、先ほどのTriggerの図に戻ってみます。例えばプレイヤーがVillageでスタートしてWoodlands方面に向かったとします。プレイヤーがWoodlands Triggerに入ると、リストの一番上に、WoodlandsのStateが挿入されます。

    もしプレイヤーがそのままWoodlandsに入っていけば、VillageのStateは単純にリストから落とされ、一番上のState (Woodlands) は、設定されたまま残ります。しかしプレイヤーがVillageに戻ることにしてVillage Triggerを出る前にWoodlandsを出たとすると、リストからWoodlandsのStateが削除され、一番上には新しいStateとしてVillage が設定されるべきです。

    このようなシステムを作成するには、リストにInsert()ファンクションを使い、Stateをリストの特定の位置に挿入するべきです。Insertファンクションの括弧内に、それを挿入する位置を指定し、次にStateプロパティを入れます。Stateを挿入するのは、いつもリストの一番上なので、0をリストのインデックス(ポジション)として宣言すればいいのです(リストは必ず0からスタートし、1、2、3と続くため)。

  15. OnTriggerEnter() ファンクションで、 OnTriggerEnterState.SetValue(); を、 ListOfStates.Insert(0, OnTriggerEnterState); に置き換えます。

        public static List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>();
        public AK.Wwise.State OnTriggerEnterState;
        public AK.Wwise.State OnTriggerExitState;
        private void OnTriggerEnter(Collider other){
            if(other.CompareTag("Player")){
                ListOfStates.Insert(0, OnTriggerEnterState);
            }
         }

    Insert() ファンクションは、絶対にターゲットポジションを上書きすることなく、残りの項目をリストの下へずらすだけです。

    次に、リストの最初の項目をアクセスするのに角括弧 [ ] を使い、アクセスしたいエレメントのポジションをカプセル化します。リストのトップにある項目は、最後に入ってきたリージョンなので、値をポジション0に設定するだけです。つまり、一番上のStateを入手するには、まずリスト名を宣言し、次に始め角括弧、そして次に数字の0、そして最後に終わり角括弧と続けます。Stateが分かれば、次に レッスン 5 のSetValue()ファンクションをコールします。

  16. ListOfStates.Insert(0, OnTriggerEnterState);のあとに新しい行を追加し、 ListOfStates[0].SetValue(); と入力します。

    public static List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>();
    public AK.Wwise.State OnTriggerEnterState;
    public AK.Wwise.State OnTriggerExitState;
    private void OnTriggerEnter(Collider other){
        if(other.CompareTag("Player")){
            ListOfStates.Insert(0, OnTriggerEnterState);
            ListOfStates[0].SetValue();
        }
    }

    これで、リストに追加して値を設定できていますが、まだTriggerを出たときに、リストから削除していません。例えば、プレイヤーがVillage Triggerを出たときに、Village TriggerのStateをリストから削除し、リストの一番上のStateを設定するべきです。

    リストの項目を削除するのにリストのRemove()ファンクションを使うと、あなたが指示したStateがどのポジションにあったとしても、それを探して削除してくれます。

  17. OnTriggerExit()ファンクションで OnTriggerExitState.SetValue();ListOfStates.Remove(OnTriggerEnterState); に置き換えて、 スクリプトを保存 します。

        private void OnTriggerExit(Collider other){
            if(other.CompareTag("Player")){
                ListOfStates.Remove(OnTriggerEnterState);
            }
         }

    この実装をテストするには、ゲームをプレイすればいいだけです。

  18. Unityで Play をクリックしてからVillage Triggerの中まで駆け込み、次にVillage Triggerを出ずに橋まで進み、次にVillageまで戻ります。

    WoodlandsとVillageの2つのTriggerが重なる部分を出るときに、音楽がVillageテーマに戻りませんでした。これは、エレメントを削除したときに、Stateが再び設定されないからです。そのため、リストの項目を1つ削除したら、一番上にくるStateを新たに設定する必要があります。ここでゲームを停めてください。

  19. ListOfStates.Remove(OnTriggerEnterState); のあとに新しい行を追加し、 ListOfStates[0].SetValue(); と入力します。

    private void OnTriggerExit(Collider other){
        if(other.CompareTag("Player")){
            ListOfStates.Remove(OnTriggerEnterState);
            ListOfStates[0].SetValue();
        }
    }

    あともう少しです!問題が1つ残っています。どのMusic State Triggerにもいないときは、NowhereというStateを設定するべきです。現在の実装では、リストが空になり、 Null Reference Exception が返ってきます。これを解消するには、リストに項目が入っているかどうかを検証するif文を追加し、もし何もなければ、NowherのStateに設定させます。リストに項目が入っているのかどうかを確認するには、リストのエレメント数の入った.countプロパティを使いますが、もし数字が0より大きければ、リストが空でないことが分かります。

  20. ListOfStates.Remove(OnTriggerEnterState); のあとに新しい行を追加し、 if(ListOfStates.Count > 0){ と入力します。

        if(other.CompareTag("Player")){
            ListOfStates.Remove(OnTriggerEnterState);
            if(ListOfStates.Count > 0){
                ListOfStates[0].SetValue();
        }

    この行に、始めの波括弧' { 'だけを書いていて、終わりの' } 'がないのに注目してください。. 'ListOfStates[0].SetValue();'は条件がtrueのときだけ実行されればいいので、'}'は、'ListOfStates[0].SetValue();'のあとに書き、if文がtrueの場合に実行する必要のあるコードの部分を、定義します。

  21. ListOfStates[0].SetValue(); のあとに新しい行を入れ、終わりの 波括弧 ' }' を書きます。.

        if(other.CompareTag("Player")){
            ListOfStates.Remove(OnTriggerEnterState);
            if(ListOfStates.Count > 0){
                ListOfStates[0].SetValue();
            }
        }

    空でない場合に限り、リストの値を設定しています。では、すべてのMusic Triggerのないエリアで使用されている、NowhereのStateはどうでしょう?リストのカウントが0であれば、NowhereのStateを設定します。リストが空かどうかを確認するのに、if文と、else{} 条件を使ってみます。

    else文を使えば、false条件のコールを、else文の波括弧内にあるコードへ向けることができます。

  22. if文の終わり波括弧のあとに、 else {を追加します。

            if(other.CompareTag("Player")){
                ListOfStates.Remove(OnTriggerEnterState);
                if(ListOfStates.Count > 0){
                    ListOfStates[0].SetValue();
                }else{
            }

    [注釈]

    もしコードエディタが自動的に、終わり波括弧でelse文を完成させてくれた場合は、次の手順を無視できます。

  23. Enterを2回押し、終わりの 波括弧 '}' を入力します。

            if(ListOfStates.Count > 0){
                ListOfStates[0].SetValue();
            }
            else{
    
            }

    これで、 else コードの部分がコールされたときは、StateがNowhereに設定されます。こうすれば、プレイヤーが特にMusicリージョンのTriggerに入っていなければ、グローバルのStateがNohwereに設定され、音楽はAmbientテーマが再生されます。

  24. Else文の波括弧内に、 OnTriggerExitState.SetValue(); と書きます。

        public class SetMusicState : MonoBehaviour {
            public static List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>();
            public AK.Wwise.State OnTriggerEnterState;
            public AK.Wwise.State OnTriggerExitState;
            private void OnTriggerEnter(Collider other){
                if(other.CompareTag("Player")){
                    ListOfStates.Insert(0, OnTriggerEnterState);
                    ListOfStates[0].SetValue();
                }
            }
            private void OnTriggerExit(Collider other){
                if(other.CompareTag("Player")){
                    ListOfStates.Remove(OnTriggerEnterState);
                    if(ListOfStates.Count > 0){
                        ListOfStates[0].SetValue();
                    }else{
                        OnTriggerExitState.SetValue();
                    }
                }
            }

    それでは、試してください!

  25. Unityで Play をクリックしてからVillage Triggerの中まで駆け込み、次にVillage Triggerを出ずに橋まで進み、次にVillageまで戻ります。

    あなたがVillageに入ると、最初にVillageテーマが流れ、次に橋の上のTriggerが重複する部分に入ると、テーマがWoodlandsに切り換わりますが、もう一度Villageの中に戻ると、再度Villageテーマが流れます。

  26. そのままWoodlands Triggerの方に行き、Village Triggerを出ます。

    Woodlandsの中に入り、Villageをあとにすると、VillageテーマがWoodlandsテーマに切り換わり、あなたがもう一度、Village Triggerに入るまで再生が続きます。スクリプトに追加した条件をもとに、プレイヤーが音楽のTriggerの外にいるときに限り、NowhereのStateが設定されます。ゲームを停めてください。

以上で、複数のTriggersを取り扱うシステムを作成できます。今回、この技能検定で初めてコードを書いた人は、大いに誇りに思ってください。まだプログラマになった気はしなくても、多くのプログラマが実際に日々行っている基礎的なテクニックを学べたのです。リストファンクションを賢く使えば(RemoveやInsert)、どのような類のプロパティや変数であれ、まとめてコントロールできるはずです。また、プログラマ達はオーディオインテグレーション以外のことを開発するのにも、これらのテクニックの多くを利用しています。そこで、if文やListなどのテクニックを少し習得しただけで、プログラミング全般について学べたわけです。