unity-向量数学:由浅入深详解

第一部分:向量基础 - 从零开始理解

1. 向量是什么?一个生动的比喻

想象你在地图上:

  • (标量):"我在北京天安门" - 这是一个固定的位置

  • 向量:"向北走5公里" - 这是方向距离的组合

在Unity 3D中:

csharp

// 点:一个具体位置
Vector3 position = new Vector3(10, 0, 5);

// 向量:一个位移或方向
Vector3 movement = new Vector3(0, 0, 5); // 向前5个单位

2. Unity中的向量表示法

csharp

Vector2 v2 = new Vector2(x, y);      // 2D向量(UI、2D游戏)
Vector3 v3 = new Vector3(x, y, z);   // 3D向量(3D游戏)
Vector4 v4 = new Vector4(x, y, z, w); // 4D(颜色、矩阵等高级应用)

// 常用预定义向量
Vector3.zero     // (0, 0, 0) - 零向量
Vector3.one      // (1, 1, 1)
Vector3.forward  // (0, 0, 1) - 世界前方
Vector3.up       // (0, 1, 0) - 世界上方
Vector3.right    // (1, 0, 0) - 世界右方

第二部分:向量基本运算 - "游戏世界的加减法"

1. 向量加法:位移的合成

现实例子:先向东走10米,再向北走5米 → 最终位移

csharp

// Unity中的向量加法
Vector3 walkEast = new Vector3(10, 0, 0);
Vector3 walkNorth = new Vector3(0, 0, 5);
Vector3 finalDisplacement = walkEast + walkNorth; // (10, 0, 5)

// 游戏应用:更新位置
void Update() {
    // 每秒向右移动2单位,向上移动1单位
    Vector3 movement = new Vector3(2, 1, 0) * Time.deltaTime;
    transform.position += movement; // 位置累加
}

2. 向量减法:获取方向和距离

核心用途:从A到B的方向 = B - A

csharp

// 计算敌人到玩家的方向
Vector3 playerPosition = player.transform.position;
Vector3 enemyPosition = enemy.transform.position;

// 方向向量 = 目标 - 起点
Vector3 directionToPlayer = playerPosition - enemyPosition;

// 游戏应用:让敌人看向玩家
transform.LookAt(playerPosition); // Unity自动计算方向

// 或者手动计算旋转
void FaceTarget(Vector3 target) {
    Vector3 direction = (target - transform.position).normalized;
    Quaternion lookRotation = Quaternion.LookRotation(direction);
    transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * rotationSpeed);
}

3. 向量数乘:缩放大小

csharp

Vector3 direction = new Vector3(1, 0, 0); // 向右的方向
float speed = 5.0f;
Vector3 velocity = direction * speed; // (5, 0, 0)

// 实际游戏中的标准移动模式
void Update() {
    Vector3 moveDirection = GetMoveDirection(); // 获取输入方向
    moveDirection.Normalize(); // 确保对角线移动不会更快
    Vector3 movement = moveDirection * moveSpeed * Time.deltaTime;
    transform.position += movement;
}

第三部分:向量长度与标准化 - "控制大小"

1. 向量长度(模)

csharp

Vector3 v = new Vector3(3, 4, 0);
float magnitude = v.magnitude; // 5 (勾股定理:√(3²+4²)=5)

// 手动计算:Vector3.Distance计算两点间距离
float distance = Vector3.Distance(pointA, pointB);
// 等同于:(pointB - pointA).magnitude

2. 向量标准化(归一化)

为什么需要? 获取纯方向,忽略长度

csharp

Vector3 v = new Vector3(3, 4, 0);
Vector3 normalizedV = v.normalized; // (0.6, 0.8, 0)

// 验证:标准化后的长度总是1
Debug.Log(normalizedV.magnitude); // 输出:1.0

// ⚠️ 常见错误:忘记标准化
Vector3 input = new Vector3(0.5f, 0, 0.5f); // 对角线输入
float rawMagnitude = input.magnitude; // 约0.707(小于1)

// 如果直接使用:
transform.position += input * speed * Time.deltaTime;
// 对角线移动会比轴向移动慢!这是个常见bug

3. 标准化的重要性:修复移动速度

csharp

