ゴマちゃんフロンティア

気まぐれと勢いで作るUnityゲーム開発日記です。

【Unity】画面中央に一番近いオブジェクトを対象とするロックオン方式の実装

time 2018/03/22

というわけで、今回は3Dゲームにおけるロックオンに関するお話です。
以前「自キャラに最も近いオブジェクトをロックオン」という仕組みを作ったので、次は「画面中央に一番近いオブジェクトをロックオン」する仕組みを考えてみました。

話としては前回の続きになるので、↓の記事を先に閲覧いただくことをおすすめします。

【Unity】自キャラに一番近いオブジェクトを対象とするロックオン方式の実装

画面中央に近いオブジェクトの取得

ロックオン対象取得用の関数を1つ追加し、画面中央に近いオブジェクトを取得する処理を組み立てます。

protected GameObject SetTargetClosestScreenCenter()
{
    float search_radius = 10f;

    var hits = Physics.SphereCastAll(
        player.transform.position,
        search_radius,
        player.transform.forward,
        0.01f,
        LayerMask.NameToLayer("LockonTarget")
    ).Select(h => h.transform.gameObject).ToList();

    hits = FilterTargetObject(hits);

    if (0 < hits.Count()) {
        float min_target_distance = float.MaxValue;
        GameObject target = null;

        foreach (var hit in hits) {
            Vector3 targetScreenPoint = Camera.main.WorldToViewportPoint(hit.transform.position);
            float target_distance = Vector2.Distance(
                new Vector2(0.5f, 0.5f),
                new Vector2(targetScreenPoint.x, targetScreenPoint.y)
            );
            Debug.Log(hit.gameObject + ": " +  target_distance);

            if (target_distance < min_target_distance) {
                min_target_distance = target_distance;
                target = hit.transform.gameObject;
            }
        }

        return target;
    } else {
        return null;
    }
}

protected List<GameObject> FilterTargetObject(List<GameObject> hits)
{
    return hits
        .Where(h => {
            Vector3 screenPoint = Camera.main.WorldToViewportPoint(h.transform.position);
            return screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1;
        })
        .Where(h => h.tag == "Enemy")
        .ToList();
}

超短距離にSphereCastすること、フィルター用関数で対象を絞ることは前回と同じです。
Camera.WorldToViewportPoint()を使用し、各オブジェクトの画面上での座標を取得します。値は正規化されているので、XY軸共に0~1の範囲になります。
画面中央は0.5・0.5なので、Vector2.distance()で中央からオブジェクトの座標までの距離を測り、最も距離が離れているオブジェクトを保持を返します。

ロックオン中のカメラの制御は以下の記事で紹介しております。基本的にはTransform.LookAt()を使うだけです。

【Unity】3Dのカメラ視点移動とロックオン機能について

ロックオンカーソルの表示

単に敵の方を向くだけではゲーム的に地味なので、ロックオン中のみカーソルを表示するようにしてみます。
まずはGIMPでそれっぽい画像を作ります。

画像をインポートし、Canvasの子オブジェクトに設定します。ゲーム開始時はロックオンしていないので、非アクティブにしておきましょう。
次にカーソルを制御するためのスクリプトを作成します。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// ロックオンカーソルを制御するクラス
/// </summary>
public class LockonCursor : MonoBehaviour
{
    // 自身のRectTransform
    protected RectTransform rectTransform;

    // カーソルのImage
    protected Image image;

    // ロックオン対象のTransform
    protected Transform LockonTarget { get; set; }

    void Start()
    {
        rectTransform = this.GetComponent<RectTransform>();

        image = this.GetComponent<Image>();
        image.enabled = false;
    }

    void Update()
    {
        if (image.enabled) {
            rectTransform.Rotate(0, 0, 1f);

            if (LockonTarget != null) {
                Vector3 targetPoint = Camera.main.WorldToScreenPoint(LockonTarget.position);
                rectTransform.position = targetPoint;
            }
        }
    }

    public void OnLockonStart(Transform target)
    {
        image.enabled = true;
        LockonTarget = target;
    }

    public void OnLockonEnd()
    {
        image.enabled = false;
        LockonTarget = null;
    }
}

「ロックオン開始時」と「ロックオン終了時」で呼び出される関数を定義しておきます。Image.enableを切り替えることでカーソルの表示・非表示を行い、同時にロックオン対象の設定を行います。
Update()で常にZ軸に対して回転を掛けるとちょっと雰囲気がでていい感じです。またターゲットが設定されている場合はWorldToScreenPoint()で画面上の位置を取得し、そのままカーソルの表示位置として設定します。

最後に表示を制御するため、カメラ制御用のスクリプトを修正します。全て載せると長いので、修正部分のStart()Update()のみ記載します。

[SerializeField]
private GameObject lockOnTarget;

protected LockonCursor lockonCursor;

void Start()
{
    player = GameObject.FindGameObjectWithTag("Player");
    lockonCursor = GameObject.FindObjectOfType<LockonCursor>();
}

void Update()
{
    transform.position = player.transform.position;

    if (Input.GetKeyDown(KeyCode.R)) {
        if (lockOnTarget == null) {
            // ロックオンターゲット取得
            GameObject target = SetTargetClosestScreenCenter();

            if (target != null) {
                lockOnTarget = target;
                lockonCursor.OnLockonStart(target.transform);
            } else {
                // 視点リセット
                iTween.RotateTo(gameObject, iTween.Hash(
                    "rotation", player.transform.eulerAngles,
                    "time", 0.5f
                ));
            }
        } else {
            // 既にターゲットが設定されていた場合は解除
            lockOnTarget = null;
            lockonCursor.OnLockonEnd();
        }
    }
}

Start()でカーソルの参照を取得しておき、カーソル制御クラスで定義した関数をロックオン開始時と終了時に呼び出します。
あとはLockonCursor側の関数で表示・非表示が行われます。

実際に動かすと以下のような感じです。

カーソルがチープな感じですが、挙動事態は問題なさそうです。

スポンサーリンク

down

コメントする



ツイッター