These development logs have been few and far between, mostly because I'm lazy, but also because it's hard to explain features that people can't see. The last development log was for from the prototype to alpha 0.0.0. Between 0.0.0 and 0.0.1, there were two big features that made the other parts of that release possible: the new package loading system and text rendering. 0.0.2 added a little bit more content to 0.0.1. The big jump to 0.0.3 introduced extensions, which are how most in-game interaction will be supported. 0.0.4 is mostly user interface parts and some fixes to show off what was possible in 0.0.3.
The most important change included in 0.0.1 was the switch to a reflection-based packaging system. Json.NET is capable of deserializing json into objects, but I often use different representations for in-game objects and on-disk objects. Previously, every type that used this system had its own method that copied all the appropriate data out of a definition; now, there's a method that will copy the values out of a definition and into an object, using a series of attributes to define things like nested types and conversions to in-game representations (for example, List<> to HashSet<>). It also adds a bunch of quality of life things, like object name validation (changing "Name" to "MyPackage.Name") and automatic delegate insertion. The first thing to use this were the biome definitions, which consist of several nested things to define. With 0.0.3 and extensions, this system is somewhat more abused: extension types are chosen and created at runtime and populated via this system. As long as the chosen extension type is in the dictionary of allowed types, it's possible to initialize and populate something this way, which shows off both the power and limitations of the system. Because I didn't design the system to be used outside of the "SomethingDefinition yields a SomethingObject" way, you can't use attributes to aid in deserialization, which is something I plan on fixing in the next release. In fact, I might rebuild large sections of it to reduce work and more cleanly load simpler objects. Most changes I'd need to make for that boil down to changing some types throughout the program, adding attributes to things, and changing where the system looks for said attributes.
Among other major changes coming to the packaging system is proper support for overriding other objects. A basic inheritance model is in place for materials--behind the scenes, materials are just block definitions; however, values are still filled when deserializing something: even if a block starts out as a clone of the material block type, the system will copy over all the zeros and empty strings if fields are uninitialized in json. To get around this, it's possible to mark a field as inherited if it matches a default value. That's easy for strings and lists but problematic for booleans and integers. There's multiple ways to tackle this, from parsing the raw json to adding a field to definition objects that lets people define what they're inheriting/overriding. While the solution will probably be a combination of the two (and a lot of refactoring), it touches on an idea that's important to a game with mods: overriding. The system currently supports a complete replacement of an object in the game: ensure your package is loaded after the one you want to override and present and object to replace the previous version, using that package's name as a prefix. I think it'd be a lot safer to provide modders (and myself) with a simple way to define an object as a parent to inherit from, basically copying it into a new name and changing some fields, or define an object to override selected fields with.
Overriding also touches on a different issue: packages. Right now, packages are dumb containers for assets and definitions. The game looks at them once, loads everything in them, and never bothers them again. To support things like hierarchies, packages need to be able to define a load order. Another small problem I run into with packages has to do with fonts. Currently, a font is loaded at all the default sizes, which means for every font, the game makes six or so versions at various font sizes. This is easily fixed by letting packages define the sizes for the fonts it includes; it just wasn't important enough to implement before. Eventually, I'll add and read more data about packages, and add menus that interact with them--things like a package chooser when creating new world and splitting the creative menu by package.
One of the other important features added in 0.0.1 was text rendering. SDL has an extension library that handles the drawing of glyphs to a SDL_Surface, however, it’s quite slow, so often-changing text is problematic, plus, it’d mean I’d have to push new surfaces to the graphics card every frame. Instead, I pre-render each font to a texture, tracking the areas of all the glyphs. An in-game label looks up the appropriate glyphs and draws them, using the generated texture. Because resizing the window destroys all target textures, I have to re-render every font when the window is resized, but it’s better than trying to create a new surface whenever a line of text changes. My system doesn’t currently handle kerning, which I’m thinking about adding. SDL_TTF doesn’t hand you nice kerning data, so I’d probably have to extract it by checking each two-character combination and finding the difference in width for the kerned v. unkerned versions, then shifting each character by that when drawn, but I’m not sure if anybody would notice either way. (Plus, it’s probably a good idea to minimize the number of Dictionary>’s floating around)
Extensions were the big thing added in 0.0.3, allowing me to track data and events for in-game objects. An extension is just a class with a bunch of event handlers, capable of saving and loading which events it subscribes to through the standard save/load methods. This isn’t quite as simple as it sounds--C# delegates can’t be safely serialized--so it has to track the names and types of the delegates it has, then load them from GameInfo (the registry where types of things are kept) when being deserialized. I have play a few tricks with reflection to make registering delegate and extension types and adding delegates to extensions not just easy, but also automatic. Instead of talking about how the extension system works, I’ll go through how all of the extended blocks and tools function:
- Laser: all tools use an ItemExtension to get access to BeginUse, Update, EndUse, mainly. The laser is quite simple: in BeginUse, it sets its Active flag on, then damages the first block in a raycast from the player during Update.
- Builder: the update function calculates a raycast every frame to accurately draw the beam. BeginUse places a block at the end of the raycast.
- Mover: in BeginUse, it checks its ray to see if it hits an Entity. If it does, and the entity is a minimum distance away from the player, it stores a pointer to it. During Update, it sets the velocity to a vector pointing to the mouse, and tries to rotate it back to 0 degrees.
- Launcher: this one only has a BeginUse function, where it creates a grenade entity and initializes it to fly in the direction of the mouse. Soon there will also be entity extensions, so instead of the logic behind grenades being locked up in compiled bytecode, I can expose it to json like other in-game objects.
- The “overpowered” tools: these use the same code as the other tools, but with all the numbers increased.
- The test tools: these all use ItemExtension as their extension class, meaning they rely on Extension.UserData to store data relating to their abilities. Most of them have a BeginUse handler, but a few use Update.
- Explosives: blocks have an appropriately named BlockExtension that gets access to Place, Break and Update events, but also similar events for adjacent blocks. Explosives are simple: when they break, they call Sector.TriggerExplosion.
- Damage Balancer: I think these are probably the coolest out of the series of example blocks. They have an update function that attempts to balance the damage from the surrounding eight blocks based on each block’s maximum allowed damage. These in chain can make some cool shield-like structures.
- Self-Repairer: a very simple block that removes damage from itself in Update.
- Super Damage Balancer: this block combines the previous three: on update, it attempts to balance damage between its Moore neighborhood and itself. It’s more efficient than a regular damage balancer, but it explodes when it breaks! I think it’s a neat way to show off easy it is to create new blocks from old ones: all it has to do is define the UserData needed to support explosions and repair, then add the appropriate delegates.
- Rigger: this is probably the most confusing of the bunch. When it’s placed, it attaches extensions to the blocks next to it, so that they explode when they break. Apart from that, it doesn’t do anything else, and can be safely placed and removed to make traps.
Sparse has come a long way, and certainly feels as if it’s on the brink of becoming a “real game” as it were, instead of the sort of tech demo/toy it’s been for over a year now. However, it’s not there yet, and there’s still lots of work to do, both in making it a game, but also in finishing the things I was talking about in the other development logs! I’m nowhere close to building a runtime property inspector, which was one of the first things I wanted to add. It’s easy to focus on the far-away things and miss out on the medium-away things. I think the next release will be the last release where I’m purely working on toys for the game, and the release after will start bringing things I could call “gameplay” while keeping a straight face. I’m excited.
You can get the latest build of Sparse (0.0.4) in the release thread