Noxmore / bevy_trenchbroom

19 stars 3 forks source link

Entity Definition API Overhaul #11

Open Noxmore opened 1 week ago

Noxmore commented 1 week ago

I figure I'd put this into an issue to track it, make sure my thoughts are in order, and in case anyone who is using this crate wants to weigh in on the new API's design.

The current system

My problems with this approach

Requirements for a new solution

My current idea

Because you can derive from as many base classes as you want in FGDs, we might be able to just treat them like an analog to Bevy components, this should fit in very nicely with the required components coming in Bevy 0.15, which I'm planning to release the next version of this crate using. Specifically, the programmer will use procedural macros, sort of like this:

/// Spawnpoint for players. Emits a signal to `target` when someone has spawned, and toggles enabled when receiving a signal.
#[derive(Component, Reflect, PointClass)]
// Targetable and Targets give the `targetname` and `target`/`killtarget` properties respectively, still not 100% on the names though.
#[require(Transform, Targetable, Targets, ParentedToName)]
// Convention seems to be snake_case for quake entities, so it'll probably be converted automatically, with an optional attribute to disable such functionality.
#[classname(PascalCase)]
#[model("models/info_player_start.glb")]
pub struct InfoPlayerStart {
    /// Whether or not to start being able to spawn players.
    #[default(true)] // This is identical to how `smart-default` does things, we should probably read from the `Default` implementation instead
    pub start_enabled: bool,
}

#[derive(Component, Reflect, SolidClass)]
#[require(Transform, Targetable, ParentedToName)]
// We'll probably have a default BrushSpawnSettings for this.
#[geometry(BrushSpawnSettings::new().smooth_by_default_angle().pbr_mesh().with_lightmaps())]
pub struct FuncDoor {
    /// Door speed in m/s.
    #[default(3.)]
    pub speed: f32,
}

#[derive(Component, Reflect, BaseClass)]
pub struct ParentedToName {
    pub parent: String,
}

// This is how we would do `choices` properties.
#[derive(Reflect, FGDType)]
pub enum CollisionType {
    /// Uses colliders defined in the model, or none if the model doesn't have any
    Model,
    /// Mesh bounding box collider
    BoundingBox,
    // No collision
    None,
}

This gets a bit dicey when requiring components that aren't don't implement BaseClass, we could use a #[base(...)] attribute instead, but I'd really like to treat entity classes as close to components as possible. An alternative could be the user just not being able to require() such components, and using component hooks instead?

Entity classes will only be able to contain fields of types that implement FGDType (currently called TrenchBroomValue).

Geometry

For BSPs, Mesh3ds pointing to a mesh handle from the BSP asset should be just fine. For .maps, we'll probably have a Brushes component that computes the meshes at runtime, and can be easily serialized. This would be wasteful for use cases that only include static geometry, but in those cases i can't think of a reason BSP wouldn't be preferable to use anyway, so it's probably fine.

Registering

Noxmore commented 5 days ago

It's also very likely with this new approach I'll make maps spawn via a regular Bevy Scene asset, then having Map (to be renamed to QMap most likely) and Bsp acting like Gltf: just a middle man containing handles and source data.