今回はUnityでFPSゲームを作成してみました。完成したゲームはこちらです。https://ma510884.com/FPS_Game

参考にした記事、使用したアセットは以下になります。
- シンプルなFPS「ゾンビスレイヤー」 | Unityでゲームを作ろう!https://unity.moon-bear.com/zombie-slayer/
- 【Unity C#】制限時間になれば停止する – フタバゼミhttps://futabazemi.net/unity/timeup_stop/
- 【Unity】重複ありのBGMの鳴らし方「Play()」「PlayOneShot()」 | すくまりのメモ帳
https://squmarigames.com/2018/12/19/unity-beginner-audiosource/ - First Person All-in-One | 入出力管理 | Unity Asset Store
https://assetstore.unity.com/packages/tools/input-management/first-person-all-in-one-135316 - M4A1 PBR | 3D 銃器 | Unity Asset Store
https://assetstore.unity.com/packages/3d/props/guns/m4a1-pbr-85713 - War FX | ビジュアルエフェクト パーティクル | Unity Asset Store
https://assetstore.unity.com/packages/vfx/particles/war-fx-5669 - Zombie | 3D ヒューマノイド | Unity Asset Store
https://assetstore.unity.com/packages/3d/characters/humanoids/zombie-30232 - Cartoon Low Poly City Pack Lite | 3D アーバン | Unity Asset Store
https://assetstore.unity.com/packages/3d/environments/urban/cartoon-low-poly-city-pack-lite-166617
続いてはゲームクリアの条件とゲームオーバーの仕組みを組み込んでいきます。参考にした記事では一定数の敵を倒すとゲームクリアとなっていますが、それではスコアが似通ってしまうので、制限時間内でどれだけ倒せるかという風にしました。別の記事を参考に、void Update内に記述していきます。そのままではゲームスタート時のカウントダウン中も時間が過ぎてしまいますので、if文でカウントダウン後に計測開始するようにしました。詳しくは後述のスクリプトをご覧ください。
ゲームオーバーは記事をそのまま参考にしたものと、追加でフィールドから落ちてもゲームオーバーになるようにしました。フィールドの下にブロックを置き、Box Colliderを追加してIs Triggerにチェックをいれます。タグを設定し、ブロックに接触するとゲームオーバーになるように条件を追加しました。普段は見えないように、Mesh Rendererのチェックは外しておきます。そして、以下のスクリプトをアタッチしました。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DropScript : MonoBehaviour
{
[SerializeField]
GameManager gameManager;
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Cube")
{
gameObject.GetComponent<Rigidbody>().isKinematic = true;
StartCoroutine(gameManager.GameOver());
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
最後に、音の設定をしていきます。必要なものはBGM、ゲームクリア時とゲームオーバー時の音楽、銃の発砲音、空薬莢の落ちる音です。ゲーム終了時の音楽は以前使ったものを、それ以外はYouTubeクリエイターキット等のフリー素材を使用しました。
まずはゲーム終了時の音楽ですが、それぞれAudioClipとして読み込ませ、ゲーム終了時に他の音を止めてからPlayOneShotで再生しています。
銃の発砲音と空薬莢の落ちる音は、マウスをクリックしている間だけループ再生し、離したら止める必要があるため、BGMと共にAudioSourceとして配列で読み込ませました。配列を使う場合は、GetComponentsと複数型にしないとエラーが出ます。逆に、配列を使わないのにsをつけると「配列じゃない」とエラーが出ます。ここで少しつまずきました。
後は、ゲーム中だけBGMとSEが鳴るようにします。GameControler.csの if文にfirstPerson.playerCanMove == trueを追加して対応しました。SEにはLoopにチェックを入れておきます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
[SerializeField]
int maxScore = 999999;
[SerializeField]
Text scoreText;
[SerializeField]
FirstPersonAIO firstPerson;
[SerializeField]
FirstPersonGunController gunController;
[SerializeField]
Text centerText;
[SerializeField]
float waitTime = 2;
[SerializeField]
EnemySpawner[] spawners;
public float time = 60;
public Text timerText;
public bool isTimeUp;
public GameObject gameOverUI;
private AudioSource[] audioSource;
public AudioClip gameclear;
public AudioClip gameover;
private bool switchBool = false;
int score = 0;
bool gameOver = false;
bool gameClear = false;
public int Score
{
set
{
score = Mathf.Clamp(value, 0, maxScore);
scoreText.text = score.ToString("D6");
}
get
{
return score;
}
}
void Start()
{
InitGame();
StartCoroutine(GameStart());
isTimeUp = false;
audioSource = GetComponents<AudioSource>();
SetSpawners(false);
}
void Update()
{
if (0 < time & firstPerson.playerCanMove == true)
{
time -= Time.deltaTime;
timerText.text = time.ToString("F2");
}
else if (time < 0)
{
isTimeUp = true;
if (!switchBool)
{
switchBool = true;
StartCoroutine(GameClear());
}
}
if (Input.GetMouseButtonDown(0) & firstPerson.playerCanMove == true)
{
audioSource[1].Play();
audioSource[2].Play();
}
if (Input.GetMouseButtonUp(0))
{
audioSource[1].Stop();
audioSource[2].Stop();
}
}
void InitGame()
{
Score = 0;
firstPerson.playerCanMove = false;
firstPerson.enableCameraMovement = false;
gunController.shootEnabled = false;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
public IEnumerator GameStart()
{
yield return new WaitForSeconds(waitTime);
centerText.enabled = true;
centerText.text = "3";
yield return new WaitForSeconds(1);
centerText.text = "2";
yield return new WaitForSeconds(1);
centerText.text = "1";
yield return new WaitForSeconds(1);
centerText.text = "GO!!";
yield return new WaitForSeconds(1);
// centerText.text = "";
centerText.enabled = false;
firstPerson.playerCanMove = true;
firstPerson.enableCameraMovement = true;
gunController.shootEnabled = true;
SetSpawners(true);
audioSource[0].Play();
}
public IEnumerator GameOver()
{
if (!gameOver)
{
gameOver = true;
firstPerson.playerCanMove = false;
firstPerson.enableCameraMovement = false;
gunController.shootEnabled = false;
SetSpawners(false);
yield return new WaitForSeconds(1);
DestroyEnemies();
centerText.enabled = true;
centerText.text = "Game Over";
gameOverUI.SetActive(true);
isTimeUp = false;
// centerText.text = "";
// centerText.enabled = false;
gameOver = false;
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
audioSource[0].Stop();
audioSource[0].PlayOneShot(gameover);
}
else
{
yield return null;
}
}
public IEnumerator GameClear()
{
if (!gameClear)
{
gameClear = true;
firstPerson.playerCanMove = false;
firstPerson.enableCameraMovement = false;
gunController.shootEnabled = false;
SetSpawners(false);
yield return new WaitForSeconds(1);
DestroyEnemies();
centerText.enabled = true;
centerText.text = "Game Clear!!";
gameOverUI.SetActive(true);
// centerText.text = "";
// centerText.enabled = false;
gameClear = false;
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
// yield return SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().buildIndex);
audioSource[0].Stop();
audioSource[0].PlayOneShot(gameclear);
}
else
{
yield return null;
}
}
void DestroyEnemies()
{
GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
foreach (GameObject enemy in enemies)
{
Destroy(enemy);
}
}
void SetSpawners(bool isEnable)
{
foreach (EnemySpawner spawner in spawners)
{
spawner.spawnEnabled = isEnable;
}
}
}
これでUnityを実行してみましょう。「3、2、1、Go!!」の後にBGMが再生され、射撃時にSEが鳴るはずです。また、StartボタンやRetryボタンを押した時には音が出なくなっていると思います。ゲームクリア時、ゲームオーバー時の音楽も確認しましょう。以上で問題がなければゲームは完成です。
余談ですが、M4A1の銃声音は5.56×45mm NATO弾を使用していますのでもっと軽いのですが、銃を乱射している雰囲気を出したかったので.50BMG 12.7×99mm NATO弾クラスの迫力あるものに変更しました。個人的にはとても気に入っています。

弾は小さいものから9mmパラベラム弾(拳銃用)、5.56×45mm弾(アサルトライフル用)、7.62×51mm弾(バトルライフル用)、12.7×99mm弾(対物ライフル・重機関銃用)となっています。火薬、雷管を外した実物です。
以上、最後までお読みいただきありがとうございました。