7.1 数学库
数学计算在游戏开发中扮演着极其重要的角色,从简单的分数计算到复杂的物理模拟,无处不需要数学函数。Lua提供了基本的数学库,而UE5.2则拥有更加强大和游戏导向的数学功能。
7.1.1 基本数学函数
Lua的数学库提供了基本的数学函数:
lua
-- 基本数学常量
print(math.pi) -- 3.1415926535898
print(math.huge) -- 正无穷大
-- 取整函数
print(math.floor(3.7)) -- 3 (向下取整)
print(math.ceil(3.7)) -- 4 (向上取整)
-- 三角函数 (参数使用弧度)
print(math.sin(math.pi/2)) -- 1.0
print(math.cos(math.pi)) -- -1.0
print(math.tan(math.pi/4)) -- 约为1.0
-- 指数和对数
print(math.exp(1)) -- e^1, 约为2.718
print(math.log(10)) -- ln(10), 约为2.302
print(math.log(100, 10)) -- log10(100), 等于2
-- 幂运算
print(math.pow(2, 3)) -- 2^3, 等于8
-- 平方根
print(math.sqrt(16)) -- 4
-- 随机数
math.randomseed(os.time()) -- 设置随机数种子
print(math.random()) -- 0到1之间的随机数
print(math.random(10)) -- 1到10之间的随机整数
print(math.random(5, 10)) -- 5到10之间的随机整数
-- 最大值和最小值
print(math.max(5, 10, 3)) -- 10
print(math.min(5, 10, 3)) -- 3
-- 绝对值
print(math.abs(-10)) -- 10
UE5.2中的数学函数:
UE5.2提供了强大的数学库,主要通过FMath命名空间提供:
C++数学函数示例:
cpp
// 数学常量
float Pi = PI; // 3.1415926535897932
float HalfPi = PI_2; // PI/2
float TwoPi = PI * 2.0f; // 2*PI
float InvPi = 1.0f / PI; // 1/PI
// 取整函数
int32 FloorValue = FMath::FloorToInt(3.7f); // 3
int32 CeilValue = FMath::CeilToInt(3.7f); // 4
int32 RoundValue = FMath::RoundToInt(3.7f); // 4
float TruncValue = FMath::TruncToFloat(3.7f); // 3.0
// 三角函数(参数使用弧度)
float SinValue = FMath::Sin(PI * 0.5f); // 1.0
float CosValue = FMath::Cos(PI); // -1.0
float TanValue = FMath::Tan(PI * 0.25f); // 约为1.0
// 角度与弧度转换
float Degrees = 45.0f;
float Radians = FMath::DegreesToRadians(Degrees); // 约为0.785
float BackToDegrees = FMath::RadiansToDegrees(Radians); // 45.0
// 指数和对数
float ExpValue = FMath::Exp(1.0f); // e^1, 约为2.718
float LogValue = FMath::Loge(10.0f); // ln(10), 约为2.302
float Log10Value = FMath::Log10(100.0f); // log10(100), 等于2
// 幂运算
float PowValue = FMath::Pow(2.0f, 3.0f); // 2^3, 等于8
// 平方根
float SqrtValue = FMath::Sqrt(16.0f); // 4.0
float InvSqrtValue = FMath::InvSqrt(16.0f); // 1/4 = 0.25
// 随机数
int32 Seed = FMath::Rand(); // 获取随机数
FMath::SRandInit(FPlatformTime::Cycles()); // 初始化随机数种子
float RandomFloat = FMath::FRand(); // 0到1之间的随机数
int32 RandomInt = FMath::RandRange(5, 10); // 5到10之间的随机整数
float RandomFloatRange = FMath::FRandRange(5.0f, 10.0f); // 5到10之间的随机浮点数
// 最大值和最小值
float MaxValue = FMath::Max(5.0f, 10.0f); // 10.0
float MinValue = FMath::Min(5.0f, 10.0f); // 5.0
int32 ClampValue = FMath::Clamp(15, 0, 10); // 10 (限制在0到10之间)
// 绝对值
float AbsValue = FMath::Abs(-10.0f); // 10.0
// 线性插值
float LerpValue = FMath::Lerp(0.0f, 10.0f, 0.5f); // 5.0 (50%插值)
// 检查浮点数是否接近
bool bIsNearlyEqual = FMath::IsNearlyEqual(0.1f + 0.2f, 0.3f); // 通常为true
bool bIsNearlyZero = FMath::IsNearlyZero(0.00001f); // 检查是否接近零
// 计算交叉乘积
float CrossProduct2D = FMath::CrossProduct2D(
FVector2D(1.0f, 0.0f),
FVector2D(0.0f, 1.0f)
); // 1.0
蓝图数学函数:
UE5.2蓝图系统提供了丰富的数学节点:
在蓝图中创建简单的随机数生成器:
- 创建一个函数,接收最小值和最大值参数
- 使用"Random Float in Range"节点生成随机数
- 返回生成的随机值
7.1.2 向量运算
游戏开发中,向量运算是物理模拟、空间计算和运动控制的基础:
Lua中模拟向量运算:
lua
-- 自定义简单的向量库
local vector = {}
function vector.new(x, y, z)
return {x = x or 0, y = y or 0, z = z or 0}
end
-- 向量加法
function vector.add(a, b)
return vector.new(a.x + b.x, a.y + b.y, a.z + b.z)
end
-- 向量减法
function vector.sub(a, b)
return vector.new(a.x - b.x, a.y - b.y, a.z - b.z)
end
-- 向量点乘
function vector.dot(a, b)
return a.x * b.x + a.y * b.y + a.z * b.z
end
-- 向量叉乘
function vector.cross(a, b)
return vector.new(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
)
end
-- 向量长度
function vector.length(a)
return math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z)
end
-- 向量单位化
function vector.normalize(a)
local len = vector.length(a)
if len > 0 then
return vector.new(a.x / len, a.y / len, a.z / len)
else
return vector.new(0, 0, 0)
end
end
-- 使用向量
local v1 = vector.new(1, 2, 3)
local v2 = vector.new(4, 5, 6)
local v3 = vector.add(v1, v2) -- (5, 7, 9)
local dotProduct = vector.dot(v1, v2) -- 32
local crossProduct = vector.cross(v1, v2) -- (-3, 6, -3)
local length = vector.length(v1) -- 约为3.74
local normalized = vector.normalize(v1)
UE5.2中的向量运算:
UE5.2提供了强大的向量类和操作:
C++向量运算:
cpp
// 创建向量
FVector Vector1(1.0f, 2.0f, 3.0f);
FVector Vector2(4.0f, 5.0f, 6.0f);
// 向量加法
FVector Sum = Vector1 + Vector2; // (5, 7, 9)
// 向量减法
FVector Difference = Vector1 - Vector2; // (-3, -3, -3)
// 标量乘法
FVector Scaled = Vector1 * 2.0f; // (2, 4, 6)
// 点乘
float DotProduct = FVector::DotProduct(Vector1, Vector2); // 32
// 叉乘
FVector CrossProduct = FVector::CrossProduct(Vector1, Vector2); // (-3, 6, -3)
// 向量长度
float Length = Vector1.Size(); // 约为3.74
float SqrLength = Vector1.SizeSquared(); // 约为14
// 单位化向量
FVector Normalized = Vector1.GetSafeNormal(); // 返回单位向量
Vector1.Normalize(); // 原地单位化
// 向量夹角(弧度)
float Angle = FMath::Acos(
FVector::DotProduct(Vector1.GetSafeNormal(), Vector2.GetSafeNormal())
);
// 向量投影
FVector Projection = Vector1.ProjectOnTo(Vector2);
// 向量插值
FVector Lerp = FMath::Lerp(Vector1, Vector2, 0.5f); // 50%插值
// 向量反射
FVector Normal = FVector(0.0f, 0.0f, 1.0f); // 向上的法线
FVector Incident = FVector(1.0f, 1.0f, -1.0f);
FVector Reflected = FMath::GetReflectionVector(Incident, Normal);
// 2D向量
FVector2D Vec2D(1.0f, 2.0f);
float Length2D = Vec2D.Length(); // 约为2.24
// 4D向量(常用于四元数和齐次坐标)
FVector4 Vec4D(1.0f, 2.0f, 3.0f, 1.0f);
// 使用向量进行游戏计算
void AMoveComponent::MoveAlongDirection(FVector Direction, float Speed)
{
// 确保方向是单位向量
Direction.Normalize();
// 计算移动增量
FVector DeltaMove = Direction * Speed * GetWorld()->DeltaTimeSeconds;
// 应用移动
GetOwner()->AddActorWorldOffset(DeltaMove, true);
}
蓝图向量运算:
蓝图系统提供了全面的向量操作节点:
在蓝图中创建角色朝向目标的功能:
- 获取角色当前位置和目标位置
- 使用"Subtract"节点计算从角色到目标的向量
- 使用"Normalize"节点获取单位方向向量
- 使用"Make Rotation from X"节点创建朝向该方向的旋转
- 使用"Set Actor Rotation"设置角色朝向
7.1.3 矩阵和四元数
矩阵和四元数在3D游戏中用于表示和操作旋转、缩放和位置变换:
Lua中模拟简单矩阵操作:
lua
-- 简单的4x4矩阵实现(仅示例)
local matrix = {}
function matrix.new()
-- 创建单位矩阵
return {
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1}
}
end
-- 矩阵乘法(简化版,仅示意)
function matrix.multiply(a, b)
local result = matrix.new()
for i = 1, 4 do
for j = 1, 4 do
result[i][j] = 0
for k = 1, 4 do
result[i][j] = result[i][j] + a[i][k] * b[k][j]
end
end
end
return result
end
-- 创建平移矩阵
function matrix.translation(x, y, z)
local m = matrix.new()
m[4][1] = x
m[4][2] = y
m[4][3] = z
return m
end
-- 简单的四元数实现(仅示例)
local quaternion = {}
function quaternion.new(x, y, z, w)
return {x = x or 0, y = y or 0, z = z or 0, w = w or 1}
end
-- 从欧拉角创建四元数(简化版)
function quaternion.fromEuler(pitch, yaw, roll)
-- 实际实现需要复杂的数学计算
-- 这里仅为示意
return quaternion.new(pitch/10, yaw/10, roll/10, 1)
end
UE5.2中的矩阵和四元数:
UE5.2提供了高效的矩阵和四元数类:
C++矩阵和四元数:
cpp
// 矩阵操作
// 创建变换矩阵
FTransform Transform;
Transform.SetLocation(FVector(100.0f, 200.0f, 300.0f));
Transform.SetRotation(FQuat(FRotator(0.0f, 90.0f, 0.0f)));
Transform.SetScale3D(FVector(2.0f, 2.0f, 2.0f));
// 获取矩阵
FMatrix Matrix = Transform.ToMatrixWithScale();
// 矩阵乘法
FMatrix Matrix1 = FRotationMatrix::Make(FRotator(0.0f, 90.0f, 0.0f));
FMatrix Matrix2 = FTranslationMatrix(FVector(100.0f, 0.0f, 0.0f));
FMatrix Combined = Matrix1 * Matrix2; // 先旋转后平移
// 从矩阵中提取变换信息
FVector Translation;
FQuat Rotation;
FVector Scale;
Combined.DecomposeNoScale(Translation, Rotation);
Translation = Combined.GetOrigin();
Scale = Combined.GetScaleVector();
// 矩阵变换点
FVector Point(1.0f, 0.0f, 0.0f);
FVector TransformedPoint = Combined.TransformPosition(Point);
// 矩阵变换向量(不包括平移)
FVector Direction(1.0f, 0.0f, 0.0f);
FVector TransformedDirection = Combined.TransformVector(Direction);
// 四元数操作
// 创建四元数
FQuat Quaternion1 = FQuat::Identity; // 单位四元数
FQuat Quaternion2 = FQuat(FRotator(0.0f, 90.0f, 0.0f)); // 从旋转器创建
// 四元数乘法(组合旋转)
FQuat CombinedQuat = Quaternion1 * Quaternion2;
// 四元数旋转向量
FVector RotatedVector = Quaternion2.RotateVector(FVector(1.0f, 0.0f, 0.0f));
// 四元数插值(平滑旋转)
FQuat InterpolatedQuat = FQuat::Slerp(Quaternion1, Quaternion2, 0.5f);
// 从四元数获取旋转角度(弧度)
float Angle;
FVector Axis;
Quaternion2.ToAxisAndAngle(Axis, Angle);
// 四元数逆
FQuat InverseQuat = Quaternion2.Inverse();
// 在游戏中应用旋转
void ASpaceshipController::RotateShip(FRotator TargetRotation, float InterpSpeed)
{
FQuat CurrentQuat = GetActorQuat();
FQuat TargetQuat = TargetRotation.Quaternion();
// 平滑插值到目标旋转
FQuat NewQuat = FQuat::Slerp(CurrentQuat, TargetQuat, InterpSpeed * GetWorld()->DeltaTimeSeconds);
// 应用旋转
SetActorRotation(NewQuat);
}
蓝图矩阵和四元数:
蓝图中也可以使用矩阵和四元数:
在蓝图中创建平滑旋转相机的功能:
- 获取当前相机旋转(四元数)
- 获取目标旋转(四元数)
- 使用"Quaternion Slerp"节点进行平滑插值
- 应用插值后的旋转到相机
7.1.4 应用:弹道轨迹计算
结合数学库功能创建实用的游戏功能:
Lua中计算抛物线轨迹:
lua
function calculateProjectilePath(startPos, velocity, gravity, timeStep, steps)
local path = {}
local pos = {x = startPos.x, y = startPos.y, z = startPos.z}
local vel = {x = velocity.x, y = velocity.y, z = velocity.z}
for i = 1, steps do
-- 更新位置
pos.x = pos.x + vel.x * timeStep
pos.y = pos.y + vel.y * timeStep
pos.z = pos.z + vel.z * timeStep
-- 更新速度(仅受重力影响)
vel.z = vel.z + gravity * timeStep
-- 记录路径点
table.insert(path, {x = pos.x, y = pos.y, z = pos.z})
end
return path
end
-- 计算投射物落点
function calculateLandingPoint(startPos, velocity, gravity)
-- 解二次方程:z = startPos.z + velocity.z * t + 0.5 * gravity * t^2 = 0
-- 假设地面高度为0
local a = 0.5 * gravity
local b = velocity.z
local c = startPos.z
-- 计算正时间根
local discriminant = b * b - 4 * a * c
if discriminant < 0 then
return nil -- 无解,不会落地
end
local t = (-b + math.sqrt(discriminant)) / (2 * a)
if t < 0 then
return nil -- 负时间,不合理
end
-- 计算落点
return {
x = startPos.x + velocity.x * t,
y = startPos.y + velocity.y * t,
z = 0
}
end
UE5.2中实现弹道轨迹:
UE5.2提供了多种方式实现弹道计算:
C++弹道轨迹计算:
cpp
// 计算抛物线轨迹点
TArray<FVector> UProjectileHelper::CalculateProjectilePath(
const FVector& StartPosition,
const FVector& InitialVelocity,
float Gravity,
float TimeStep,
int32 Steps)
{
TArray<FVector> Path;
Path.Reserve(Steps);
FVector Position = StartPosition;
FVector Velocity = InitialVelocity;
for (int32 i = 0; i < Steps; i++)
{
// 添加当前位置到路径
Path.Add(Position);
// 更新位置
Position += Velocity * TimeStep;
// 更新速度(仅受重力影响)
Velocity.Z += Gravity * TimeStep;
}
return Pat