Unity勉強中!あこがれだったプログラマーに今からなろう!

昔あこがれだったプログラマー。今からでもUnityを勉強してみようと思い立ち、チャレンジ開始! Unity勉強メモや、悪戦苦闘な日々の記録です。

プレイヤーを誘導しよう!案内役の導入。:3Dアクションゲームを作ろう(19)

プレイヤーはこちらの思った通りに行動してくれるとは限らない。
どこかの動画で、そんなフレーズを聞いた覚えがあったけど、このゲームでもそれは当てはまると思う。

ジェムを出すまで敵を倒すってのは、こちらが想定している動きで、必ずしもプレイヤーがそう行動してくれるわけではない。
ジェムを出せたら出せたで、同じ敵ばかりを延々を倒すかもしれない。
かといって、それをシステム的に説明するのでは味気ないし🧐
それなら、自然にそれを説明してくれる存在がいれば良い。ってわけで案内役を作成することにした。

案内役はプレイヤーに追従して動いて欲しい。川があるから飛んでるキャラが良いなと、AssetStoreでアセットを見ていたら、妖精キャラが無料であったので早速採用。

assetstore.unity.com

・・・採用したんだけど、移動モーションが良い感じではなかったので、残念ながら変更。

assetstore.unity.com

idle状態のモーションが移動させていてもしっくりくるので本採用。
案内役、イーグル君です。

イーグル君の追従だけど、プレイヤーの子にしてしまうと、あまりにピッタリと追従してしまって不自然なので、自主的にプレイヤーを追いかける様にしたい。
追従する動きについてググったら、ぴったりの記事が😆

gomafrontier.com

イーグル君にアタッチするスクリプトTalkManagerの中の追従機能のメソッドとして、紹介されていたスクリプトをちょこっと改造。

基本的構造は変えていないけれど、一番最初でfollow Target、つまりプレイヤーとイーグル君の距離を測定して、
距離がある程度(0.2以下)近づいたら移動を止めて方向変換だけにした。

これで追従してくれる様になったが、敵の中には遠距離攻撃してくる奴もいるし、あまりにプレイヤーに近いところをうろうろしていると
攻撃モーションが被ってしまう。
イーグル君には攻撃が当たらないようにColliderを設定するにしても、モーションを避けさせるのは面倒なので、
戦闘中にはイーグル君には離れてもらった方が都合が良い。

じゃあ、戦闘中ってのをどうやって判定するか。
複数の敵が同時にプレイヤーと戦闘中になることも考えると、単純なBoolではうまくいかなさそう。

色々と考えていて、ふとロックオン機能で使ったListに思い至った。
敵がプレイヤーをTargetにしたら専用のListに敵自身を登録する様にして、
そのListのCountが1以上になったらイーグル君に退避行動を取らせればうまくいきそう。

自動的にListに登録する方法は、ロックオン機能で学んだので、その応用でいけるだろう。

www.youtube.com

List、敵意のListということでHostilityListと、それを管理するManagerを作成。
敵Objectの子にあるStatusManagerがプレイヤーをTargetする機能を持っているので、そこに

hostilityListManager.HostilityList.Add(Main.gameObject);

を追加して、Listに敵自身を追加させる。

敵がプレイヤーを見失ったらListから敵自身を抜くのも忘れないように

Main.Target=null;

for (int i = 0; i < hostilityListManager.HostilityList.Count; i++)
{   
    if(hostilityListManager.HostilityList[i]==Main.gameObject)
    {
        hostilityListManager.HostilityList.RemoveAt(i);
    }
}

