极速狂飙 - 3D赛车游戏技术文档


极速狂飙 - 3D赛车游戏技术文档

基于 Three.js 构建的浏览器3D赛车游戏完整技术说明 版本: v1.1.0 | 更新日期: 2026-07-10


📋 项目概述

项目简介

《极速狂飙》是一款基于 Three.js 构建的 3D 赛车游戏,具备完整的物理引擎、赛道系统、圈速计时和车辆控制系统。支持 GLB 模型加载,实现了真实的轮胎滚动和转向效果。

技术栈

技术 版本 用途
Three.js 0.160.0 3D 渲染引擎
jQuery 3.7.1 DOM 操作与事件绑定
GLTFLoader - GLB/GLTF 模型加载
HTML5 Canvas - 小地图渲染
CSS3 - 界面样式与动画

项目结构

极速狂飙/
├── index.html              # 主游戏文件(包含所有逻辑)
├── models/                  # 3D 模型目录
│   ├── Toyota-RAV4.glb
│   ├── Ford-F150.glb
│   ├── FORD-Mustang2019.glb
│   └── FORD-Mustang2016.glb
├── js/
│   ├── three.module.js     # Three.js 核心库
│   └── jquery-3.7.1.min.js # jQuery 库
└── css/
    ├── google-fonts.css    # Google 字体样式
    └── font-awesome.min.css # Font Awesome 图标库

🏗️ 系统架构

核心模块关系图

┌─────────────────────────────────────────────────────────────────┐
│                       游戏主循环                                │
│                  requestAnimationFrame                         │
└─────────────────────────────────────────────────────────────────┘

          ┌─────────────────────┼─────────────────────┐
          ▼                     ▼                     ▼
┌─────────────────┐   ┌─────────────────┐   ┌─────────────────┐
│    物理引擎      │   │    渲染系统      │   │    HUD 系统     │
│                 │   │                 │   │                 │
│ • 速度计算      │   │ • 3D 场景渲染   │   │ • 速度表        │
│ • 转向控制      │   │ • 相机跟随      │   │ • 圈速显示      │
│ • 路面检测      │   │ • 阴影处理      │   │ • 小地图        │
│ • 轮胎控制      │   │ • 模型加载      │   │ • 路面指示      │
│ • 圈速检测      │   │ • 光照系统      │   │ • 档位显示      │
└─────────────────┘   └─────────────────┘   └─────────────────┘
          │                     │                     │
          └─────────────────────┼─────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                         数据模型                                │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 车辆状态: { pos, heading, speed }                      │   │
│  │ 赛道数据: { controlPoints, curve, segments }          │   │
│  │ 游戏状态: { lapCount, lapTime, bestLap, gameState }   │   │
│  │ 输入状态: { up, down, left, right, space }            │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

游戏状态机

┌─────────┐    点击"开始比赛"   ┌───────────┐    倒计时结束   ┌──────────┐
│  MENU   │ ─────────────────► │ COUNTDOWN │ ──────────────► │ PLAYING  │
│  菜单   │                     │   倒计时   │                  │  游戏中   │
└─────────┘                     └───────────┘                  └──────────┘
     ▲                                │                            │
     │                                │                            │
     │         点击"再来一局"          │    按 ESC 键               │
     └────────────────────────────────┼────────────────────────────┘

                               ┌──────────┐    点击"继续"    ┌──────────┐
                               │  PAUSED  │ ──────────────► │ PLAYING  │
                               │  暂停中   │                  │  游戏中   │
                               └──────────┘                  └──────────┘

                                    │ 完成 3 圈

                               ┌──────────┐
                               │ FINISHED │
                               │  已完成   │
                               └──────────┘

🎮 游戏设计

游戏规则

规则项 说明
比赛圈数 3 圈
计时方式 从出发开始计时
最佳单圈 自动记录最快圈速
路面影响 草地速度 = 赛道速度 × 90%
漂移模式 空格键激活,转向更灵敏

控制方案

