第一部分:向量基础 - 从零开始理解
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);
}
}
总结:向量思维训练
-
看到位置想点:
transform.position是空间中的一个点 -
看到移动想向量:位移 = 方向 × 速度 × 时间
-
看到方向想标准化:
.normalized是你的好朋友 -
看到角度想点积:
Vector3.Dot判断前后关系 -
看到垂直想叉积:
Vector3.Cross生成法线或判断左右 -
看到距离想平方:比较距离时优先用
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可视化你的向量,你会很快掌握这门"方向与力的语言"。

1301

被折叠的 条评论
为什么被折叠?