void ProcessMovement() {
    // 获取原始输入(值在-1到1之间)
    float horizontal = Input.GetAxis("Horizontal");
    float vertical = Input.GetAxis("Vertical");
    
    // 方法1:正确的方式
    Vector3 moveInput = new Vector3(horizontal, 0, vertical);
    Vector3 moveDirection = moveInput.normalized; // 关键步骤!
    transform.position += moveDirection * speed * Time.deltaTime;
    
    // 方法2:Unity已经为你考虑到了
    // Input.GetAxisRaw 配合标准化,或者使用CharacterController
}

第四部分:向量点积 - "角度的秘密探测器"

1. 点积的几何意义

公式:A·B = |A| * |B| * cosθ
结果:一个标量(数值)

csharp

Vector3 A = new Vector3(1, 0, 0); // 向右
Vector3 B = new Vector3(0, 0, 1); // 向前

float dotProduct = Vector3.Dot(A, B); // 0(因为cos90°=0)

2. 点积的三种关键判断

csharp

// 场景:玩家前方检测
Vector3 playerForward = transform.forward;
Vector3 toEnemy = (enemy.position - transform.position).normalized;

float dot = Vector3.Dot(playerForward, toEnemy);

if (dot > 0.7f) {
    // 敌人在玩家前方(角度约小于45°)
    Debug.Log("敌人在前方!");
} else if (dot < -0.7f) {
    // 敌人在玩家后方(角度约大于135°)
    Debug.Log("敌人在后方!");
} else if (Mathf.Abs(dot) < 0.3f) {
    // 敌人在玩家侧面(角度约60°-120°)
    Debug.Log("敌人在侧面!");
}

// 更直观的角度计算
float angle = Mathf.Acos(dot) * Mathf.Rad2Deg; // 点积转角度

3. 实战应用示例

应用1:前方扇形检测

csharp

bool IsTargetInFront(GameObject target, float fieldOfViewAngle) {
    Vector3 toTarget = (target.transform.position - transform.position).normalized;
    float dot = Vector3.Dot(transform.forward, toTarget);
    
    // 将点积转换为角度阈值(cos(角度))
    float cosThreshold = Mathf.Cos(fieldOfViewAngle * 0.5f * Mathf.Deg2Rad);
    
    return dot > cosThreshold && // 在角度范围内
           Vector3.Distance(transform.position, target.transform.position) < detectionRange;
}
应用2:背面伤害判定

csharp

bool IsBackstab(Transform attacker, Transform victim) {
    Vector3 victimForward = victim.forward;
    Vector3 attackDirection = (attacker.position - victim.position).normalized;
    
    // 如果攻击者大致在受害者后方(点积接近-1)
    return Vector3.Dot(victimForward, attackDirection) < -0.8f;
}
应用3:光照强度模拟

csharp

float CalculateLightIntensity(Vector3 lightDirection, Vector3 surfaceNormal) {
    // 法线方向与光线方向点积 = cosθ
    float dot = Vector3.Dot(surfaceNormal.normalized, -lightDirection.normalized);
    
    // 确保非负(背面不受光)
    return Mathf.Max(0, dot);
}

第五部分:向量叉积 - "生成垂直方向"

1. 叉积的几何意义

公式:A × B 结果是一个垂直于A和B所在平面的新向量

csharp

Vector3 up = Vector3.up;      // (0, 1, 0)
Vector3 right = Vector3.right; // (1, 0, 0)

Vector3 forward = Vector3.Cross(up, right); // (0, 0, 1) 或 -1,取决于顺序

右手定则:食指指向A,中指指向B,拇指指向结果

2. 叉积的实用特性

csharp

// 1. 生成垂直向量
Vector3 GetPerpendicular(Vector3 v) {
    // 任意找一个不平行于v的向量
    Vector3 notParallel = (Mathf.Abs(v.y) < 0.999f) ? Vector3.up : Vector3.forward;
    return Vector3.Cross(v, notParallel).normalized;
}

// 2. 计算表面法线(用于光照、反弹等)
Vector3 CalculateSurfaceNormal(Vector3 p1, Vector3 p2, Vector3 p3) {
    Vector3 edge1 = p2 - p1;
    Vector3 edge2 = p3 - p1;
    return Vector3.Cross(edge1, edge2).normalized;
}

// 3. 判断左右关系
Vector3 playerToEnemy = enemy.position - player.position;
Vector3 playerForward = player.forward;

Vector3 cross = Vector3.Cross(playerForward, playerToEnemy);
if (cross.y > 0) {
    Debug.Log("敌人在玩家右侧");
} else if (cross.y < 0) {
    Debug.Log("敌人在玩家左侧");
}

