Unity 2D ヴァンサバ風オートエイムの作り方と障害物を考慮する方法

Unity解説

ヴァンパイアサバイバーズみたいなサバイバー系のゲームを作りたいのだけど、魔法の杖(Magic Wand)みたいなオートエイムのやり方がわからない!

大丈夫、そんなに難しくないから解説するね!

障害物ごしの敵は狙わないような考慮もするよ

オートエイムはプレイヤーに直接的な狙いを定めさせずに敵を攻撃できるようにする機能です。これにより操作の複雑さが減少し、ゲームの他の要素に集中できるようになります。

本記事では、Unityでヴァンパイアサバイバー系ゲームに必須のオートエイム(自動攻撃)を実装する方法を解説します。

まずは結論ですが、

Enemyタグで探して近い敵に撃つ

  1. Enemyタグをすべてスキャンする
  2. プレイヤーとの距離を比較し最も近い敵を決める
  3. 位置の差(方向)をノーマライズして方向を決める
  4. 一定間隔で撃てるようにセットする

※本記事にはアフィリエイトが含まれます。ブログのサーバ等の運営費などに充てております。

[PR] 動画中で使っているアセット紹介。使いやすいヒーロー、と大量のモンスター、汎用性の高いUI画像集です。

オートエイム機能の作り方

では順を追ってオートエイムの作り方を解説していきましょう。

必要なコンポーネント

オートエイムを実装するには、以下のコンポーネントが必要です。

  • ターゲットを選定するスキャナ
    敵オブジェクトを検出することから始まります。敵にはタグや特定のレイヤーを設定しておいてスキャナはそれらを検出します。
  • 弾を発射する砲台
    ターゲットが検出されたら攻撃を自動的に開始します。
    これにはアニメーションの再生や弾丸の生成が含まれます。
  • 一定間隔でそれらを実行するタイマー
    ヴァンサバはオートエイム+自動攻撃です。
    一定間隔で攻撃できるようタイマーを用意します。

敵スキャナ

まずは敵スキャナ。
本記事のメインの部分ですね。
例ではプレイヤーに一番近い敵をスキャンしていますが、ランダムや特定の敵だけといった応用もできると思います。

敵のリストを得るにはいろいろな手法があると思いますが、今回は単純で高速なタグ検索を使います。

  1. 全敵オブジェクトをタグ指定で取得
  2. 拾ってきた敵を全部見てプレイヤーに一番近いものを判定

EnemyScanner.cs

using UnityEngine;

public class EnemyScanner : MonoBehaviour
{
    /// <summary>
    /// いま保持している最近接ターゲット
    /// </summary>
    [HideInInspector]
    public GameObject Target;

    /// <summary>
    /// 敵タグから一番近いゲームオブジェクトを探します。
    /// </summary>
    /// <returns></returns>
    public GameObject ScanWithFindTag()
    {
        GameObject[] _targets = GameObject.FindGameObjectsWithTag("Enemy");

        float tmp = float.MaxValue; // 距離比較するための一時保存
        foreach (GameObject o in _targets)
        {
            // 最も近い敵に入れ替え
            float distance_to_enemy = 
        Vector3.Distance(transform.position, o.transform.position);
            if (distance_to_enemy < tmp)
            {
                tmp = distance_to_enemy;
                Target = o;
            }
        }
        return Target;
    }
}

最小距離のオブジェクトを取得しているだけですね。分かってしまえばなんてことはないものです。

ちなみに、別の方法には、Physics2Dのオーバーラップ(円型のレイキャスト)やコライダーを使うものが思いつきますが、300体の敵で試してみても今紹介した方法が高速だったので特段理由がなければタグでいいと思います。

砲台

スキャンが出来たら弾を撃つ砲台を準備します。
弾の実装では敵の方向に向けて撃つ必要があることに注意です。砲台の本体では、さきほどのEnemyScannerを使ってターゲットを決定し、弾を生成します。

BulletLauncher.cs

using UnityEngine;

public class BulletLauncher : MonoBehaviour
{
    [SerializeField] Knife knife_prefab; // 弾の名前がナイフなのは気にしないでください
    [SerializeField] Transform MuzzlePosition; // 弾発生の位置

    /// <summary>
    /// 弾を投げつけるターゲット
    /// </summary>
    GameObject Target;

    /// <summary>
    /// 敵スキャン用
    /// </summary>
    EnemyScanner enemyScanner;

    private void Start()
    {
        enemyScanner = GetComponent<EnemyScanner>();
    }