按键 功能 说明
加速 持续按住加速
刹车/倒车 减速或倒车
左转 转向左侧
右转 转向右侧
Space 漂移模式 按住进入漂移
ESC 暂停/继续 切换暂停状态
鼠标拖拽 视角旋转 拖拽画布旋转视角
滚轮 缩放 滚动缩放视角

🏎️ 车辆系统

物理参数配置

const CFG = {
    // ========== 速度限制 ==========
    maxSpeed: 75,          // 赛道最大速度 (约 270 km/h)
    maxReverse: -18,       // 倒车最大速度 (约 -65 km/h)
    
    // ========== 加减速性能 ==========
    accel: 28,             // 加速度 (单位/秒²)
    brake: 45,             // 刹车减速度 (单位/秒²)
    
    // ========== 摩擦力 ==========
    friction: 0.985,       // 赛道摩擦力系数
    grassFriction: 0.97,   // 草地摩擦力系数
    driftFriction: 0.997,  // 漂移摩擦力系数
    
    // ========== 转向控制 ==========
    turnSpeed: 1.8,        // 基础转向速度 (弧度/秒)
    driftTurnMul: 1.6,     // 漂移转向倍率
    
    // ========== 比赛规则 ==========
    totalLaps: 3,          // 总圈数
};

物理更新循环

function updatePhysics(dt) {
    // 1. 加速/刹车
    if (keys.up) carState.speed += CFG.accel * dt;
    if (keys.down) {
        carState.speed > 0.5
            ? (carState.speed -= CFG.brake * dt)
            : (carState.speed -= CFG.accel * 0.4 * dt);
    }
    
    // 2. 摩擦力计算
    const isDrift = keys.space;
    const onTrack = getSurfaceInfo();
    
    if (isDrift) {
        carState.speed *= CFG.driftFriction;
    } else if (!onTrack) {
        carState.speed *= CFG.grassFriction;
    } else {
        carState.speed *= Math.pow(CFG.friction, dt * 60);
    }
    
    // 3. 速度限制
    const maxSpd = onTrack ? CFG.maxSpeed : CFG.maxSpeed * GRASS_SPEED_RATIO;
    carState.speed = Math.max(CFG.maxReverse, Math.min(maxSpd, carState.speed));
    if (Math.abs(carState.speed) < 0.08) carState.speed = 0;
    
    // 4. 转向计算
    const sf = Math.min(1, Math.abs(carState.speed) / 10);
    const tm = isDrift ? CFG.driftTurnMul : 1;
    const sign = carState.speed >= 0 ? 1 : -1;
    const ta = CFG.turnSpeed * tm * sf * dt * sign;
    
    if (keys.left) carState.heading += ta;
    if (keys.right) carState.heading -= ta;
    
    // 5. 位置更新
    carState.pos.x += Math.sin(carState.heading) * carState.speed * dt;
    carState.pos.z += Math.cos(carState.heading) * carState.speed * dt;
    
    // 6. 软边界约束
    const {t, distSq} = getClosestT(carState.pos);
    const maxD = CFG.trackWidth / 2 + 800;
    if (Math.sqrt(distSq) > maxD) {
        const cp = trackCurve.getPointAt(t);
        carState.pos.copy(cp).add(
            carState.pos.clone().sub(cp).normalize().multiplyScalar(maxD)
        );
        carState.speed *= 0.2;
    }
}

车辆模型系统

默认赛车(内置)

使用 Three.js 基础几何体构建:

部件 几何体 材质颜色
底盘 BoxGeometry #222222
车身 BoxGeometry #ff2200
车顶 BoxGeometry #dd1800
车窗 BoxGeometry #556677 (玻璃)
轮胎 CylinderGeometry #1a1a1a
轮毂 CylinderGeometry #aaaaaa
头灯 BoxGeometry #ffffee (自发光)
尾灯 BoxGeometry #ff0000 (自发光)

GLB 模型加载

