プレイヤーはこちらの思った通りに行動してくれるとは限らない。
どこかの動画で、そんなフレーズを聞いた覚えがあったけど、このゲームでもそれは当てはまると思う。
ジェムを出すまで敵を倒すってのは、こちらが想定している動きで、必ずしもプレイヤーがそう行動してくれるわけではない。
ジェムを出せたら出せたで、同じ敵ばかりを延々を倒すかもしれない。
かといって、それをシステム的に説明するのでは味気ないし🧐
それなら、自然にそれを説明してくれる存在がいれば良い。ってわけで案内役を作成することにした。
案内役はプレイヤーに追従して動いて欲しい。川があるから飛んでるキャラが良いなと、AssetStoreでアセットを見ていたら、妖精キャラが無料であったので早速採用。
・・・採用したんだけど、移動モーションが良い感じではなかったので、残念ながら変更。
idle状態のモーションが移動させていてもしっくりくるので本採用。
案内役、イーグル君です。
イーグル君の追従だけど、プレイヤーの子にしてしまうと、あまりにピッタリと追従してしまって不自然なので、自主的にプレイヤーを追いかける様にしたい。
追従する動きについてググったら、ぴったりの記事が😆
イーグル君にアタッチするスクリプトTalkManagerの中の追従機能のメソッドとして、紹介されていたスクリプトをちょこっと改造。
基本的構造は変えていないけれど、一番最初でfollow Target、つまりプレイヤーとイーグル君の距離を測定して、
距離がある程度(0.2以下)近づいたら移動を止めて方向変換だけにした。
これで追従してくれる様になったが、敵の中には遠距離攻撃してくる奴もいるし、あまりにプレイヤーに近いところをうろうろしていると
攻撃モーションが被ってしまう。
イーグル君には攻撃が当たらないようにColliderを設定するにしても、モーションを避けさせるのは面倒なので、
戦闘中にはイーグル君には離れてもらった方が都合が良い。
じゃあ、戦闘中ってのをどうやって判定するか。
複数の敵が同時にプレイヤーと戦闘中になることも考えると、単純なBoolではうまくいかなさそう。
色々と考えていて、ふとロックオン機能で使ったListに思い至った。
敵がプレイヤーをTargetにしたら専用のListに敵自身を登録する様にして、
そのListのCountが1以上になったらイーグル君に退避行動を取らせればうまくいきそう。
自動的にListに登録する方法は、ロックオン機能で学んだので、その応用でいけるだろう。
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の説明だけど、どうせなら発言は一気に出るのではなく、一文字ずつ表示する様にさせたいので、
この記事から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メソッドを呼んでやれば良いだろう。
完成形はこんなイメージ。