Fractals, Fresh Eyes, and the Fear of Feedback

This week, I have been wrapping up a three-level demo of NEON STRUCT for future playtesting and press needs. This means bringing the first three levels in the game to a representative shippable level of completion.

Polishing a level in a game is like recursively refining a fractal. As I zoom in on the details, I find an infinite amount of further work I could do to improve the game. The shape of NEON STRUCT is not substantially different than the shape of most big budget video games, but the distinction lies in where we choose to stop iterating on the fractal, refining the little details. In Eldritch, I chose to go deep on systems and mechanics, at the expense of virtually any aesthetic polish. In NEON STRUCT, I’m spreading my time a little more evenly; but with a limited budget, it is important that I know when to stop polishing and call it done.

The playtest build will go out to a small number of trusted players within a week. I usually dread the feedback that comes from playtests, because it disrupts my schedule and damages my confidence, but I know it is an essential part of making a successful game. It is also exactly what I need right now. I have been building and playing this game too long to regard it objectively anymore. I need fresh players to evaluate the controls, the tuning of the stealth systems, the writing, and more.

I also need to do the playtest so I can realistically target a release date and start doing PR in earnest. If the feedback is mostly in line with my current plans, I will be on track for an alpha build in early February and a release date around the end of March. If the response implies the need for bigger changes, NEON STRUCT‘s release date will probably be later, around April or May.

Pigeons

Brief update today.

I woke up on Tuesday like, “I’m going to put pigeons in NEON STRUCT!” It had been on my task list for months, but it suddenly seemed critically important to do immediately. In the middle of developing a demo build. While one entire level for that demo still had no primary path scripting.

Mechanically, pigeons are simple alarm systems. If the player disturbs a pigeon or wanders too close, it will fly away for a bit, alerting nearby guards to her presence. Pigeons can’t distinguish between human factions, so enemies may disturb them too. If the player sees a couple of birds fly past, it’s a good bet there’s something approaching.

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.