Iterating on the Blueprints UI

It’s been awhile since a Triverse update, but there’s been plenty of work on it! Most has involved internal systems to support networked multiplayer, large worlds, and Unity integration, but more recently I’ve spent time on UI changes that are a bit more visual.

The previous UI for part selection worked decently, but it had a few deficiencies and needed to support several new features. One of those features was unit blueprints, where players can save a copy of a unit layout and fabricate it later. During prototyping, this looked like a great way to support fleets at a larger scale, and it would also support players sharing their designs later on.

Two big questions:

  • Is it feasible to include both parts and blueprints in the same portion of the UI. In initial attempts, I tried drawing unit outlines and fitting them into part borders, but they didn’t look very good or convey the scale of the object.
  • Can the action bar go elsewhere? I’d initially wanted it to go at the bottom because that’s where they typically go, plus it would be near the other controls. However, it’s also been a bit confusing to see both the action bar and parts bar together.

The latest attempt has proportionally-sized layouts drawn as connected graphs, with cores highlighted:

JaggedBlueprintBarCropped

I tried another variation where only a portion of of the blueprint is shown. Vertical size doesn’t matter much for clickability since these are at the bottom of the screen, where you can always slam your mouse and hit a clickable region. It’s a cleaner, more uniform bar at the cost of omitting a portion of the structure:

BlueprintBarUniformHeightCropped2

Parts show up in the same UI bar alongside blueprints, although they behave somewhat differently during placement:

BlueprintBarWithPartsCropped2

Finally, unit blueprint placement:

BlueprintPlacementUI

On a side note, I’ve found it incredibly useful to use a shader with replaceable color channels to test out different color variations on the fly. The problem is that I can never find the right color combination, so I end up making adjustments that ultimately probably don’t matter much. Or maybe I just need more colors!

On a related side note, the parts/placement UI is now blue instead of green, which should be good news to any colorblind players who want to distinguish valid and invalid placement regions:

InvalidPlacement

Writing a Concurrent Job Queue

I recently saw a review of the Doom 3 BFG edition source code. In particular, the multithreaded job system caught my attention because it’s something I’ve written in the past, and I wanted to revisit it for my current game projects. So naturally, I had to roll another one using a similar approach as id Tech 5, which puts strong emphasis on avoiding locks. Keep in mind that in many scenarios, performance loss from locking may be either nonexistent or not worth the hassle of writing lockless code.

I’d also read about the LMAX disruptor last year, which is the name of the architecture of a high-traffic financial platform. The novelty it brought was heavily optimizing a single business logic thread to run all requests rather than splitting the work among separate threads and synchronizing. The system moved data to/from the business logic thread using ring buffer pipelines. These are great for moving data across threads without involving locks, and I’ve made generous use of them in my design. LMAX also included an event sourcing model to support failover scenarios, and the model of a single thread processing requests in serial works well for deterministic behavior and replays.

Threading

For anyone unfamiliar with threading, locking is a way of ensuring that only a single thread of execution runs in a block of code (a “critical section”) at any given time. Locking involves calls to the operating system and causes other threads to halt execution if they attempt to execute the critical section while one thread claims it. On the other hand, lockless approaches attempt to keep threads running by attempting to write to variables using operations atomic at the hardware level. When write the attempt fails, the thread may “spin” and try again, or it may go off and perform some other work. It’s great for avoiding blocking, but unfortunately lockless approaches can quickly become nontrivial and difficult to understand. One approach or the other may be more appropriate depending on the expected levels of contention over shared resources.

Besides contention, another big concern with multithreaded programming is passing data from one thread to another. Immutable objects are great because we can pass them around without ever worrying about how they’ll be used, but depending on garbage collection concerns and object usage, mutability may be more appropriate. For example, we may have large buffers passed around and employ pooling to reuse the space to avoid memory allocations or garbage collection. In this case, we need to consider whether there are other references to the object and decide whether or not to transfer ownership, and thus responsibility for destroying or recycling the object, to the called method. We could also clone the object and guarantee that nothing else references the new object, but this may have its own overhead depending on the cost of copying.

