找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1618

积分

0

好友

206

主题
发表于 前天 04:17 | 查看: 11| 回复: 0

Bevy游戏引擎官方介绍图

当你独自驾车行驶在无尽的公路上,两旁是悬崖和树林,脚下是油门...等等,别踩太猛,会掉下去的!

作为一名开发者,你是否曾想过亲手打造一款属于自己的游戏?今天,我们将一同踏上使用 Rust 语言和 Bevy 游戏引擎的旅程,从零开发一款名为“孤独的公路”(Lonely Highway)的 3D 无限驾驶游戏。这篇教程将为你详细拆解每一步,实现包括车辆控制、无限地图生成、物理碰撞等在内的完整游戏功能。

游戏概览:Lonely Highway

Lonely Highway 是一款风格简约的 3D 无限驾驶游戏,核心玩法包括:

  • 🚗 驾驶一辆红色跑车,在自动生成的公路上飞驰。
  • 🛣️ 小心躲避路上的障碍物,保持车辆在路面,防止跌落悬崖。
  • ⚡ 游戏速度会随时间逐渐提升,最高可达 100 km/h,挑战你的反应极限。
  • 🌊 驶过水坑时会触发溅起水花的粒子特效。
  • 🌲 公路两旁点缀着简单的树木,但请专注于驾驶,别撞上去。

为什么选择 Rust + Bevy 组合?

在开始敲代码之前,我们先谈谈技术选型。选择 Rust 搭配 Bevy 来开发游戏,是一个兼顾性能、安全性和开发体验的现代方案。

Rust 带来的优势

  • 内存安全:其独特的所有权系统在编译期保证了内存安全,无需垃圾回收器,从根本上避免了内存泄漏、数据竞争等常见问题。
  • 高性能:能够提供与 C/C++ 相媲美的运行时效率,对于追求流畅帧率的游戏至关重要。
  • 现代语法:模式匹配、强大的类型系统等特性让代码表达力更强,也更易于维护。

Bevy 引擎的魅力

Bevy 是一个完全使用 Rust 编写的开源游戏引擎,它的设计哲学是“简单且数据驱动”。

  • ECS 架构:采用 Entity-Component-System(实体-组件-系统)设计模式。这种模式强制进行代码解耦,使得游戏逻辑清晰,易于扩展和优化。
  • 热重载支持:支持资产(如纹理、场景)甚至部分代码的热重载,可以极大地提升开发迭代速度。
  • 真正的跨平台:可轻松编译并运行在 Windows、macOS、Linux 以及 Web(通过 WebAssembly)等多个平台。
  • 活跃的社区:项目迭代迅速,文档日益丰富,插件生态也在不断成长。

第一步:搭建开发环境与项目结构

创建新项目

首先,请确保你的系统已经安装了 Rust 工具链(可通过 rustup 安装)。然后,打开终端,创建一个新的 Rust 项目:

cargo new lonely-highway
cd lonely-highway

配置项目依赖

编辑项目根目录下的 Cargo.toml 文件,添加 Bevy 等依赖:

[package]
name = "lonely-highway"
version = "0.1.0"
edition = "2024"

[dependencies]
bevy = "0.18"
rand = "0.10.0"

规划项目模块

为了保持代码清晰,我们采用模块化设计。建议的项目结构如下:

lonely-highway/
├── Cargo.toml
└── src/
    ├── main.rs          # 应用入口,负责注册系统和插件
    ├── player.rs        # 玩家车辆生成与控制逻辑
    ├── road.rs          # 无限公路的生成与回收系统
    ├── camera.rs        # 摄像机跟随逻辑
    ├── environment.rs   # 光照、天空盒等环境设置
    ├── game.rs          # 游戏核心逻辑(计分、碰撞检测等)
    ├── ui.rs            # 用户界面(分数、速度显示)
    ├── components.rs    # 定义所有ECS组件(Component)
    └── resources.rs     # 定义所有游戏资源(Resource)

第二步:定义核心数据结构(组件与资源)

在 ECS 架构中,我们需要先定义构成游戏世界的“零件”。

resources.rs 中定义游戏状态

这里定义游戏运行时需要共享的配置和数据。

use bevy::prelude::*;
use bevy::render::mesh::Mesh;

