あのゲームのあの機能ってどうやって作るんだろう?を考えていきます。
効率のいい作り方などはわからないので、そういったものをお求めの方は回れ右。
今回はシューティングゲームです。自機が移動でき、弾を発射し、それに当たれば敵が消滅する。敵を倒すとスコアが加算される。敵や敵の攻撃に当たると自機がダメージを受ける、または消滅してGameOverになる。細かい要素はゲームによって異なるでしょうが概ねこんなところでしょう。
私は式神の城というゲームが好きなので、それを真似して作ってみたいのですが、流石に素材を作るのが大変なので最初は四角や丸のオブジェクトを使っていきます。
ちなみにどうやって作るかを知っているわけではなく、ゲームを遊んだり動画を見ながらあーでもないこーでもないと考えながら作っているので、解釈や実際の動きは異なります。
まずは弾が出てくるところから
ゲームに限らず、ものを作るときは細部からではなく、大枠から作っていって細かいところを詰めていけと誰かが言ってました。
シューティングゲームで重要なのはプレイヤーでも敵キャラでもなく弾です(異論は認めます)。その弾を撃ったり撃たれたりするということはどういうことなのでしょうか。式神の城は敵の弾を(ギリギリで)避けるのが醍醐味なのでまずはそのへんから着手します。
敵の攻撃パターンは大きく分けて2つとして進めてみます。
- 一つは決まったパターンで画面に表示される
- もう一つはプレイヤーの位置を追尾
プレイヤーの位置を追尾するほうを考えていきます。
- プレイヤーの位置(transform)を取得する
- 弾を生成する
- プレイヤーへの方向を計算する
- 弾を移動させる
という感じでしょうか。コードにしてみます。
using UnityEngine;
public class EnemyShoot : MonoBehaviour
{
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private float bulletSpeed = 10.0f;
[SerializeField] private Transform player;
private void Update()
{
if (player == null)
{
GameObject playerObj = GameObject.FindWithTag("Player");
if (playerObj != null)
player = playerObj.transform;
}
else
{
ShootAtPlayer();
}
}
private void ShootAtPlayer()
{
GameObject bullet = Instantiate(bulletPrefab, this.transform.position, Quaternion.identity);
Vector3 direction = (player.position - this.transform.position);
bullet.GetComponent<Rigidbody>().velocity = direction * bulletSpeed;
}
}

Player(Cubeオブジェクト)とEnemy(Capsuleオブジェクト)を用意して、それぞれに同名のタグをつけています(画面右上 InspectorのTag)。Playerの位置情報をUpdateが呼ばれるたびにplayer変数がnullなら探して取得します。PlayerをUpdateの中でFindWithTagで探しているのは、例えばダメージを受けたときに一時的にプレイヤーがいないようにしないと、常に追尾されてしまうことになります。その時はまたPlayerを探しにいけるようにこのような作りにしてみています。
あとはShootAtPlayer()で先程の流れの通り、プレイヤーの位置めがけて弾を移動させるためにインスタンスの作成→Playerへの方向を計算→その方向へ移動させます。
Enemyオブジェクトから弾を発射するとき、銃を持っているキャラクターを妄想して発射口(Muzzle)オブジェクトをEnemyオブジェクトの下に用意してみました。
弾自体はPrefabに用意をして、方向を決めるためにRigidbodyと、当たり判定のためにColliderをつけています。それでは発射してみます。

なんだか弾が散らばっている感じがします。これは生成された弾が他の弾とぶつかってしまっているようです。それを避けるためにPhysicsのLayerCollisionMatrixでチェックボックスを外す必要があります。弾オブジェクトのLayerを設定(EnemyBullet)し、以下のようにチェックボックスを外しました。


弾が散らばらなくなり、移動すると追尾するようになりました。
弾を消す
生成した弾Prefabはそのままだと大量に生成されてしまうので、どこかのタイミングで削除したいですね。今回はカメラの範囲を超えたときに削除するようにしました。
using UnityEngine;
public class BulletBoundaryCheck : MonoBehaviour
{
private Camera mainCamera;
[SerializeField] private float extraBoundary = 0.2f;
private void Start()
{
mainCamera = Camera.main;
}
void Update()
{
Vector3 viewPortPosition = mainCamera.WorldToViewportPoint(this.transform.position);
if (viewPortPosition.x < 0 - extraBoundary || viewPortPosition.x > 1 + extraBoundary
|| viewPortPosition.y < 0 - extraBoundary || viewPortPosition.y > 1 + extraBoundary)
{
Destroy(gameObject);
}
}
}
カメラが見ている範囲(Viewport)、その範囲ピッタリで消えると違和感があるかもしれないので、そのちょっと外側で消えるようにしました。
Viewportは左下が(0,0)で右上が(1,1)になるので、xとyそれぞれの0より小さく1より大きい範囲であればオブジェクトを削除するようになっています(この場合、このスクリプトが適用されている弾自身)。
今回はここまで。次回以降は
- プレイヤーを動かす
- 弾を撃つ
- 弾がぶつかったあとに消えるようにする
- プレイヤーが画面の外に行かないようにする
この辺に取り組みたいと思います。