今回は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
では続いて敵キャラクターを作成していきます。記事にならってZombieをインポートし、Prefabの中のZombie01オブジェクトをHierarchyにドラッグ&ドロップします。
次にAnimator Controllerを新たに作成します。初期状態では3つステートが表示されていますので、他に必要なものを追加していきます。

Z_Idleをデフォルトのステートにしますので最初にドラッグ&ドロップし、続いてZ_Walk、Z_Attack、Z_FallingBackと追加していきます。そしてそれらを図のように繋ぎます。

繋いだ矢印をクリックし、InspecterでHas Exit Time(終了時間)とConditionsを設定します。設定が終わったらZombie01のControllerを今作成したものに変更します。

続いてRigidbodyとBox Colliderを追加し、記事のように数値を設定します。同様に、ナビメッシュのベイクも行います。地面をまとめて選択し(ShiftキーまたはCtrlキーを押しながら、もしくはHierarchyから選択できます)、ベイクすると道路が青くなると思います。


次にスクリプトをアタッチします。この際、スクリプトを少し書き換えて一緒にゾンビの声も追加します。ゾンビの声はYouTubeクリエイターキットのフリー素材を使用しました。また、1種類だけではゲームが単調になってしまいますので、ゾンビのPrefabをコピーし、ターゲットをプレイヤーではなくゲームオーバー地点に変更した物も用意しました。 やり方は後述するゲームオーバー地点にタグを設定し、ゾンビのターゲットをその地点に変更するだけです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyController : MonoBehaviour
{
public bool moveEnabled = true;
[SerializeField]
int maxHp = 3;
[SerializeField]
int ammoDamage = 5;
[SerializeField]
int attackInterval = 1;
[SerializeField]
int score = 100;
[SerializeField]
string targetTag = "Player";
[SerializeField]
float deadTime = 3;
bool attacking = false;
int hp;
float moveSpeed;
Animator animator;
BoxCollider boxCollider;
Rigidbody rigidBody;
NavMeshAgent agent;
Transform target;
GameManager gameManager;
FirstPersonGunController player;
private AudioSource audioSource;
public int Hp
{
set
{
hp = Mathf.Clamp(value, 0, maxHp);
if (hp <= 0)
{
StartCoroutine(Dead());
}
}
get
{
return hp;
}
}
void Start()
{
animator = GetComponent<Animator>();
boxCollider = GetComponent<BoxCollider>();
rigidBody = GetComponent<Rigidbody>();
agent = GetComponent<NavMeshAgent>();
target = GameObject.FindGameObjectWithTag(targetTag).transform;
gameManager = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
player = GameObject.FindGameObjectWithTag("Player").GetComponentInChildren<FirstPersonGunController>();
InitCharacter();
audioSource = GetComponent<AudioSource>();
}
void Update()
{
if (moveEnabled)
{
Move();
audioSource.Play();
}
else
{
Stop();
}
}
void InitCharacter()
{
Hp = maxHp;
moveSpeed = agent.speed;
}
void Move()
{
agent.speed = moveSpeed;
animator.SetFloat("Speed", agent.speed, 0.1f, Time.deltaTime);
agent.SetDestination(target.position);
rigidBody.velocity = agent.desiredVelocity;
}
void Stop()
{
agent.speed = 0;
animator.SetFloat("Speed", agent.speed, 0.1f, Time.deltaTime);
}
IEnumerator Dead()
{
moveEnabled = false;
Stop();
gameManager.Score += score;
animator.SetTrigger("Dead");
boxCollider.enabled = false;
rigidBody.isKinematic = true;
yield return new WaitForSeconds(deadTime);
Destroy(gameObject);
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Player")
{
StartCoroutine(AttackTimer());
}
}
IEnumerator AttackTimer()
{
if (!attacking)
{
attacking = true;
moveEnabled = false;
animator.SetTrigger("Attack");
player.Ammo -= ammoDamage;
yield return new WaitForSeconds(attackInterval);
attacking = false;
moveEnabled = true;
}
yield return null;
}
}
では次の記事にならって敵生成オブジェクトとスクリプトを追加します。1ヶ所では物足りなかったので、車の陰にそれぞれ配置し2ヶ所としました。
次はゲームシステムと効果音を追加していきます。