#[derive(Resource)]
pub struct RoadConfig {
    pub segment_length: f32,  // 每一段公路的长度
    pub num_segments: usize,  // 同时存在于场景中的最大段数
    pub lane_width: f32,      // 单条车道的宽度
}

#[derive(Resource)]
pub struct GameStats {
    pub score: f32,
    pub speed: f32,
    pub level: u32,
    pub time_elapsed: f32,
    pub is_game_over: bool,
}

#[derive(Resource, Default)]
pub struct RoadState {
    pub last_segment_pos: Vec3,
    pub current_curve: f32,
}

#[derive(Resource)]
pub struct GameTextures {
    pub road: Handle<Image>,
    pub grass: Handle<Image>,
}

components.rs 中标记实体类型

组件用于标记和区分不同类型的实体。

use bevy::prelude::*;

#[derive(Component)]
pub struct PlayerCar;

#[derive(Component)]
pub struct RoadSegment;

#[derive(Component)]
pub struct Obstacle;

#[derive(Component)]
pub struct Collider {
    pub radius: f32,
}

#[derive(Component)]
pub struct Puddle;

#[derive(Component)]
pub struct CarWheel;

第三步:实现游戏核心——无限生成的公路

游戏的核心体验在于那条永远开不到头的公路。我们采用 “分段生成,动态回收” 的策略来实现。

核心思路

  • 公路由多个长度固定的“段”首尾相连而成(例如每段50米)。
  • 玩家车辆不断前进,当车辆前方即将没有路时,系统动态生成新的公路段。
  • 车辆后方已经驶过的、远离视野的公路段会被系统自动回收(销毁实体)。
  • 场景中始终只保持一定数量(如15段)的公路,从而模拟出无限延伸的效果。

