三维数学(3)-向量运算

By | 2021-02-03

点乘
又称“点积”或“内积”
公式:各分量乘积和
[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);
}
判断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();
}