【第013问 Unity如何判定玩家和敌人的位置关系?】

视频讲解

一、背景

在游戏开发中,我们经常会遇到如何计算玩家和其它玩家的位置、视野关系;比如我前面有哪些玩家、我的后面有哪些玩家、在我的视野范围内有哪些玩家。这些都是平时会遇到的,那么怎么计算玩家之间的位置关系、视野问题就是本节内容的一个记录。

二、如何计算玩家之间的位置关系

2.1、位置关系

在计算玩家与玩家的位置关系之前,需要线确定计算的玩家位置关系,有以下几个:

  • 前方【敌人在我的前方】
  • 后面【敌人在我的后面】
  • 左边【敌人在我的左边】
  • 右边【敌人在我的右边】

上面只是基本的说明4个方位,但是远远不止这些;左前方、右前方、右后方等等的方位组合都是位置关系;甚至可以划分的更细一些也是可以的。
如下图说明:
在这里插入图片描述
动画中的A显示出了A的前方和右方向,通过旋转可以看出A的的前方和右方的实际指向是不一样的。当然这里也可以显示出更多的方向箭头,这里只显示了前方和右方。

2.2、确定要计算的方向

我们所说的要计算我和某人的方向,这里是有一个方向的选择前提。比如我要确定我和你的位置关系,首先要进行以下的方向确定:

  • 是从我的什么方向作为参考方向,是我的前面、后面、左边、右边;这里需要选择;比如选择我的前面,判断你是不是在我的前面。
  • 要计算出我到玩家的方向;要判断两个玩家的位置关系,这里是需要计算出我到这个玩家的方向是什么;通过玩家的位置减去我的位置就可以计算我到玩家的方向;

如下图:
在这里插入图片描述
从上图中的玩家A到敌人B的连接线,也就是向量AB就是A到B的方向。用AB=B-A

2.3、计算公式

上面我们准备好了要计算的相关参数,那么计算公式是什么呢?
计算公式就是 【点乘

a.b=|a||b|cos角度

上面是点乘的计算公式,我们先来了解一下这几个参数代表的是什么

  • a在这里代表上文中玩家A的正前方,也就是transformA.forward
  • b在这里代表A到B的方向,也就是transformB.position - transformA.position
  • |a|就表示a向量的长度,这里就是transformA.forward.magnitude
  • |b|就表示b向量的长度,这里就是aToB.magnitude
  • 角度就表示向量A和向量B形成的角度

这里我们可以直接通过

float dotAToB = Vector3.Dot(transformA.forward, aToB);

计算出两个向量的点积结果;这里的结果分为以下几种情况:

  • 结果>0:表示它们的方向是基本相同的,也就是基本在同一个方向
  • 结果=0:表示玩家A和玩家B当前的位置关系是垂直关系
  • 结果<0:表示玩家B在玩家A的后面,方向基本相反

通过上面结果我们可以计算出玩家A和玩家B之间的角度

float aToBAngel = Mathf.Acos(dotAToB / (aTobLength * aForwardLenght)) * Mathf.Rad2Deg;

我们也可以通过Unity提供的API直接计算出两个向量的角度:

float aToBAngel_ = Vector3.Angle(transformA.forward, aToB);

这里计算的角度结果是一样的;
这的方向、角度都计算出来;就可以根据点积的结果判断玩家在我的什么位置,是在前方还是后方;是不是在我的视野范围内,这里的方向和视野范围要注意以下问题:

  • 在我的前后用transform.forward计算,如果判断左右可以用transform.right来计算
  • 在我的视野范围内首先要确定是不是在你规定的视野角度,比如你的视野角度是在45-90度之间,看看上面计算的角度在这个范围;另外还要计算你可以看到的视野最远距离,如果不在你的视野距离之内,即使角度满足也是不行的;视野角度和视野距离这两个条件要同时满足。

三、动画演示

下面是基本的动画演示,详细内容可以访问:
在这里插入图片描述

