June 15, 2018•687 words
lets see how this works
Introduction and Background
Separating game data from logic in Unity has never been something that’s been particularly easy. The introduction of ScriptableObjects was welcome addition that addressed some of this concern, but despite their utility they have certain limitations that still make them a non-ideal data container.
Data still needs to be authored and edited inside of Unity.
ScriptableObjects are managed assets, meaning they need to be properly incorporated/imported into Unity’s internal AssetDatabase.
A ScriptableObject isn’t “pure data” - you still need the constructs of a class to define the data and then make an instance of that class to hold your data.
Runtime editing of ScriptableObjects writes changes to the source file, meaning you need to create shim objects to act as a data interface to “protect” your data.
ScriptableObjects don’t natively scale. Having 100 items in your game would mean you need 100 individual assets (Uber-objects are a possible, but bad, pattern that could circumvent this).
None of the above make working with ScriptableObjects a “bad” experience. I think if you’re working on a small project they are totally serviceable, but as projects grow in scope ScriptableObjects can easily become unwieldy.
I was thinking about this recently and thought to myself that there had to be a better way. Something that really allowed you to separate data management almost entirely from Unity, but also provide a great interface to using it.
I think that a lot of people that get this itch settle on finding some way to manage external data through .csv or .xml files and then manage integrations with those data types. This satisfies the need to have your data be centralized, but even this felt unnecessarily bulky, as you still need to manually create objects and classes whose signatures somewhat match the data your loading. I instead wanted something that felt like it flowed, something that made data feel like an intrinsic part of my code, something that went beyond data loading and unloading.
It wasn’t until a few weeks after having this thought that I found this article on Dead Cells on Gamasutra and learned about a curious little tool called CastleDB, which made me think that something like what I wanted to do was possible.
CastleDB is, in short, a WYSIWYG JSON editor that makes working with JSON feel more like working with a traditional spreadsheet. It’s also made with the explicit purpose of being a “database editor for games”. It was created by Nicholas Canasse, a relatively prolific game developer who is probably better known as the creator of the Haxe programming language (which is also what CastleDB is written in). CastleDB represents his attempt to unify game data with game code, and looks to be successful at doing so. CastleDB has since been leveraged on the recently fully-released Dead Cells, and seems to be used behind the scenes for a fair number of other Haxe games.
CastleDB is also meant for Haxe, and as such uses a specific language integration that, once I saw it, looked exactly like what I was imagining when I was thinking about “data as code”. The code sample below is how you interface with CastleDB in Haxe:
static function main()
I won’t spend too much time talking about the appeal of Haxe for game development (there are already a few articles out there), nor is this article an attempt to convert you to Haxe (we’re talking about Unity!), but it is worth touching on what makes the Haxe/CastleDB interop special.
CastleDB leverages Haxe’s ability to use macros. A macro can mean a lot of things but in this case it allows you to do some really interesting things with edit-time code completion. In the video below you can see how, by creating and then importing a database object, you have direct access to the data at edit time and can, in a typesafe manner, access items from your database as soon as they are added.