async function loadGLBModel(path) {
    try {
        const gltf = await gltfLoader.loadAsync(path);
        const model = gltf.scene;
        
        // 自动缩放模型
        const box = new THREE.Box3().setFromObject(model);
        const size = box.getSize(new THREE.Vector3());
        const maxDim = Math.max(size.x, size.y, size.z);
        const scale = 10.0 / maxDim;
        model.scale.setScalar(scale);
        
        // 居中处理
        const center = box.getCenter(new THREE.Vector3());
        model.position.sub(center.multiplyScalar(scale));
        model.position.y = 0.45;
        
        return model;
    } catch (error) {
        console.error('GLB 加载失败:', error);
        return null;
    }
}

轮胎控制系统

轮胎识别策略

策略 方法 说明
名称匹配 查找包含 wheel/tire/rim 的节点 适用于标准命名模型
几何体识别 检测圆柱体/圆环体 Mesh 适用于复杂模型
位置聚类 K-means 聚类找 4 个角落 适用于任意模型
顶点筛选 取最小顶点数的 Mesh 备选方案

轮胎分组算法(K-means 聚类)

function findWheelGroups(meshes) {
    // 1. 收集所有 Mesh 的世界位置
    const positions = meshes.map(m => m.worldPos);
    
    // 2. K-means 聚类 (K=4)
    const clusters = [];
    const threshold = 1.5; // 距离阈值
    
    positions.forEach(pos => {
        let found = false;
        for (const cluster of clusters) {
            const dist = distance(pos, cluster.center);
            if (dist < threshold) {
                cluster.meshes.push(pos);
                updateCenter(cluster);
                found = true;
                break;
            }
        }
        if (!found) {
            clusters.push({
                center: { x: pos.x, z: pos.z },
                meshes: [pos]
            });
        }
    });
    
    // 3. 取最大的 4 个组作为轮胎
    clusters.sort((a, b) => b.meshes.length - a.meshes.length);
    return clusters.slice(0, 4);
}

轮胎旋转控制

function updateWheels(dt) {
    const speed = carState.speed;
    const isDrift = keys.space;
    
    // 1. 滚动速度 (所有轮胎)
    const rollMul = isDrift ? 4.0 : 2.5;
    const rollSpeed = speed * dt * rollMul;
    
    // 2. 前轮转向角度
    const speedFactor = Math.min(1, Math.abs(speed) / 30);
    const turnInput = (keys.left ? 1 : 0) - (keys.right ? 1 : 0);
    const maxAngle = isDrift ? 0.7 : 0.5;
    const turnAngle = turnInput * maxAngle * speedFactor;
    
    // 3. 更新所有轮胎滚动
    wheelData.all.forEach(wheel => {
        wheel.rotation.x += rollSpeed;
    });
    
    // 4. 更新前轮转向
    wheelData.front.forEach(wheel => {
        const base = wheel.userData?.baseRotation?.y || 0;
        wheel.rotation.y = base + turnAngle;
    });
}

🛤️ 赛道系统

赛道生成

// 控制点定义 (17 个点形成闭环)
const trackCPs = [
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(45, 0, -5),
    new THREE.Vector3(90, 0, -15),
    new THREE.Vector3(120, 0, -45),
    new THREE.Vector3(125, 0, -85),
    new THREE.Vector3(105, 0, -115),
    new THREE.Vector3(65, 0, -125),
    new THREE.Vector3(35, 0, -105),
    new THREE.Vector3(15, 0, -75),
    new THREE.Vector3(-15, 0, -55),
    new THREE.Vector3(-35, 0, -75),
    new THREE.Vector3(-55, 0, -105),
    new THREE.Vector3(-80, 0, -90),
    new THREE.Vector3(-90, 0, -55),
    new THREE.Vector3(-85, 0, -15),
    new THREE.Vector3(-60, 0, 2),
    new THREE.Vector3(-25, 0, 5),
];

// 创建 CatmullRom 曲线 (闭环)
const trackCurve = new THREE.CatmullRomCurve3(
    trackCPs,
    true,           // 闭环
    'catmullrom',   // 曲线类型
    0.5             // 张力参数
);

