-
Notifications
You must be signed in to change notification settings - Fork 0
Extensibility
Void was built to be extended, not just used. Every major system exposes interfaces and base classes that you can replace, customize, or ignore entirely.
Extend, don't modify.
You should never have to modify Void's internal code to make it do what you want. This matters for several reasons:
- Upgrades are safe — You can update Void without losing your customizations
- Your code stays clean — Your game logic stays separate from engine code
- Collaboration is easier — Multiple developers can extend different systems independently
- Debugging is simpler — Engine bugs can be reported without your custom code getting in the way
Every major system in Void exposes an extension point:
- IAsset: Define new asset types. Load custom models, encrypted data, or proprietary formats. Register them with the AssetManager and load them the same way as built-in assets.
- IMount: Add custom asset sources. Read assets from network drives, cloud storage, proprietary archives, or any other source. Mounts are searched in priority order.
- IAtlasPacker: Plug in your own texture packing algorithm. The engine ships with Guillotine and Skyline packers, but you can implement your own for specific packing needs.
- ILogSink: Send logs anywhere. Write to databases, remote servers, custom file formats, or any other destination. Sinks run on the background logging thread.
- IBatcher: Custom rendering logic. Replace the sprite or primitive batcher with your own implementation for specialized rendering.
- IRenderTarget: Custom render surfaces. Create render targets for specific use cases beyond the built-in texture render target.
- ContentTypeWriterReader: Any save data type. The save system handles encryption, compression, manifest verification, and atomic writes automatically.
You implement the interface, register it with the engine, and Void handles the rest.
// Replace the atlas packer
GameSettings.Instance.SetAtlasPacker(typeof(MyPacker));
// Add a custom mount
AssetManager.Instance.AddMountToStart(new CloudMount());
// Register a new asset type
AssetManager.Instance.RegisterAssetType<MyAsset>(
new[] { ".myext" },
(id, data, tag) => new MyAsset(id, data, tag)
);
// Add a custom log sink
Logger.Instance.AddSink(new DatabaseSink());
// Replace the save system with custom serialization
public class MySaveSystem : ContentTypeWriterReader<MyData>
{
protected override void Write(MyData data, ContentWriter writer)
{
writer.Write(data.Name);
writer.Write(data.Score);
}
protected override MyData Read(ContentReader reader)
{
return new MyData
{
Name = reader.ReadString(),
Score = reader.ReadInt32()
};
}
}
No engine code modification. No forking the repo. No fighting the framework. Just clean, simple extension points that work the way you need them to.
Create a class that implements IAsset:
public class MyAsset : IAsset
{
public uint Id { get; }
public string Tag { get; }
public byte[] Data { get; }
public bool IsValid { get; private set; }
public AssetType Type => AssetType.Normal;
public DateTime LastAccessTime { get; private set; }
public MyAsset(uint id, byte[] data, string tag)
{
Id = id;
Data = data;
Tag = tag;
LastAccessTime = DateTime.Now;
}
public void Load()
{
// Parse data, load into memory
IsValid = true;
}
public void Unload()
{
// Release memory
IsValid = false;
}
public void Dispose()
{
// Clean up resources
}
}
Register it with the asset manager:
AssetManager.Instance.RegisterAssetType<MyAsset>(
new[] { ".myasset", ".mya" },
(id, data, tag) => new MyAsset(id, data, tag)
);
Load it like any other asset:
var myAsset = AssetManager.Instance.Load<MyAsset>("data.myasset");
Create a class that implements IMount:
public class CloudMount : IMount
{
public bool HasFile(string path)
{
// Check if file exists in cloud storage
}
public byte[] ReadFile(string path)
{
// Download file from cloud storage
}
}
Add it to the asset manager:
AssetManager.Instance.AddMountToStart(new CloudMount());
Create a class that implements IAtlasPacker:
public class MyPacker : IAtlasPacker
{
public int UsedSpace { get; }
public int TotalSpace { get; }
public float Fragmentation { get; }
public bool TryPack(int width, int height, out Rect2 packedRect)
{
// Your custom packing logic
}
public void Free(Rect2 rect)
{
// Mark space as free
}
public List<(Rect2 OldRect, Rect2 NewRect)> Defrag()
{
// Repack textures, return moves
}
public void Clear()
{
// Reset packer state
}
}
Set it in GameSettings:
GameSettings.Instance.SetAtlasPacker(typeof(MyPacker));
Create a class that implements ILogSink:
public class DatabaseSink : ILogSink
{
public void Write(LogEntry entry)
{
var json = JsonSerializer.Serialize(entry);
// Send to database
}
}
Add it to the logger:
Logger.Instance.AddSink(new DatabaseSink());
Create a save system for your specific data type:
public class PlayerSaveSystem : ContentTypeWriterReader<PlayerSaveData>
{
protected override void Write(PlayerSaveData data, ContentWriter writer)
{
writer.Write(data.Name);
writer.Write(data.Level);
writer.Write(data.Position);
writer.WriteObject(data.Inventory);
}
protected override PlayerSaveData Read(ContentReader reader)
{
return new PlayerSaveData
{
Name = reader.ReadString(),
Level = reader.ReadInt32(),
Position = reader.ReadVect2(),
Inventory = reader.ReadObject<List<Item>>()
};
}
}
Use it with the save system:
var saveSystem = new PlayerSaveSystem("my-secret-key");
saveSystem.TrySave("player.sav", data, out var error);
Void includes a discovery system that automatically finds and loads your extensions at runtime. Mark your class as discoverable:
[Discoverable(Name = "MyMod", Category = "Gameplay")]
public class MyMod : IMod
{
// Your mod implementation
}
Find all discoverable types:
List<Type> mods = DiscoverableHelper.FindAll<IMod>();
Or find a specific one:
Type mod = DiscoverableHelper.FindSingleByName<IMod>("MyMod");
| System | Interface/Base Class | Registration Method |
|---|---|---|
| Asset Types | IAsset | AssetManager.RegisterAssetType() |
| Asset Sources | IMount | AssetManager.AddMountToStart() |
| Atlas Packing | IAtlasPacker | GameSettings.SetAtlasPacker() |
| Logging | ILogSink | Logger.AddSink() |
| Rendering | IBatcher | (Custom batcher implementation) |
| Render Targets | IRenderTarget | RenderTarget.Get() / Return() |
| Save Data | ContentTypeWriterReader | (Instantiate and use) |