In C#, an obvious choice is to use the Task Parallel Library and TPL Dataflow, but I needed a solution to work in Unity3D, which is still at the equivalent of .NET 3.5 and does not include these APIs, although I’ve seen TPL backported to 3.5. I’m also slightly concerned about how a large number of Task objects (which cannot be reused) would affect garbage collection times.

Approach

We want to avoid processing spikes on the main thread that cause frames to be dropped. One way to mitigate spikes is to smooth them out by limiting the amount of work allowed in a single frame. This requires that the task can be partially completed, and continuations are a convenient means of creating a state machine to support this. Another way is to offload tasks to background threads, and the purpose of the job/task system is to make that easy.

The basic steps:

  1. Partition app/game into subsystems that support N threads in parallel with little or no synchronization. For example, a map generation subsystem might be stateless and support any number of threads, but a world simulation subsystem may have many complex internal interactions and only support a single thread at a time.
  2. On the main thread, queue up jobs for each subsystem. While this step only involves appending a job callback and argument to a list, careful thought needs to go into the ownership of any mutable arguments passed to the job system.
  3. Once per frame on the main thread, submit the batch of queued jobs to another set of shared pending queues accessible by the background worker threads.
  4. As new batches become available, the worker threads copy them into their own thread-local storage, and from there the jobs can be ordered, traversed, and selected freely without worrying about modifying a shared collection of jobs.
  5. While jobs remain unfinished, the worker threads attempt to acquire a lock on a particular job (using atomic primitives), and if the job is already taken, they continue on to search for another.
  6. Once a job is obtained, the worker thread runs it and posts its output value to an output queue readable by the main thread.
  7. When no jobs remain in local storage or in the pending queues, the worker threads sleep and wait for a signal to notify them that jobs are available again.
  8. Once per frame on the main thread, output queue entries are consumed and completion callbacks are called if the job was not canceled.

Many of the queues are implemented using a ring buffer with an overflow list. Ring buffers are allocated to a fixed power-of-two size and have head and tail pointers, with simple atomic operations to enqueue and dequeue that support concurrent access by a single producer and single consumer. Once capacity is reached, entries go into the overflow list, which does involve OS locks. Alternately, this could be implemented using a resizing buffer, but the code would be more complex and the intent is that a particular ring buffer size is chosen large enough to avoid reaching capacity under nominal loads.

There’s plenty more to talk about, but I’ll save it for another post.

Triverse Neon Graphics Revision

After getting feedback and comparing the old neon look and the new lighted look with a variety of ships, I chose to go back to the neon look and enhance it to support team/faction colors. With the neon look, I found that:

  • Parts are more distinguishable.
  • Part power levels are more visible.
  • Less repetition when tiling due to color variation (aesthetic).
  • Unique, recognizable aesthetic for Triverse.

The main concern was how to clearly distinguish opponents from the player’s own units, which is why I’d started looking at tile alternatives in the first place. With the original neon look, each tile could have a separate color, and an obvious way to show opponents would be to apply an overall tint or hue shift to the colors. However, every variation I tried looked terrible, and it also confused the notion of part colors corresponding to power levels.

Revised Neon Graphics

Fortunately the changes I made for lighting also provide support for defining separate colors for regions within tiles defined by RGB channels, which allows for more granular control over tile coloring.

Here’s a test of a color theme for opponents, with cores glowing red:

DarkEnemyColors6Cropped

So the plan now is to define an overall theme color and a core glow color for each separate team. Using cores as a primary identifier fits well with the game logic because ships require at least one core, otherwise it’s a floating piece of scrap without any owner. Cores also never had any strong color variations from power level as other parts do, so it isn’t much change to use a specific color for them. The difficulty is mainly in getting them to look decent in using the separate region color channels.

Triverse Tile Graphics Update

Graphics have gotten a major update! Here are some ships with the new look:

NewGraphicsShip1NewGraphicsShip3
NewGraphicsShip2NewGraphicsShip4

Gameplay comes first, but graphics bubbled back up as a priority because they were blocking several gameplay issues:

  • Visualizing team membership: The previous graphics supported a single color per part, but several variables need mapping: team, power level, energy/damage level, and whether the part is firing, selected, or highlighted.
  • Streamlining tile asset pipeline: I can create tiles in Inkscape, but it’s a manual approach. I want more automation in the process as well as control over what assets are produced from my original content.
  • Aesthetics: I want a less abstract appearance and a better sense of depth. This is about immersion; ships should feel like solid, physical objects.