赛道组件

组件 实现方式 功能
路面 BufferGeometry + Canvas 纹理 主要赛道表面
路肩 红白相间网格 赛道边界标识
起跑线 棋盘格纹理 比赛起点标记
路灯 CylinderGeometry + SphereGeometry 赛道照明
树木 InstancedMesh (150 棵) 环境装饰

圈速检测系统

function checkLap(currentT) {
    // 象限检测法
    const quadrant = Math.floor(currentT * 4) % 4;
    quadrantFlags[quadrant] = true;
    
    // 检测是否完整通过所有象限
    if (lastTrackT > 0.85 && currentT < 0.15 && 
        quadrantFlags.every(f => f)) {
        lapCount++;
        quadrantFlags.fill(false);
        
        const now = performance.now();
        const lapTime = now - lapStartTime;
        lapStartTime = now;
        
        if (lapTime < bestLapTime) bestLapTime = lapTime;
        
        if (lapCount >= CFG.totalLaps) {
            finishRace();
        } else {
            showToast(`第 ${lapCount} 圈完成  ${formatTime(lapTime)}`);
        }
    }
    lastTrackT = currentT;
}

🎥 相机系统

视角预设

视角 俯仰角 (rad) 距离 特点
追踪 (Chase) 0.32 25 默认跟车视角
近跟 (Close) 0.22 10 近距离跟随
俯视 (Top) 1.45 50 鸟瞰视角
车头 (Hood) 1.50 2.2 第一人称

相机跟随算法

function updateCamera() {
    // 平滑插值
    camTheta += (camTargetTheta - camTheta) * CFG.cameraLerp;
    camPhi += (camTargetPhi - camPhi) * CFG.cameraLerp;
    camDist += (camTargetDist - camDist) * CFG.cameraLerp;
    
    // 计算偏移位置
    const offset = new THREE.Vector3(
        Math.sin(camTheta) * Math.cos(camPhi) * camDist,
        Math.sin(camPhi) * camDist,
        Math.cos(camTheta) * Math.cos(camPhi) * camDist
    );
    
    // 目标点:车辆位置上方
    const lookTarget = carGroup.position.clone().add(
        new THREE.Vector3(0, 1.2, 0)
    );
    
    // 应用位置
    camera.position.lerp(lookTarget.clone().add(offset), 0.09);
    camera.lookAt(lookTarget);
}

交互控制

交互方式 控制效果
鼠标拖拽 旋转视角 (Theta/Phi)
滚轮 缩放距离
触摸拖拽 旋转视角 (移动端)
按钮切换 快速切换预设视角

📊 HUD 系统

界面组件布局

┌──────────────────────────────────────────────────────────────┐
│ [视角按钮]     [暂停按钮]          [圈速信息]               │
│  追踪 近跟 俯视 车头     ⏸     圈数: 1/3                   │
│                                  当前: 00:12.345            │
│                                  最佳: 00:11.234            │
│                                  总时: 00:12.345            │
│                                                            │
│                                                            │
│      速度表                                               │
│      ┌─────────┐                                          │
│      │  128    │   [小地图]                               │
│      │ KM/H    │   ┌────────┐                            │
│      │ ██████░░ │   │  🏁    │                            │
│      │   6档   │   └────────┘                            │
│      └─────────┘                                          │
│   [路面指示: 🏁 赛道 100%]                                │
└──────────────────────────────────────────────────────────────┘

HUD 更新逻辑

