Extract all JSON structures to their own classes
jakubg1 opened this issue · comments
All JSON structures that exist should be given their own classes. This will greatly improve code clarity and will make development easier.
Some insights:
- All structure-related classes should be in a separate folder.
- Some JSON files should have not just one class representation, but a few, due to some microstructures being bundled inside.
- Introduce all needed getters and setters, but also some useful functions. For example, in a collectible generator data class, we could define a function which evaluates it and returns a randomly selected output.
We need to consider migrating some classes, too.
Here's a list of the more important resource types used in the engine:
Name | Extension | Located in | Stored as | Class name | Loaded by | Notes |
---|---|---|---|---|---|---|
Image | *.png |
/images |
Class | Essentials/Image |
ResourceManager | |
Sprite | *.json |
/sprites |
Class | Essentials/Sprite |
ResourceManager | |
Sound | *.wav/ogg/... |
/sounds |
Class | Essentials/Sound |
ResourceManager | |
Sound Event | *.json |
/sound_events |
Class | Essentials/SoundEvent |
ResourceManager | |
Music | *.wav/ogg/... |
/music |
Class | Essentials/Music |
ResourceManager | |
Font | *.json |
/fonts |
Class | Essentials/Font |
ResourceManager | |
Font File | *.ttf |
/font_files |
LOVE2D Object | (none) | Font | This is an example of very bad implementation! |
Particle | *.json |
/particles |
Table | (none) | ResourceManager | |
Color Palette | *.png |
--- | Class | Essentials/ColorPalette |
ResourceManager | Stored and loaded in Load List |
Sphere | *.json |
/config/spheres |
Table | (none) | ConfigManager | |
Sphere Effect | *.json |
/config/sphere_effects |
Table | (none) | ConfigManager | |
Collectible | *.json |
/config/collectibles |
Table | (none) | ConfigManager | |
Collectible Generator | *.json |
/config/collectible_generators |
Class | CollectibleGenerator/Entry |
CollectibleGeneratorManager | |
Color Generator | *.json |
/config/color_generators |
Table | (none) | ConfigManager | |
Level | *.json |
/config/levels |
Table | (none) | ConfigManager | |
Map | *.json |
/maps/<map name>/config.json |
Table | (none) | ConfigManager | Load list defined by Levels |
Some insights I can see from the table:
- Font files should be given their own resource and class, and be properly loaded by ResourceManager.
- Collectible Generators should get rid of its manager and be handled like other similar structures.
- Color Palettes should get their own folder.
Ideally, either:
- everything should be stored in the ResourceManager, except for files and structures which appear exactly once in the config,
- or everything what isn't JSON should be stored in the ResourceManager, and everything what is JSON should be stored in ConfigManager.
Also:
- All resources should be accessed by just name, not the path such as
sound_events/button_hover.json
->button_hover
. Currently, ConfigManager does that but ResourceManager does not.- Rebuild ResourceManager so resources stored inside are keyed by names, not whole file paths.
Additional key benefits by introducing this change:
- Data can be easily validated in a clean way, without messing with game classes themselves.
- Data can be also patched over time, prepending or changing certain variables as they change their format over time.
An example involving the shooter data structure has been added in commit 77cf19b.
A few notable observations one can see in my approach:
- All resource paths/Vector structures are immediately converted to a more useful form.
- The structure of the original file is preserved.
- Most of the variable types are rectified using the
---@type
tags. - There are no getters. It's stupid as it doesn't add any value while adding lots of not-very-useful boilerplate code.
- There is no data verification, as this would double the JSON schema checks performed by VS Code.
- However, some checks can exist if for example an illegal type is provided.
API draft for the new Resource Manager:
ResourceManager:resolveAssetPath(path, [namespace])
: Resolves and outputs the entire asset path starting from the game folder. As per #103.- Example:
resolveAssetPath(":flame.spr", "Map1")
will return"maps/Map1/sprites/flame.spr"
.
- Example:
ResourceManager:resolveAssetType(path)
: Resolves and outputs the resource type of this file, based on its extension.- Example:
resolveAssetType(":flame.spr")
will return"sprite"
.
- Example:
ResourceManager:getAsset(path, [namespace], [batches])
: If the resource has been already loaded earlier, returns this resource. If not, immediately loads it in, optionally as a part of designated batches. (TODO: Decide whether to make variants for different asset types or not. LDoc might have a problem with this.)ResourceManager:loadAsset(path, [namespace], [batches])
: If the resource has been already loaded earlier, do nothing. Otherwise, load this asset (not immediately - treat this as queueing the resource to be loaded soon!), optionally as a part of designated batches. If many calls are performed in a quick succession, the load order will be preserved. This function will never return anything.ResourceManager:loadAssetFromServer(path, [namespace], [batches], [cache])
: Proposed way of loading resources from the server, for use in Cosmic Crash.ResourceManager:releaseAssetBatch(batch)
: Removes all mentions of the given batch from all the loaded resources. For any resource, if that was the only batch, unloads it completely.ResourceManager:startLoadCounter(name)
: Creates a counter which will be counting all resources queued from this point onwards. The total number of resources and the number of resources already loaded will be tracked.ResourceManager:stopLoadCounter(name)
: Stops counting the resources that are queued. Does not destroy the counter: the counter will be hanging around forever. The number of loaded resources will still rise when they are loaded afterwards.ResourceManager:getLoadProgress(name)
: Returns the percentage of loaded resources out of queued resources (between thestartLoadCounter
andstopLoadCounter
calls).- Example of all three:
function loadGame() resourceManager:startLoadCounter("main") resourceManager:loadAsset(...) resourceManager:loadAsset(...) resourceManager:loadAsset(...) ... resourceManager:stopLoadCounter("main") end function draw() local progress = resourceManager:getLoadProgress("main") if progress == 1 then drawStartButton() else drawProgressBar(progress) end end
Notes:
- Use
getAsset
for loading the maps and the splash screen itself, andloadAsset
for all game resources which are loaded during the splash screen.