ストーリーに沿ってゲームを進行させる方法を考えてみました。
上の動画では、プレイヤーがコライダーに入ると秒読みが始まり、3秒後に球を出現させて、プレイヤーが球の方を向くと別のコライダーが出現し、そのコライダーに入るとゲーム終了のテキストが出て、2秒後にシーンが再読み込みされます。
まず、シーンにプレイヤーとSphere、2つのCube、テキストを置きました。
Cubeにはタグを付けて、コライダーのIs Triggerにチェックを入れます。
Sphereにも別のタグを付けます。
そして、空のゲームオブジェクトにスクリプトとAudioSourceコンポーネントを追加しました。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameScript : MonoBehaviour
{
[SerializeField] GameObject[] colliders; // Cube
[SerializeField] Text text; // テキスト
[SerializeField] Transform target; // Sphere
public static bool onTriggerEnter;
int chapter;
int state;
float sec;
Transform playerHead;
AudioSource audioSource;
// Start is called before the first frame update
void Start()
{
chapter = 1;
state = 1;
text.text = "";
playerHead = Camera.main.transform;
target.gameObject.SetActive(false);
audioSource = GetComponent();
// コライダーを非アクティブ
foreach (GameObject col in colliders)
{
col.SetActive(false);
}
switch (chapter)
{
case 1:
colliders[0].SetActive(true);
break;
case 2:
break;
}
}
// Update is called once per frame
void Update()
{
switch (chapter)
{
case 1:
switch (state)
{
case 1:
if(onTriggerEnter)
{
onTriggerEnter = false;
audioSource.Play();
colliders[0].SetActive(false); // 赤いのコライダーを非アクティブ
state = 2; // 次のステートへ
}
break;
case 2:
sec += Time.deltaTime;
text.text = sec + "";
if (sec >= 3f)
{
sec = 0f;
text.text = "";
target.gameObject.SetActive(true);
state = 3; // 次のステートへ
}
break;
case 3:
RaycastHit hit;
Vector3 dir = target.position - playerHead.position;
if (Physics.Raycast(playerHead.position, dir, out hit, 20f))
{
if (hit.collider.tag == "Target")
{
float dot = Vector3.Dot(playerHead.forward, Vector3.Normalize(dir));
// プレイヤーがターゲットの方を見たら
if (dot > 0.9f)
{
target.gameObject.SetActive(false);
colliders[1].SetActive(true); // 緑のコライダーをアクティブ
chapter = 2;// 次のチャプターへ
state = 1;
}
}
}
break;
}
break;
case 2:
switch (state)
{
case 1:
if (onTriggerEnter)
{
onTriggerEnter = false;
audioSource.Play();
colliders[1].SetActive(false); // 緑のコライダーを非アクティブ
state = 2;
text.text = "ゲーム終了";
}
break;
case 2:
sec += Time.deltaTime;
if (sec >= 2f)
{
SceneManager.LoadScene("New Scene");
}
break;
}
break;
}
}
}
SphereやCube、テキストはスクリプトにアタッチします。Start()でそれらを全て非アクティブにしておいて、チャプター1のときは赤いCubeだけアクティブにします。
target.gameObject.SetActive(false);
// コライダーを非アクティブ
foreach (GameObject col in colliders)
{
col.SetActive(false);
}
switch (chapter)
{
case 1:
colliders[0].SetActive(true);
break;
case 2:
break;
}
Update()でもチャプターとステートによって別の処理をします。まず、アクティブになっている赤いCubeに入ったときに音を鳴らして次のステートに行くようにしています。
switch (chapter)
{
case 1:
switch (state)
{
case 1:
if(onTriggerEnter)
{
onTriggerEnter = false;
audioSource.Play();
colliders[0].SetActive(false); // 赤いのコライダーを非アクティブ
state = 2; // 次のステートへ
}
break;
コライダーに入ったときにFirstPersonControllerクラスで、このスクリプトの静的メンバ変数をtrueにします。
// FirstPersonController
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Collider")
{
GameScript.onTriggerEnter = true;
}
}
ステート2の間、時間をカウントしてテキストに表示します。3秒立つとSphereをアクティブにして次のステートへ行きます。
case 2:
sec += Time.deltaTime;
text.text = sec + "";
if (sec >= 3f)
{
sec = 0f;
text.text = "";
target.gameObject.SetActive(true);
state = 3; // 次のステートへ
}
break;
ステート3では、プレイヤーの頭からSphereへレイがあたっているときに、プレイヤーの視線と、頭からSphereまでのベクトルの内積を計算して、プレイヤーがSphereの方を見たら次のチャプターへ遷移します。
case 3:
RaycastHit hit;
Vector3 dir = target.position - playerHead.position;
if (Physics.Raycast(playerHead.position, dir, out hit, 20f))
{
if (hit.collider.tag == "Target")
{
float dot = Vector3.Dot(playerHead.forward, Vector3.Normalize(dir));
// プレイヤーがターゲットの方を見たら
if (dot > 0.9f)
{
target.gameObject.SetActive(false);
colliders[1].SetActive(true); // 緑のコライダーをアクティブ
chapter = 2;// 次のチャプターへ
state = 1;
}
}
}
break;
同様に次のチャプターでも各ステートの中の処理と、遷移の条件、遷移するときの処理を書いていくとストーリーに沿って順にゲームの進行管理ができました。
case 2:
switch (state)
{
case 1:
if (onTriggerEnter)
{
onTriggerEnter = false;
audioSource.Play();
colliders[1].SetActive(false); // 緑のコライダーを非アクティブ
state = 2;
text.text = "ゲーム終了";
}
break;
case 2:
sec += Time.deltaTime;
if (sec >= 2f)
{
SceneManager.LoadScene("New Scene");
}
break;
}
break;