On Writing, or Things I Know Nothing About

NEON STRUCT is my first game in which writing plays a major role. In Eldritch and my older hobby projects, narrative was thin to non-existent. I can’t recall ever writing character dialogue.

My inexperience in that realm—and its disproportional importance in this game—is part of why I chose NEON STRUCT over other game concepts I was considering this year. If I don’t challenge myself to acquire new skills, I will stagnate. I don’t want to keep making the same games over and over.

Because I feel far less competent as a writer than I do as a programmer or even as a designer, I haven’t blogged much about the narrative of NEON STRUCT. But I recently had an interesting experience with writing that seemed worth sharing.

By virtue of writing a game with a fixed and clearly defined scope, I have naturally landed upon what I guess is called the fractal or snowflake method of writing. Beginning with a high level premise (“A spy on the run from her former agency uncovers a global surveillance conspiracy.”) and a series of levels which seemed like interesting stealth game destinations, I have gradually explored and expanded the story as I work on each level.

This week, I built the game’s second level. I have known for months that this level would serve as the catalyzing moment in the story, but the details of it remained fuzzy until this very week. As I considered the specific conversations that the player would have with characters in the level, I began to worry that the premise of the level and possibly the entire game was flawed. By working from a top-down story expansion, I had only considered major beats and not such subtleties as character motivations and backstories.

So I stepped back from the game’s content and explored the story further. I developed backstories for each party and found rational explanations for their actions. I even discovered a clever way to connect two remote parts of the story. It felt like solving a jigsaw puzzle, turning the various pieces until they aligned and cohered into a complete picture.

Then I wrote the actual game dialogue, and almost none of that background made it on screen.

I think that’s okay. I want the dialogue in the game to be concise, even minimalist. Maybe it’s my lack of confidence as a writer, but I want the game to say only what it needs to say and nothing more. But the exercise was not in vain. My exploration of the story solidified the foundation for what is presented to the player. It can now withstand questions about logic and motivation. And if nothing else, I learned something valuable from the experience.

Out-of-Order Execution

One of the simplest but most useful lessons I learned while working at 2K Marin was: build the first level of the game last.

The first level of the game has a critical function. It introduces players to the world, the story, and the mechanics. But it must not feel like a history lesson or an instruction manual. The first level is the first impression a player will have of the game (marketing materials aside). It should be exciting, visually striking, and uniquely memorable. If the first level is boring or confusing, the player may put the controller down and never come back.

These myriad requirements are difficult to balance at any point during development, but they are virtually impossible to satisfy at the start of production. When a project is young, there are systems and assets to be built, best practices to be discovered, scheduling snafus to be resolved. The process of development informs further development. At inception, there is barely any context for how to make a good level, let alone the most critical level in the game.

So the practice goes: build the first level of the game last. At the end of production, with all the acquired tools and techniques and code and content, a developer is best equipped to tackle the challenge of the game’s opening moments.

The first set of levels I built for NEON STRUCT are those in the middle third of the game. I originally planned to continue to the endgame content and finally come back and build the first third last. I changed that plan recently to support a playable demo for testing and marketing purposes. I need the demo to stand alone as a coherent experience, which means it carries many of the same expectations as the first level: it must introduce the story and systems while making a great first impression. As I could not afford to build a throwaway level, the most appropriate plan was to build the first level now and use it as the demo.

Although I did not wait until the absolute end of the project to build the first level, the benefits of doing it later in production were clear. I had a larger set of assets. I had an improved scripting language. I more fully understood the story. I knew how to work within the constraints of the voxel engine to produce interesting, visually appealing spaces.

And perhaps there will be a separate advantage in not waiting until the last minute to build this level. I now have sufficient time to playtest it and make any necessary improvements for the shipping version.

Semantics and Syntax of Scripting

The core game mechanics/scripting architecture used in NEON STRUCT is event-based. When an interesting event occurs, an event message is sent to relevant observers to be handled as needed. For example, when the player interacts with an object, that object receives an OnFrobbed event message and may implement a reaction to it. The desk lamp in the screenshot has an OnFrobbed reaction that plays a switching sound effect and toggles its light component on or off.

This event/response design is not unique or original to NEON STRUCT. I first developed it for Eldritch, but event-based systems have been used in games for many years, and this particular implementation was largely inspired by Elan Ruskin’s GDC 2012 presentation, “Rule Databases for Contextual Dialog and Game Logic.” (PDF, 18MB)