function updateHUD() {
    // 速度更新
    const kmh = Math.abs(Math.round(carState.speed * 3.6));
    $('#speed-value').text(kmh);
    $('#speed-fill').css('width', Math.min(100, kmh / 270 * 100) + '%');
    
    // 档位计算
    let gear = 'N';
    if (carState.speed < -0.5) gear = 'R';
    else if (carState.speed > 0.5) {
        if (kmh < 40) gear = '1';
        else if (kmh < 80) gear = '2';
        else if (kmh < 130) gear = '3';
        else if (kmh < 180) gear = '4';
        else if (kmh < 230) gear = '5';
        else gear = '6';
    }
    $('#gear-display').text(gear);
    
    // 圈速更新
    const now = performance.now();
    if (gameState === 'playing') {
        $('#lap-time').text(formatTime(now - lapStartTime));
        $('#total-time').text(formatTime(now - raceStartTime));
    }
    if (bestLapTime < Infinity) {
        $('#best-time').text(formatTime(bestLapTime));
    }
    
    // 路面状态
    const si = $('#surface-indicator');
    if (getSurfaceInfo()) {
        si.removeClass('grass').addClass('track')
          .html('<i class="fas fa-road"></i> 赛道 100%');
    } else {
        si.removeClass('track').addClass('grass')
          .html(`<i class="fas fa-seedling"></i> 草地 90%速度`);
    }
}

小地图实现

function drawMinimap() {
    const w = 300, h = 300;
    mmCtx.clearRect(0, 0, w, h);
    
    // 1. 计算赛道边界并映射到画布
    // 2. 绘制赛道路径
    mmCtx.strokeStyle = '#666';
    mmCtx.lineWidth = 4;
    mmCtx.beginPath();
    for (let i = 0; i <= 200; i++) {
        const p = trackCurve.getPointAt(i / 200);
        const mx = (p.x - tCx) * mScale + w / 2;
        const my = (p.z - tCz) * mScale + h / 2;
        i === 0 ? mmCtx.moveTo(mx, my) : mmCtx.lineTo(mx, my);
    }
    mmCtx.closePath();
    mmCtx.stroke();
    
    // 3. 绘制车辆位置和朝向
    const cx = (carState.pos.x - tCx) * mScale + w / 2;
    const cy = (carState.pos.z - tCz) * mScale + h / 2;
    mmCtx.strokeStyle = '#ff4d00';
    mmCtx.lineWidth = 2;
    mmCtx.beginPath();
    mmCtx.moveTo(cx, cy);
    mmCtx.lineTo(cx + Math.sin(carState.heading) * 8, 
                  cy + Math.cos(carState.heading) * 8);
    mmCtx.stroke();
    
    // 4. 绘制车辆点
    mmCtx.fillStyle = '#ff4d00';
    mmCtx.beginPath();
    mmCtx.arc(cx, cy, 4, 0, Math.PI * 2);
    mmCtx.fill();
}

🔧 性能优化

渲染优化

优化技术 实现方式 性能收益
实例化渲染 InstancedMesh (树木) 减少 Draw Call
阴影优化 限制阴影贴图分辨率 2048×2048 提升帧率
帧率控制 Math.min(clock.getDelta(), 0.05) 防止卡顿
视锥体裁剪 Three.js 自动处理 减少不可见物体渲染

内存管理

function replaceCarModel(newModel) {
    // 清理旧模型资源
    while (carGroup.children.length) {
        const child = carGroup.children[0];
        // 释放几何体
        if (child.geometry) {
            child.geometry.dispose();
        }
        // 释放材质
        if (child.material) {
            if (Array.isArray(child.material)) {
                child.material.forEach(m => m.dispose());
            } else {
                child.material.dispose();
            }
        }
        carGroup.remove(child);
    }
    // 添加新模型
    carGroup.add(newModel);
}

性能监控

function showFPS() {
    let frames = 0;
    let lastTime = performance.now();
    
    setInterval(() => {
        const now = performance.now();
        const fps = frames / ((now - lastTime) / 1000);
        console.log(`FPS: ${fps.toFixed(1)}`);
        frames = 0;
        lastTime = now;
    }, 1000);
    
    // 在动画循环中: frames++;
}

🐛 调试工具

控制台调试命令

// 1. 查看模型结构
function debugModelStructure() {
    const model = carGroup.children[0];
    if (!model) {
        console.log('❌ 没有加载模型');
        return;
    }
    console.log('📦 模型:', model.name || '未命名');
    model.traverse((child) => {
        if (child.isMesh) {
            console.log('  🟦 Mesh:', child.name || '未命名', 
                       '位置:', child.position);
        }
    });
}

