この記事は「恋愛ホラー風ノベルゲーム」の作り方講座の第10回です。
前回まででキャラクターの画像や文字送りとページ送りで文章を変化させることで一通りの会話ができるようになりました。
前回の記事:
今回はさらに選択肢システムを実装します。
会話中にプレイヤーが選択をして会話の進行に変化を作れるようにしていきます。シナリオ分岐はテキストアドベンチャー形式のゲームや恋愛ノベルゲームには必須の機能になるので作り方をマスターしていきましょう。
Unityで選択肢UIを作成する
完成版の選択肢UIと基礎部分の作成
まずは表示されている状態のUIを配置していきます。
こちらが完成した選択肢UIです。ボタンを3つ会話より手前に配置し背景を暗くします。ボタンの数は処理によって変更できるようにしていきます。
では上記画像のようなUIを作成していきます。
まず、選択肢を表示させるためのUIをまとめる親になるゲームオブジェクトを「TalkWindow」の子に作成していきます。
ヒエラルキーで「TalkWindow」の子になるように右クリックし、「CreateEmpty」で空オブジェクトを作成し画面いっぱいに広げておきます。
作成したオブジェクト名を「SelectButtons」とします。
画面いっぱいに広げるには下記のようにインスペクター画面でRectTransformを設定すればOKです。
この「SelectButtons」の子に、まずはUI > Imageで新しく「Image」オブジェクトを作成します。そしてこちらもRect Transformを変更して画面いっぱいまで広げておきます。
画像は設定しなくていいので色を黒、そして「アルファ(Aの値)」を調節して(RGBモード0-255でここでは180に設定)半透明にします。
これで画面いっぱいに半透明の画像が広がり表示が暗くなりました。このImageを「bg」という名前にします。選択肢UIが表示された時の背景画面としてこのオブジェクトを使用します。
先にこの「bg」に「UITransition.cs」を付与しておきましょう。フェードイン・アウトのトランジションのみでいいので「Fade」のIs Activeをチェック、「Scale」のIs ActiveはチェックしないでDurationを「0.5」にしておきます。
次に、「SelectButtons」の子にさらにCreateEmptyで空オブジェクトを作成します。こちらのRectTransformは下記のように縦方向にStretch、横幅は「800」に指定しています。
こちらの名前は「Buttons」とします。このゲームオブジェクトの子にボタンを作成していきます。
選択肢となるボタンオブジェクトの作成
次にボタンを作成します。
先ほどの「Buttons」の子に、まずは「UI/Button – TextMeshPro」を作成します。名前を「SelectButton」とします。
「Width」を「800」、「Height」を「150」に設定、位置は「0」にしておきます。ボタンの画像を「GUI_11」にします。
ボタンの子にある(自動的に生成される)Textコンポーネントのフォント、フォントサイズ、表示内容を以下のように変更します。
今回はこのボタンをプレハブにしていきます。また、自動的に綺麗に縦に並ぶように設定しておきます。
まずは作成した「SelectButton」をProjectウインドウの「Assets/AppMain/02_Game/Prefabs」(Prefabsは新規フォルダとして作成)の中にドラック&ドロップします。
これで「SelectButton」がプレハブ化されました。
ボタンの並び方を確認&調整するために一旦「SelectButton」を複製して追加で2つ作成しておきましょう。名前はそのままで大丈夫です。「SelectButton(1)」「SelectButton(2)」などになると思います。
この段階では、真ん中に3つ重なって表示されています。
次に、ボタンの親「Buttons」のInspectorで「AddComponent」を行い、「VerticalLayoutGroup」を付与します。
これは子に配置したものを自動で縦に一定幅に綺麗に並べるためのコンポーネントです。
パラメータは下記のように設定しましょう。すると3つのボタンが自動的に縦に3つに並んだと思います。
パラメータのうち初期値から変更が必要なのは「Spacing」、「ChildAlignment」そして「Width」「Height」と書いてある3つのフラグ部分です。
簡単に各値を解説します。
Padding | 上下左右の余白 |
Spacing | 各要素の間隔 |
ChildAlignment | 配置の基準位置 |
ReverseArrangement | 反転フラグ |
Width,Heghtの3つのフラグ | 子に配置する要素の大きさの自動調整の設定 |
今回は「Spacing」を「50」にしたので、各ボタンの間隔が「50」で、「ChildAlignment」を「MiddleCenter」にしたので、中央を基準に配置されます。
また、Child Force Expandのチェックを外すとボタン間の距離が変わります。
できたら試しに配置したボタンを非アクティブにしてみてください。1つの時は中心に一つ、2つだと等間隔に二つと自動的に配置されるようになりました。
(つまり、選択肢の数に応じてリアルタイムにゲームオブジェクトを縦に綺麗に並べてくれます)
これで準備完了です。「Buttons」の子にある「SelectButton」3つは消してしまって構いません。
「Buttons」は消さないように気をつけてください。スクリプトでここに必要な数のボタンを生成するようにしていきます。
Unity C#で会話中に選択肢を表示させるスクリプトを作成
では作成したUIを使って、スクリプトからボタンを生成し会話の中で使用できるように処理を作成していきます。
ノベルゲームのための選択肢ボタン処理を作成
まずはプレハブから生成するボタンに使うスクリプトから作成します。
「../02_Game/Scripts/」内に「SelectButton.cs」というC#ファイルを作成します。
一気に追加していきます。
TalkWindowで使用できるように処理を作成
ここまでで選択肢ボタンを作れたので「TalkWindow」の会話の中で表示できるようにしていきます。
まずは、会話パラメータの中でどのような入力で選択肢を表示するかの仕様を決めます。
今回キャラ名を「1」、「2」などの数字で表しているので、このキャラの値が「30」のときには選択肢で、会話内容にはその選択肢の内容をコンマ(,)区切りで記入する形式にします。
まずは、その前提で選択肢用の値をデータに追加しておきます。
「TalkWindow」の「Talks」変数にInspectorから下記のように値を入れておきましょう。
「Name」の値を「30」にして、「Talk」にはコンマ区切りで選択肢の数だけ文字を入れます。
「Left」などのキャラ表示は今までと同じように必要に応じて入れておきます。
では、この値から選択肢を表示するための処理を「TalkWindow.cs」に追加していきます。
追加するのは変数を一つと、「TalkStart()」関数です。「TalkStart()」に関しては追加部分を含め内容を全て記載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
・・・ public class TalkWindow : MonoBehaviour { ・・・ // 選択肢. [SerializeField] SelectButtonDialog selectButtonDialog = null; ・・・ // ----------------------------------------------------------------- // 会話の開始. // ----------------------------------------------------------------- public async UniTask<List<int>> TalkStart( List<StoryData> talkList, float wordInterval = 0.2f ) { currentLeft = ""; currentCenter = ""; currentRight = ""; List<int> responseList = new List<int>(); foreach( var talk in talkList ) { // 選択肢の場合. if( talk.Name == "30" ) { goToNextPage = false; currentPageCompleted = false; isSkip = false; nextArrow.gameObject.SetActive( false ); SetCharacter( talk ).Forget(); string[] arr = talk.Talk.Split(','); // 文字列を「,」で分割 var res = await selectButtonDialog.CreateButtons( true, arr ); Debug.Log( "Response = " + res ); responseList.Add( res ); goToNextPage = true; } else { nameText.text = data.GetCharacterName( talk.Name ); talkText.text = ""; goToNextPage = false; currentPageCompleted = false; isSkip = false; nextArrow.gameObject.SetActive( false ); await SetCharacter( talk ); await UniTask.Delay( (int)( 0.5f * 1000f ) ); foreach( char word in talk.Talk ) { talkText.text += word; await UniTask.Delay( (int)( wordInterval * 1000f ) ); if( isSkip == true ) { talkText.text = talk.Talk; break; } } } currentPageCompleted = true; nextArrow.gameObject.SetActive( true ); await UniTask.WaitUntil( () => goToNextPage == true ); } return responseList; } ・・・ } |
まずは「SerializeField」の変数「selectButtonDialog」を作成します。これは後ほどInspectorから作成したものを入れます。
では「TalkStart()」関数を見ていきましょう。
まず返り値が「UniTask<List<int>」となっています。この内容は後ほど解説します。
処理内の変更部分としてまずは、「List<int> responseList = new List<int>();」で新しいintリストを作成しておきます。
これは選択肢を選んだ時のレスポンスを保管しておくためのものです。
そしてメインは「foreach」内です。
まずは「if( talk.Name == “30” )」で「talkList」を「foreach」する時の値「StoryData」の「Name」つまり名前の番号が「30」の時に、選択肢としての処理を行います。
この「30」に特に意味はありませんが、キャラとして使用していない番号にします。
そして、「else」部分にこれまで通りの通常キャラの処理を入れておきます(elseブロックで囲んでインデントしただけです)。
変更は加えていないのですが、if文の後の処理を見てみましょう。通常の会話文表示と選択肢表示の後の処理を共通化して扱っています。
1 2 3 |
currentPageCompleted = true; nextArrow.gameObject.SetActive( true ); await UniTask.WaitUntil( () => goToNextPage == true ); |
のところですね。
ここは通常キャラの会話の場合は、1ページの会話が終わって次のページにいく時のための待機処理になります。
「goToNextPage」が「true」になるまで待って、タップによりそれが満たされると次にいきます。
選択肢の場合は、選択が終わったらページ終了で次に行っていいので選択肢用の括弧内の処理の最後に「goToNextPage == true」を加えています。そのためタップなしで処理が進む形になっています。
(「nextArrow.gameObject.SetActive( true ); 」はそのまま残しておいても、選択肢の場合に影響はありません。)
では「「if( talk.Name == “30” )」内を見てみましょう。
最初の初期化部分
1 2 3 |
currentPageCompleted = false; isSkip = false; nextArrow.gameObject.SetActive( false ); |
この部分は同じですが、「nameText」「talkText」を初期化していません。
これは選択肢が表示されている時に、背景の前のテキストが表示されていてもいいなという判断の元こうしています。
そして「SetCharacter( talk ).Forget();」ですが、こちらは「await」ではなく、待機をしない「.Forget()」にしています。
選択肢の場合はキャラの設定はしますが、背景になるので表示を待つ必要がないので待機をしていません。
次が大切で「string[] arr = talk.Talk.Split(‘,’);」という処理です。
この時「talk.Talk」というのは会話内容の文字列です。先ほど選択肢の場合は会話の内容をコンマ区切りで記載するという仕様を決めました。
そのため、
これにより、コンマを区切りとして文字列を分けています。
例えば「選択肢1,選択肢2, 選択肢3」という文字列をコンマで「Split」すると「選択肢1」、「選択肢2」、 「選択肢3」が格納された配列ができます。その配列をarrに入れておきます。
その配列を「var res = await selectButtonDialog.CreateButtons( true, arr );」で先に作成しておいた「CreateButtons()」関数に引数「bgOpen」に「true」、「selectParams」に先ほどの「arr」を入れて実行します。
選択されるまで待機して選択されたら、その返り値を「res」として保管して「responseList.Add( res );」でリストに保管しておきます。
最後に「goToNextPage = true;」にして、選択が終わったら次のページに行けるようにしておきます。
この関数の最後には「return responseList;」として、選択肢がある場合の答えをリストとして返します。このリストを受け取るためにTalkStart関数の戻り値としてUniTask<List<int>>を受け取れるようにしていました。
これで処理の完成です。Unityに戻って必要な設定をします。
作成した変数「SelectButtonDialog」に「SelectButtons」を入れます。そして最後にSelectButtonsの子オブジェクトのbgを非アクティブにすれば準備完了です。
Unityエディタで再生して選択肢を表示させてみよう
これで一通りの作成が完了しましたので、再生してみましょう。
今までの会話をして、選択肢がでます。
どれかを選択すると閉じます。
ここでもし選択の内容によって変化を起こしたい場合は、返り値の「List<int>」に押した番号が返ってくるのでそれを使用して次の処理を作成します。
まとめと次回に向けて
ここまでの内容でキャラクターの会話処理の中で選択肢を表示させてシナリオを分岐させるための基本システムを構築しました。
次回は会話中に背景画像を変更する方法を解説します。また、ここまで作成してきた会話の途中でシーン移動した際に生じるUniTaskの不具合解決のための方法などを習得していきます。
次回の記事:
コメント