本講座ではUnityとC#を用いて3Dレースゲームの作り方について説明しています。
今回は第5回となります。
前回は周回数を数える処理を作成しました。今回はそれを使ってスタート、ゴールの処理を作成を行います。
前回の記事 :
マリオカートのようなレーシングゲームではカウントダウンがあってからレースが始まりますよね。今回の講座ではそのようなレーススタートやゴールに関する機能を作成します。
では始めていきましょう!
ゲームスタートの作成 ステートを作って分岐させよう
「スタート→ゲーム中→ゴール」のような進行を表現するために、まずはゲームにステート(状態)を作成します。
まずはゲーム全体を管理するためのゲームコントローラーから作成していきましょう。
Projectウインドウの「Assets/AppMain/Script」に「GameController」という新しいC#スクリプトを作成します。
この「GameController」は文字通りゲーム全体を管理するものと考えましょう。
レーススタートのカウントダウン処理を実装する
ではまずスタート時のカウントダウンから作成します。
GameControllerを開きましょう。まずステートの作成とカウントダウン、タイマーを追加しました、先に全文記載します。
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class GameController : MonoBehaviour { // ------------------------------------------------------- /// <summary> /// ゲームステート. /// </summary> // ------------------------------------------------------- public enum PlayState { None, Ready, Play, Finish, } // 現在のステート. public PlayState CurrentState = PlayState.None; //! カウントダウンスタートタイム. [SerializeField] int countStartTime = 5; //! カウントダウンテキスト. [SerializeField] Text countdownText = null; //! タイマーテキスト. [SerializeField] Text timerText = null; // カウントダウンの現在値. float currentCountDown = 0; // ゲーム経過時間現在値. float timer = 0; void Start() { CountDownStart(); } void Update() { timerText.text = "Time : 000.0 s"; // ステートがReadyのとき. if( CurrentState == PlayState.Ready ) { // 時間を引いていく. currentCountDown -= Time.deltaTime; int intNum = 0; // カウントダウン中. if ( currentCountDown <= (float)countStartTime && currentCountDown > 0 ) { // int(整数)に. intNum = (int)Mathf.Ceil( currentCountDown ); countdownText.text = intNum.ToString(); } else if( currentCountDown <= 0 ) { // 開始. StartPlay(); intNum = 0; countdownText.text = "Start!!"; // Start表示を少しして消す. StartCoroutine( WaitErase() ); } } // ステートがPlayのとき. else if( CurrentState == PlayState.Play ) { timer += Time.deltaTime; timerText.text = "Time : " + timer.ToString( "000.0" ) + " s"; } else { timer = 0; timerText.text = "Time : 000.0 s"; } } // ------------------------------------------------------- /// <summary> /// カウントダウンスタート. /// </summary> // ------------------------------------------------------- void CountDownStart() { currentCountDown = (float)countStartTime; SetPlayState( PlayState.Ready ); countdownText.gameObject.SetActive( true ); } // ------------------------------------------------------- /// <summary> /// ゲームスタート. /// </summary> // ------------------------------------------------------- void StartPlay() { Debug.Log( "Start!!!" ); SetPlayState( PlayState.Play ); } // ------------------------------------------------------- /// <summary> /// 少し待ってからStart表示を消す. /// </summary> // ------------------------------------------------------- IEnumerator WaitErase() { yield return new WaitForSeconds( 2f ); countdownText.gameObject.SetActive( false ); } // ------------------------------------------------------- /// <summary> /// 現在のステートの設定. /// </summary> /// <param name="state"> 設定するステート. </param> // ------------------------------------------------------- void SetPlayState( PlayState state ) { CurrentState = state; } } |
結構がっつりなので、順に解説します。
まずusing表記に
1 |
using UnityEngine.UI; |
を忘れずに追加しましょう。Textなどを使用するために必要になります。
次に一番上
1 2 3 4 5 6 7 |
public enum PlayState { None, Ready, Play, Finish, } |
これは「enum(列挙型)」と言い、ここでは「PlayState」という型を定義しています。型というのは「float」とか「Rigidbody」とかのことで、このように定義するとそれらと同じように「PlayState」という型が作成できます。
そして、その方の値は「None」「Ready」「Play」「Finish」の4つのみしか取れない型になります。記載するときは「PlayState.〇〇」のように記載します。
その下にある変数が
1 |
public PlayState CurrentState = PlayState.None; |
「PlayState」型の「 CurrentState」という名前の変数になります。これは今まで出てきてる
1 2 |
型 変数名 = 値; 例) float num = 1f; |
と同じ形になります。この「enum」はコードをわかりやすくするためにとても便利なので覚えておきましょう。
(関連記事:Unity C#での列挙型と定数の使い方)
他の変数はそれぞれコメントを参照しましょう。「Text」の「SerializeField」は後ほどUnityで設定します。「Text」は文字通りUnityの文字コンポーネントです。
Start関数内は、後ほど解説する「CountDownStart()」関数を実行しています。
Update
次にUpdate関数ですが、ちょっと長いので分解して解説します。
ステートがReadyの時
まず「if( CurrentState == PlayState.Ready )」つまり「CurrentState」が「PlayerState.Ready」の場合の処理です。「CurrentState」は先に解説した列挙型の「PlayerState」です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
timerText.text = "Time : 000.0 s"; // 時間を引いていく. currentCountDown -= Time.deltaTime; int intNum = 0; // カウントダウン中. if ( currentCountDown <= (float)countStartTime && currentCountDown > 0 ) { // int(整数)に. intNum = (int)Mathf.Ceil( currentCountDown ); countdownText.text = intNum.ToString(); } else if( currentCountDown <= 0 ) { // 開始. StartPlay(); intNum = 0; countdownText.text = "Start!!"; // Start表示を少しして消す. StartCoroutine( WaitErase() ); } |
最初にまだ計測していない「timer」のテキストを「0」の状態にしておきます。
次に「currentCountDown -= Time.deltaTime」とあります。まずこの「-=」ですが、これは「currentCountDown = currentCountDown – Time.deltaTime」と同じ意味になります。「+=」も同様で、変数を単純に置き換えると下記のようになります。
そして「Time.deltaTime」は1フレームの時間を表しています。「currentCountDown」は現在のカウントダウンタイムで、「Time.deltaTime」を1フレーム毎に実行される「Update」内でマイナスすることにより、「currentCountDown」を秒単位で減少させていくことになります。
逆にプラスしていくと経過時間を秒で測ることができます。
次に「int(整数)」で「intNum」という変数を用意します。
そして条件「if ( currentCountDown <= (float)countStartTime && currentCountDown > 0 )」ですが、まずif文で複数の条件を書く場合下記のような決まりがあります。
条件1 && 条件2 | 条件1と条件2の両方を満たす場合 |
条件1 || 条件2 | 条件1もしくは条件2のどちらかを満たす場合 |
今回はまず「currentCountDown <= (float)countStartTime」は現在のカウントダウンタイム(currentCountDown)がカウントダウンのスタートタイム(countStartTime)以下の場合となります。
「(float)」はキャストと言われるもので、本来int(整数)である「countStartTime」を小数(float)に変換しています。左辺のcurrentCountDownがfloatであるためそれに合わせています。
これは何にでも適用できるものではありませんが、int ⇄ float など数値同士は簡単に変換できるので覚えておきましょう。
そしてもう一つの条件は「currentCountDown > 0」つまり現在のカウントダウンタイムが0以上の場合です。
「カウントダウン開始時間が0より大きい」、つまりカウントダウン最中であることを表現しています。
if文内の処理「Mathf.Ceil( 〇〇 )」とあります。これは「〇〇」の数値の小数点以下を切り上げた数値を返します。そして「(int)」で整数にキャストして、先に用意した「intNum」に保管します。
そして「countdownText」というTextコンポーネントの「text」つまり文字を設定します。文字は「inNum」を設定したいのですが、「intNum」は整数なのでそれを「ToString()」という関数を使って文字列に変換し設定します。
次の条件式「else if( currentCountDown <= 0 )」ですが、まず「else if」は先の「if」でない場合にさらに他の条件を満たす場合の条件になります。
(関連記事:Unity C#の条件分岐の使い方 if文・switch文)
条件は「currentCountDown」が0以下の場合、つまりカウントが終わったらです。
中の処理はまず「StartPlay()」関数を実行、「intNum」を0にする、「countdownText」にスタート文字を設定します。
次にある「StartCoroutine( 〇〇 )」ですが、これはコルーチンと言われる処理をスタートする関数です。コルーチンとは簡単にいうと処理の中断や停止、待機などができる処理のことを言います。
そのコルーチンが「WaitErase()」という関数です。この「StartPlay」「WaitErase」は後ほど解説します。
(関連記事:Unity C#のIEnumerable・IEnumeratorとコルーチンの使い方・作り方)
ステートがPlayの時
次にステートがPlay(ゲーム中)の時です。
1 2 |
timer += Time.deltaTime; timerText.text = "Time : " + timer.ToString( "000.0" ) + " s"; |
最初は「timer」という変数に「Time.deltaTime」を足しています。これは先に解説したように、ゲームの経過時間を計測しています。
そして「timerText」の文字を設定します。まずは「Time : 」という文字をそのまま表示します。このように「” “」で囲まれた文字は文字列として扱われます。
そこに「timer」を文字列に変換するために「ToString()」をします。ここの括弧内に「000.0」という表記があります。これは表示する形を指定しています。
例として変数「 a = 10.55f 」という値の場合これをToString()すると下記のようになります。
ToString() | 10.55 |
ToString( 000.0 ) | 010.5 |
ToStrng( 00.00 ) | 10.55 |
ToString( 0000 ) | 0010 |
それ以外のステート
1 |
timer = 0; |
最後は簡単です。「timer」を0にしています。
以降の関数
では他の関数を解説します
1 2 3 4 5 6 |
void CountDownStart() { currentCountDown = (float)countStartTime; SetPlayState( PlayState.Ready ); countdownText.gameObject.SetActive( true ); } |
カウントダウンを開始するための処理です。
最初にカウントダウンタイムを計測する数値「currentCountDown」を、カウントダウンの初期値「countStartTime」に設定します(floatにキャストします)。
次に「SetPlayState()」という関数を引数「PlayState.Ready」として実行します。この関数は後ほど解説します。
最後に「countdownText」のGameObjectを「SetActive( true )」つまりアクティブ状態にします。
次の関数です
1 2 3 4 5 |
void StartPlay() { Debug.Log( "Start!!!" ); SetPlayState( PlayState.Play ); } |
これはゲーム開始処理です。まだ開始にあたっての処理はないので一旦Untiyのコンソールに「Start!!!」というログを出しましょう。
そして先にも出てきた「SetPlayState()」を今度は「PlayState.Play」として実行します。
1 2 3 4 5 |
IEnumerator WaitErase() { yield return new WaitForSeconds(2f); countdownText.gameObject.SetActive(false); } |
これがコルーチンの処理になります。コルーチンで実行する処理は、返り値(戻り値)を「IEnumerator」にする必要があります。今はあまり難しく考えずにコルーチンの時は「IEnumerator」を付けることだけ覚えておきましょう。
(詳しく知りたい方向けの記事:Unity C#のIEnumerable・IEnumeratorとコルーチンの使い方・作り方)
最初の処理「yield return new WaitForSeconds( ◯◯ )」がコルーチンならではの処理で、この一行で「〇〇」秒待機して次の処理を行う、という意味になります。
そして待機したあと「countdownText」のGameObjectを非アクティブ(消す)にしています。
最後に
1 2 3 4 |
void SetPlayState( PlayState state ) { CurrentState = state; } |
何度か出てきた関数です。これは現在のステートを設定するための関数です。現在は「CurrentState」に引数で渡されたステートを設定しています。後ほどここに追加していきます。
Unityでのテキスト設定
長くなりましたが、次はUnity側で設定をしていきます。
最初にHierarchyで空オブジェクト(Create Empty)を作成し「GameController」と言う名前にしておきましょう。InspectorのTransformはなんでもいいですが、Position、Rotationは全て0、Scaleは1にしておくとなんとなく綺麗です。
ではここに作成したスクリプト「GameController.cs」をプロジェクトウインドウからドラック&ドロップして付与しておきましょう。
次にUIを作成していきます。まず最初にHierarchyで右クリック「UI/Canvas」でキャンバスを作成します。このキャンバスが文字通りUIを配置していくキャンバスになります。
UIを作成すると自動的に「EventSystem」が生成されます。これはUIを押したりイベントを実行するのに必要なので消さないようにしましょう。
この「Canvas」の子に最初に「Text」と「Empty」を作成します。そしてこの「Empty」の子に「Image」「Text」を作成します。この段階でのCanvas下の構造は
1 2 3 4 5 |
Canvas > Text > GameObject(Empty) >> Image >> Text |
になります。これらの名前を変更します。
1 2 3 4 5 |
Canvas > CountdownText (Text) > Header(Empty) >> Bg (Image) >> TimerText (Text) |
とします。この時点でのHierarchyは下記のようになります。(「GameController」や「Canvas」の順番は変更していますが、わかりやすければどこでもOKです)
ここからUIを設定していきます。
まず「CountdownText」を選択しInspectorの「RectTransform」の左上四角のマークをクリックし、ウインドウ内右下の十字の矢印の部分をクリックします。これはサイズを縦横両方を画面の大きさに応じて引き伸ばす設定です。
そして右の値の部分を全て0にしておきましょう。これで画面いっぱいに縦横が引き伸ばされます。
次に、「Text」コンポーネントを見てください。
最初に「Text」の項目に「5」と入力(後で変わるのでなんでもOK)、その下「FontSize」を「200」、「Alignment」を左右の3つのボタンのうちどちらも真ん中を選択、「Color」の右の四角をクリックするとカラーパレットがでますのでそこで黄色を選択しましょう。
次に「Header」を選択し「RectTransform」を設定します。先程と同じように左上の四角をクリックし、今度は「right/top」の交点にあるところをクリックします。そして数値を下記のようにせってします。
1 2 3 4 5 6 |
PosX = 0 PosY = 0 PosZ = 0 Width = 600 Height = 100 Pivot X = 1, Y = 1 |
最初に四角をクリックして設定したのがアンカーで、どこからの距離でPosを設定するかを設定しています。そして「Pivot」がこのRectTransformの原点になります。
この設定によりこの「Header」は画面右上にピッタリついた幅600、高さ100の四角になります。
次にその下の「Bg」「TimerText」の「RectTransform」は縦横ストレッチにして値全てを0にします。「Bg」の「Image」は色を黒の半透明にします。「TimerText」は「Text」を「Timer : 000.0」、「FontSize」を「50」、色は白、Alignmentをどちらも真ん中を選択にします。
駆け足で説明しましたが、下記画像を参考に設定してみましょう。ちなみに色を半透明にするにはカラーパレットで「RGBA」のうち「A」の値を変更しましょう。
ここまで設定するとGame画面は下記のようになりますので、最後に「GameContorller」を選択しInspectorの「GameController」で「CountdownText」「TimerText」をそれぞれドラック&ドロップしましょう。
ではここで一旦再生してみましょう。
スタートしたら真ん中の黄色い文字が「5、4、3、2、1、Start!!」となり、Untiyコンソールに「Start!!!」と出て、右上タイマーが動き出す。「Start!!」の黄色文字が少しして消える。までできていれば成功です。
プレイヤーのスタート
今のままだと、カウントダウンはしてもその間にプレイヤーが動かせてしまいます。
そこで、次はプレイヤーをスタートと同時に移動できるようにしていきます。
「PlayerController.cs」を開いて、追記していきます(変化の無い所の一部を省略しています)。
Unity側の設定
ではUnityに戻って必要な設定をしましょう。
まずは、HIerarchyで「TimerText」をコピーして、コピーしたものを「LapText」としましょう。そしてこの「TimerText」と「LapText」を上の「Bg」の子に移動します。
そして「Bg」を選択しInspectorの「AddComponent」をクリックして「Horizontal Layout Group」を付与します。その「Horizontal Layout Group」を下記のようにチェックを入れましょう。
最後に、「GameContorller」を選択し先程作成した「LapText」変数にHierarchyの「LapText」をドラック&ドロップしましょう。
すると下記のように横並びに整列します。(右上の文字)
先に作成方法を解説しましたが、「Horizontal Layout Group」というのは、子に配置したUIを横(Horizontal)に整列してくれるコンポーネントです。
ここまでできたら再生して確かめてみましょう。必要周回数を走って、ゴールしたらプレイヤーが動けなくなる+Goalの文字が表示+タイマーが止まってる。までできていればOKです。
今回は以上になります。次回はマップやゴール後の再スタートなどの処理を作成していきます。
次回の記事 :
コメント