Although this event system is expressive and has been useful for me to author the game logic and scripting in two games so far, I recently found myself struggling with the particular syntax I used to described event rules and reactions.

Under the hood (in the C++ library that implements this system), each rule and each action is an object whose members are populated at runtime, like all data in my engine, from a configuration file. (These configuration files are written in a modified INI format and parsed and transformed into a binary format as a content bake step.)

As such, my default way to set up rules and reactions was simply to write an INI block for each object. Here is an actual example, of the reaction which generates an AI noise (i.e., a virtual sound which AIs can “hear” and react to) when an unconscious body lands on the ground after being dropped by the player.

[BodyLandedReaction]
Rule       = "BodyLandedRule"
NumActions = &
@ Action
@@&        = "BodyLandedPlayAINoise"

    [BodyLandedRule]
    Event         = "OnLanded"
    NumConditions = &
    @ Condition
    @@&           = "IsDeadPE"

        [IsDeadPE]
        PEType = "QueryActionStack"
        Key    = "IsDead"

    [BodyLandedPlayAINoise]
    ActionType    = "SendEvent"
    EventName     = "OnAINoise"
    NumParameters = &
    @ Parameter
    @@&Name       = "NoiseRadius"
    @@^Value      = "BodyLandedNoiseRadiusPE"

        [BodyLandedNoiseRadiusPE]
        PEType = "ConstantFloat"
        Value  = 2.5

Despite appearances, this is a fairly simple script which can be stated in one sentence: when the body receives an OnLanded event with the context “IsDead”, it generates an AI noise with a radius of 2.5 meters.

The syntax of the format isn’t especially relevant here. What is important to notice is just how much overhead a simple definition like this one required. In particular, the parts highlighted in red are semantic cruft used to reference each object, and the parts highlighted in blue are syntactic cruft used to build arrays of items, despite each array having only one element in this example.

After a few months of writing increasingly complicated scripts and suffering with the burden of this syntax, I began to consider alternatives. At first, I thought of replacing this system wholesale with a well-tested language like Lua, but that was too risky to do in the middle of a project. I wanted a solution that would not destabilize NEON STRUCT, would continue to support all the existing content, and would expedite future development.

Given those goals, my ideal solution was to design a higher level language which would compile down to the INI file format I was previously writing by hand. Existing scripts would still work. I wouldn’t have to change a single line of C++ code; all the game and engine and tools code would continue to work just as they always had.

As I’m the only intended user of the language, the design phase was extremely simple. Starting with some example scripts similar to the one above, I mocked up new versions in a streamlined syntax. With about half a dozen test scripts, I covered the vast majority of my real world use cases.

Over the past weekend, I wrote a simple compiler in Python. I somewhat underestimated the challenge of that part, thinking that transforming from one textual representation of the data to another would be fairly straightforward. But in order to support the syntax I wanted in my new language, it became necessary to implement a complete compiler with lexing, parsing, and semantic analysis.

And now for the big reveal. Remember that block of crufty INI file definitions I pasted up above? With the new language, that same event reaction can be expressed like so:

Reaction( Rule( "OnLanded", QueryActionStack( "IsDead" ) ) )
{
    SendEvent( "OnAINoise", NoiseRadius = ConstantFloat( 2.5 ) )
}

All the important data is still expressed here—the “OnLanded” event, the “IsDead” context lookup, and the AI noise with the 2.5m radius—and the extra garbage that the game needs to load that data gets automatically generated as a content bake step.

I’ve been using this new language to implement NEON STRUCT‘s introductory level this week, and it has erased virtually all the cognitive overhead of scripting. I even knocked out a few extra scripting tasks I had been putting off for months, just because it was now so easy and pleasant to do.

All of the Lights

A few friends have commented on or asked about the lighting in NEON STRUCT, so I’m going to provide a brief overview of how it works.

For context, let’s first look at the lighting in Eldritch to see how the system developed.

In Eldritch, there are few lights and usually a bright directional ambient term. (I’ve chosen a dark level here to illustrate other lighting characteristics, but levels like R’lyeh were almost entirely lit by the ambient term.)

Lighting is semi-static and baked into the voxel grid. Each 1m3 cell acts as a low frequency light probe and stores the incoming light on each axis: X±, Y±, Z±. Each light value is represented as a 4-dimensional vector of floats, and each cell contains six of these. 4 bytes * 4 * 6 = 96 bytes per cell. In Eldritch, these values were stored in a flat array, totaling about 6MB of light data per map. Lights can be added, removed, or changed at runtime, but this requires resolving all light sources influencing the local voxels and rebuilding the local voxel hull meshes. This can usually be done within the span of a 60Hz frame without causing a hitch, mostly because Eldritch has few lights, and they have relatively small radii.

