Ducks in a Row

As previously announced, NEON STRUCT will be released early next year on Windows, Mac, and Linux. I don’t think I have specifically said so before, but the plan has always been to launch simultaneously on all three platforms. Like Eldritch, the Mac and Linux versions will be native ports, not Wine-wrapped Windows builds. Unlike Eldritch, I’m not waiting until the game is finished to start working on those versions.

The hardest parts of porting a Windows game to Mac and Linux—writing an OpenGL renderer parallel to the Direct3D one, and replacing certain low-level system calls with SDL—were already done from my work on Eldritch. I had written a few new file system utilities for NEON STRUCT that needed to be implemented with equivalent POSIX functions, but the most substantial thing left to do was simply to set up the workspaces and start compiling.

Another technical hurdle which I wanted to resolve sooner rather than later was Steamworks integration for achievements and stats tracking (and later, Steam Workshop—but probably as a post-release update). That process was mostly painless; the API is simple and fairly well documented, and the sample project provides everything needed to get started even before getting a Steam AppID.

To be clear, NEON STRUCT will not use Steamworks for DRM. As a player, I personally don’t mind Steam’s DRM. I think it is among the most painless, user-friendly implementations of DRM. But as we state in our “Plain English” EULA, Kyle and I believe there is no real value in choosing to use DRM. My intention is that, just like Eldritch, the Steam version of NEON STRUCT will work even if the user is not running Steam.

So, I didn’t get a lot of creative work done this week. But I got Steamworks-enabled builds running on every target platform, which clears up a considerable amount of uncertainty in the latter part of my schedule.

Voxel Veneers

Although it is not a “voxel game” in the contemporary sense—it has neither procedural nor deformable terrain—NEON STRUCT uses an advanced version of Eldritch‘s voxel system for quick and easy level building.

Voxels in this engine function like Minecraft‘s blocks: each one has a singular ID to define its properties, and is textured with up to three quads on it, for the top, sides, and bottom.

This system was perfectly sufficient for Eldritch‘s lo-fi, procedural worlds. But building more realistic spaces in NEON STRUCT, I kept running into a problem. In order to put different materials on each side of a wall, I had to use a two-voxel wide wall. At the 1m³ resolution of the voxel world, this requires 2m walls. It looks absurd and wastes space.

The worst case I encountered so far was trying to put an air duct (because of course this game has air ducts) between two rooms: 2m for the first wall, 1m for the duct, and 2m for the second wall. It was untenable.

Every couple of weeks, I would revisit the problem and try to find a solution. Fundamentally, I wanted to transform the world from an array of volumes into an array of faces so that they could be painted separately. For example, where a north-facing wall met an east-facing wall, each could have a separate texture, like so:

But many other systems—lighting, physics, pathfinding, and more—are dependent on the voxel array. Getting rid of that was not an option, and storing a parallel array of face data seemed wasteful and redundant.

I finally discovered a solution earlier this week. It seems obvious in retrospect, but it took me a few months to arrive at it.

The “eureka” moment was when I realized that I did not need to store a large parallel array of face data to accomplish my intention. Voxels already define their faces, and 99% of the time, I just want to leave those alone. In a few special cases, I want to override the default textures on a particular face, metaphorically applying a veneer over the voxel.

I implemented this as a sparse map of veneers, keyed off a bitwise combination of the voxel array index and an enumeration of the face normal (+/- X/Y/Z). When constructing the hull mesh around a voxel chunk, I check that map to see if there is a veneer for each face, and apply that texture instead of the default one if there is.

Now I can implement air ducts and other cases with much less wasted space:

I have also been using the veneer system to add decorative highlights which would otherwise have required an excessive number of custom voxel definitions.

Because the veneer is applied during mesh construction, there is no rendering overhead for using veneers. And because it uses a sparse array, it has a minimal impact on the file size of a level.

At the moment, surface properties (which are used to pick footstep sounds, for example) are still defined solely by the voxel instead of by veneers, but that could easily be overridden too if it becomes a problem.

Custom Missions and Mods

Just a brief update today to demonstrate the Custom Missions pipeline I’ve been working on. Users will be able to build levels (using the same tools I’m using to develop the game’s main levels), package them up along with any new content they want, and share them via Steam Workshop.

At the moment, these tools are only supported on Windows (because that’s where I do my development), and that is how I expect to ship them in early 2015. But they should be fairly straightforward to port—the editor is built into the game’s executable, and the other tools are simple command line utilities built from the game’s shared codebase—and I would aim to bring them to OS X and Linux shortly after.

(Just to avoid any confusion, the game itself will ship simultaneously on Windows, Mac OS X, and Linux; and custom missions can be downloaded and played on all three platforms. Only the tools to build missions will be limited to Windows.)

After downloading, the missions will populate this menu on the title screen. All sorts of other mods are supported too, and will be applied automatically just by putting them in a Mods folders. Incidentally, this should streamline the process of localization compared to Eldritch, as translators will be able to test their own changes immediately instead of waiting for me to create a new build.