初始公路生成(road.rs

在游戏开始时,生成初始的几段公路。

use bevy::prelude::*;
use crate::components::{Collider, Puddle, Obstacle};
use crate::resources::{RoadConfig, GameTextures, RoadState};
use crate::player::PlayerCar;

#[derive(Component)]
pub struct RoadSegment;

pub fn spawn_initial_road(
    mut commands: Commands,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
    road_config: Res<RoadConfig>,
    game_textures: Res<GameTextures>,
    mut road_state: ResMut<RoadState>,
) {
    road_state.last_segment_pos = Vec3::ZERO;
    road_state.current_curve = 0.0;

    for _ in 0..road_config.num_segments {
        spawn_next_segment(&mut commands, meshes, materials,
            &road_config, &game_textures, &mut road_state);
    }
}

fn spawn_next_segment(
    commands: &mut Commands,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
    config: &RoadConfig,
    textures: &GameTextures,
    state: &mut RoadState,
) {
    let segment_length = config.segment_length;

    // 计算位置,支持弯道
    let x_shift = state.current_curve * (segment_length / 2.0);
    let start_pos = state.last_segment_pos;
    let end_pos = start_pos + Vec3::new(x_shift, 0.0, -segment_length);
    let mid_pos = (start_pos + end_pos) / 2.0;

    // 生成公路段
    spawn_road_segment_at(commands, meshes, materials,
        config, textures, mid_pos);

    state.last_segment_pos = end_pos;
}

生成单个公路段的细节

每一段公路都不是简单的一块板,它包含地面、路面、装饰物等子实体。

pub fn spawn_road_segment_at(
    commands: &mut Commands,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
    config: &RoadConfig,
    textures: &GameTextures,
    position: Vec3,
) {
    let segment_length = config.segment_length;
    let ground_width = config.lane_width * 4.0; // 草地宽度

    commands.spawn((
        Mesh3d(meshes.add(Plane3d::default().mesh().size(ground_width, segment_length))),
        MeshMaterial3d(materials.add(StandardMaterial {
            base_color_texture: Some(textures.grass.clone()),
            ..default()
        })),
        Transform::from_translation(position),
        RoadSegment,
    )).with_children(|parent| {
        // 沥青路面
        parent.spawn((
            Mesh3d(meshes.add(Plane3d::default().mesh().size(config.lane_width * 3.0, segment_length))),
            MeshMaterial3d(materials.add(StandardMaterial {
                base_color_texture: Some(textures.road.clone()),
                ..default()
            })),
            Transform::from_xyz(0.0, 0.06, 0.0),
        ));

        // 车道线
        parent.spawn((
            Mesh3d(meshes.add(Plane3d::default().mesh().size(0.2, segment_length))),
            MeshMaterial3d(materials.add(StandardMaterial {
                base_color: Color::WHITE,
                unlit: true,
                ..default()
            })),
            Transform::from_xyz(0.0, 0.07, 0.0),
        ));

        // 随机生成障碍物(岩石、箱子)
        if rand::random::<f32>() < 0.2 {
            let lane = (rand::random::<i32>() % 3 - 1) as f32 * config.lane_width;
            let z = (rand::random::<f32>() - 0.5) * segment_length * 0.8;

            parent.spawn((
                Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
                MeshMaterial3d(materials.add(StandardMaterial {
                    base_color: Color::srgb(0.4, 0.4, 0.4),
                    ..default()
                })),
                Transform::from_xyz(lane, 1.0, z),
                Obstacle,
                Collider { radius: 1.0 },
            ));
        }
    });
}

动态更新系统:回收与生成

这个系统在每个游戏帧运行,负责维护公路的“无限”状态。

pub fn update_road(
    mut commands: Commands,
    road_config: Res<RoadConfig>,
    car_query: Query<&Transform, With<PlayerCar>>,
    road_query: Query<(Entity, &Transform), With<RoadSegment>>,
    mut road_state: ResMut<RoadState>,
    stats: Res<GameStats>,
) {
    if let Some(car_transform) = car_query.iter().next() {
        let car_z = car_transform.translation.z;

        // 回收后方的公路段
        for (entity, transform) in road_query.iter() {
            if transform.translation.z > car_z + road_config.segment_length * 2.0 {
                commands.entity(entity).despawn();
            }
        }

        // 生成前方新的公路段
        let view_distance = road_config.segment_length * (road_config.num_segments as f32);
        if road_state.last_segment_pos.z > car_z - view_distance {
            spawn_next_segment(&mut commands, &mut meshes, &mut materials,
                &road_config, &game_textures, &mut road_state);
        }
    }

    // Level 2 后开始出现弯道,增加难度
    if stats.level >= 2 {
        road_state.current_curve = (stats.time_elapsed * 0.5).sin() * 0.5;
    }
}

第四步:创建玩家车辆与控制系统

生成车辆模型(player.rs

我们用简单的几何体(立方体、圆柱体)拼装出一辆红色跑车。

use bevy::prelude::*;
use crate::components::Collider;
use crate::resources::GameStats;
use crate::road::RoadSegment;

#[derive(Component)]
pub struct PlayerCar;

pub fn spawn_player(
    commands: &mut Commands,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
) {
    commands.spawn((
        Transform::from_xyz(0.0, 0.0, 0.0),
        Visibility::default(),
        PlayerCar,
        Collider { radius: 1.5 },
    )).with_children(|parent| {
        // 车身
        parent.spawn((
            Mesh3d(meshes.add(Cuboid::new(2.4, 0.5, 4.8))),
            MeshMaterial3d(materials.add(StandardMaterial {
                base_color: Color::srgb(0.9, 0.2, 0.2), // 红色
                metallic: 0.8,
                perceptual_roughness: 0.2,
                ..default()
            })),
            Transform::from_xyz(0.0, 0.6, 0.0),
        ));

        // 车顶
        parent.spawn((
            Mesh3d(meshes.add(Cuboid::new(1.6, 0.5, 2.5))),
            MeshMaterial3d(materials.add(StandardMaterial {
                base_color: Color::srgb(0.05, 0.05, 0.05),
                metallic: 0.9,
                ..default()
            })),
            Transform::from_xyz(0.0, 1.1, -0.3),
        ));

        // 轮子
        let wheel_mesh = meshes.add(Cylinder::new(0.45, 0.5));
        let wheel_mat = materials.add(StandardMaterial {
            base_color: Color::BLACK,
            ..default()
        });

        let wheel_positions = [
            Vec3::new(-1.3, 0.45, 1.6),   // 左前
            Vec3::new(1.3, 0.45, 1.6),    // 右前
            Vec3::new(-1.3, 0.45, -1.6),  // 左后
            Vec3::new(1.3, 0.45, -1.6),   // 右后
        ];

        for pos in wheel_positions {
            parent.spawn((
                Mesh3d(wheel_mesh.clone()),
                MeshMaterial3d(wheel_mat.clone()),
                Transform::from_translation(pos)
                    .with_rotation(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2)),
            ));
        }
    });
}

车辆移动与简单物理系统

这个系统处理玩家的键盘输入,并模拟车辆移动、速度提升以及掉落悬崖的物理效果。

pub fn move_car(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut query: Query<&mut Transform, With<PlayerCar>>,
    time: Res<Time>,
    mut stats: ResMut<GameStats>,
    road_query: Query<&Transform, (With<RoadSegment>, Without<PlayerCar>)>,
) {
    if stats.is_game_over {
        return;
    }

    if let Some(mut transform) = query.iter_mut().next() {
        let speed = stats.speed;
        let turn_speed = 15.0;

        // 自动前进
        transform.translation.z -= speed * time.delta_secs();

        // 左右转向
        if keyboard_input.pressed(KeyCode::ArrowLeft) || keyboard_input.pressed(KeyCode::KeyA) {
            transform.translation.x -= turn_speed * time.delta_secs();
        }
        if keyboard_input.pressed(KeyCode::ArrowRight) || keyboard_input.pressed(KeyCode::KeyD) {
            transform.translation.x += turn_speed * time.delta_secs();
        }

        // 速度随时间递增
        if speed < 100.0 {
            stats.speed += 0.5 * time.delta_secs();
        }

        // 检查车辆是否在公路范围内
        let car_pos = transform.translation;
        let mut on_ground = false;

        for road_transform in road_query.iter() {
            let z_dist = (road_transform.translation.z - car_pos.z).abs();
            if z_dist < 25.0 {
                let local_pos = road_transform.rotation.inverse()
                    * (car_pos - road_transform.translation);
                if local_pos.x.abs() < 8.0 && local_pos.z.abs() < 25.0 {
                    on_ground = true;
                    break;
                }
            }
        }

        // 如果不在公路上,模拟掉落
        if !on_ground {
            transform.translation.y -= 9.8 * time.delta_secs();
            transform.rotation *= Quat::from_rotation_x(time.delta_secs());
        }

        // 游戏结束判定:掉落太深
        if transform.translation.y < -5.0 {
            stats.is_game_over = true;
        }
    }
}

第五步:设置摄像机与用户界面

第三人称跟随摄像机(camera.rs

让摄像机平滑地跟随在车辆后方,提供最佳驾驶视角。

use bevy::prelude::*;
use crate::player::PlayerCar;

pub fn setup_camera(mut commands: Commands) {
    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(0.0, 10.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));
}

pub fn update_camera(
    car_query: Query<&Transform, With<PlayerCar>>,
    mut camera_query: Query<&mut Transform, (With<Camera3d>, Without<PlayerCar>)>,
) {
    if let Some(car_transform) = car_query.iter().next() {
        if let Some(mut camera_transform) = camera_query.iter_mut().next() {
            // 目标位置:车辆后上方
            let target_pos = Vec3::new(
                car_transform.translation.x * 0.5,
                10.0,
                car_transform.translation.z + 20.0,
            );

            // 平滑跟随
            camera_transform.translation = camera_transform.translation.lerp(target_pos, 0.05);
            camera_transform.look_at(car_transform.translation, Vec3::Y);
        }
    }
}

游戏UI显示(ui.rs

在屏幕左上角实时显示分数、速度和等级。

use bevy::prelude::*;
use crate::resources::GameStats;

#[derive(Component)]
pub struct ScoreText;

pub fn setup_ui(mut commands: Commands) {
    commands.spawn((
        Text::new("Score: 0"),
        Node {
            position_type: PositionType::Absolute,
            top: Val::Px(20.0),
            left: Val::Px(20.0),
            ..default()
        },
        TextFont {
            font_size: 32.0,
            ..default()
        },
        TextColor(Color::WHITE),
        ScoreText,
    ));
}

pub fn update_ui(
    stats: Res<GameStats>,
    mut query: Query<&mut Text, With<ScoreText>>,
) {
    if let Some(mut text) = query.iter_mut().next() {
        text.0 = format!(
            "Score: {:.0}\nSpeed: {:.0} km/h\nLevel: {}",
            stats.score, stats.speed, stats.level
        );
    }
}

第六步:整合所有模块,启动游戏

最后,在 main.rs 中将所有系统组装起来,并配置游戏初始状态。

use bevy::prelude::*;

mod components;
mod resources;
mod player;
mod camera;
mod environment;
mod road;
mod game;
mod ui;

use resources::{RoadConfig, GameStats, RoadState};
use camera::{setup_camera, update_camera};
use player::{spawn_player, move_car};
use road::{spawn_initial_road, update_road};
use game::{update_score, check_collisions, check_puddles, update_particles};
use ui::{setup_ui, update_ui};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(ClearColor(Color::srgb(0.5, 0.7, 0.9))) // 天空蓝背景
        .insert_resource(RoadConfig {
            segment_length: 50.0,
            num_segments: 15,
            lane_width: 4.0,
        })
        .insert_resource(GameStats {
            score: 0.0,
            speed: 30.0,
            level: 1,
            time_elapsed: 0.0,
            is_game_over: false,
        })
        .init_resource::<RoadState>()
        .add_systems(Startup, (setup, setup_ui, spawn_initial_entities.after(setup)))
        .add_systems(Update, (
            move_car,
            update_camera,
            update_road,
            check_collisions,
            update_score,
            check_puddles,
            update_particles,
            update_ui
        ))
        .run();
}

fn setup(mut commands: Commands) {
    setup_camera(commands.reborrow());
    // 此处可添加光照、天空盒、纹理资源加载等代码...
}

fn spawn_initial_entities(
    mut commands: Commands,
    road_config: Res<RoadConfig>,
    road_state: ResMut<RoadState>,
) {
    spawn_initial_road(/* ... */);
    spawn_player(/* ... */);
}

运行你的游戏!

一切就绪后,在项目根目录下运行命令,启动游戏:

cargo run

操作指南

  • A 键或 方向键:控制车辆向左转向。
  • D 键或 方向键:控制车辆向右转向。
  • 车辆会自动向前行驶,并且速度会随着时间逐渐加快。

Lonely Highway 3D游戏运行效果截图

(上图为游戏运行效果,车辆在无限公路上行驶,UI显示了分数、速度和等级)

游戏进阶技巧与扩展方向

当你成功运行基础版本后,可以尝试以下挑战和扩展:

游戏技巧

  1. 保持居中行驶:尽量让车辆位于公路中央,避免因过于靠近边缘而掉落。
  2. 提前预判障碍:注意观察前方随机生成的岩石或箱子,及时变道躲避。
  3. 适应加速节奏:游戏后期速度很快,需要更集中的注意力和更快的反应。
  4. 迎接弯道挑战:坚持60秒进入第2等级后,公路会开始出现弯道,游戏难度显著提升!

项目扩展方向(欢迎在云栈社区分享你的创意):

  • 丰富内容:添加多种车辆模型选择、不同类型的障碍物、动态天气系统(昼夜、雨雪)。
  • 增强体验:引入背景音乐与音效(引擎声、碰撞声)、粒子特效系统(漂移尘土、尾气)。
  • 完善系统:实现更精确的物理碰撞、添加本地排行榜功能、设计完整的游戏菜单和状态管理。
  • 跨平台发布:将游戏编译为 WebAssembly,使其能在浏览器中运行。

技术总结与回顾

通过这个项目,我们实践了现代游戏开发中的几个关键概念:

特性 实现方式
无限游戏世界 分段动态生成 + 超出视距回收实体
基础物理与交互 手动检测地面碰撞 + 简单重力模拟
渐进式难度 速度随时间线性增长 + 后期引入弯道
模块化与可维护性 严格遵守 ECS 架构,系统职责单一清晰

使用 Rust 和 Bevy 进行游戏开发是一次富有成效的体验。Bevy 的 ECS 框架迫使你以数据驱动的方式思考,最终产出的代码结构通常非常清晰,易于调试和扩展。这不仅是完成了一个小游戏,更是一次对系统编程、实时应用架构的深入实践。

希望这篇详尽的指南能成为你进入 Rust 游戏开发世界的一块坚实跳板。动手尝试,修改参数,添加你自己的想法,最重要的是享受编码和游戏创造的乐趣!

Happy Coding & Happy Gaming! 🎮




上一篇:字节跳动2024年大规模扩招芯片团队,聚焦AI芯片、架构师与验证工程师岗位
下一篇:从路由表到寻址:详解路由器工作原理与技术流程
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 09:02 , Processed in 0.694473 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表