When the voxel hull meshes are constructed, Eldritch queries the singular probe on the front face of each voxel and lights the voxel by baking that light value into the mesh’s vertex color. Using only the local light value produces discontinuities between voxels, causing the “blocky” effect indicated in the image above. (The most recent update to Eldritch adds light smoothing from NEON STRUCT as described below, but the blockiness was present in all prior versions.)

Objects are also lit using the voxel light grid, but in a different manner because the lighting cannot be baked into the vertex stream. Each tick, the renderer looks up the light probe nearest to each object’s centroid and passes its values to the pixel shader. After transformation, the mesh is lit by multiplying the components of its per-pixel normal by the corresponding values in the light probe, like so:

float4 LightProbe[6]; // X+/-, Y+/-, Z+/-
float4 GetLight( float3 InNormal )
{
    float3 NormalSq = InNormal * InNormal;
    int3 IsNeg = InNormal < 0.0f;
    return
        NormalSq.x * LightProbe[IsNeg.x] +
        NormalSq.y * LightProbe[IsNeg.y + 2] +
        NormalSq.z * LightProbe[IsNeg.z + 4];
}

This produces soft per-pixel lighting which roughly approximates the directions of incoming light.

NEON STRUCT builds upon Eldritch‘s lighting system, with a few important changes. The two most apparent differences are smooth world lighting (no more blocky discontinuities between voxels) and cel shading on objects.

Smoothed world lighting is simple in theory: just blend the light values between each voxel. In practice, it was somewhat trickier than I expected. Each corner of a voxel’s face is lit by the local light probe and the three probes adjacent to that corner, but only if the adjacent voxels are non-solid. If an adjacent voxel contains geometry, it needs to be ignored in the smoothing operation or else it will darken the voxel’s face in an undesired way. (Voxel lighting in both games already has an ambient occlusion factor to darken concave edges and corners.)

Object lighting in NEON STRUCT is similar to that in Eldritch, but the shader is modified to produce a pseudo cel shaded effect. Unlike typical cel shading, in which the color is quantized to produce discontinuities, I chose to quantize the direction of the per-pixel normal to the cardinal axes. This produces discontinuities in color even when the colors in each direction are relatively similar, so objects in low light scenes will retain some definition and not be completely flattened out.

float4 LightProbe[6]; // X+/-, Y+/-, Z+/-
float4 GetCelLight( float3 InNormal )
{
    float3 NormalSq = InNormal * InNormal;
    int3 IsNeg = InNormal < 0.0f;
    int UseX = NormalSq.x >= NormalSq.y && NormalSq.x >= NormalSq.z;
    int UseY = ( 1 - UseX ) * ( NormalSq.y >= NormalSq.z );
    int UseZ = 1 - ( UseX + UseY );

    return
        UseX * LightCube[IsNeg.x] +
        UseY * LightCube[IsNeg.y + 2] +
        UseZ * LightCube[IsNeg.z + 4];
}

The less visible changes since Eldritch include significant optimizations for adding/removing/changing lights, as described in a couple of previous posts. Furthermore, the flat array of lighting data (which was over 6MB for Eldritch‘s small levels) needed to be converted into a sparse array for NEON STRUCT‘s larger levels. Now, any voxel which is lit only by the ambient term does not store a light probe.

And while it’s not strictly part of the lighting system, the addition of bloom in post processing contributes a lot to the apparent brightness of the scene. Post process effects could be the subject of a future post, but in brief, NEON STRUCT‘s post chain starts with chromatic aberration (RGB separation), adds bloom, subtracts film grain noise, and finally applies color grading.

Meet the Cast

I finished drawing the portraits for the primary cast this week, so today’s post will be a quick one to introduce them. As with everything described in this blog, names and details are subject to change.

This blog has been hacked by Unit. "password123", David? You can do better. —Unit

Spoiler warning: I’m not going to give away any major plot points here, but there is obviously some spoilage potential in knowing who the characters in NEON STRUCT are.

Hello dear reader. Don't believe the intel here! We know the truth. —Unit

Jillian Cleary
The protagonist of NEON STRUCT and the character you’ll be playing as. Jill is a senior field agent for the Agency, a monolithic U.S. intelligence-gathering agency.

Vinod Chhibber
As Jill’s handler at the Agency, Vinod provides guidance, intel, and support while Jill is in the field. An advocate of privacy rights, he is deeply concerned about secret mass surveillance programs in development at the Agency.
We can trust him. —Unit