    public void ThrowKnife()
    {
        Target = enemyScanner.ScanWithFindTag();

        if (Target == null) return;
        GameObject go = Instantiate(knife_prefab.gameObject
           , MuzzlePosition.position
           , Quaternion.identity);
        Knife b = go.GetComponent<Knife>();
        b.Shot(Target.transform.position);
    }
}

生成した弾のShotを呼び出していることに注目してください。弾にターゲットを渡して弾側で方向を決定します。弾側での実装したことに特に意図はないので、砲台側で飛んでく方向を計算して弾に渡してあげてもいいです。

この弾を撃つメソッドは次項で紹介するタイマーから呼びます
(弾の名前がナイフなのは気にしないでください)

弾のほうはシンプルで、ターゲットの方向を初期化時に計算してずっと移動し続けます。

Knife.cs

using System.Collections;
using UnityEngine;

public class Knife : MonoBehaviour
{
    [SerializeField] float Speed = 20;

    bool _enbled = false;
    Vector3 _direction;

    private void FixedUpdate()
    {
        if (_enbled)
        {
            transform.Translate(_direction.normalized * Speed * Time.deltaTime);
        }
    }

    public void Shot(Vector3 target)
    {
        _direction = target - gameObject.transform.position;
        _enbled = true;
    }
}

ポイントはShotの中にある_directionです。ターゲットと自分の位置差すなわち方向×距離 を取っています。

FixedUpdate() で弾が移動する際は_directionのnormalized を方向として扱っています。ベクトルの大きさが1になるオマジナイで、方向の成分だけを取ることができます。

あ、Prefabにアタッチするのを忘れずに!

[PR] 冒頭の動画では、弾に高品質なエフェクト集のRPG VFX Bundleを使っています。HITしたときのエフェクトもロジック付きで実装されていて、そのままでも使いやすい印象でした。

なお、弾にはObjectPoolを使った方がいいですが本記事では割愛します、ごめんなさい
解説もしてますので気になればご覧ください

UnityのObjectPool (2 / 2) -使い方-

タイマー

砲台が敵をスキャンして弾をうつところができてますので、ヴァンサバっぽいく自動で実行するようにしましょう。これもいろいろ方法がありますが、タイマー専用のコンポーネントを紹介します。

IntervalTimer.cs

using UnityEngine;
using UnityEngine.Events;

public class IntervalTimer : MonoBehaviour
{
    public bool LoopActive;
    [SerializeField] float interval = 1;
    [SerializeField] UnityEvent doSomething;
    
    float interval_cnt =0;

    private void Start()
    {
        interval_cnt = interval;
    }

    void FixedUpdate()
    {
        if (LoopActive)
        {
            if (interval_cnt <= 0)
            {
                doSomething?.Invoke();
                interval_cnt = interval;
            }
            if (interval_cnt > 0) interval_cnt -= Time.deltaTime;
        }
    }
}

やっていることは単純で、セットした時間が経過するとインスペクタで設定したメソッドを実行するようになっています。
タイマーのカウントを停止したり開始したりするスイッチLoop Activeの変数を持っています。

プレイヤにアタッチして、さっきの弾を撃つメソッドをセットしますと、一定間隔ごとに実行されて自動攻撃になります。

これで、オートエイム自動攻撃ができるようになりました! やった!

壁など障害物の考慮

さて、冒頭の動画をもう一度ご覧ください。一番近い敵を撃つロジックにしているので、壁越しにゴーレムを狙っているのがわかると思います。

ゲームによっては障害物ごしに狙ってしまうと不都合があるときもありますよね。
純サバイバー系だとあまりなさそうなシチュエーションですが、ステージが凝っていると隣の部屋の敵ばっかり狙うみたいなことがあります。

では、壁があったら狙わないようにしてみましょう!

障害物ごしに攻撃しない実装

障害物ごしに撃たないためにズバリどうするかというと、Raycastを撃ちます。タグで見つけた敵全員に対してそれぞれRaycastしていき、ある敵に撃ったRayが障害物にHITしたならばその敵は障害物ごしと判定します。

using UnityEngine;

public class EnemyScanner : MonoBehaviour
{
    /// <summary>
    /// いま保持している最近接ターゲット
    /// </summary>
    [HideInInspector]
    public GameObject Target;

    /// <summary>
    /// 障害物のレイヤ(壁とか)
    /// </summary>
    [SerializeField] LayerMask ObstacleLayer = 256; // インスペクタで入れてください

