现代游戏渲染管线技术
1. 渲染管线概述
现代游戏引擎通常采用延迟渲染(Deferred Rendering)或前向渲染(Forward Rendering)管线。延迟渲染将几何处理和光照计算分离,适合处理大量光源的场景。
2. PBR 材质系统
基于物理的渲染(Physically Based Rendering)使用真实世界的物理规律来模拟光照,提供更加真实的视觉效果。
// GLSL 顶点着色器示例
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 aTangent;
out VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
mat3 TBN;
} vs_out;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
vs_out.TexCoords = aTexCoords;
// 计算 TBN 矩阵用于法线贴图
vec3 T = normalize(vec3(model * vec4(aTangent, 0.0)));
vec3 N = normalize(vec3(model * vec4(aNormal, 0.0)));
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
vs_out.TBN = mat3(T, B, N);
vs_out.Normal = N;
gl_Position = projection * view * vec4(vs_out.FragPos, 1.0);
}
// GLSL PBR 片段着色器
#version 330 core
out vec4 FragColor;
in VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
mat3 TBN;
} fs_in;
// 材质参数
uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D metallicMap;
uniform sampler2D roughnessMap;
uniform sampler2D aoMap;
uniform vec3 camPos;
uniform vec3 lightPositions[4];
uniform vec3 lightColors[4];
const float PI = 3.14159265359;
// 法线分布函数 (Trowbridge-Reitz GGX)
float DistributionGGX(vec3 N, vec3 H, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
// 几何函数 (Schlick-GGX)
float GeometrySchlickGGX(float NdotV, float roughness) {
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// 菲涅尔方程 (Fresnel-Schlick)
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
void main() {
// 采样材质贴图
vec3 albedo = pow(texture(albedoMap, fs_in.TexCoords).rgb, vec3(2.2));
float metallic = texture(metallicMap, fs_in.TexCoords).r;
float roughness = texture(roughnessMap, fs_in.TexCoords).r;
float ao = texture(aoMap, fs_in.TexCoords).r;
// 从法线贴图获取法线
vec3 normal = texture(normalMap, fs_in.TexCoords).rgb;
normal = normalize(normal * 2.0 - 1.0);
vec3 N = normalize(fs_in.TBN * normal);
vec3 V = normalize(camPos - fs_in.FragPos);
// 计算基础反射率
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
// 反射方程
vec3 Lo = vec3(0.0);
for(int i = 0; i < 4; ++i) {
vec3 L = normalize(lightPositions[i] - fs_in.FragPos);
vec3 H = normalize(V + L);
float distance = length(lightPositions[i] - fs_in.FragPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lightColors[i] * attenuation;
// Cook-Torrance BRDF
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
vec3 specular = numerator / denominator;
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
float NdotL = max(dot(N, L), 0.0);
Lo += (kD * albedo / PI + specular) * radiance * NdotL;
}
// 环境光
vec3 ambient = vec3(0.03) * albedo * ao;
vec3 color = ambient + Lo;
// HDR 色调映射
color = color / (color + vec3(1.0));
// Gamma 校正
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);
}
3. 延迟渲染实现
延迟渲染将渲染过程分为几何通道和光照通道两个阶段。
// C++ 延迟渲染管线设置
class DeferredRenderer {
private:
GLuint gBuffer;
GLuint gPosition, gNormal, gAlbedoSpec;
GLuint rboDepth;
int screenWidth, screenHeight;
public:
void setupGBuffer(int width, int height) {
screenWidth = width;
screenHeight = height;
glGenFramebuffers(1, &gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
// 位置颜色缓冲
glGenTextures(1, &gPosition);
glBindTexture(GL_TEXTURE_2D, gPosition);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height,
0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, gPosition, 0);
// 法线颜色缓冲
glGenTextures(1, &gNormal);
glBindTexture(GL_TEXTURE_2D, gNormal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height,
0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
GL_TEXTURE_2D, gNormal, 0);
// 颜色 + 镜面反射缓冲
glGenTextures(1, &gAlbedoSpec);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2,
GL_TEXTURE_2D, gAlbedoSpec, 0);
// 告诉OpenGL我们将使用哪些颜色附件进行渲染
GLuint attachments[3] = {
GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT1,
GL_COLOR_ATTACHMENT2
};
glDrawBuffers(3, attachments);
// 创建深度缓冲
glGenRenderbuffers(1, &rboDepth);
glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT,
width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, rboDepth);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "GBuffer 不完整!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void geometryPass(const std::vector<Model>& models,
const Camera& camera) {
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 渲染场景几何体
for (const auto& model : models) {
model.render(camera);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void lightingPass(const std::vector<Light>& lights,
const Camera& camera) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 绑定 GBuffer 纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gPosition);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
// 使用光照着色器并渲染全屏四边形
renderQuad();
}
};
4. 阴影映射
实现动态阴影的常用技术是阴影贴图(Shadow Mapping)。
// 阴影贴图生成
class ShadowMapper {
private:
GLuint depthMapFBO;
GLuint depthMap;
const unsigned int SHADOW_WIDTH = 2048;
const unsigned int SHADOW_HEIGHT = 2048;
public:
void setupShadowMap() {
glGenFramebuffers(1, &depthMapFBO);
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
SHADOW_WIDTH, SHADOW_HEIGHT, 0,
GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void renderShadowMap(const Light& light,
const std::vector<Model>& models) {
// 计算光源空间的投影矩阵和视图矩阵
glm::mat4 lightProjection = glm::ortho(-10.0f, 10.0f,
-10.0f, 10.0f,
1.0f, 75.0f);
glm::mat4 lightView = glm::lookAt(light.position,
glm::vec3(0.0f),
glm::vec3(0.0, 1.0, 0.0));
glm::mat4 lightSpaceMatrix = lightProjection * lightView;
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
// 从光源视角渲染场景
for (const auto& model : models) {
model.renderDepth(lightSpaceMatrix);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
};
物理引擎集成
1. 刚体动力学
使用物理引擎(如 Bullet、PhysX)模拟真实的物理行为。
// Bullet Physics 集成示例
#include <btBulletDynamicsCommon.h>
class PhysicsWorld {
private:
btDefaultCollisionConfiguration* collisionConfiguration;
btCollisionDispatcher* dispatcher;
btBroadphaseInterface* overlappingPairCache;
btSequentialImpulseConstraintSolver* solver;
btDiscreteDynamicsWorld* dynamicsWorld;
std::vector<btRigidBody*> rigidBodies;
public:
PhysicsWorld() {
// 设置碰撞检测
collisionConfiguration = new btDefaultCollisionConfiguration();
dispatcher = new btCollisionDispatcher(collisionConfiguration);
// 宽相位检测
overlappingPairCache = new btDbvtBroadphase();
// 约束求解器
solver = new btSequentialImpulseConstraintSolver;
// 创建物理世界
dynamicsWorld = new btDiscreteDynamicsWorld(
dispatcher, overlappingPairCache, solver, collisionConfiguration
);
// 设置重力
dynamicsWorld->setGravity(btVector3(0, -9.81, 0));
}
btRigidBody* createRigidBody(float mass,
const btTransform& startTransform,
btCollisionShape* shape) {
btVector3 localInertia(0, 0, 0);
if (mass != 0.0f) {
shape->calculateLocalInertia(mass, localInertia);
}
btDefaultMotionState* motionState =
new btDefaultMotionState(startTransform);
btRigidBody::btRigidBodyConstructionInfo rbInfo(
mass, motionState, shape, localInertia
);
btRigidBody* body = new btRigidBody(rbInfo);
dynamicsWorld->addRigidBody(body);
rigidBodies.push_back(body);
return body;
}
void createBox(const glm::vec3& position,
const glm::vec3& size,
float mass) {
btCollisionShape* boxShape = new btBoxShape(
btVector3(size.x/2, size.y/2, size.z/2)
);
btTransform transform;
transform.setIdentity();
transform.setOrigin(btVector3(position.x, position.y, position.z));
createRigidBody(mass, transform, boxShape);
}
void createSphere(const glm::vec3& position,
float radius,
float mass) {
btCollisionShape* sphereShape = new btSphereShape(radius);
btTransform transform;
transform.setIdentity();
transform.setOrigin(btVector3(position.x, position.y, position.z));
createRigidBody(mass, transform, sphereShape);
}
void stepSimulation(float deltaTime) {
dynamicsWorld->stepSimulation(deltaTime, 10);
}
glm::mat4 getRigidBodyTransform(btRigidBody* body) {
btTransform trans;
body->getMotionState()->getWorldTransform(trans);
glm::mat4 matrix;
trans.getOpenGLMatrix(glm::value_ptr(matrix));
return matrix;
}
~PhysicsWorld() {
// 清理资源
for (auto body : rigidBodies) {
dynamicsWorld->removeRigidBody(body);
delete body->getMotionState();
delete body;
}
delete dynamicsWorld;
delete solver;
delete overlappingPairCache;
delete dispatcher;
delete collisionConfiguration;
}
};
2. 碰撞检测
实现高效的碰撞检测系统是游戏物理的关键。
// 简单的AABB碰撞检测
struct AABB {
glm::vec3 min;
glm::vec3 max;
bool intersects(const AABB& other) const {
return (min.x <= other.max.x && max.x >= other.min.x) &&
(min.y <= other.max.y && max.y >= other.min.y) &&
(min.z <= other.max.z && max.z >= other.min.z);
}
glm::vec3 getCenter() const {
return (min + max) * 0.5f;
}
glm::vec3 getExtents() const {
return (max - min) * 0.5f;
}
};
// 射线与AABB相交测试
struct Ray {
glm::vec3 origin;
glm::vec3 direction;
bool intersectsAABB(const AABB& aabb, float& tMin, float& tMax) const {
glm::vec3 invDir = 1.0f / direction;
glm::vec3 t0s = (aabb.min - origin) * invDir;
glm::vec3 t1s = (aabb.max - origin) * invDir;
glm::vec3 tsmaller = glm::min(t0s, t1s);
glm::vec3 tbigger = glm::max(t0s, t1s);
tMin = glm::max(tsmaller.x, glm::max(tsmaller.y, tsmaller.z));
tMax = glm::min(tbigger.x, glm::min(tbigger.y, tbigger.z));
return tMin <= tMax && tMax >= 0;
}
};
// 球体碰撞检测
struct Sphere {
glm::vec3 center;
float radius;
bool intersects(const Sphere& other) const {
float distSq = glm::length2(center - other.center);
float radiusSum = radius + other.radius;
return distSq <= radiusSum * radiusSum;
}
};
游戏 AI 算法
1. A* 寻路算法
A*是游戏中最常用的寻路算法,结合了 Dijkstra 算法和贪心最佳优先搜索。
import heapq
from typing import List, Tuple, Set
class Node:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.g = float('inf') # 从起点到当前节点的实际代价
self.h = 0 # 从当前节点到终点的启发式估计
self.f = float('inf') # f = g + h
self.parent = None
def __lt__(self, other):
return self.f < other.f
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
class AStar:
def __init__(self, grid: List[List[int]]):
"""
grid: 2D网格,0表示可通行,1表示障碍物
"""
self.grid = grid
self.rows = len(grid)
self.cols = len(grid[0]) if grid else 0
def heuristic(self, node: Node, goal: Node) -> float:
"""曼哈顿距离启发式函数"""
return abs(node.x - goal.x) + abs(node.y - goal.y)
def get_neighbors(self, node: Node) -> List[Node]:
"""获取相邻可通行节点"""
neighbors = []
directions = [(0, 1), (1, 0), (0, -1), (-1, 0), # 四方向
(1, 1), (1, -1), (-1, 1), (-1, -1)] # 对角线
for dx, dy in directions:
x, y = node.x + dx, node.y + dy
# 检查边界和障碍物
if (0 <= x < self.rows and
0 <= y < self.cols and
self.grid[x][y] == 0):
# 对角线移动需要检查两侧是否可通行
if dx != 0 and dy != 0:
if (self.grid[node.x + dx][node.y] == 1 or
self.grid[node.x][node.y + dy] == 1):
continue
neighbors.append(Node(x, y))
return neighbors
def find_path(self, start: Tuple[int, int],
goal: Tuple[int, int]) -> List[Tuple[int, int]]:
"""
寻找从起点到终点的最短路径
返回路径坐标列表,如果无法到达则返回空列表
"""
start_node = Node(start[0], start[1])
goal_node = Node(goal[0], goal[1])
start_node.g = 0
start_node.h = self.heuristic(start_node, goal_node)
start_node.f = start_node.h
open_set = [start_node]
closed_set: Set[Node] = set()
# 用于快速查找节点
node_map = {(start_node.x, start_node.y): start_node}
while open_set:
current = heapq.heappop(open_set)
# 到达目标
if current == goal_node:
return self._reconstruct_path(current)
closed_set.add(current)
for neighbor in self.get_neighbors(current):
if neighbor in closed_set:
continue
# 计算从起点经过current到neighbor的代价
# 对角线移动代价为sqrt(2),直线移动代价为1
move_cost = 1.414 if (
abs(neighbor.x - current.x) +
abs(neighbor.y - current.y) == 2
) else 1.0
tentative_g = current.g + move_cost
# 获取或创建neighbor节点
pos = (neighbor.x, neighbor.y)
if pos not in node_map:
node_map[pos] = neighbor
else:
neighbor = node_map[pos]
if tentative_g < neighbor.g:
neighbor.parent = current
neighbor.g = tentative_g
neighbor.h = self.heuristic(neighbor, goal_node)
neighbor.f = neighbor.g + neighbor.h
if neighbor not in open_set:
heapq.heappush(open_set, neighbor)
return [] # 无法找到路径
def _reconstruct_path(self, node: Node) -> List[Tuple[int, int]]:
"""重建路径"""
path = []
current = node
while current:
path.append((current.x, current.y))
current = current.parent
return path[::-1]
# 使用示例
grid = [
[0, 0, 0, 0, 0],
[0, 1, 1, 0, 0],
[0, 0, 0, 0, 1],
[0, 1, 0, 0, 0],
[0, 0, 0, 1, 0]
]
pathfinder = AStar(grid)
path = pathfinder.find_path((0, 0), (4, 4))
print(f"路径: {path}")
2. 行为树
行为树是一种分层的AI决策系统,广泛应用于游戏AI。
from enum import Enum
from abc import ABC, abstractmethod
class NodeStatus(Enum):
SUCCESS = 1
FAILURE = 2
RUNNING = 3
class BehaviorNode(ABC):
"""行为树节点基类"""
@abstractmethod
def tick(self, context) -> NodeStatus:
pass
class SequenceNode(BehaviorNode):
"""序列节点:依次执行子节点,全部成功则成功"""
def __init__(self, children):
self.children = children
self.current_child = 0
def tick(self, context) -> NodeStatus:
while self.current_child < len(self.children):
status = self.children[self.current_child].tick(context)
if status == NodeStatus.FAILURE:
self.current_child = 0
return NodeStatus.FAILURE
elif status == NodeStatus.RUNNING:
return NodeStatus.RUNNING
self.current_child += 1
self.current_child = 0
return NodeStatus.SUCCESS
class SelectorNode(BehaviorNode):
"""选择节点:依次执行子节点,有一个成功则成功"""
def __init__(self, children):
self.children = children
self.current_child = 0
def tick(self, context) -> NodeStatus:
while self.current_child < len(self.children):
status = self.children[self.current_child].tick(context)
if status == NodeStatus.SUCCESS:
self.current_child = 0
return NodeStatus.SUCCESS
elif status == NodeStatus.RUNNING:
return NodeStatus.RUNNING
self.current_child += 1
self.current_child = 0
return NodeStatus.FAILURE
class ConditionNode(BehaviorNode):
"""条件节点"""
def __init__(self, condition_func):
self.condition_func = condition_func
def tick(self, context) -> NodeStatus:
return NodeStatus.SUCCESS if self.condition_func(context) else NodeStatus.FAILURE
class ActionNode(BehaviorNode):
"""动作节点"""
def __init__(self, action_func):
self.action_func = action_func
def tick(self, context) -> NodeStatus:
return self.action_func(context)
# 示例:敌人AI行为树
class EnemyAI:
def __init__(self):
self.health = 100
self.player_distance = 100
self.has_ammo = True
# 构建行为树
self.behavior_tree = SelectorNode([
SequenceNode([
ConditionNode(lambda ctx: ctx.health < 30),
ActionNode(self.flee)
]),
SequenceNode([
ConditionNode(lambda ctx: ctx.player_distance < 50),
ConditionNode(lambda ctx: ctx.has_ammo),
ActionNode(self.attack)
]),
ActionNode(self.patrol)
])
def flee(self, context) -> NodeStatus:
print("逃跑!")
return NodeStatus.SUCCESS
def attack(self, context) -> NodeStatus:
print("攻击玩家!")
return NodeStatus.SUCCESS
def patrol(self, context) -> NodeStatus:
print("巡逻中...")
return NodeStatus.SUCCESS
def update(self):
self.behavior_tree.tick(self)
3. 状态机
有限状态机(FSM)是另一种常用的AI决策系统。
from enum import Enum
from typing import Dict, Callable
class State(Enum):
IDLE = "idle"
PATROL = "patrol"
CHASE = "chase"
ATTACK = "attack"
FLEE = "flee"
class StateMachine:
def __init__(self):
self.current_state = State.IDLE
self.states: Dict[State, Dict] = {}
def add_state(self, state: State,
enter: Callable = None,
execute: Callable = None,
exit: Callable = None):
"""添加状态"""
self.states[state] = {
'enter': enter,
'execute': execute,
'exit': exit
}
def change_state(self, new_state: State, context):
"""切换状态"""
if self.current_state in self.states:
exit_func = self.states[self.current_state]['exit']
if exit_func:
exit_func(context)
self.current_state = new_state
if new_state in self.states:
enter_func = self.states[new_state]['enter']
if enter_func:
enter_func(context)
def update(self, context):
"""更新当前状态"""
if self.current_state in self.states:
execute_func = self.states[self.current_state]['execute']
if execute_func:
execute_func(context)
# 示例:敌人状态机
class Enemy:
def __init__(self):
self.health = 100
self.player_distance = 100
self.fsm = StateMachine()
# 配置状态
self.fsm.add_state(State.IDLE,
enter=self.enter_idle,
execute=self.update_idle)
self.fsm.add_state(State.PATROL,
enter=self.enter_patrol,
execute=self.update_patrol)
self.fsm.add_state(State.CHASE,
execute=self.update_chase)
self.fsm.add_state(State.ATTACK,
execute=self.update_attack)
def enter_idle(self, context):
print("进入待机状态")
def update_idle(self, context):
if self.player_distance < 100:
self.fsm.change_state(State.CHASE, self)
def enter_patrol(self, context):
print("开始巡逻")
def update_patrol(self, context):
if self.player_distance < 80:
self.fsm.change_state(State.CHASE, self)
def update_chase(self, context):
print("追逐玩家")
if self.player_distance < 20:
self.fsm.change_state(State.ATTACK, self)
elif self.player_distance > 150:
self.fsm.change_state(State.PATROL, self)
def update_attack(self, context):
print("攻击!")
if self.player_distance > 30:
self.fsm.change_state(State.CHASE, self)
def update(self):
self.fsm.update(self)
网络同步技术
1. 客户端预测与服务器和解
在多人游戏中,为了降低延迟对玩家体验的影响,通常采用客户端预测技术。
// 简化的网络同步系统
class NetworkedEntity {
private:
glm::vec3 position;
glm::vec3 velocity;
glm::vec3 serverPosition;
float serverTimestamp;
float clientTimestamp;
std::queue<PlayerInput> inputBuffer;
const float reconciliationThreshold = 0.1f;
public:
void applyInput(const PlayerInput& input, float deltaTime) {
// 客户端预测:立即应用输入
velocity = input.direction * input.speed;
position += velocity * deltaTime;
// 保存输入历史用于服务器和解
inputBuffer.push(input);
clientTimestamp += deltaTime;
}
void receiveServerUpdate(const ServerUpdate& update) {
serverPosition = update.position;
serverTimestamp = update.timestamp;
// 服务器和解
float positionError = glm::length(position - serverPosition);
if (positionError > reconciliationThreshold) {
// 差异太大,回滚并重新应用输入
position = serverPosition;
// 重新应用服务器确认之后的输入
std::queue<PlayerInput> tempInputs = inputBuffer;
while (!tempInputs.empty()) {
const auto& input = tempInputs.front();
if (input.timestamp > serverTimestamp) {
applyInput(input, input.deltaTime);
}
tempInputs.pop();
}
}
// 清理已确认的输入
while (!inputBuffer.empty() &&
inputBuffer.front().timestamp <= serverTimestamp) {
inputBuffer.pop();
}
}
// 插值显示其他玩家
void interpolate(const std::vector<ServerSnapshot>& snapshots,
float currentTime) {
// 找到两个最近的快照
int from = -1, to = -1;
for (size_t i = 0; i < snapshots.size() - 1; i++) {
if (snapshots[i].timestamp <= currentTime &&
snapshots[i+1].timestamp >= currentTime) {
from = i;
to = i + 1;
break;
}
}
if (from >= 0 && to >= 0) {
float totalTime = snapshots[to].timestamp - snapshots[from].timestamp;
float elapsed = currentTime - snapshots[from].timestamp;
float t = elapsed / totalTime;
// 线性插值
position = glm::mix(snapshots[from].position,
snapshots[to].position, t);
}
}
};
2. 延迟补偿
服务器需要考虑网络延迟,在玩家看到的位置进行碰撞检测。
class LagCompensation {
private:
struct HistoryFrame {
float timestamp;
std::map<int, glm::vec3> playerPositions;
};
std::deque<HistoryFrame> history;
const float maxHistoryTime = 1.0f; // 保存1秒历史
public:
void recordFrame(float timestamp,
const std::map<int, Player*>& players) {
HistoryFrame frame;
frame.timestamp = timestamp;
for (const auto& [id, player] : players) {
frame.playerPositions[id] = player->getPosition();
}
history.push_back(frame);
// 清理旧历史
while (!history.empty() &&
timestamp - history.front().timestamp > maxHistoryTime) {
history.pop_front();
}
}
bool checkHit(int shooterId,
float shooterPing,
const Ray& ray,
int& hitPlayerId) {
// 回溯到射击者看到的游戏状态
float compensatedTime = getCurrentTime() - shooterPing / 2000.0f;
// 找到对应时间的历史帧
HistoryFrame* frame = nullptr;
for (auto& f : history) {
if (abs(f.timestamp - compensatedTime) < 0.05f) {
frame = &f;
break;
}
}
if (!frame) return false;
// 在历史位置进行碰撞检测
for (const auto& [id, pos] : frame->playerPositions) {
if (id == shooterId) continue;
if (rayIntersectsSphere(ray, pos, 0.5f)) {
hitPlayerId = id;
return true;
}
}
return false;
}
};