游戏开发技术笔记

现代游戏渲染管线技术

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;
    }
};