UnityでFPSをつくる その10-6 [ シーン遷移 ]
前回まででタイトルシーンにポストプロセスを加えることができました。
今回はスクリプトで制御をしてメインシーンへと遷移させます。
以下が完成イメージです

HierarchyウィンドウへCreate Emptyで空のGameObjectを作り、「SceneManager」にリネームします。


SceneManagerへ、以前作成したスクリプト「CursorState」をアタッチします。

これでカーソルが非表示になり、画面中央に固定されました。
次に「PRESS START」のテキストを点滅させて、ボタンクリックでメインシーンへ遷移するようにします。
新規に「ToMainScene」という名前でスクリプトを作り、

以下のコードをコピペしてください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
//シーンを扱う為の名前空間
using UnityEngine.SceneManagement;
public class ToMainScene : MonoBehaviour
{
[SerializeField] Text pressStart;
[SerializeField] Image fadeBoard;
[SerializeField] SpriteRenderer titleImage;
//色、透明度
float fadeBoardColorRed, fadeBoardColorGreen, fadeBoardColorBlue, fadeBoardColorAlpha,
titleImageColorRed, titleImageColorGreen, titleImageColorBlue, titleImageColorAlpha,
pressStartColorRed, pressStartColorGreen, pressStartColorBlue, pressStartColorAlpha;
//点滅の間隔
float textBlinkDuration = 8f;
//アルファ値加算許可、スタートボタン押下済みフラグ
bool alphaAdditionFlag, pressStartDoneFlag;
// Start is called before the first frame update
void Start()
{
//colorの個々の値は取得できても変更できない為、変数に代入する
fadeBoardColorRed = fadeBoard.color.r;
fadeBoardColorGreen = fadeBoard.color.g;
fadeBoardColorBlue = fadeBoard.color.b;
fadeBoardColorAlpha = fadeBoard.color.a;
titleImageColorRed = titleImage.color.r;
titleImageColorGreen = titleImage.color.g;
titleImageColorBlue = titleImage.color.b;
titleImageColorAlpha = titleImage.color.a;
pressStartColorRed = pressStart.color.r;
pressStartColorGreen = pressStart.color.g;
pressStartColorBlue = pressStart.color.b;
pressStartColorAlpha = pressStart.color.a;
}
// Update is called once per frame
void Update()
{
BeforePressStart();
if (Input.GetMouseButtonDown(0))
{
//スタートボタン押下前のテキスト点滅間隔を終了
pressStartDoneFlag = true;
StartCoroutine(FadeOut());
}
}
IEnumerator FadeOut()
{
//フェードアウト
var isFadeOut = true;
//何秒かけて暗転させるか
var fadeOutDuration = 3f;
//点滅の間隔
var textBlinkDurationAfterPressStart = 0.3f;
while (isFadeOut)
{
//fadeOutDurationの時間で暗転するよう、アルファ値を加算
fadeBoardColorAlpha += Time.deltaTime / fadeOutDuration;
//titleImageはfadeBoard暗転の影響を受けないので、個別にアルファ値を減算
titleImageColorAlpha -= Time.deltaTime / fadeOutDuration;
//STARTキーのクリック前より、短い間隔でアルファ値 0.5 ~ 1 の間を行き来するようにする
if (pressStartColorAlpha >= 0.5f && !alphaAdditionFlag)
{
pressStartColorAlpha -= Time.deltaTime / textBlinkDurationAfterPressStart;
pressStart.color = new Color(pressStartColorRed, pressStartColorGreen, pressStartColorBlue, pressStartColorAlpha);
if (pressStartColorAlpha <= 0.5f)
{
alphaAdditionFlag = true;
}
}
else if (pressStartColorAlpha <= 1f && alphaAdditionFlag)
{
pressStartColorAlpha += Time.deltaTime / textBlinkDurationAfterPressStart;
pressStart.color = new Color(pressStartColorRed, pressStartColorGreen, pressStartColorBlue, pressStartColorAlpha);
if (pressStartColorAlpha >= 1f)
{
alphaAdditionFlag = false;
}
}
//アルファ値以外は変更せずにcolorに値を代入
fadeBoard.color = new Color(fadeBoardColorRed, fadeBoardColorGreen, fadeBoardColorBlue, fadeBoardColorAlpha);
titleImage.color = new Color(titleImageColorRed, titleImageColorGreen, titleImageColorBlue, titleImageColorAlpha);
//fadeBoardの透明度が1(完全に不透明)かつ、titleImageの透明度が0(完全に透明)になったら
if (fadeBoardColorAlpha >= 1 && titleImageColorAlpha <= 0)
{
//フェードアウト終了
isFadeOut = false;
}
yield return null;
}
yield return new WaitForSeconds(1f);
//メインシーンに切り替え
SceneManager.LoadScene("MainScene");
}
//スタートボタン押下前のテキスト点滅
void BeforePressStart()
{
//アルファ値加算許可&スタートボタン押下済みフラグが立っていない場合
if (pressStartColorAlpha >= 0.5f && !alphaAdditionFlag && !pressStartDoneFlag)
{
pressStartColorAlpha -= Time.deltaTime / textBlinkDuration;
pressStart.color = new Color(pressStartColorRed, pressStartColorGreen, pressStartColorBlue, pressStartColorAlpha);
if (pressStartColorAlpha <= 0.5f)
{
alphaAdditionFlag = true;
}
}
//完全に透明にならないよう、アルファ値 0.5 ~ 1 の間を行き来するようにする
else if (pressStartColorAlpha <= 1f && alphaAdditionFlag && !pressStartDoneFlag)
{
pressStartColorAlpha += Time.deltaTime / textBlinkDuration;
pressStart.color = new Color(pressStartColorRed, pressStartColorGreen, pressStartColorBlue, pressStartColorAlpha);
if (pressStartColorAlpha >= 1f)
{
alphaAdditionFlag = false;
}
}
}
}
スクリプトの中身を見ていきます。
4,6行目では、今回もUIとシーン遷移を扱う為にnamespaceを追加しています。

