#游戏引擎 #创建 # #系统 #组件 #有趣 #概念

nate-engine-macros

为游戏引擎编写的宏,仅供娱乐

5个版本

0.1.4 2024年4月30日
0.1.3 2024年4月30日
0.1.2 2024年4月29日
0.1.1 2024年4月29日
0.1.0 2024年4月29日

#2246游戏开发


nate-engine 使用

MIT/Apache

30KB
484

Nate-Engine

描述

Nate-Engine 是我在无聊时做的一个概念验证项目。主要思想是 Nate-Engine 是一个基于实体组件系统 (ECS) 模型的游戏引擎。我可能会用它来制作一些 tui 游戏,但我强烈建议不要使用它,因为它纯粹是一个测试,并且大多数真实项目可能应该使用类似 bevy 的东西。

目标

项目的目标是使通过几个宏创建 ECS 系统变得极其简单,如下所示

组件

组件宏应该使定义项目中要使用的组件列表变得容易。也许还可以拥有不针对每个实体重复的项目。可能如下所示

#[world(singular=[canvas])]
pub struct World {
    position: (f32, f32),
    player_velocity: (f32, f32),
    object_velocity: (f32, f32),
    sprite: Sprite,
    canvas: [[bool; 10]; 10],
}

这可以翻译为

pub struct World {
    entities: Arc<RwLock<Vec<u32>>>,

    pub canvas: Arc<RwLock<Option<[[bool; 10]; 10]>>>,

    pub position: Arc<RwLock<Vec<Option<(f32, f32)>>>>,
    pub player_velocity: Arc<RwLock<Vec<Option<(f32, f32)>>>>,
    pub object_velocity: Arc<RwLock<Vec<Option<(f32, f32)>>>>,
    pub sprite: Arc<RwLock<Vec<Option<Sprite>>>>,
}

系统

对于系统,我希望使其能够自动生成迭代器 + 过滤器,这样在 proc-macro 中用户只需要指定应该存在哪些字段,还有一个可选的过滤参数。例如

#[system(world=DinosaurWorld, read=[object_velocity], write=position)]
fn position_update_system() {
    *position = (position.0 + object_velocity.0, position.1 + object_velocity.1);
}

这将扩展为

fn position_update_system(world: Arc<DinosaurWorld>) {
    let object_velocity = world.object_velocity.read().unwrap();
    let mut position = world.positions.write().unwrap();
    for (object_velocity, position) in object_velocity.iter().zip(position.iter_mut()).filter(|v| v.0.is_some() && v.1.is_some()) {
        let object_velocity = object_velocity.as_ref().unwrap();
        let mut position = position.as_mut().unwrap();

        position = (position.0 + object_velocity.0, position.1 + object_velocity.1);
    }
}

这也可能非常有用,添加某种类型的过滤器,允许用户过滤不仅存在某个组件,而且该组件具有某些值。例如,考虑以下情况

#[world]
pub struct World {
    position: (u32, u32),
    damage_zone: (u32, u32),
    health: u32,
}

#[system(
    world=World,
    write=[health],
    read=[position, damage_zone],
    filter="position == damage_zone"
)]
fn position_damage_system() {
    health -= 1;
}

这将扩展为

pub struct World {
    entities: Arc<RwLock<Vec<u32>>>,

    positions: Arc<RwLock<Vec<Option<(u32, u32)>>>>,
    damage_zones: Arc<RwLock<Vec<Option<(u32, u32)>>>>,
    healths: Arc<RwLock<Vec<Option<u32>>>>,
}

fn position_damage_system(world: Arc<World>) {
    let positions = world.positions.read().unwrap();
    let damage_zones = world.damage_zones.read().unwrap();
    let mut healths = world.healths.write().unwrap();
    for ((_position, _damage_zone), health) in positions.iter().zip(damage_zones.iter()).zip(healths.iter_mut()).filter(|v| v.0.0.is_some() && v.0.1.is_some() && v.1.is_some() && v.0.0 == v.0.1) {
        *health -= 1
    }
}

也可以通过使用 _read 和 _write 作为其标识符来在系统中引用单个组件。例如

#[system(world=World, _read=[canvas], _write=[exit])]
fn read_canvas_and_exit() {
    ...
}

这将允许用户通过读取访问 canvas 并写入 exit

依赖关系

~280–730KB
~17K SLoC