Frank Furtwengler
The current Director of the Agency, Frank is responsible for implementing its broadest surveillance programs.
Possible ties to Oculi Mundi? —Unit

Alyssa Lawrence
No intel available.
Possible smokescreen? Should warn AL she's on file. —Unit

Grace and Phil Bouchard
Old college friends and former roommates of Jill’s. They had a falling-out with Jill around the time they got engaged and have not spoken with her in years.

Peter Tannhäuser
Entrepreneur. Venture capitalist. Founder and CEO of TannCo, a private media corporation based in Cologne. Depending on whom you ask, Peter is either a fierce advocate of liberty and transparency, or an anarcho-capitalist bent on taking down the state for his own gain.

Beatrix Cheung
British Consul to Germany.
What's her connection to Dir. Furtwengler? —Unit

PAX, Procrastination, Persistence, and Plans

Despite missing last week’s devlog update, I don’t have a whole lot of substantial progress to talk about this week. I was at PAX Prime over the weekend (just for fun—Minor Key Games wasn’t showing anything there), and my work focus has remained somewhat disrupted since I got back.

Most of my schedule was planned around the approximately two dozen levels in NEON STRUCT—which is also why many of my previous updates have been level-centric—but I have a modest backlog of tech and gameplay tasks to tackle as well. For months, I had been putting off that work to focus on the fun parts of level building. But since returning from Seattle, I’ve found it difficult to get excited about building levels, so I decided to revisit those overdue tasks.

As the primary developer on NEON STRUCT, it doesn’t really matter what order I do my tasks in. It all has to get done for the game to be finished. And I’m constantly busy, so there are no “pipeline stalls” even if one task is blocked by some other task. With this perspective, “procrastination” isn’t necessarily a bad thing. As I mentioned last month, periodic task switching keeps me energized and allows more time for me to reflect on the needs of a particular level or feature before continuing with its implementation.

At my last job, I often quoted Joel Spolsky’s “fire and motion” as a shorthand for, “You have to move forward a little bit, every day.” My progress this week has definitely been only moving forward a little bit, every day; but the little bits are adding up, and small progress is better than no progress at all.

One of these “little bits” is a non-combat enemy AI behavior. It is sometimes useful to have characters in stealth levels who regard the player as an enemy, but do not carry weapons or attack the player. When they discover the player, they will flee and alert other AIs in the vicinity.

This feature primarily depended on a new A* path search mode. My existing modes included a basic path search (where the heuristic minimizes distance to a destination) and a wander mode (where the heuristic is constant and the path expands randomly until a given distance is reached). In order to make characters run away from the player or any other entity, I added a flee mode (in which the heuristic maximizes distance from the target, up to a given distance).

The rest of the implementation was just small changes to the AI motion component (to periodically recheck the flee target’s location and repath if needed) and the character’s behavior tree to actually invoke the behavior.

Another recent “little bit” is the addition of flying drones. They operate as a hybrid of non-combat characters and mobile security cameras. I already had a good chunk of AI flight code from Eldritch, and it took only a few tweaks to produce the desired motion for these drones. In Eldritch, flying enemies would always descend to the player’s level when attacking. In NEON STRUCT, that behavior looked strange; my expectation was that drones would hover some meters above their target, so I simply added a vertical offset.

As these and other little bits come online, I am contemplating putting together an IGF build. The timing of the IGF isn’t ideal for me—NEON STRUCT is only 30-40% finished, and what I would submit would be only a slice of the final game—but it provides an incentive to make a tighter, more polished playtest build than I might otherwise commit to.

Stealth AI on a Budget

Enemy AI in stealth games can be tricky to get right. Enemies need to appear smart and responsive, yet be predictable enough that the player can form and execute a plan to avoid or escape them.

One of the best tricks to make enemy AI look smart is to make the character say something relevant to the situation. For example, if two agents engage in a brief call-and-response while searching for a target, many players will believe the AI is sharing knowledge between the two agents, regardless of whether it actually is.

In NEON STRUCT, I’m working without that particular secret weapon. For scope, budget, and tone reasons, I’ve chosen to forgo voice acting, so I’ve had to find other ways to indicate AI state. The quintessential example of a voiceless AI indicator is Metal Gear Solid‘s exclamation point, so I’m starting there. Enemies in NEON STRUCT have multiple awareness states, so each one has a different icon (and associated audio tone).

