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

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

3D脱出ゲーム:GameModeの整理をしよう。

今日は、ゲームのメイン部分である、イベントの仕組みを作成していきます。
イベントの定義などを作成していくのですが、まずその前に、昨日の説明の中で唐突に出てきたGameModeを整理しておこうと思います。

ゲーム中、Playerが今どのような状態なのかを区分けすることで、その時にできる事、できない事の判定がしやすくなります。
また、メッセージを表示している最中に、もう一度メッセージを表示しようとすると、メッセージウィンドウの挙動がおかしくなってしまうなどの
動作異常を防止することもできます。

各クラスから参照させたいので、GameModeはGameManager に保持させるようにしています。

現段階で、どれくらいのModeを用意するか、実はまだ決まりきってはいません😅
とりあえず、以下の4つで作成を始めています。
・Normal:
 通常状態。
 移動ができる。
 ItemBoxからアイテムをドラッグして使用できる。

・Search:
 虫眼鏡をクリックして、マウスポインタが虫眼鏡になった状態。通常より詳しく調べるモード。
 移動はできない。
 ItemBoxのアイテムをクリックして拡大表示できる。
 ItemBoxからアイテムをドラッグして使用できる。

・Message:
 メッセージウィンドウが開いている状態。移動や他のクリックで、一つ前のNormalもしくはSearchに戻る。

・Terminal:
 端末操作中の状態。
 移動はできない。
 ItemBoxの操作はできない。

こんな感じです。

そして、イベントの中心的な機能といえば、やはりメッセージ表示。
これは、3Dアクションでも作成したので、その応用というか、ほぼ丸コピーです。

追加機能として、今回はメッセージをスクリプタブルオブジェクト(SO)から取得してくる方法以外に、メッセージを表示させるイベントから
直接メッセージを送り込んで表示することもできるようにしています。
これは、アイテムを取得した時に、Item名付きの「***を手に入れた。」というメッセージを表示させるのに、SOからの定型分だけでは
どうしても都合が悪いと思ったからです。

実際には、SO内のメッセージに変数名を割り当てる方法があるのかもしれませんが、僕は残念ながら、まだその方法を知らないので、
パッと思いついた方法で作成することにしました。

作成するといっても、

logicalbeat.jp

相変わらず、この記事のコピーもじりではありますが😅
ほんと、いつもお世話になっております。🙇

using UnityEngine;
using TMPro;
using DG.Tweening;
using Cysharp.Threading.Tasks;
using System.Threading;
using System;

public class MessageManager : MonoBehaviour
{

    private CancellationTokenSource cts;
    CancellationToken token;
    CancellationToken Destroytoken;

    [SerializeField] TMP_Text MessageText;

    public GameObject MessageImage;
    //
    int message_no=0;

    int NextMessageNo;
    [SerializeField] RectTransform panel;
    private PlayerInput inputSystemSC;

    public static MessageManager instance;
    private void Awake() 
    {
        if(instance==null)
        {
            instance=this;
        }
    }

    private void Start() 
    {
        inputSystemSC=new PlayerInput();
        inputSystemSC.Enable(); 
        cts = new CancellationTokenSource();
        token = cts.Token;
        Destroytoken=this.GetCancellationTokenOnDestroy();
    }


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

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

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

            // 一文字ごとに0.05秒待機
            await UniTask.Delay(50);

            SetTextAlpha(MessageText, i, 255);
        }

        await UniTask.WaitUntil(() => Input.GetMouseButtonDown(0)||
                                      inputSystemSC.Player.Move.ReadValue<Vector2>() != Vector2.zero||
                                      inputSystemSC.Player.Look.ReadValue<Vector2>() != Vector2.zero
                                      , cancellationToken: Destroytoken);

        if(nextMessageNo!=0) //次のメッセージがある際は、もう一回メッセージ取得から繰り返す。
        {
            foreach(Message i in MessageSO.Entity.MessageList)
            {
                if (i.MessageNo != nextMessageNo) continue;
                MessageText.text=i.MessageText+"\n"+"-- Click to close --";
                message_no=i.MessageNo;
                NextMessageNo=i.NextMessageNo;
                //messagetype=i.type;
            }

            FadeIn(NextMessageNo).Forget();
            return;
        }

       //次のメッセージがないなら、メッセージウィンドウを閉じる。
        MessageText.text="";
        await panel.DOScale(new Vector2(1,0.01f), 0.3f).AsyncWaitForCompletion();
        await panel.DOScale(new Vector2(0,0), 0.3f).AsyncWaitForCompletion();
        GameManager.instance.gameMode=GameManager.instance.beforeGameMode;//GameModeを戻す。

        MessageImage.SetActive(false);

    }

    // charIndexで指定した文字の透明度を変更
    private void SetTextAlpha(TMP_Text text, int charIndex, byte alpha)
    {
        
        // charIndex番目の文字のデータ構造体を取得
        TMP_TextInfo textInfo = text.textInfo;
        TMP_CharacterInfo charInfo = textInfo.characterInfo[charIndex];

        // 文字を構成するメッシュ(矩形)を取得
        TMP_MeshInfo meshInfo = textInfo.meshInfo[charInfo.materialReferenceIndex];

        // 矩形なので4頂点
        var rectVerticesNum = 4;

        for (var i = 0; i < rectVerticesNum; ++i)
        {
            // 一文字を構成する矩形の頂点の透明度を変更
            meshInfo.colors32[charInfo.vertexIndex + i].a = alpha;
        }
        
        // 頂点カラーを変更したことを通知
        text.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32);

    }


    public async UniTask ShowGetMessage(int message_no)//メッセージをSOから取得する際に呼ばれるメソッド。
    {
        // メッセージウィンドウを開く
        MessageImage.SetActive(true);

        await panel.DOScale(new Vector2(1,0.01f), 0.3f).AsyncWaitForCompletion();
        await panel.DOScale(new Vector2(1,1), 0.3f).AsyncWaitForCompletion();
        
        // SOからメッセージを取得
        foreach(Message i in MessageSO.Entity.MessageList)
        {
            if (i.MessageNo != message_no) continue;
            MessageText.text=i.MessageText+"\n"+"-- Click to close --";
            message_no=i.MessageNo;
            NextMessageNo=i.NextMessageNo;
            Debug.Log(NextMessageNo);
        }

        FadeIn(NextMessageNo).Forget();
    }

    public async UniTask ShowMessage(String message)//メッセージ自体を受け取って表示する際に呼ばれるメソッド。
    {
        // メッセージウィンドウを開く
        MessageImage.SetActive(true);

        await panel.DOScale(new Vector2(1,0.01f), 0.3f).AsyncWaitForCompletion();
        await panel.DOScale(new Vector2(1,1), 0.3f).AsyncWaitForCompletion();
        
         MessageText.text=message+"\n"+"-- Click to close --";

        FadeIn(0).Forget();
    }

    private void OnDisable() 
    {
        cts.Cancel();    
    }

    private void OnDestroy() 
    {
        cts.Cancel();    
    }
}

次は今度こそ、イベント本体の作成をしよう😅