[学习笔记]三维数学(3)-向量运算

点乘

  • 又称“点积”或“内积”
  • 公式:各分量乘积和
    [x1,y1,z1]*[x2,y2,z2] = x1x2+y1y2+z1z2
  • 几何意义:ab = |a| |b| * cos<a,b>
    两个向量的单位向量相乘后再乘以二者夹角的余弦值。
  • API:float dot = Vector3.Dot(va,vb);
  • 点乘只能求得两向量最小的夹角(0~180°)

叉乘

  • 又称“叉积”或“外积”。
  • 公式:[x1,y1,z1] x [x2,y2,z2] = [y1z2-z1y2,z1x2-x1z2,x1y2-y1x2]
  • 几何意义:结果为两个向量所组成面的垂直向量,模长为两向量模长乘积再乘夹角的正弦值。
  • API:Vector3 vec = Vector3.Cross(a,b);

结果与角的关系

叉乘所得向量的模长与角度的关系:0~90度角

Vector3 cross = Vector3.Cross(v1.normalized,v2.normalized);
float angle = Mathf.Asin(cross.magnitude) * Mathf.Rad2Deg;

应用

  • 创建垂直与平面的向量。
  • 判断两向量的相对位置。

实例

判断两向量之间的夹角是否大于60度

float dot = Vector3.Dot(v1.normalized, v2.normalized);

//写法一,优点:可读性好,缺点:效率差些
float angle = Mathf.Acos(dot)*Mathf.Rad2Deg;
if(angle>60){...}

//写法二,优点:效率较高,缺点:可读性差
if(dot>0.5){...}

画出两向量的叉乘向量

先放置两个cube,并画出指向cube的两向量,再画出两向量的叉乘向量,代码如下:

private void Demo2()
{
        Debug.DrawLine(Vector3.zero, cube.transform.position);
        Debug.DrawLine(Vector3.zero, cube2.transform.position);
        Debug.DrawLine(Vector3.zero, Vector3.Cross(cube.transform.position, cube2.transform.position),Color.yellow);
}

效果
画出叉乘向量.gif

判断player是否进入cube的前方半径10角度120°的扇形攻击范围内

这里有两种做法,效果一样,一种是先用Vector3.Distance判断两物体之间的间距,如果小于10,再做后面判断:求出cube指向扇形最左边顶点的向量,记为left,然后求得cube指向player的向量。然后用点乘求得这两向量的夹角,判断是否小于120°,再用叉乘的y值判断这个夹角是内角还是外角。如果条件都符合说明是在范围内,代码如下:

TestEmptyScript.cs

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

public class TestEmptyScript : MonoBehaviour
{
    // Start is called before the first frame update
    private GameObject player;
    public Vector3 forward;        //移动方向
    public float speed = 3;
    public float fireTime = 0.5f;
    public float distance = 20;         //当与玩家距离小于distance时就开火
    private float lastFireTime;
    private Vector3 left;
    private Vector3 right;

    private Vector3 targetFW;
    private Vector3 VelFW;
    void Start()
    {
        player = GameObject.Find("Player");
        lastFireTime = Time.time;
        left = new Vector3(-Mathf.Cos(30 * Mathf.Deg2Rad) * distance, 0, Mathf.Sin(30 * Mathf.Deg2Rad) * distance);
        right = new Vector3(Mathf.Cos(30 * Mathf.Deg2Rad) * distance, 0, Mathf.Sin(30 * Mathf.Deg2Rad) * distance);
    }

    // Update is called once per frame
    void Update()
    {
        draw();
        if (Vector3.Distance(player.transform.position, this.transform.position) <= distance && Time.time - lastFireTime >= fireTime)
        {
            //是否在攻击角度内
            float dot = Vector3.Dot((transform.TransformPoint(left) - transform.position).normalized, (player.transform.position - this.transform.position).normalized);
            Vector3 cross = Vector3.Cross((transform.TransformPoint(left) - transform.position).normalized, (player.transform.position - this.transform.position).normalized);
            if (dot >= -0.5 && cross.y > 0)
            {
                lastFireTime = Time.time;
                transform.forward = (player.transform.position - this.transform.position).normalized;
                fireBall(); 
            }
        }
        
        /*if (Input.GetKey(KeyCode.K) == false)
            transform.Translate(Vector3.forward * Time.deltaTime * speed);*/
    }

    private void draw()
    {
        Debug.DrawLine(transform.position, transform.TransformPoint(left));
        Debug.DrawLine(transform.position, transform.TransformPoint(right));
    }
    private void fireBall()
    {
        GameObject bullet = (GameObject)Resources.Load("Bullet");
        bullet = Instantiate(bullet);
        bullet.transform.position = this.transform.position;
        bullet.transform.rotation = this.transform.rotation;
    }
}


BulletScript.cs

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

public class BulletScript : MonoBehaviour
{
    public float speed = 50;
    private int maxTF = 250;
    private Rigidbody rig;
    private float beginTime;
    // Start is called before the first frame update
    void Start()
    {
        transform.Translate(Vector3.forward);
        rig = transform.GetComponent<Rigidbody>();
        Vector3 vel = this.transform.forward*speed;
        rig.velocity = vel;
        beginTime = Time.time;
    }

    // Update is called once per frame
    void Update()
    {
        //transform.Translate(Vector3.forward * Time.deltaTime * speed);
        //出了最大有效坐标范围,就删掉对象
        if (Mathf.Abs(transform.position.x) > maxTF || Mathf.Abs(transform.position.z) > maxTF||Time.time-beginTime>10)
            Destroy(gameObject);
        
    }
}


另一种是直接算cube指向player的向量与cube的foward向量的夹角是否小于120°/2即60°,如果小于六十度并且两物体间距小于10则说明进入了扇形范围内。需要把34~42行的代码改为如下代码:

//是否在攻击角度内
float dot = Vector3.Dot(transform.forward.normalized, (player.transform.position - this.transform.position).normalized);
if (dot >= 0.5)
{
    lastFireTime = Time.time;
    transform.forward = (player.transform.position - this.transform.position).normalized;
    fireBall(); 
}

效果

判断进入扇形范围.gif

本文作者:六月丶

本文链接:https://hctra.cn/index.php/archives/495/

版权声明:如无特别声明,本文即为六月'blog原创,仅代表个人观点,如要转载请务必注明文章出处。
最后修改:2020 年 01 月 15 日 08 : 03 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论