10~14行目で、Inspectorから各コンポーネントを指定できるようクラス型の変数をSerializeField化。

17~19行目で、各コンポーネントの色(RGB)と透明度(A)を取得する為、変数を宣言。

22行目でスタートボタンが押される前のテキストの点滅間隔を決める変数を宣言。

25行目でアルファ値を一定の範囲で増減させる為のフラグと、スタートボタンが押されたことを判定するフラグを宣言。

今回のようにアルファ値だけを変更する場合でも、結果を反映させる為にnew Color(r,g,b,a)の形式で戻り値を利用する必要があるので、31~53行目で各コンポーネントのRGBA値を変数に取得します。

59行目でスタートボタンが押される前のテキストの挙動を定義した関数を呼び出しています。

定義元へ移動するには関数名をクリックして「F12」キーを押します。

定義元では、「1」からスタートするテキストのアルファ値に対して、指定した時間間隔(textBlinkDuration)で透明になっていくように減算しています。

アルファ値減算の結果を反映させる為に、new Color(r,g,b,a)の形式でテキストのクラス型変数に戻り値を代入します。

アルファ値が「0.5」以下になったら加算許可のフラグを立て、「1」以上になるまで減算の時と同じ時間間隔で加算していき、「1」以上になったら加算許可のフラグを下ろし、減算処理が始まります。
以降、スタートボタンが押されるまで繰り返されます。

スタートボタンにあたるマウスの左ボタンがクリックされたら、スタートボタン押下フラグを立てて上記の繰り返し処理を終了させ、コルーチンを開始します。

70~132行目でコルーチンの定義をしています。
フェードアウト開始フラグを立て、時間間隔を設定し、暗転時はテキストをスタートボタン押下前よりも短い間隔で点滅させる為に、textBlinkDurationよりも小さい値を設定しています。

fadeBoardのアルファ値を初期値「0」から加算、titleImageはfadeBoardより前面に位置していて影響を受けないので、アルファ値を初期値「1」から減算して透明にします。


テキストのアルファ値はスタートボタン押下前より短い間隔で点滅させ、ユーザーの入力に対しての反応動作を表現します。

アルファ値の加減算をfadeBoardとtitleImageに反映させ、画面が真っ暗になった時点でフェードアウトを終了させます。

この処理を1フレームにつき1回繰り返すようにし、画面が真っ暗になって1秒経過後にMainSceneへ遷移します。

このスクリプトをSceneManagerオブジェクトへアタッチして、各コンポーネントへの参照を以下のようにします。

最後にシーンを登録します。
メニューバーからFile → Build Settingsを開いてください。

Build Settingsウィンドウが開いた状態で「Add Open Scenes」ボタンをクリック、現在編集中のシーンが追加されます。

シーン名の右横に表示された数字が、ビルド後にゲームを起動した際の実行順になるので、TitleSceneをドラッグして一番上に置きます。

実行して、スタートボタンを押した後にメインシーンへ遷移するか確認してみます。

想定どおりの挙動になりました。
ただ、現状ではメインシーンへ遷移してすぐに戦闘開始になるので、一拍置きたいと思います。
メインシーンに切り替えて、HierarchyウィンドウからUI → Textを追加します。

追加したTextオブジェクトを「GET READY」にリネーム、Inspectorウィンドウから各項目を以下のように設定します。

ここへ、Add Componentから UI → Effects → Shadowを追加します。

Shadowコンポーネントの項目を以下のように設定します。

これで、テキストオブジェクト「GET READY」の外観が完成しました。

この「GET READY」を表示するタイミングは後ほどスクリプトで制御するので、一旦、非表示にしておきます。

同様に、もう一つテキストオブジェクトを追加します。
Hierarchyウィンドウで「GET READY」が選択されている状態から、「Ctrl + D」で複製します。
複製された「GET READY (1)」を「START」にリネーム、各項目を以下のように設定します。

ここへ、Add Componentから UI → Effects → Outlineを追加します。

追加されたOutlineコンポーネントの項目を以下のように設定します。

テキストコンポーネントにチェックを入れて表示すると、以下のようになります。(外観の確認が終わったらチェックを外して非表示に戻してください。)

