Bombfuse / emerald

A 2D rust game engine focused on portability.
MIT License
558 stars 16 forks source link

load_world_file.rs example is not compatible with current Emerald version #251

Closed gcardozo123 closed 1 year ago

gcardozo123 commented 1 year ago

Hi,

It seems the emerald/examples/load_world_file.rs example is not compatible with the current version of the engine:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: EmeraldError { message: "toml::de::Error \"invalid type: string \\\"tileset.png\\\", expected struct TilesetResource for key `tileset` at line 4 column 11\"" }', src\main.rs:18:57

It would be nice to have a save_world_file.rs example that would programmatically create a world and then serialize it into examples\assets\example.wrld. Then in the load_world_file.rs we could go:

self.world = emd.loader().world("example.wrld").expect("Failed to load 'example.wrld', consider executing the 'save_world_file.rs' example first.");

this way we could remove the example.wrld file from the repo and future proof it against engine changes. I can open an MR if I figure out how to serialize the world (I'm quite new to Rust)šŸ˜…

[Edit] The same line self.world = emd.loader().world("example.wrld").unwrap(); also fails in the ent.rs example.

gcardozo123 commented 1 year ago

So... as far as I understand, Emerald uses Serde to allow users to persist their game worlds. I can see that some of Emerald's structs have a "schema" equivalent which is used for serializaition/deserialization, for example , for Tilemap there's a TilemapSchema. With a TilemapSchema it's possible to obtain a regular Tilemap via to_tilemap. On the other hand, I can't find how to obtain the schema via the regular tilemap. I was expecting Emerald would have some utility somewhere to help with serializing custom data, but I can't seem to find it. šŸ˜•

Bombfuse commented 1 year ago

Yo! Thanks for reporting this issue, I sometimes forget to run all examples to make sure they work properly, hopefully someday the CI/CD actions can do this. I can hopefully get to fixing this specific issue soon, but my time is somewhat limited right now as I'm working on engine porting efforts.

As for serialization, unfortunately world serialization is not directly supported by Emerald. A custom serialization method would likely need to be written.

The .wrld files are intended to make world creation simpler for handwriting and (in the future) an editor application.

In the future, we could likely support direct serialization of Emerald components (sprite, label, etc), but users will still be responsible for serialization of their own custom components.

gcardozo123 commented 1 year ago

As for serialization, unfortunately world serialization is not directly supported by Emerald. A custom serialization method would likely need to be written.

Oh, yeah, absolutely. I don't mean world serialization literally, let me rephrase that. I'm new to Rust, Emerald and their whole ecosystem. I realized that Emerald ships with Serde and that I can probably work out a way of serializing/deserializing my own custom objects with it, but I'm confused as to how I could serialize Emerald's Tilemap for example. But if I understood you correctly that's not possible, we can currently only deserialize a Tilemap, but not serialize one. Is that so?

I can hopefully get to fixing this specific issue soon, but my time is somewhat limited right now as I'm working on engine porting efforts.

I mean no pressure whatsoever, I'm more curious from a learning standpoint and hoping I could help rather than asking for features/fixes.

Bombfuse commented 1 year ago

Oh yah, currently Tilemaps and other components can only be deserialized. If we were to serialize it, I think we'd just need to write a function that converts the tilemap back into a TilemapSchema and shove it into the wrld file.

And for sure, I'm always here to answer any questions! And we always welcome contributions šŸ˜„

gcardozo123 commented 1 year ago

Got it, thx for the replies! šŸ˜„

gcardozo123 commented 1 year ago

About serializing a Tilemap, would you be interested in something like this?

impl Serialize for Tilemap {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let resource = TilesetResource {
            height: self.tilesheet_height,
            width: self.tilesheet_width,
            texture: String::from(self.tilesheet.label()),
        };

        let mut tiles: Vec<TileSchema> = vec![];
        for (i, tile_id) in self.tiles.iter().enumerate() {
            if tile_id.is_none() {
                continue;
            }

            let x = i % self.width();
            let y = i / self.width();
            tiles.push(TileSchema {
                x: x,
                y: y,
                id: tile_id.unwrap(),
            });
        }

        let tilemap_schema = TilemapSchema {
            width: self.width(),
            height: self.height(),
            tileset: Some(resource),
            resource: None,
            visible: true, //TODO
            z_index: 0.0,  //TODO
            tiles: tiles,
        };

        TilemapSchema::serialize(&tilemap_schema, serializer)
    }
}

Example usage:

emd.set_asset_folder_root(String::from("./examples/assets/"));
let tileset_key = emd.loader().texture("tileset.png").unwrap();
let mut tilemap = Tilemap::new(tileset_key, Vector2::new(16, 16), 2, 2, 30, 30);

tilemap.set_tile(0, 0, Some(0)).unwrap();
tilemap.set_tile(1, 0, Some(1)).unwrap();
tilemap.set_tile(0, 1, Some(2)).unwrap();
tilemap.set_tile(1, 1, Some(3)).unwrap();
tilemap.set_tile(0, 2, Some(0)).unwrap();
tilemap.set_tile(1, 2, Some(1)).unwrap();
tilemap.set_tile(0, 3, Some(3)).unwrap();
tilemap.set_tile(1, 3, Some(3)).unwrap();
tilemap.set_tile(1, 6, Some(3)).unwrap();
tilemap.set_tile(2, 6, Some(3)).unwrap();

let serialized = serde_json::to_string(&tilemap).unwrap();
println!("serialized: {}", serialized);

Output:

serialized: {"width":30,"height":30,"tileset":{"texture":"tileset.png","height":2,"width":2},"resource":null,"visible":true,"z_index":0.0,"tiles":[{"x":0,"y":0,"id":0},{"x":1,"y":0,"id":1},{"x":0,"y":1,"id":2},{"x":1,"y":1,"id":3},{"x":0,"y":2,"id":0},{"x":1,"y":2,"id":1},{"x":0,"y":3,"id":3},{"x":1,"y":3,"id":3},{"x":1,"y":6,"id":3},{"x":2,"y":6,"id":3}]}

If you're interested I can put up a PR once I fix my //TODOs, but no pressure, I understand you might have different plans on how a serialization API should look in Emerald.

Bombfuse commented 1 year ago

that looks good to me! I'd definitely be down for that, as long as it's able to serialize to toml and back (for the .wrld file) then it should work perfectly

gcardozo123 commented 1 year ago

Good point! For whatever reason it doesn't seem to work with toml. I need to dig more into this and figure out what toml needs that my function is not providing.

gcardozo123 commented 1 year ago

Oh, I think I found the issue:

TOML can't have any subtables inside the table, by construction; in other words, all simple fields, like numbers and strings, must be written before the complex types like structs (which are mapped to tables).

So I can give it another try, just need to reorder some attributes in the TilemapSchema to make Serde happy. :D