個人的勉強メモ置き場

プログラミングど素人のメモ置き場

近くの敵にロックオンカーソルをつける

こんな感じにしたい

f:id:zykb:20210118132336g:plain

一番近い敵を検出する


いろいろやり方があるんだなとネットで調べながら思いましたが今回はコライダー接触ではなくプレイヤーと敵の距離を計算して一番近い敵オブジェクトを検出します。

private float shortestDistance;
private GameObject nearestEnemy;
public GameObject Player;

void GetNearEnemy()
    {
        shortestDistance = 100f; // 基準距離
        GameObject[] enemys = GameObject.FindGameObjectsWithTag("Enemy");
        foreach(GameObject enemy in enemys)
        {
            float distance = Vector2.Distance(player.transform.position, enemy.transform.position);
            if(distance < shortestDistance)
            {
                shortestDistance = distance; // 最短距離の更新
                nearestEnemy = enemy; 
            }
        }
    }


FindGameObjectsWithTagでEnemyタグをつけたオブジェクトを全てenemys配列に格納しています。
その後全ての配列要素に対してVector2.Distance(a,b)でa-b間の距離を測定。一番値の小さいゲームオブジェクトをnearestEnemyに入れています。

これをもう少し変更して画面内の敵オブジェクトのみ計算するようにします。

void GetNearEnemy()
    {
        shortestDistance = 100f; // 基準距離
        GameObject[] enemys = GameObject.FindGameObjectsWithTag("Enemy");
        foreach(GameObject enemy in enemys)
        {
            if(!enemy.GetComponent<EnemyManager>().GetIsRendering())
            {
                return; // _isRenderingがfalseなら計算せずにreturn
            }
            float distance = Vector2.Distance(player.transform.position, enemy.transform.position);
            if(distance < shortestDistance)
            {
                shortestDistance = distance; // 最短距離の更新
                nearestEnemy = enemy; 
            }
        }
    }
public class EnemyManager : MonoBehaviour
{
    public bool _isRendering = false;
    public bool GetIsRendering()
    {
        return _isRendering;
    }
    void Update()
    {
        if(!GetComponent<SpriteRenderer>().isVisible)
        {
            _isRendering = false;
        }
        else
        {
            _isRendering = true;
        }
    }
}


isVisibleはカメラに写ってないときにfasle、一つでもカメラに写っていればtrueを返します。このカメラはGameViewのカメラだけではなく、SceneViewのカメラにも映っていればtrueになるので注意。

ほかのやり方としてはOnWillRenderObject()を使う方法もありますがなんかエラーが出てしまったので今回はisVisibleでごり押すことに

ロックオンカーソルを表示する

ロックオンする対象オブジェクトを得られるようになったので表示するロックオンカーソルを用意します。素材のカーソルはイラストACさんの素材をお借りしました。

先ほどのコードに更に追加します

using UnityEngine.UI;

public class LockOnSystem : MonoBehaviour
{
    private float shortestDistance;
    private GameObject nearestEnemy;
    private Image image;
    private RectTransform rectTransform;
    public GameObject player;

    void Start() 
    {
        image = GetComponent<Image>();
        rectTransform = GetComponent<RectTransform>();
        image.enabled = false;    
    }
    void Update()
    {
        GetNearEnemy();
        LockonTarget();
    }

    void GetNearEnemy()
    {
        shortestDistance = 100f;
        GameObject[] enemys = GameObject.FindGameObjectsWithTag("Enemy");
        foreach(GameObject enemy in enemys)
        {
            if( !enemy.GetComponent<EnemyManager>().GetIsRendering())
            {
                return;
            }
            
            float distance = Vector2.Distance(player.transform.position, enemy.transform.position);
            if(distance < shortestDistance)
            {
                shortestDistance = distance; // 最短距離の更新
                nearestEnemy = enemy; 
            }
        }
        
    }

    void LockonTarget()
    {
        if(nearestEnemy != null)
        {
            image.enabled = true;
            Vector2 targetPoint = RectTransformUtility.WorldToScreenPoint(Camera.main, nearestEnemy.transform.position);
            rectTransform.position = targetPoint;
        }
    }
}


ヒエラルキーからUI→Canvus→Cursor(image)として生成。CursorにこのLockOnSystemをアタッチします。LockonTarget()ではRectTransformUtility.WorldToScreenPointでワールド座標からスクリーン座標に変換しています。このときCanvusの設定はオーバーレイにしています(cameraなどの場合、変換方法が異なる)

また、スタート時にはenabled = falseで非表示にし、nearestEnemyが存在するときtrueにしています。

動かす


そのままでもいい感じですが、カーソルを回転させるとよりロックオン感が出てきます。今回は簡素にアニメーションでRotation.z値を動かしています


f:id:zykb:20210118183329g:plain


ちょっと色々と粗が目立つ実装なので少しずつ改善したいですね