そして、イーグル君のスクリプトTalk Managerで
hostilityListManager.HostilityList.Countが0でない時には退避行動を取らせる様にして、さらに追従行動も取らないようにしてやれば良い。
完成形の追従メソッドはこんな感じ。

    void Update()
    {
        if(hostilityListManager.HostilityList.Count!=0)
        {
            talkanimator.SetBool("Leave",true);//退避アニメに変更する。
        }

        DoAutoMovement();
    }

    protected void DoAutoMovement()
    {
        if(hostilityListManager.HostilityList.Count==0) //敵意Listが0でなければ追従開始
        {
            talkanimator.SetBool("Leave",false);//退避アニメを解除。
            float BirdDistance=Vector3.Distance(followTarget.position,transform.position);
            Quaternion move_rotation = Quaternion.LookRotation(followTarget.transform.position - transform.position, Vector3.up);
            transform.rotation = Quaternion.Lerp(transform.rotation, move_rotation, 0.1f);
            if(BirdDistance>0.2f) //イーグル君とプレイヤーの距離判定
            {   
                float movingspeed=move_speed*BirdDistance;
                movingspeed = System.Math.Min(movingspeed, move_speed);
                rb.velocity = transform.forward * move_speed*BirdDistance;
            }
            else //近すぎるなら移動は止めて方向変換だけにする。
            {
                transform.rotation=followTarget.rotation;
                rb.velocity=Vector3.zero;
            }
        }
    }

追従機能はできたので、肝心の案内。喋る機能の作成をしよう。

まずはイーグル君の発言を表示するウィンドウを作成する。


ヴァーチャルコントローラーがあるせいで、ちょっと左ずれだけどまぁいいだろう。

色々な記事や動画を見ていると、セリフは大抵の場合はScriptableObjectを作って、そこに書く様なので、
僕もそうすることにした。


Next Talk Noが0でなければ続きのセリフがある様にしている。

発言用のメソッドはこんな感じ。

    public void Talking(int talkno)
    {
        talkwindow.SetActive(true); //発言ウィンドウを表示
        
        foreach(TalkData i in TalkSO.TalkEntity.talkDataList) //ScriptableObjectからセリフを拾ってくる。
        {
            if (i.talkNo != talkno) continue;
            talkText.text=i.talkText;
            _nextTalkNo=i.nextTalkno; //次のセリフが設定されていれば、それも拾ってくる。
        }

        StartCoroutine(FadeIn(2.5f,_nextTalkNo));
    }

FadeInの説明だけど、どうせなら発言は一気に出るのではなく、一文字ずつ表示する様にさせたいので、

logicalbeat.jp

この記事からFadeInやSetTextAlphaを使わせてもらった。
ついでに、ここにnextTalknoの判定と次のセリフ表示の機能を、ちょこっと追加。

    private IEnumerator FadeIn(float waitsec,int nextno)
    {

        // script上でテキストを更新した場合、TMPの更新が終わっていない場合があるので再生成
        talkText.ForceMeshUpdate(true);
        TMP_TextInfo textInfo = talkText.textInfo;
        TMP_CharacterInfo[] charInfos = textInfo.characterInfo;

        // 全ての文字を一度非表示にする(特殊文字の兼ね合いで要素と文字の数が一致しない場合がある)
        for (var i = 0; i < charInfos.Length; i++)
        {
            SetTextAlpha(talkText, i, 0);
        }

        // charInfosの要素数分ループ
        for (var i = 0; i < charInfos.Length; i++)
        {
            // 空白または改行文字の場合は無視
            if (char.IsWhiteSpace(charInfos[i].character)) continue;

            // 一文字ごとに0.05秒待機
            yield return new WaitForSeconds(0.05f);
            player.TalkSE();//一文字表示するたびに音を出す。

            SetTextAlpha(talkText, i, 255);
        }

        yield return new WaitForSeconds(waitsec);
        talkText.text="";
        talkwindow.SetActive(false);

        if(nextno!=0)//引数で受け取ったnext Talk Noが0ではなければ続きのセリフがある。
        {
            //talkwindowが閉じられるまで待つ。
            yield return new WaitUntil(()=>(talkwindow.activeInHierarchy!=true));
            yield return new WaitForSeconds(0.5f);            
            Talking(nextno);
        }

    }

これで追従と発言機能が完成したので、案内させたい場所で、各種のスクリプト達からこのTalkingメソッドを呼んでやれば良いだろう。

完成形はこんなイメージ。

youtu.be