// 2. 检查轮胎识别
function debugWheels() {
    console.log('📊 轮胎数据:');
    console.log('  总数:', wheelData.all.length);
    console.log('  前轮:', wheelData.front.length);
    console.log('  后轮:', wheelData.rear.length);
    wheelData.all.forEach((w, i) => {
        console.log(`  轮胎${i+1}: `, w.position);
    });
}

// 3. 检查赛道位置
function debugTrackPosition() {
    const {t, distSq} = getClosestT(carState.pos);
    console.log(`📍 赛道位置: t=${t.toFixed(3)}, 距离=${Math.sqrt(distSq).toFixed(2)}m`);
}

// 4. 性能监控
function showFPS() {
    let frames = 0;
    let lastTime = performance.now();
    setInterval(() => {
        const now = performance.now();
        const fps = frames / ((now - lastTime) / 1000);
        console.log(`📊 FPS: ${fps.toFixed(1)}`);
        frames = 0;
        lastTime = now;
    }, 1000);
    // 在动画循环中: frames++;
}

📦 部署指南

环境要求

要求 版本
浏览器 Chrome 80+, Firefox 75+, Safari 13+
WebGL 2.0 支持
本地服务器 任意 HTTP 服务器 (用于加载 GLB 模型)

快速开始

# 使用 Python 启动本地服务器
python -m http.server 8000

# 使用 Node.js 的 serve
npx serve

# 使用 PHP 内置服务器
php -S localhost:8000

# 访问
http://localhost:8000

目录结构要求

project/
├── index.html          # 主游戏文件
├── models/             # GLB 模型必须在此目录
│   └── *.glb
├── js/
│   ├── three.module.js
│   └── jquery-3.7.1.min.js
└── css/
    ├── google-fonts.css
    └── font-awesome.min.css

常见问题

问题 解决方案
GLB 模型加载失败 确保 models/ 目录存在且有 .glb 文件
跨域错误 使用本地服务器,不要直接打开 HTML
黑屏 检查浏览器 WebGL 支持
性能问题 降低阴影分辨率或关闭阴影

🔮 未来扩展规划

短期计划 (v1.2)

  • 音效系统 (引擎声、轮胎摩擦声)
  • 粒子效果 (尾气、尘土)
  • 赛道障碍物
  • 更真实的车辆物理

中期计划 (v2.0)

  • AI 对手车辆
  • 多赛道选择
  • 车辆改装系统
  • 排行榜

长期计划 (v3.0)

  • 多人联机模式
  • 赛道编辑器
  • VR 支持
  • 移动端 App 封装

📚 技术参考

相关链接

依赖版本

Three.js: 0.160.0
jQuery: 3.7.1
Font Awesome: 4.7.0
Google Fonts: Orbitron, Rajdhani

📝 更新日志

v1.1.0 (2026-07-10)

新增功能:

  • ✅ 智能轮胎识别系统 (K-means 聚类)
  • ✅ 轮胎分组功能
  • ✅ 默认加载 GLB 模型
  • ✅ 完整的调试工具集
  • ✅ 性能监控工具

优化改进:

  • 🔧 优化轮胎识别算法
  • 🔧 改进模型加载流程
  • 🔧 完善错误处理

v1.0.0 (2026-07-09)

初始版本:

  • ✅ 完整赛道系统 (CatmullRom 曲线)
  • ✅ 物理引擎 (速度/转向/摩擦)
  • ✅ GLB 模型支持
  • ✅ 轮胎控制系统
  • ✅ 圈速计时 (3圈)
  • ✅ 多视角相机 (4种预设)
  • ✅ 小地图
  • ✅ HUD 界面
  • ✅ 鼠标/键盘/触摸控制


附注: 本文档随游戏代码同步更新,如有不一致之处,以代码为准。