読者です 読者をやめる 読者になる 読者になる

遊べるゲームを作りたい

作りたいです。

7つめの改良 Unityでロックオン機能を実装

f:id:hogemarunosuke:20170129183110p:plain

遊び方

WASDキーで移動。スペースキーでジャンプ。マウスで視点移動。
左クリックで射撃。右クリックでロックオンモード。

某DASHのようなロックオン機能の実装を目指しました。

ロックオン対象の取得

相変わらずLINQは便利。どこかの記事でUnityのLINQはパフォーマンスに影響を与えるとか書いてあったような気もしますが、気にしないことにします。しかし横に長くなってよろしくないですな・・・。書き方の問題だと思いますが・・・。

// ロックオン対象の敵を取得
private GameObject GetTargetEnemy(bool isNearestPlayer = false, bool isScreenCentral = false)
{
    var enemys = GameObject.FindGameObjectsWithTag("Enemy")
        .Where(enemy => enemy.GetComponent<BaseEnemy>().IsVisible() == true)                                                    // 画面内にいるか
        .Where(enemy => Camera.main.WorldToScreenPoint(enemy.transform.position).z > -(mainCamera.transform.localPosition.z))   // カメラから見てプレイヤーより遠くにいるか
        .Where(enemy => Vector3.Distance(transform.position, enemy.transform.position) < lockonMaxDistance)                     // ロックオン可能範囲にいるか
        .Where(enemy => RaycastEnemy(enemy) == true);                                                                           // 射線が通るか

    // プレイヤーに一番近い敵を取得
    if (isNearestPlayer)
    {
        enemys = enemys.OrderBy(enemy => Vector3.Distance(transform.position, enemy.transform.position));
    }

    // 画面中央に一番近い敵を取得
    if (isScreenCentral)
    {
        enemys = enemys.OrderBy(enemy => Vector2.Distance(new Vector2(Screen.width / 2.0f, Screen.height / 2.0f), Camera.main.WorldToScreenPoint(enemy.transform.position)));
    }

    return enemys.FirstOrDefault();
}

画面内にいるかどうか

敵オブジェクトごとにOnWillRenderObject()イベントでセットしておきます。で、取得用のIsVisible()を用意してそこから取得。

// 画面内にいるかどうか
private bool isVisible = false;

private void Update()
{
    this.isVisible = false;
}

private void OnWillRenderObject()
{
    this.isVisible = true;
}

public bool IsVisible()
{
    return this.isVisible;
}

射線が通るかどうか

地形に隠れているのにロックオンできたらイヤですからね。初めてRaycastのレイヤーマスクを使用してみましたがとても便利。おっと、ビット演算子を発見伝。かなり久しぶりに使いました。

// 指定した敵へと射線が通るかチェック
private bool RaycastEnemy(GameObject enemy)
{
    var heading = enemy.transform.position - mainCamera.transform.position;    // プレイヤーではなくカメラの位置を原点とする
    var distance = heading.magnitude;
    var direction = heading / distance; // This is now the normalized direction.

    int layerMask = (1 << LayerMask.NameToLayer("Enemy")) + (1 << LayerMask.NameToLayer("Terrain")); // "Enemy"と"Terrain"レイヤー以外を無視
    var hits = Physics.RaycastAll(mainCamera.transform.position, direction, lockonMaxDistance, layerMask);

    foreach (var hit in hits.OrderBy(hit => hit.distance))
    {
        if (hit.collider.CompareTag("Terrain"))
        {
            // カメラがプレイヤーより下の場合は地形を無視する(めり込み対策)
            if (transform.position.y > mainCamera.transform.position.y)
            {
                continue;
            }

            return false;
        }
        else if (hit.collider.CompareTag("Enemy") && hit.transform.position == enemy.transform.position)
        {
            return true;
        }
    }

    return false;
}

これでロックオン対象を取得できたら、あとはそちらの方を向くようにカメラを動かすだけ。やったー、それっぽい感じになった。

偏差射撃の実装

ロックオン中の偏差射撃の実装は↓のサイトを参考にさせていただきました。
ありがとうございます。
redhu.hatenablog.com