klangner / mapgen.rs

Map generator for games.
https://klangner.github.io/mapgen.rs/
Apache License 2.0
44 stars 5 forks source link

Add ability to associate arbitrary `BuilderData` with maps. #40

Closed ndarilek closed 2 years ago

ndarilek commented 2 years ago

I'm finding that, for best results, I need to integrate everything into my map generation process. So for instance, object/monster spawns need to run as a filter so they can influence future steps.

This associates a Clone + Default type with maps and makes it available to filters. NoData exists for the current behavior.

All examples/tests/demos have been updated accordingly.

ndarilek commented 2 years ago

Hold off on merging this one for a bit. It passes all tests, but I'm having issues integrating it with my game. It should be fine--I just need to make sure I can get the right combination of type bounds to make it play nice with Bevy. I'll ping here when I've done that.

Thanks!

klangner commented 2 years ago

Hey @ndarilek . Could you tell me what kind of objects you want to add? I don't really like how those generic are polluting the source code here. If you need monsters for some reason as a middle filter, then I would prefer to add add Monster struct and field monsters: Vec to the Map struct. This is what I did with the starting_pointfield in Map struct. Since I needed it I haven't added generic for it, but added it as field. What you think about it?

ndarilek commented 2 years ago

I want a lot more than just monsters. I don't think it makes sense to add very specific fields to maps. My next game may want political influence, for instance, and it wouldn't make sense to add that to the crate.

Can you explain what you mean by polluting the source code? I guess I don't really see it that way. I'm having lots of trouble in my game because I need to make my level spawning a pipeline, where level/map generation may be influenced by previous phases, and where future filters may spawn in game-specific items. Admittedly the generic does add a bit of noise, but without it I think the usefulness of this crate will be a bit limited, and lots of crates do something similar (I.e. Rapier allows you to associate physics objects with arbitrary user data.) I think it makes sense for start/exit points to be on the Map struct since those are fairly common and there's legitimate benefit to baking those in directly to the filters. But if you've got a faction territory generation filter creating faction boundaries in one pass, and a monster/item spawner placing faction-specific spawns in territories in another, that doesn't make sense to add to the generic Map structure.

If you need more examples, look to the roguelike tutorial book. There's a field called builder_data or something similar in all the map examples. In the book it's limited to fields for the specific game being made, but this solution lets you make it more generic.

Thanks.

klangner commented 2 years ago

Pollution means all those generic type declarations. Also this doesn't really solve the problem from my perspective. Let's say that you will create 2 filters one for adding the monsters and second for adding traps. If I will create new filter for adding monsters that the map will have declaration: Map<Monsters>, but if someone else will want to add traps he/she will require filters expecting Map<Traps> Those filters will never compose with themself.

I don't know your project, but if I would like to extend this for my specific purposes that probably I would do it this way:

  1. Create MyMap structure

    struct MyMap {
    map: Map,
    my_specific_fields
    }
  2. Create my own filters for monsters etc and one filter wrapping filters from mapgen. this wrapper will only access map and pass it to mapgen::filter.

  3. Create my own MapBuilder for building MyMap. you need new builder since it will work on your filters.

This way you can use all filters from this lib and also with minimal effort extend map generation with you own custom fields and filters.

ndarilek commented 2 years ago

That seems a bit more complicated than necessary. Here's what I'm currently doing. Note that I haven't attempted any filters yet but I don't see why this shouldn't work:

#[derive(Clone, Default)]
pub struct MapBuilderData {
    pub robot_count: usize,
    pub robot_spawns: Vec<(RobotType, (usize, usize))>,
    pub powerup_spawns: Vec<(PowerupType, (usize, usize))>,
}

impl BuilderData for MapBuilderData {} // Making this derivable would be nice.

So you shouldn't need a new type for each thing, just bundle up all the data your filters would need to generate maps for your game in a single struct/tuple.

This lets me integrate more complex logic in the level creation process. I can have powerup spawns run normally, then add another filter that strips out certain types later for specific game modes. I can use my regular robot-spawning logic, but also insert a filter for spawning robots in specific configurations to set up ambushes in rooms and such. And then the actual game systems can read the structs off the map and spawn in whatever they need.

Unless this turns out to be horribly broken, I'm going ahead with this. If you'd rather I close this PR and move ahead on my own fork that's fine. I'm also open to another solution that lets me do this without having to wrap everything in another layer of complication, but I've already got a layer around the map and don't want another.

Thanks.

klangner commented 2 years ago

Ok. Don't get me wrong. I really appreciate that you want to extend this library. But this is not a direction which I want to take. So I guess the best solution would be for you to fork this project and do whatever you want. I'm not actively working on this project, so you won't lose any new features anyway. This is just one of my hobby project and I don't plan to make it a popular library. Anyway. Happy coding your game! :-)

ndarilek commented 2 years ago

Understood. Thanks for the work!