-
Notifications
You must be signed in to change notification settings - Fork 1
LDtk Settings
LDtk allows you to add custom fields to levels, layers, and entities. These fields can be of many types: integers, floats, booleans, strings, colors, points, enums, file paths, tile references, entity references, and arrays of any of these.
The problem is that LDtk stores these as JSON data. Most integrations give you raw JSON and force you to manually parse each field. This is error-prone, slow, and creates a lot of garbage.
Void provides a strongly typed setting system that handles all the parsing for you. Each setting is stored as a typed LDtkSetting object, and you access values using type-safe methods.
When the LDtk map loads, the JSON is parsed once. Each setting is converted to the appropriate strongly typed object. The settings are stored in a dictionary keyed by hash for O(1) lookups.
JSON: { "__identifier": "Health", "__type": "Int", "__value": 100 }
|
+-- LDtkIntSettings: Value = 100 (int)
Each setting is a strongly typed class that inherits from LDtkSetting.
// These are the actual types used
LDtkBoolSettings // bool
LDtkIntSettings // int
LDtkFloatSettings // float
LDtkStringSettings // string
LDtkColorSettings // Color
LDtkPointSettings // Vect2
LDtkTileSettings // LDtkTile
LDtkEntityRefSettings // LDtkEntityRef
LDtkEnumSettings // string (enum value as string)
LDtkFilePathSettings // stringAll setting types have array variants.
LDtkBoolArraySettings // List<bool>
LDtkIntArraySettings // List<int>
LDtkFloatArraySettings // List<float>
LDtkStringArraySettings // List<string>
LDtkColorArraySettings // List<Color>
LDtkPointArraySettings // List<Vect2>
LDtkTileArraySettings // List<LDtkTile>
LDtkEntityRefArraySettings // List<LDtkEntityRef>
LDtkEnumArraySettings // List<string>
LDtkFilePathArraySettings // List<string>Use the LDtkSetting static class to access values. The methods use hash-based lookups and Try patterns.
var settings = level.Settings;
// Get a boolean setting
if (LDtkSetting.TryGetBoolSetting(settings, "IsActive", out bool isActive))
{
// Use isActive
}
// Get an integer setting
if (LDtkSetting.TryGetIntSetting(settings, "Health", out int health))
{
// Use health
}
// Get an enum setting
if (LDtkSetting.TryGetEnumSetting<EnemyType>(settings, "Type", out var type))
{
// Use type
}
// Get a point setting
if (LDtkSetting.TryGetPointSetting(settings, "SpawnPoint", out Vect2 spawnPoint))
{
// Use spawnPoint
}
// Get a tile setting
if (LDtkSetting.TryGetTileSetting(settings, "TileName", out var tile))
{
// Use tile
}
// Get an entity reference setting
if (LDtkSetting.TryGetEntityRefSetting(settings, "TargetEntity", out var entityRef))
{
// Use entityRef
}// Get an integer array setting
if (LDtkSetting.TryGetIntArraySetting(settings, "Waypoints", out var waypoints))
{
foreach (int point in waypoints)
{
// Use point
}
}
// Get a point array setting
if (LDtkSetting.TryGetPointArraySetting(settings, "Path", out var path))
{
foreach (Vect2 point in path)
{
// Use point
}
}
// Get an enum array setting
if (LDtkSetting.TryGetEnumArraySetting<EnemyType>(settings, "SpawnTypes", out var types))
{
foreach (var type in types)
{
// Use type
}
}The setting system is designed for performance and GC friendliness.
Settings are stored in dictionaries keyed by hash. Lookups are O(1).
// Name is hashed once
settings.ContainsKey(HashHelper.Cache32(name));
// The hash is used directly in the lookup
settings.TryGetValue(HashHelper.Cache32(name), out var result);All parsing happens once when the map loads. When you access a setting, the value is already typed and ready to use.
// No parsing happens here
int health = LDtkSetting.GetIntSetting(settings, "Health");The setting system is designed to minimize garbage collection.
When the LDtk map loads, the JSON is parsed once and all settings are converted to their strongly typed objects. This happens during the initial load and never again. The settings are then stored in dictionaries and reused for the lifetime of the map.
When you access a setting, you are retrieving an object that already exists. There are no allocations. No temporary objects. No parsing. Just a dictionary lookup and a cast.
| Operation | Allocation |
|---|---|
| Initial map load | All settings created once |
| Lookup by name | None |
| Getting a value | None (already typed) |
| Getting an array | None (already stored) |
| Repeated lookups | None |
This means you can access settings every frame without worrying about GC pressure.
The system uses hash-based lookups instead of string comparisons. This is faster and avoids the overhead of string comparisons.
// What actually happens
settings.TryGetValue(HashHelper.Cache32("Health"), out var result);
// Not this
settings.TryGetValue("Health", out var result);All setting access uses Try methods. This means no exceptions are thrown for missing settings. The system returns false and you handle the fallback.
if (LDtkSetting.TryGetIntSetting(settings, "Health", out int health))
{
// Setting exists, use it
}
else
{
// Setting doesn't exist, use default
health = 100;
}This is faster and cleaner than throwing and catching exceptions.
The LDtkSetting class is the base for all setting types.
public class LDtkSetting(object value)
{
public object Value { get; } = value;
public T ValueAs<T>() => (T)Value;
// Static helper methods for all types
public static bool GetBoolSetting(...);
public static bool TryGetBoolSetting(...);
// ... and so on for all types
}| LDtk Type | Void Type |
|---|---|
Int |
LDtkIntSettings (int) |
Float |
LDtkFloatSettings (float) |
Bool |
LDtkBoolSettings (bool) |
String |
LDtkStringSettings (string) |
Color |
LDtkColorSettings (Color) |
Point |
LDtkPointSettings (Vect2) |
Tile |
LDtkTileSettings (LDtkTile) |
EntityRef |
LDtkEntityRefSettings (LDtkEntityRef) |
LocalEnum |
LDtkEnumSettings (string) |
FilePath |
LDtkFilePathSettings (string) |
Array<Int> |
LDtkIntArraySettings (List)
|
Array<Float> |
LDtkFloatArraySettings (List)
|
Array<Bool> |
LDtkBoolArraySettings (List)
|
Array<String> |
LDtkStringArraySettings (List)
|
Array<Color> |
LDtkColorArraySettings (List)
|
Array<Point> |
LDtkPointArraySettings (List)
|
Array<Tile> |
LDtkTileArraySettings (List)
|
Array<EntityRef> |
LDtkEntityRefArraySettings (List)
|
Array<LocalEnum> |
LDtkEnumArraySettings (List)
|
Array<FilePath> |
LDtkFilePathArraySettings (List)
|
// Load the map
var map = AssetManager.Instance.Load<LDtkMap>("levels/world.ldtk");
// Get a level
var level = map.GetLevelByName("Level_01");
// Access level settings
if (LDtkSetting.TryGetStringSetting(level.Settings, "LevelName", out string levelName))
{
Console.WriteLine($"Level: {levelName}");
}
// Get an entity
var entity = map.GetEntityById("abc-123-def-456");
// Access entity settings
if (LDtkSetting.TryGetIntSetting(entity.Settings, "Health", out int health))
{
Console.WriteLine($"Health: {health}");
}
if (LDtkSetting.TryGetEnumSetting<EnemyType>(entity.Settings, "Type", out var type))
{
Console.WriteLine($"Enemy Type: {type}");
}
if (LDtkSetting.TryGetPointArraySetting(entity.Settings, "Waypoints", out var waypoints))
{
foreach (var point in waypoints)
{
Console.WriteLine($"Waypoint: {point}");
}
}Home · Getting Started · Rendering · Custom Renderers · GitHub · Report an Issue
Built with VOID Engine · MIT License