四、源码

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class ValakiObjPosDemos : MonoBehaviour
{
    
    
    [SerializeField] private Transform transformA;
    [SerializeField] private Transform transformB;


    private string[] forwardTip = new[] {
    
    "前", "后"};
    private string[] leftTips = new[] {
    
    "右", "左"};


    private float directionLength = 15;


    [Header("坐标显示")] [SerializeField] private bool showCoordinate = false;

    [Header("显示Plyaer-Enemy原点连接向量")] [SerializeField]
    private bool showZeroPoint = false;

    [Header("AB方向显示")] [SerializeField] private bool showDirection = false;
    [Header("正前方角度显示")] [SerializeField] private bool showForwardAngel = false;
    [Header("右方角度显示")] [SerializeField] private bool showRightAngel = false;
    [Header("计算公式")] [SerializeField] private bool shorDotFormula = false;
    [Header("A到B方向显示")] [SerializeField] private bool showAToBDirection = false;
    [Header("计算显示")] [SerializeField] private bool showCalcuDesc = false;


    [SerializeField] private Material material;

    private Vector3 zeroPoint = Vector3.zero;

    [SerializeField] private MeshFilter forwardMeshFilter;
    [SerializeField] private MeshFilter rightMeshFilter;

    private void OnDrawGizmos()
    {
    
    
        GUI.skin.label.fontSize = 35;
        DrawLabel(transformA.position, "A玩", Color.white, new Vector2(200, 70));
        DrawLabel(transformB.position, "B玩", Color.white, new Vector2(200, 70));

        
        //计算出a到b方向
        Vector3 aToB = transformB.position - transformA.position; 
        float dotAToB = Vector3.Dot(transformA.forward, aToB);
        /*
         *    1 > 0 0-90 基本方向相同
    *         2 = 0 90  垂直
         *     3 < 0  > 90  积本相反
         *
         * 
         */
          
        float aTobLength = aToB.magnitude;
        float aForwardLenght = transformA.forward.magnitude;

        //前后计算
        float aToBAngel = Mathf.Acos(dotAToB / (aTobLength * aForwardLenght)) * Mathf.Rad2Deg;
        float aToBAngel_ = Vector3.Angle(transformA.forward, aToB);  
        
         
        //左右计算
        float dotRight = Vector3.Dot(transformA.right, aToB);
        float aRightLength = Vector3.right.magnitude;
        float aRightAngel = Mathf.Acos(dotRight / (aRightLength * aTobLength)) * Mathf.Rad2Deg;
        float aRightAngel_ = Vector3.Angle(transformA.right, aToB);
 

        if (showDirection)
        {
    
    
            GUI.skin.label.fontSize = 18;


            Debug.DrawLine(transformA.position, transformA.position + transformA.right * directionLength, Color.green);
            Debug.DrawLine(transformA.position, transformA.position + transformA.forward * directionLength, Color.blue);
            DrawLabel(transformA.position + transformA.forward * directionLength, "Z[正前方]", Color.yellow,
                new Vector2(1000, 100));
            DrawLabel(transformA.position + transformA.right * directionLength, "X[正右方]", Color.yellow,
                new Vector2(1000, 100));
        }

        if (showAToBDirection)
        {
    
    
            //绘制出偏移的方向
            Debug.DrawLine(transformA.position, aToB + transformA.position, Color.magenta);
        }


        GUI.skin.label.fontSize = 22;
        if (showCalcuDesc)
        {
    
    
            CalculateShowTips(dotAToB, aToBAngel, aToBAngel_, dotRight, aRightAngel, aRightAngel_);
        }

        if (showZeroPoint)
            DrawZeroLine();
        if (showCoordinate)
            ShowCoordenate3();

        forwardMeshFilter.mesh = null;
        rightMeshFilter.mesh = null;
        if (showForwardAngel)
            ShowForwardAngel(aToB);

        if (showRightAngel)
            ShowRightAngel(aToB);

        if (shorDotFormula)
            ShowDotFormula();
    }

    private void ShowForwardAngel(Vector3 aToB)
    {
    
    
        Mesh mesh = new Mesh();

        Vector3 vertext1 = transformA.position + transformA.forward * 2;
        Vector3 vertext2 = transformA.position;
        Vector3 vertext3 = transformA.position + aToB * 0.2f;

        mesh.vertices = new[]
        {
    
    
            vertext1, vertext2, vertext3, vertext1, vertext2,
            vertext3
        };
        mesh.SetIndices(new[] {
    
    0, 1, 2, 0, 2, 1}, MeshTopology.Triangles, 0);
        forwardMeshFilter.sharedMesh = mesh;
    }

    private void ShowRightAngel(Vector3 aToB)
    {
    
    
        Mesh mesh = new Mesh();

        Vector3 vertext1 = transformA.position + transformA.right * 3;
        Vector3 vertext2 = transformA.position;
        Vector3 vertext3 = transformA.position + aToB * 0.3f;

        mesh.vertices = new[]
        {
    
    
            vertext1, vertext2, vertext3, vertext1, vertext2,
            vertext3
        };
        mesh.SetIndices(new[] {
    
    0, 1, 2, 0, 2, 1}, MeshTopology.Triangles, 0);
        rightMeshFilter.sharedMesh = mesh;
    }


    /// <summary>
    /// 描述计算
    /// </summary>
    /// <param name="dotAToB"></param>
    /// <param name="aToBAngel"></param>
    /// <param name="aToBAngel_"></param>
    /// <param name="dotRight"></param>
    /// <param name="aRightAngel"></param>
    /// <param name="aRightAngel_"></param>
    private void CalculateShowTips(float dotAToB, float aToBAngel, float aToBAngel_, float dotRight, float aRightAngel,
        float aRightAngel_)
    {
    
    
        string forwardDesc = dotAToB > 0 ? "敌人在前面" : "敌人在后面";
        string axb = forwardDesc + "\n" + "forward.AB=" + dotAToB + "  forward与AB夹角=" + aToBAngel +
                     "  forward:" +
                     transformA.forward.ToString();

        string rightdDesc = dotRight > 0 ? "敌人在右边" : "敌人在左边";
        string rightDesc = rightdDesc + "\n right.AB=" + dotRight + "  right与AB夹角=" + aRightAngel +
                           "  right:" +
                           transformA.right.ToString();


        Handles.BeginGUI();
        GUI.skin.label.normal.textColor = Color.blue;
        GUI.Label(new Rect(0, 0, 1500, 200), axb);
        GUI.skin.label.normal.textColor = Color.magenta;
        GUI.Label(new Rect(0, 100, 1500, 200), rightDesc);
        HandleUtility.Repaint();
        Handles.EndGUI();
    }


    void DrawLabel(Vector3 pos, string text, Color color, Vector2 size)
    {
    
    
        Vector2 pos2D = HandleUtility.WorldToGUIPoint(pos);
        Handles.BeginGUI();

        GUI.skin.label.normal.textColor = color;
        GUI.Label(new Rect(pos2D.x, pos2D.y, size.x, size.y), text);
        HandleUtility.Repaint();
        Handles.EndGUI();
    }

    void DrawZeroLine()
    {
    
    
        Debug.DrawLine(Vector3.zero, transformA.position, Color.white);
        Debug.DrawLine(Vector3.zero, transformB.position, Color.white);
        DrawLabel(Vector3.zero, "O", Color.white, new Vector2(40, 40));
    }

    private Color zColor = new Color(58 / 255.0f, 122 / 255.0f, 248 / 255.0f, 237 / 255.0f);
    private Color xColor = new Color(219 / 255.0f, 62 / 255.0f, 29 / 255.0f, 237 / 255.0f);
    private Color yColor = new Color(154 / 255.0f, 243 / 255.0f, 72 / 255.0f, 237 / 255.0f);

    void ShowCoordenate3()
    {
    
    
        Color zColor = new Color(58 / 255.0f, 122 / 255.0f, 248 / 255.0f, 237 / 255.0f);
        Color xColor = new Color(219 / 255.0f, 62 / 255.0f, 29 / 255.0f, 237 / 255.0f);
        Color yColor = new Color(154 / 255.0f, 243 / 255.0f, 72 / 255.0f, 237 / 255.0f);
        Debug.DrawLine(Vector3.forward * -150f, Vector3.forward * 150f,
            zColor);
        Debug.DrawLine(Vector3.right * -150f, Vector3.right * 150f,
            xColor);
        Debug.DrawLine(Vector3.up * -150f, Vector3.up * 150f, yColor);
    }

    void ShowDotFormula()
    {
    
    
        Handles.BeginGUI();
        string desc = "公式:a.b=|a||b|cos角度";
        GUI.skin.label.fontSize = 40;
        GUI.skin.label.normal.textColor = Color.magenta;
        GUI.Label(new Rect(800, 0, 500, 200), desc);
        HandleUtility.Repaint();
        Handles.EndGUI();
    }
 
    
}

结语:

动漫看多了其实不好【valaki】

猜你喜欢

转载自blog.csdn.net/valaki/article/details/123857592
013