次に、この「GET READY」と「START」をスクリプトで制御していきます。
「ImmediatryAfterTheStart」の名前で新規スクリプトを作成し、以下のコードをコピペしてください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ImmediatelyAfterTheStart : MonoBehaviour
{
[SerializeField] PlayerMove playerMove;
[SerializeField] SightDisplay sightDisplay;
[SerializeField] Image sight;
[SerializeField] EnemyMove enemyMove;
[SerializeField] EnemyShooting enemyShooting;
[SerializeField] Text getReady;
[SerializeField] Text start;
float timeElapsed,
flameOutPositionY = 600f,
getReadyTextStartTime = 0.5f,
getReadyTextEndTime = 2f,
startTextStartTime = 2f,
startTextEndTime = 3.5f,
battleStartTime = 4.5f;
bool getReadyTextFlag,
startTextFlag,
battleStartFlag;
// Start is called before the first frame update
//ゲーム開始直後では停止させておきたいスクリプトやUIをOFF
void Start()
{
playerMove.enabled = false;
sightDisplay.enabled = false;
sight.enabled = false;
enemyMove.enabled = false;
enemyShooting.enabled = false;
}
// Update is called once per frame
void Update()
{
//時間計測
timeElapsed += Time.deltaTime;
//開始時間になったらgetReadyを表示、処理の繰り返しを避ける為にフラグを立てる
if (timeElapsed >= getReadyTextStartTime && !getReadyTextFlag)
{
getReady.enabled = true;
getReadyTextFlag = true;
}
//終了時間になったらgetReadyを非表示
else if (timeElapsed >= getReadyTextEndTime && getReady.enabled)
{
getReady.enabled = false;
}
//開始時間になったらstartを表示、処理の繰り返しを避ける為にフラグを立てる
if (timeElapsed >= startTextStartTime && !startTextFlag)
{
start.enabled = true;
startTextFlag = true;
}
//終了時間になったらstartを+y方向へフレームアウト
else if (timeElapsed >= startTextEndTime && start.transform.localPosition.y < flameOutPositionY)
{
//どのくらい時間をかけるのか
var frameOutDuration = 0.1f;
start.transform.localPosition += new Vector3(0f, flameOutPositionY * Time.deltaTime / frameOutDuration, 0f);
}
//バトル開始時間になったらスクリプトや、UIをON。処理の繰り返しを避ける為にフラグを立てる
if (timeElapsed >= battleStartTime && !battleStartFlag)
{
playerMove.enabled = true;
sightDisplay.enabled = true;
sight.enabled = true;
enemyMove.enabled = true;
enemyShooting.enabled = true;
battleStartFlag = true;
}
}
}
このスクリプトをSceneManagerオブジェクトへアタッチして、各項目を以下のように設定してください。

実行すると以下のようになります。

コードを見ていきます。
UIの表示・非表示を扱うので、namespaceにUnityEngine.UIを宣言。

対象となるスクリプトやUIをInspectorウィンドウから設定できるよう、SerializeFiled化。

時間の区切りに使う、float型変数を宣言。

処理の繰り返しを避ける為の判定フラグ用、bool型変数を宣言。

PlayerとEnemyオブジェクトにアタッチされた、移動と攻撃を制御しているスクリプトと、Playerの照準UIをOFF。

Time.deltaTimeでフレーム毎の経過時間を加算していき、表示や非表示等を制御するタイミングの目安にします。

時間経過に合わせて最初に「GET READY」を表示、一度表示をONにしたら{}内の処理を再度行う必要がないのでフラグで管理しています。

「GET READY」を非表示にすると同時に「START」を表示、フレームの差異を無くす為にTime.deltaTimeを掛けて1秒でフレームアウトをするところ、指定時間で到達するよう除算して調整しています。

最後に、停止しておいたスクリプトやUIをONにして戦闘を開始します。

前回まででポストプロセスによる演出を学習したので、メインシーンにもポストプロセスを加えてみましょう。
まずは、HierarchyウィンドウからDirectional Lightを選択、「Directional Light (Wall)」にリネーム、InspectorウィンドウからTransformコンポーネントをResetして、

値を以下のようにします。

同様にLightコンポーネントもResetして、各項目を以下のようにします。

このLightを四方の壁と床に配置していきます。
HierarchyウィンドウからDirectional Light (Wall)をCtrl + Dで4つ複製します。

Directional Light (Wall) (1)~(3)のTransformコンポーネントの値を以下のようにします。



Directional Light (Wall) (4)は「Directional Light (Ground)」にリネームし、TransformとLightの項目を以下のようにします。

後々編集しやすいように、HierarchyウィンドウでDirectional Light (Wall)をDirectional Light (Wall) (1)~(3)、Directional Light (Ground)の近くに配置しておきます。

次に、前回同様、Main Cameraへポストプロセスの設定をし、Post-process VolumeコンポーネントからBloomのIntensityを「25」に設定します。

ライトに照らされたオブジェクトに、滲みの表現が加わりました。

ただ、このままでは