    /// <summary>
    /// 敵タグから一番近いゲームオブジェクトを探します。
    /// 障害物
    /// </summary>
    /// <returns></returns>
    public GameObject ScanWithFindTagObstacle()
    {
        GameObject[] _targets = GameObject.FindGameObjectsWithTag("Enemy");
        float tmp = float.MaxValue;
        foreach (GameObject o in _targets)
        {
            // 壁レイヤのオブジェクトにRayがHITしたら、
            // 敵に弾は届かないのでターゲット候補から除外する
            Vector2 _RaycastDirection = o.transform.position - transform.position;
            RaycastHit2D hit = Physics2D.Raycast(
                               transform.position
                               , _RaycastDirection
                               , _RaycastDirection.magnitude
                               , ObstacleLayer);
            if (hit.collider != null)
            {
                continue;
            }

            // 最も近い敵に入れ替え
            float distance_to_enemy = 
                        Vector3.Distance(transform.position, o.transform.position);
            if (distance_to_enemy < tmp)
            {
                tmp = distance_to_enemy;
                Target = o;
            }
        }
        return Target;
    }
}

赤い文字のとこに注目してください。プレイヤから敵リストの敵がいる方向(と距離)に2D のRaycastを撃っています。引数に障害物オブジェクトのレイヤを渡しています。

従って、障害物オブジェクトは敵オブジェクトとレイヤを分けておいてください。
ビットマスクで渡せるので複数の障害物レイヤを取り扱うことはできますので障害物の種類が複数あるときはインスペクタで複数セットしましょう。

1点注意があります。2DではPhysics2DのRaycastを使ってください。PhysicsのRaycastは3D用なので2Dで使ってもうまく動作しません。

これで障害物ごしに狙わないオートエイム(自動攻撃)ができました。

[PR] ヴァンサバ風ゲーム作成に使えそうなアセット紹介

本解説記事の解説用ゲームでも使っているTopDown Engineを紹介します。
ここまでで解説はすべて終わりましたので、以降は少しの予算があって製作時間がない場合に覧ください。

TopDown Engineを使うと5日でこんなサバイバー風のゲームができました。
この強力なアセットを少し紹介します。

TopDown Engineを使って5日でできたゲーム

TopDown Engineのススメ

もしあなたがゲーム制作に精通しているのではなければ、ヴァンサバ風ゲームをフルスクラッチ(完全に自作)でGoogleやSteamストアに出すのはとても根気のいる作業になると思います(おそらく年単位)。

一例を挙げますが、ゲーム内の基礎的なシステムを構築するだけでも気の遠くなる量の作業があります。

  • プレイヤーの移動と攻撃
  • 敵の出現と行動パターン
  • ダメージと衝突計算
  • アイテムとパワーアップ
  • レベルデザインとワールド構築
  • インターフェース(UI/UX)
  • 音響とビジュアルエフェクト(FX)
  • スコアリングと進行管理
  • テストと最適化
  • プラットフォームごとの調整(PC, モバイルなど)

ここまでできて初めて自身のオリジナル要素、たとえば武器やらシステムやらストーリーやらを乗せていけるようになります。フルスクラッチはゲームになるまでが非常に遠いのです。

作業自体を楽しめているのであれば大丈夫なのですが、ゲーム制作自体よりゲーム完成の方が自分の目的に近い場合はとてもツライ作業になるかもしれません。

そこで、アセットを使ってゲームの共通的な部分を大幅に時短しましょう

特にTopDown Engineはキャラの移動、ダメージを与える仕組み、エフェクトを出す仕組み、音を出す仕組み、敵を出す仕組み、などなど総合的な戦力がとても高いアセットです。

2018年にアセットストアに出てから現在まで継続してアップデートされており、公式サポートも充実しています。有名なアセットなので日本語の解説記事が多いのもメリットですね。

実際、SteamにもいくつかTopDown Engine製のサバイバー系が出ているのを確認しています。

定価では少し高いですが、半額セールをよくやってますので見かけたらポチりましょう!

まとめ

Unity 2Dで「ヴァンパイアサバイバーズ」風のゲームを作成するためのオートエイムと障害物を考慮する方法について解説しました。

オートエイムはEnemyタグを使って最も近い敵を自動的にターゲットし、砲台を使って一定間隔で攻撃を行います。

また、Raycastを用いて障害物を考慮し、視界に障害物がある場合は攻撃しないようする方法も解説しました。

みなさまの楽しいサバイバーゲームを楽しみにしています。

かわいい我が子にオリジナルアプリを!

コメント

タイトルとURLをコピーしました