3. 实战应用:第三人称摄像机

csharp

public class ThirdPersonCamera : MonoBehaviour {
    public Transform target;
    public float distance = 5.0f;
    public float sensitivity = 2.0f;
    
    private float currentX = 0.0f;
    private float currentY = 0.0f;
    
    void Update() {
        currentX += Input.GetAxis("Mouse X") * sensitivity;
        currentY -= Input.GetAxis("Mouse Y") * sensitivity;
        currentY = Mathf.Clamp(currentY, -30, 70);
    }
    
    void LateUpdate() {
        // 计算相机位置:围绕目标旋转
        Vector3 direction = new Vector3(0, 0, -distance);
        Quaternion rotation = Quaternion.Euler(currentY, currentX, 0);
        
        // 使用叉积确保相机不会穿墙
        Vector3 desiredPosition = target.position + rotation * direction;
        
        // 简单的防穿墙检测
        RaycastHit hit;
        if (Physics.Linecast(target.position, desiredPosition, out hit)) {
            desiredPosition = hit.point + (target.position - desiredPosition).normalized * 0.5f;
        }
        
        transform.position = desiredPosition;
        transform.LookAt(target.position);
    }
}

第六部分:向量投影 - "分解力量"

1. 投影概念:将向量分解

csharp

// 将速度分解为水平和垂直分量
Vector3 velocity = new Vector3(10, 5, 3);
Vector3 groundNormal = Vector3.up; // 假设地面水平

// 计算在法线方向上的投影(垂直分量)
Vector3 verticalComponent = Vector3.Project(velocity, groundNormal);

// 计算在地面平面上的投影(水平分量)
Vector3 horizontalComponent = velocity - verticalComponent;

// 或者使用 Vector3.ProjectOnPlane
Vector3 horizontal = Vector3.ProjectOnPlane(velocity, groundNormal);

2. 实战应用:斜坡移动

csharp

void MoveOnSlope(Vector3 moveDirection, float speed) {
    // 检测地面法线
    RaycastHit hit;
    if (Physics.Raycast(transform.position + Vector3.up * 0.1f, Vector3.down, out hit, 1.0f)) {
        Vector3 groundNormal = hit.normal;
        
        // 将移动方向投影到地面平面
        Vector3 slopeDirection = Vector3.ProjectOnPlane(moveDirection, groundNormal).normalized;
        
        // 应用移动
        Vector3 movement = slopeDirection * speed * Time.deltaTime;
        
        // 确保不会陷入地面
        if (movement.y < 0) movement.y = 0;
        
        transform.position += movement;
    }
}

第七部分:实际综合案例

案例1:智能敌人AI

csharp

public class SmartEnemyAI : MonoBehaviour {
    public Transform player;
    public float moveSpeed = 3.0f;
    public float detectionRange = 10.0f;
    public float attackRange = 2.0f;
    public float fieldOfView = 90f;
    
    void Update() {
        Vector3 toPlayer = player.position - transform.position;
        float distance = toPlayer.magnitude;
        
        if (distance < detectionRange) {
            // 1. 检查是否在视野内
            float dot = Vector3.Dot(transform.forward, toPlayer.normalized);
            float cosFOV = Mathf.Cos(fieldOfView * 0.5f * Mathf.Deg2Deg);
            
            if (dot > cosFOV) {
                // 2. 判断左右关系
                Vector3 cross = Vector3.Cross(transform.forward, toPlayer.normalized);
                string side = cross.y > 0 ? "右侧" : "左侧";
                
                // 3. 根据距离采取行动
                if (distance > attackRange) {
                    // 追逐:朝玩家移动
                    Vector3 moveDir = toPlayer.normalized;
                    transform.position += moveDir * moveSpeed * Time.deltaTime;
                    
                    // 平滑转向
                    Quaternion targetRotation = Quaternion.LookRotation(toPlayer);
                    transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 
                                                          Time.deltaTime * 5f);
                } else {
                    // 攻击范围内:侧向移动(走位)
                    Vector3 strafeDir = Vector3.Cross(toPlayer, Vector3.up).normalized;
                    transform.position += strafeDir * moveSpeed * 0.5f * Time.deltaTime;
                    
                    // 始终保持面向玩家
                    transform.LookAt(player);
                }
            }
        }
    }
}

案例2:物理投射物

csharp