New Tile Rendering Mode

Tiles now have lighting applied and region-based coloring. Each tile is still rendered as a single triangle, but vertices hold more data and the shader does a bit more work. Performance is not noticeably different, although rendering is nowhere close to being a bottleneck at this point.

Drawbacks:

  • Power level visualization: Previously, power level was clearly indicated by the hue of parts, but now it’s generally more subtle through power regions and glow. Build mode might be a good place to enhance the power colors more.
  • Overall ship color to indicate team: This might be limiting in terms of what variations in color specific parts can have. In most games, team color is shown as trim on structures/units/etc, but here the parts are too small for that to work well. Some parts already have power regions as well that could make this ambiguous.
  • Portability: Shaders do more work now, which might present issues when considering less powerful devices (tablets?).
  • Modeling difficulty: My modeling approach amounts to writing functions in a text editor.

Generating Triverse Tiles

To generate tiles, I enhanced Eval2D to use DirectX 11 for sphere tracing, then wrote appropriate signed distance functions to produce shapes that look reasonable as ship parts. Turns out HLSL has built-in automatic symbolic differentiation, which means I can easily obtain normals without resorting to ddx/ddy or additional sampling. All the new tiles assets are generated as a build step or as part of unit tests for convenience. I figured this way would be more interesting than learning/automating Blender.

Normal map generated for a cell junction:

JunctionCellSmall

Obligatory noise/sphere reflections (not related to game!):

SphereTracing3Small

References:

Next Steps on Graphics

Graphics are out of the way for now, but I expect to revisit these areas at some point:

  • Terrain tiles: The rock tiles are good enough for now, but this will need improvement once I introduce additional kinds of terrain. On the aesthetic side, they aren’t exactly seamless and they don’t quite match the new look.
  • Effects: HUD, projectiles/beams/trails, particle textures can be improved, but changes would largely be aesthetic and lower priority.
  • More tile shading variety: The colors still look too flat and uniform. It might be worth looking at mapping power to lightness in some way.

Triverse Part Group UI Design

Depending on their size, ships in Triverse can have 2-7 part groups, which typically consist of weapons. These groups allow players to select a subset of parts with a single click, which they can then fire or target on enemies. Groups also provide the basis for auto-targeting and I hope to use them as gunner slots in multiplayer. As a ship is modified, groups are automatically updated to provide sensible coverage around the ship (e.g. one group is the forward-facing weapons, another has the ones in the back or the side, etc). The player can also configure groups manually (with no extra screens!).

New Design

Group visuals have long needed an update. Until recently, I’ve just used the primary weapon type as an icon, which is terribly ambiguous given the build bar below it using identical icons, albeit in a different color. Type icons also provide no information about where the parts are located within the ship. So I prototyped a few variations in Inkscape before choosing a new kind of icon that captures the spatial distribution, although unfortunately no type information.

Before, with groups on top:

Original design with group icons on top

After, with groups on bottom with new icon style:

New design with group icons on the bottom with spatial layout

The sections within the icons indicate rough regions of the ship that contain a part in the group, a bit like Talairach coordinates without having to unfold gyri. Hopefully the icons aren’t too abstract given the huge variety in ship layouts.

Aesthetics Versus Usability

I also ran into a design problem. Previously, the group icons were on the top bar, pointing downward. In the new scheme, it would be unintuitive to show icons representing the ship upside-down. I’d also tentatively planned to put gunner status information close to the group icons, so I didn’t want to put them at the very bottom of the screen. So the choices were:

  • Keep the orientation upside-down. Not good because forward is intuitively up.
  • Swap top/bottom and move both bars to the top of the screen. Not good because virtually every game has status on the top and buttons on the bottom.
  • Change from the interleaving teeth layout so both rows have point up. Tried some variations, but the layout is something I want to keep.
  • Swap top/bottom and figure out status later. Not ideal, but the other choices were worse. There’s another slight complication because I’ve mapped number keys to the build bar and function keys to groups, and those keyboard locations previously matched the UI positions.

I went with the last option. I’m not keen on seeing the build bar/icons upside-down now, but  they don’t need to convey spatial information.