# 游戏数学
> **游戏开发中 90% 的"感觉不对"问题,根源是数学没调好。** 随机太假、移动太僵、碰撞不准、概率失衡——都是数学。
---
## 目录
1. [随机数与概率](#随机数与概率)
2. [向量与几何](#向量与几何)
3. [插值与缓动](#插值与缓动)
4. [空间算法](#空间算法)
5. [噪声函数](#噪声函数)
6. [游戏平衡数学](#游戏平衡数学)
7. [常用数学模式](#常用数学模式)
---
## 随机数与概率
### 伪随机数生成器(PRNG)
| 算法 | 周期 | 速度 | 质量 | 适用场景 |
|------|------|------|------|----------|
| **System.Random** | 2^31 | 快 | 低 | 非关键随机(如粒子偏移) |
| **xorshift** | 2^128 | 极快 | 中 | 高频调用(如每帧噪声) |
| **PCG** | 2^64 | 快 | 高 | 通用游戏随机,推荐 |
| **Mersenne Twister** | 2^19937 | 中 | 极高 | 需要统计级质量的场景 |
**种子(Seed)的重要性**:
```
使用确定性的种子可以让随机序列可复现:
- 程序化生成:相同种子 = 相同世界
- 调试:可以复现玩家报告的 bug
- 回放系统:记录种子而非所有随机结果
```
### 游戏常用概率分布
```
1. 均匀分布(Uniform)
└── random(min, max)
└── 适用:骰子、随机方向
2. 加权随机(Weighted Random)
└── 按权重概率选择
└── 适用:掉落表、稀有度
3. 正态分布(Normal/Gaussian)
└── 中心值附近概率高
└── 适用:属性生成、伤害浮动
4. 泊松分布(Poisson)
└── 单位时间内事件发生次数
└── 适用:敌人刷新、事件触发
5. 幂律分布(Power Law)
└── 少数高值,多数低值
└── 适用:经济系统、玩家财富分布
```
### 加权随机实现
```python
# 加权随机选择
def weighted_random(items, weights):
total = sum(weights)
r = random() * total
cumulative = 0
for item, weight in zip(items, weights):
cumulative += weight
if r <= cumulative:
return item
# 掉落表示例
loot_table = {
'common_sword': 50, # 50% 权重
'rare_axe': 30, # 30% 权重
'epic_staff': 15, # 15% 权重
'legendary_ring': 5, # 5% 权重
}
# 验证:1000次抽取结果应接近理论分布
```
### 感知随机性
| 问题 | 现象 | 解决方案 |
|------|------|----------|
| **伪随机感** | 连续出同样结果 | 洗牌算法(Deck Shuffling) |
| **连败挫败** | 10次未命中 | 保底机制(Pity System) |
| **伪随机分布** | 结果太均匀 | 真正随机反而感觉不随机 |
| **预测性** | 玩家发现规律 | 多源随机混合 |
**保底机制(Pity System)**:
```python
# 抽卡保底实现
def draw_with_pity(pity_counter, base_rate=0.01, pity_threshold=90):
"""
基础概率 1%,90抽保底
随着抽数增加,概率线性提升
"""
if pity_counter >= pity_threshold:
return True, 0 # 强制命中,重置计数
# 动态概率:越接近保底,概率越高
dynamic_rate = base_rate + (pity_counter / pity_threshold) * 0.5
if random() < dynamic_rate:
return True, 0 # 命中,重置
else:
return False, pity_counter + 1 # 未命中,计数+1
```
---
## 向量与几何
### 游戏开发核心向量运算
| 运算 | 公式 | 用途 |
|------|------|------|
| **点积** | a·b = \|a\|\|b\|cosθ | 判断方向(面向/背向)、投影 |
| **叉积** | a×b = \|a\|\|b\|sinθ n | 计算法线、判断左右、面积 |
| **长度** | \|v\| = √(x²+y²+z²) | 距离计算 |
| **归一化** | v̂ = v / \|v\| | 方向向量 |
| **线性插值** | lerp(a,b,t) = a + (b-a)*t | 平滑过渡 |
| **反射** | r = d - 2(d·n)n | 弹道反弹、镜面反射 |
### 常用几何判断
```python
# 点是否在圆内
def point_in_circle(px, py, cx, cy, r):
return (px-cx)**2 + (py-cy)**2 <= r**2
# 点是否在矩形内
def point_in_rect(px, py, rx, ry, rw, rh):
return rx <= px <= rx+rw and ry <= py <= ry+rh
# 点到线段的最近距离
def dist_point_to_segment(px, py, x1, y1, x2, y2):
# 投影参数 t
dx, dy = x2-x1, y2-y1
if dx == 0 and dy == 0:
return sqrt((px-x1)**2 + (py-y1)**2)
t = max(0, min(1, ((px-x1)*dx + (py-y1)*dy) / (dx*dx + dy*dy)))
proj_x = x1 + t * dx
proj_y = y1 + t * dy
return sqrt((px-proj_x)**2 + (py-proj_y)**2)
# 两个圆是否相交
def circles_intersect(c1x, c1y, r1, c2x, c2y, r2):
dist_sq = (c1x-c2x)**2 + (c1y-c2y)**2
return dist_sq <= (r1+r2)**2
```
### 坐标系与变换
```
游戏常用坐标系:
2D 游戏:
├── 屏幕坐标(左上角原点,Y向下)
├── 世界坐标(自定义原点,Y向上/下)
└── 等距坐标(斜向网格)
3D 游戏:
├── 局部坐标(模型自身)
├── 世界坐标(游戏世界)
├── 观察坐标(相机视角)
├── 裁剪坐标(投影后)
└── 屏幕坐标(像素位置)
变换矩阵顺序(重要):
最终 = 投影 × 视图 × 模型
(从右向左应用)
```
---
## 插值与缓动
### 基本插值函数
| 函数 | 公式 | 特点 |
|------|------|------|
| **Linear** | t | 匀速,最基础 |
| **Ease-In** | t² | 慢开始,快结束 |
| **Ease-Out** | 1-(1-t)² | 快开始,慢结束 |
| **Ease-In-Out** | t<0.5 ? 2t² : 1-2(1-t)² | 慢-快-慢 |
| **Smoothstep** | 3t²-2t³ | 更平滑的 ease-in-out |
| **Smootherstep** | 6t⁵-15t⁴+10t³ | 极平滑 |
### 常用缓动曲线
```python
# Unity 风格缓动函数(简化版)
def ease_in_quad(t): return t * t
def ease_out_quad(t): return 1 - (1-t) * (1-t)
def ease_in_out_quad(t):
return 2*t*t if t < 0.5 else 1 - (-2*t+2)**2 / 2
def ease_in_cubic(t): return t * t * t
def ease_out_bounce(t):
# 弹跳效果
if t < 1/2.75:
return 7.5625*t*t
elif t < 2/2.75:
t -= 1.5/2.75
return 7.5625*t*t + 0.75
elif t < 2.5/2.75:
t -= 2.25/2.75
return 7.5625*t*t + 0.9375
else:
t -= 2.625/2.75
return 7.5625*t*t + 0.984375
def ease_out_elastic(t):
# 弹性效果
c4 = (2 * pi) / 3
if t == 0: return 0
if t == 1: return 1
return 2**(-10*t) * sin((t*10-0.75)*c4) + 1
```
### 弹簧/阻尼插值
```python
# 弹簧物理插值(比 ease 更自然)
def spring_lerp(current, target, velocity, stiffness=0.1, damping=0.8, dt=1/60):
"""
current: 当前值
target: 目标值
velocity: 当前速度(需要保存状态)
stiffness: 刚度(0-1,越大越紧)
damping: 阻尼(0-1,越小越弹)
"""
force = (target - current) * stiffness
velocity = (velocity + force) * damping
current = current + velocity * dt
return current, velocity
# 使用示例:相机跟随
# camera_pos, camera_vel = spring_lerp(camera_pos, player_pos, camera_vel)
```
---
## 空间算法
### 四叉树 / 八叉树
```
用途:加速空间查询(碰撞检测、邻居查找)
构建:
1. 根节点覆盖整个游戏世界
2. 每个节点最多包含 N 个对象
3. 超过 N 个时,分裂为 4(2D)或 8(3D)个子节点
4. 对象存储在完全包含它的最小节点中
查询:
- 查找某点/区域附近的对象:只需遍历相关节点
- 复杂度:O(log n) 平均,O(n) 最坏
适用:
- 大量动态对象的碰撞检测
- 大范围邻居查找
- 视锥剔除加速
```
### 网格空间哈希
```python
# 空间哈希(适合均匀分布的对象)
CELL_SIZE = 100
def hash_position(x, y):
return (int(x // CELL_SIZE), int(y // CELL_SIZE))
def get_nearby_objects(obj_x, obj_y, grid):
"""获取相邻网格中的所有对象"""
cell_x, cell_y = hash_position(obj_x, obj_y)
nearby = []
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
key = (cell_x + dx, cell_y + dy)
if key in grid:
nearby.extend(grid[key])
return nearby
# 比四叉树更简单,适合对象均匀分布的场景
```
### 视锥剔除(Frustum Culling)
```python
# 简化版视锥剔除(2D)
def rect_in_frustum(rect, frustum_rect):
"""判断矩形是否在视锥范围内"""
return not (
rect.right < frustum_rect.left or
rect.left > frustum_rect.right or
rect.bottom < frustum_rect.top or
rect.top > frustum_rect.bottom
)
# 3D 视锥:用6个平面定义视锥体
# 判断包围盒(AABB)与6个平面的关系
# 完全在外部 → 剔除
# 相交或完全内部 → 保留
```
---
## 噪声函数
### 常用噪声对比
| 噪声 | 特性 | 适用场景 | 代码复杂度 |
|------|------|----------|-----------|
| **Value Noise** | 块状,简单 | 低要求场景 | 低 |
| **Perlin Noise** | 平滑连续,有方向性 | 地形、云、水 | 中 |
| **Simplex Noise** | Perlin改进,低维度高效 | 3D地形、动画 | 中 |
| **Worley Noise** | 细胞状,边缘清晰 | 石块、裂纹 | 中 |
| **FBM** | 多频段叠加 | 真实自然地形 | 中 |
### FBM(分形布朗运动)
```python
def fbm(x, y, octaves=4, persistence=0.5, lacunarity=2.0):
"""
分形布朗运动:叠加多层噪声
- octaves: 层数(细节层次)
- persistence: 每层振幅衰减
- lacunarity: 每层频率倍增
"""
total = 0
amplitude = 1
frequency = 1
max_value = 0
for i in range(octaves):
total += noise(x * frequency, y * frequency) * amplitude
max_value += amplitude
amplitude *= persistence
frequency *= lacunarity
return total / max_value # 归一化到 0-1
# 典型参数
terrain = fbm(x, y, octaves=6, persistence=0.5, lacunarity=2.0)
clouds = fbm(x, y, octaves=4, persistence=0.6, lacunarity=2.2)
```
---
## 游戏平衡数学
### 伤害公式对比
| 公式类型 | 公式 | 特点 | 适用 |
|----------|------|------|------|
| **线性** | 伤害 = 攻击 - 防御 | 简单直观 | 简单RPG |
| **乘法** | 伤害 = 攻击 × (1 - 减伤率) | 避免防御过高无敌 | MOBA |
| **除法** | 伤害 = 攻击² / (攻击 + 防御) | 防御收益递减 | RPG |
| **穿透** | 伤害 = max(攻击 - 防御, 攻击×0.1) | 保底伤害 | 动作游戏 |
### 经验曲线设计
```python
# 常见经验曲线公式
def exp_linear(level):
"""线性:每级所需经验相同"""
return level * 1000
def exp_exponential(level, base=1.5):
"""指数:后期升级极慢"""
return int(1000 * (base ** level))
def exp_polynomial(level, power=2.5):
"""多项式:平滑递增(推荐)"""
return int(1000 * (level ** power))
def exp_custom(level):
"""《魔兽世界》风格:每级经验 = 当前等级 × 基础值 × 系数"""
base = 100
if level <= 10:
return level * base
elif level <= 20:
return level * base * 1.2
else:
return int(level * base * 1.5 * (1.1 ** (level - 20)))
# 设计要点:
# - 前10级快速,建立成就感
# - 中期稳定,保持进度感
# - 后期缓慢,延长游戏寿命
```
### 经济系统基础公式
```
经济健康度检查:
1. 货币流通速度 = 总交易金额 / 货币总量
└── 健康范围:5-20(过低=囤积,过高=通胀)
2. 贫富差距系数(Gini)
└── 0 = 完全平等,1 = 一人拥有全部
└── 游戏建议:0.3-0.6(需要有贫富差距但不过度)
3. 物品稀缺度 = 需求 / 供给
└── > 1.5:价格上涨压力
└── < 0.5:价格下跌压力
```
---
## 常用数学模式
### 扇形/锥形检测
```python
def point_in_sector(px, py, cx, cy, direction, angle_width, radius):
"""判断点是否在扇形内(用于技能范围、视野检测)"""
dx, dy = px - cx, py - cy
dist_sq = dx*dx + dy*dy
if dist_sq > radius * radius:
return False
# 计算点相对于中心的角度
point_angle = atan2(dy, dx)
angle_diff = abs(normalize_angle(point_angle - direction))
return angle_diff <= angle_width / 2
def normalize_angle(angle):
"""将角度归一化到 [-pi, pi]"""
while angle > pi: angle -= 2*pi
while angle < -pi: angle += 2*pi
return angle
```
### 贝塞尔曲线
```python
def bezier_quadratic(p0, p1, p2, t):
"""二次贝塞尔曲线:p0(起点), p1(控制点), p2(终点)"""
return (1-t)**2 * p0 + 2*(1-t)*t * p1 + t**2 * p2
def bezier_cubic(p0, p1, p2, p3, t):
"""三次贝塞尔曲线:更平滑的曲线控制"""
return (1-t)**3 * p0 + 3*(1-t)**2*t * p1 + 3*(1-t)*t**2 * p2 + t**3 * p3
# 用途:
# - UI 动画路径
# - 子弹/弹道曲线
# - 相机移动轨迹
# - 粒子运动路径
```
### 距离衰减公式
```python
def linear_falloff(distance, max_dist):
"""线性衰减:声音、光照"""
return max(0, 1 - distance / max_dist)
def inverse_square_falloff(distance, max_dist):
"""平方反比衰减:真实物理(光照、引力)"""
if distance >= max_dist:
return 0
return 1 / (1 + distance * distance)
def exponential_falloff(distance, max_dist, exponent=2):
"""指数衰减:可控的衰减曲线"""
if distance >= max_dist:
return 0
return (1 - distance / max_dist) ** exponent
```
---
## 最佳实践
- [ ] **不要自己写随机数算法** — 使用经过验证的库(PCG、Mersenne Twister)
- [ ] **种子必须可配置** — 调试和可复现性
- [ ] **浮点数比较用 epsilon** — `abs(a-b) < 0.0001` 而非 `a == b`
- [ ] **预计算三角函数** — 高频调用时查表替代 `sin`/`cos`
- [ ] **缓动比线性好** — 几乎所有动画都应该有缓动
- [ ] **空间分区必须做** — 100+ 动态对象时碰撞检测从 O(n²) 降到 O(n log n)
- [ ] **伤害公式先纸上验证** — Excel 模拟 1000 次战斗再进代码
- [ ] **经验曲线画出来看** — 对数坐标检查是否平滑
---
## 相关页面
- [核心机制设计](core-mechanics.md) — 数学与机制的结合
- [程序化内容生成](procedural-content-generation.md) — 噪声与随机
- [游戏AI系统](game-ai-systems.md) — 向量运算与空间算法
- [物理系统](physics-systems.md) — 刚体动力学数学