public class Projectile : MonoBehaviour {
    public Vector3 launchVelocity;
    public float gravity = 9.81f;
    private Vector3 currentVelocity;
    private float launchTime;
    
    void Start() {
        currentVelocity = launchVelocity;
        launchTime = Time.time;
    }
    
    void Update() {
        // 计算重力影响
        Vector3 gravityEffect = Vector3.down * gravity * (Time.time - launchTime);
        
        // 更新位置
        transform.position += (launchVelocity + gravityEffect * 0.5f) * Time.deltaTime;
        
        // 更新速度
        currentVelocity += Vector3.down * gravity * Time.deltaTime;
        
        // 让投射物始终面向运动方向
        if (currentVelocity.magnitude > 0.1f) {
            transform.rotation = Quaternion.LookRotation(currentVelocity.normalized);
        }
    }
    
    // 计算发射角度(抛物线轨迹)
    public static Vector3 CalculateLaunchVelocity(Vector3 start, Vector3 target, float angle) {
        Vector3 direction = target - start;
        float height = direction.y;
        direction.y = 0;
        
        float distance = direction.magnitude;
        float radianAngle = angle * Mathf.Deg2Rad;
        
        direction.y = distance * Mathf.Tan(radianAngle);
        distance += height / Mathf.Tan(radianAngle);
        
        float velocity = Mathf.Sqrt(distance * Physics.gravity.magnitude / Mathf.Sin(2 * radianAngle));
        return direction.normalized * velocity;
    }
}

第八部分:性能优化与最佳实践

1. 避免不必要的向量运算

csharp

// 不好:每帧都重新计算整个向量
void Update() {
    Vector3 targetDir = (target.position - transform.position).normalized;
    // ...使用targetDir
}

// 较好:缓存结果,需要时再计算
private Vector3 lastTargetDir;
private float lastCalcTime;

void Update() {
    if (Time.time - lastCalcTime > 0.2f) { // 每0.2秒更新一次
        lastTargetDir = (target.position - transform.position).normalized;
        lastCalcTime = Time.time;
    }
    // ...使用lastTargetDir
}

2. 使用平方长度进行比较

csharp

// 检测距离时,避免使用耗时的开方运算
float detectionRange = 10.0f;
float sqrDetectionRange = detectionRange * detectionRange; // 100

Vector3 toPlayer = player.position - transform.position;

// 不好:使用magnitude(包含开方)
if (toPlayer.magnitude < detectionRange) { }

// 好:使用sqrMagnitude(无开方)
if (toPlayer.sqrMagnitude < sqrDetectionRange) { }

3. 向量池化(高级优化)

csharp

// 对于频繁创建的临时向量,考虑使用对象池
public class Vector3Pool {
    private Stack<Vector3> pool = new Stack<Vector3>();
    
    public Vector3 Get() {
        return pool.Count > 0 ? pool.Pop() : Vector3.zero;
    }
    
    public void Release(Vector3 vector) {
        pool.Push(vector);
    }
}

总结:向量思维训练

  1. 看到位置想点transform.position 是空间中的一个点

  2. 看到移动想向量:位移 = 方向 × 速度 × 时间

  3. 看到方向想标准化.normalized 是你的好朋友

  4. 看到角度想点积Vector3.Dot 判断前后关系

  5. 看到垂直想叉积Vector3.Cross 生成法线或判断左右

  6. 看到距离想平方:比较距离时优先用 sqrMagnitude

最后的小测验
如果你能理解下面的代码,说明你已经掌握了向量基础:

csharp

Vector3 playerPos = player.transform.position;
Vector3 enemyPos = enemy.transform.position;

// 1. 敌人到玩家的方向
Vector3 toPlayer = (playerPos - enemyPos).normalized;

// 2. 如果敌人在玩家前方45度内
if (Vector3.Dot(player.transform.forward, -toPlayer) > 0.707f) {
    // 3. 敌人侧向移动(垂直于视线方向)
    Vector3 strafeDir = Vector3.Cross(toPlayer, Vector3.up).normalized;
    enemyPos += strafeDir * moveSpeed * Time.deltaTime;
    
    // 4. 始终面向玩家
    enemy.transform.rotation = Quaternion.LookRotation(toPlayer);
}

记住:向量是游戏开发的乐高积木。从简单的移动到复杂的AI行为,都是这些基础操作的组合。多在Unity中实践,用Debug.DrawRay可视化你的向量,你会很快掌握这门"方向与力的语言"。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值