These states reflect the AI’s awareness of the player or other targets, and also correspond directly to the enemy’s behavior. The “…” means the AI has only limited awareness. It corresponds to the Notice behavior, which makes the enemy pause momentarily to look in the direction of the disturbance.

The “?” means the AI has increased awareness of the target, but not enough awareness to engage with a threat. For example, the AI may have heard loud footsteps or seen a dead body, but not yet found the actual target. This corresponds to the Search behavior, in which the enemy cautiously investigates the disturbance.

Finally, the “!” means the AI has enough visual awareness of the target to identify it as a threat. That corresponds to the Combat behavior, which is relatively simple because the player has no weapon of her own. Combative enemies chase the target to melee distance while firing a ranged weapon if available.

Awareness is a complex system that combines multiple fuzzy inputs (sight and hearing at various distances, illumination levels, etc.) into a discrete number of states. Determining the appropriate thresholds at which to switch between states is part of the secret sauce of stealth AI, and one of the factors which will be undergoing continuous evaluation and iteration during playtesting.

On the Level

I’ve spent the last couple of weeks blocking out two new levels (called “Streets3″ and “HighRise1″) and improving two older ones (“SafeHouse” and “Hospital”).

HighRise1 takes place about halfway through the game, and is mostly comprised of conversations. Writing dialogue has been a new challenge for me in NEON STRUCT, and I’m not entirely confident in what I’ve written so far; but I tend to err on the side of short conversations to keep things moving.

Streets3 has been one of my favorite levels to build so far. I’ve wanted to put a neon-lit 1980s-esque mall in a video game for a while, and I finally had a place for it in this game. I spent one morning last week looking up reference photos of dead malls, which was rather depressing despite my interest in the subject.

Streets3 is a social level the first time the player visits it, but will be repurposed as a stealth level on a return trip. I had originally planned for it to be a hub which the player would revisit several times during the middle part of the game, but streamlining the level flow negated the need for a hub. Building a level to serve as both a social and stealth space has been an interesting challenge. The social levels in NEON STRUCT tend to contain very open spaces, and Streets3 is no exception, so stealth play will necessarily be based more on shadows and enemy facing than occlusion.

I returned to the SafeHouse level to add some extra decoration and finalize the quests. I’m finding it to be more fruitful to bounce between levels than to try to complete each level in a week. It gives me a broader range of tasks to pick from on any given day, and affords a lot more time to reflect on the levels before continuing to work on them.

For example, the first pass of the Hospital was an oversized monolith of a level, built without a clear purpose for many rooms and too much concern for realism in its floor plan. I revisited it this week, hacked off a third of it, and redirected the flow to fit the tighter space. The result is a less realistic but far more interesting map, with fewer prefab rooms and more identifiable landmarks.

Finally, I threw together a training level for upcoming playtests. I don’t expect to ship this—tutorialization in the final game will be done organically in the first real level—but I needed a way to start play testers from any level of the game without confusion about the mechanics.

Whether I ship this level or not, the System Shock 2-inspired neon cube world (which I’m tentatively calling “the Struct” to retroactively justify the game’s title) will definitely be making another appearance somewhere else in the game.

Little Computer People

I’ve spent most of the last week revisiting the levels from the vertical slice milestone, rebuilding bits and polishing up things that got missed the first time around. But that’s not super interesting to write about, so I won’t.

I also finally implemented a skin tone randomizer which had been on my task list for months. I kept procrastinating because I wasn’t sure how I wanted to implement it. I was considering making a 1D skin tone gradient texture and sampling randomly from it, but first I decided to just try it using the same random color generator code I had already written for outfit randomization. The results (shown above) are surprisingly usable. There’s a few odd-looking outliers, but I think it’s good enough to keep.

The trick I use to randomly generate colors is to use HSV space instead of RGB. Randomly generated colors in RGB space tend to have a broader range of hues than is desired. By using HSV space and constraining the hue to a narrow band, I can produce a plausible range of skin tones by varying the saturation and value.

For reference, the ranges I’m using are:
H: 14.4° – 21.6°
S: 20% – 70%
V: 40% – 100%

Another long neglected task I finally got done this week was a proper treatment for conversation portraits. My original plan was to have stylized illustrations in glowing neon colors, probably traced over stock photo portraits; but I wasn’t satisfied with the effect. Recently, I had the idea to render the characters as neon signs. The line art by itself is unremarkable, but bumping up its value and applying the usual post effects chain (bloom, chromatic aberration, film grain, and color grading) makes it pop like a neon sign should.