• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
  • entries
    94
  • comments
    271
  • views
    150188

Entries in this blog

evanofsky

Alone on a Friday night, in need of some inspiration, you decide to relive some of your past programming conquests.

The old archive hard drive slowly spins up, and the source code of the glory days scrolls by...

Oh no. This is not at all what you expected. Were things really this bad? Why did no one tell you? Why were you like this? Is it even possible to have that many gotos in a single function? You quickly close the project. For a brief second, you consider deleting it and scrubbing the hard drive.

What follows is a compilation of lessons, snippets, and words of warning salvaged from my own excursion into the past. Names have not been changed, to expose the guilty.

2004

I was thirteen. The project was called Red Moon -- a wildly ambitious third-person jet combat game. The few bits of code that were not copied verbatim out of Developing Games in Java were patently atrocious. Let's look at an example.

I wanted to give the player multiple weapons to switch between. The plan was to rotate the weapon model down inside the player model, swap it out for the next weapon, then rotate it back. Here's the animation code. Don't think about it too hard.public void updateAnimation(long eTime) { if(group.getGroup("gun") == null) { group.addGroup((PolygonGroup)gun.clone()); } changeTime -= eTime; if(changing && changeTime <= 0) { group.removeGroup("gun"); group.addGroup((PolygonGroup)gun.clone()); weaponGroup = group.getGroup("gun"); weaponGroup.xform.velocityAngleX.set(.003f, 250); changing = false; }}

I want to point out two fun facts. First, observe how many state variables are involved:


  • changeTime
  • changing
  • weaponGroup
  • weaponGroup.xform.velocityAngleX


    Even with all that, it feels like something's missing... ah yes, we need a variable to track which weapon is currently equipped. Of course, that's in another file entirely.

    The other fun fact is that I never actually created more than one weapon model. Every weapon used the same model. All that weapon model code was just a liability.

    How to Improve

    Remove redundant variables. In this case, the state could be captured by two variables: [font='courier new']weaponSwitchTimer [/font]and [font='courier new']weaponCurrent[/font]. Everything else can be derived from those two variables.

    Explicitly initialize everything. This function checks if the weapon is [font=consolas][size=1]null[/font] and initializes it if necessary. Thirty seconds of contemplation would reveal that the player always has a weapon in this game, and if they don't, the game is unplayable and might as well crash anyway.

    Clearly, at some point, I encountered a [font='courier new']NullPointerException [/font]in this function, and instead of thinking about why it happened, I threw in a quick [font='courier new']null [/font]check and moved on. In fact, most of the functions dealing with weapons have a check like this!

    Be proactive and make decisions upfront! Don't leave them for the computer to figure out.

    Naming

    boolean noenemies = true; // why oh why

    Name your booleans positively. If you find yourself writing code like this, re-evaluate your life decisions:if (!noenemies) { // are there enemies or not??}

    Error Handling

    Snippets like this are sprinkled liberally throughout the codebase:static { try { gun = Resources.parseModel("images/gun.txt"); } catch (FileNotFoundException e) {} // *shrug* catch (IOException e) {}}

    You might be thinking "it should handle that error more gracefully! Show a message to the user or something." But I actually think the opposite.

    You can never have too much error checking, but you can definitely have too much error handling. In this case, the game is unplayable without the weapon model, so I might as well let it crash. Don't try to gracefully recover from unrecoverable errors.

    Once again, this requires you to make an upfront decision as to which errors are recoverable. Unfortunately, Sun decided that almost all Java errors must be recoverable, which results in lazy error handling like the above.

    2005-2006

    At this point I learned C++ and DirectX. I decided to write a reusable engine so that mankind could benefit from the vast wealth of knowledge and experience I had acquired in my fourteen years on the earth.

    If you thought the last trailer was cringey, just wait.

    By now I learned that Object-Oriented Programming is Good(TM), which resulted in monstrosities like this:class Mesh {public: static std::list meshes; // Static list of meshes; used for caching and rendering Mesh(LPCSTR file); // Loads the x file specified Mesh(); Mesh(const Mesh& vMesh); ~Mesh(); void LoadMesh(LPCSTR xfile); // Loads the x file specified void DrawSubset(DWORD index); // Draws the specified subset of the mesh DWORD GetNumFaces(); // Returns the number of faces (triangles) in the mesh DWORD GetNumVertices(); // Returns the number of vertices (points) in the mesh DWORD GetFVF(); // Returns the Flexible Vertex Format of the mesh int GetNumSubsets(); // Returns the number of subsets (materials) in the mesh Transform transform; // World transform std::vector* GetMaterials(); // Gets the list of materials in this mesh std::vector* GetCells(); // Gets the list of cells this mesh is inside D3DXVECTOR3 GetCenter(); // Gets the center of the mesh float GetRadius(); // Gets the distance from the center to the outermost vertex of the mesh bool IsAlpha(); // Returns true if this mesh has alpha information bool IsTranslucent(); // Returns true if this mesh needs access to the back buffer void AddCell(Cell* cell); // Adds a cell to the list of cells this mesh is inside void ClearCells(); // Clears the list of cells this mesh is insideprotected: ID3DXMesh* d3dmesh; // Actual mesh data LPCSTR filename; // Mesh file name; used for caching DWORD numSubsets; // Number of subsets (materials) in the mesh std::vector materials; // List of materials; loaded from X file std::vector cells; // List of cells this mesh is inside D3DXVECTOR3 center; // The center of the mesh float radius; // The distance from the center to the outermost vertex of the mesh bool alpha; // True if this mesh has alpha information bool translucent; // True if this mesh needs access to the back buffer void SetTo(Mesh* mesh);}

    I also learned that comments are Good(TM), which led me to write gems like this:D3DXVECTOR3 GetCenter(); // Gets the center of the mesh

    This class presents more serious problems though. The idea of a Mesh is a confusing abstraction that has no real-world equivalent. I was confused about it even as I wrote it. Is it a container that holds vertices, indices, and other data? Is it a resource manager that loads and unloads that data from disk? Is it a renderer that sends the data to the GPU? It's all of these things.

    How to Improve

    The Mesh class should be a "plain old data structure". It should have no "smarts", which means we can safely trash all the useless getters and setters and make all the fields public.

    Then we can separate the resource management and rendering into separate systems which operate on the inert data. Yes, systems, not objects. Don't shoehorn every problem into an object-oriented abstraction when another abstraction might be a better fit.

    The comments can be improved, mostly, by deleting them. Comments easily fall out of date and become misleading liabilities, since they're not checked by the compiler. I posit that comments should be eliminated unless they fall into one of these categories:


    • Comments explaining why, rather than what. These are the most useful.
    • Comments with a few words explaining what the following giant chunk of code does. These are useful for navigation and reading.
    • Comments in the declaration of a data structure, explaining what each field means. These are often unnecessary, but sometimes it's not possible to map a concept intuitively to memory, and comments are necessary to describe the mapping.


      2007-2008

      I call these years "The PHP Dark Ages".

      90WfuyM.png

      2009-2010

      By now, I'm in college. I'm making a Python-based third-person multiplayer shooter called Acquire, Attack, Asplode, Pwn. I have no excuse at this point. The cringe just keeps getting worse, and now it comes with a healthy dose of copyright infringing background music.

      When I wrote this game, the most recent piece of wisdom I had picked up was that global variables are Bad(TM). They lead to spaghetti code. They allow function "A" to break a completely unrelated function "B" by modifying global state. They don't work with threads.

      However, almost all gameplay code needs access to the entire world state. I "solved" this problem by storing everything in a "world" object and passed the world into every single function. No more globals! I thought this was great because I could theoretically run multiple, separate worlds simultaneously.

      In practice, the "world" functioned as a de facto global state container. The idea of multiple worlds was of course never needed, never tested, and I'm convinced, would never work without significant refactoring.

      Once you join the strange cult of global tea-totallers, you discover a whole world of creative methods to delude yourself. The worst is the singleton:class Thing{ static Thing i = null; public static Thing Instance() { if (i == null) i = new Thing(); return i; }}Thing thing = Thing.Instance();

      Poof, magic! Not a global variable in sight! And yet, a singleton is much worse than a global, for the following reasons:


      • All the potential pitfalls of global variables still apply. If you think a singleton is not a global, you're just lying to yourself.
      • At best, accessing a singleton adds an expensive branch instruction to your program. At worst, it's a full function call.
      • You don't know when a singleton will be initialized until you actually run the program. This is another case of a programmer lazily offloading a decision that should be made at design time.


        How to Improve

        If something needs to be global, just make it global. Consider the whole of your project when making this decision. Experience helps.

        The real problem is code interdependence. Global variables make it easy to create invisible dependencies between disparate bits of code. Group interdependent code together into cohesive systems to minimize these invisible dependencies. A good way to enforce this is to throw everything related to a system onto its own thread, and force the rest of the code to communicate with it via messaging.

        Boolean Parameters

        Maybe you've written code like this:class ObjectEntity: def delete(self, killed, local): # ... if killed: # ... if local: # ...

        Here we have four different "delete" operations that are highly similar, with a few minor differences depending on two boolean parameters. Seems perfectly reasonable. Now let's look at the client code that calls this function:obj.delete(True, False)

        Not so readable, huh?

        How to Improve

        This is a case-by-case thing. However, one piece of advice from Casey Muratori certainly applies here: write the client code first. I'm sure that no sane person would write the above client code. You might write something like this instead:obj.killLocal()

        And then go write out the implementation of the [font='courier new']killLocal()[/font] function.

        Naming

        It may seem strange to focus so heavily on naming, but as the old joke goes, it's one of the two remaining unsolved problems in computer science. The other being cache invalidation and off-by-one errors.

        Take a look at these functions:class TeamEntityController(Controller): def buildSpawnPacket(self): # ... def readSpawnPacket(self): # ... def serverUpdate(self): # ... def clientUpdate(self): # ...

        Clearly the first two functions are related to each other, and the last two functions are related. But they are not named to reflect that reality. If I start typing [font='courier new']self.[/font] in an IDE, these functions will not show up next to each other in the autocomplete menu.

        Better to make each name start with the general and end with the specific, like this:class TeamEntityController(Controller): def packetSpawnBuild(self): # ... def packetSpawnRead(self): # ... def updateServer(self): # ... def updateClient(self): # ...

        The autocomplete menu will make much more sense with this code.

        2010-2015

        After only 12 years of work, I actually finished a game.

        Despite all I had learned up to this point, this game featured some of my biggest blunders.

        Data Binding

        At this time, people were just starting to get excited about "reactive" UI frameworks like Microsoft's MVVM and Google's Angular. Today, this style of programming lives on mainly in React.

        All of these frameworks start with the same basic promise. They show you an HTML text field, an empty [font='courier new'][/font] element, and a single line of code that inextricably binds the two together. Type in the text field, and pow! The [font='courier new'] [/font]magically updates.

        In the context of a game, it looks something like this:public class Player{ public Property Name = new Property { Value = "Ryu" };}public class TextElement : UIComponent{ public Property Text = new Property { Value = "" };}label.add(new Binding(label.Text, player.Name));

        Wow, now the UI automatically updates based on the player's name! I can keep the UI and game code totally separate. This is appealing because we're eliminating the state of the UI and instead deriving it from the state of the game.

        There were some red flags, however. I had to turn every single field in the game into a Property object, which included a list of bindings that depended on it:public class Property : IProperty{ protected Type _value; protected List bindings; public Type Value { get { return this._value; } set { this._value = value; for (int i = this.bindings.Count - 1; i >= 0; i = Math.Min(this.bindings.Count - 1, i - 1)) this.bindings.OnChanged(this); } }}

        Every single field in the game, down to the last boolean, had an unwieldy dynamically allocated array attached to it.

        Take a look at the loop that notifies the bindings of a property change to get an idea of the issues I ran into with this paradigm. It has to iterate through the binding list backward, since a binding could actually add or delete UI elements, causing the binding list to change.

        Still, I loved data binding so much that I built the entire game on top of it. I broke down objects into components and bound their properties together. Things quickly got out of hand.jump.Add(new Binding(jump.Crouched, player.Character.Crouched));jump.Add(new TwoWayBinding(player.Character.IsSupported, jump.IsSupported));jump.Add(new TwoWayBinding(player.Character.HasTraction, jump.HasTraction));jump.Add(new TwoWayBinding(player.Character.LinearVelocity, jump.LinearVelocity));jump.Add(new TwoWayBinding(jump.SupportEntity, player.Character.SupportEntity));jump.Add(new TwoWayBinding(jump.SupportVelocity, player.Character.SupportVelocity));jump.Add(new Binding(jump.AbsoluteMovementDirection, player.Character.MovementDirection));jump.Add(new Binding(jump.WallRunState, wallRun.CurrentState));jump.Add(new Binding(jump.Rotation, rotation.Rotation));jump.Add(new Binding(jump.Position, transform.Position));jump.Add(new Binding(jump.FloorPosition, floor));jump.Add(new Binding(jump.MaxSpeed, player.Character.MaxSpeed));jump.Add(new Binding(jump.JumpSpeed, player.Character.JumpSpeed));jump.Add(new Binding(jump.Mass, player.Character.Mass));jump.Add(new Binding(jump.LastRollKickEnded, rollKickSlide.LastRollKickEnded));jump.Add(new Binding(jump.WallRunMap, wallRun.WallRunVoxel));jump.Add(new Binding(jump.WallDirection, wallRun.WallDirection));jump.Add(new CommandBinding(jump.WalkedOn, footsteps.WalkedOn));jump.Add(new CommandBinding(jump.DeactivateWallRun, (Action)wallRun.Deactivate));jump.FallDamage = fallDamage;jump.Predictor = predictor;jump.Bind(model);jump.Add(new TwoWayBinding(wallRun.LastWallRunMap, jump.LastWallRunMap));jump.Add(new TwoWayBinding(wallRun.LastWallDirection, jump.LastWallDirection));jump.Add(new TwoWayBinding(rollKickSlide.CanKick, jump.CanKick));jump.Add(new TwoWayBinding(player.Character.LastSupportedSpeed, jump.LastSupportedSpeed));wallRun.Add(new Binding(wallRun.IsSwimming, player.Character.IsSwimming));wallRun.Add(new TwoWayBinding(player.Character.LinearVelocity, wallRun.LinearVelocity));wallRun.Add(new TwoWayBinding(transform.Position, wallRun.Position));wallRun.Add(new TwoWayBinding(player.Character.IsSupported, wallRun.IsSupported));wallRun.Add(new CommandBinding(wallRun.LockRotation, (Action)rotation.Lock));wallRun.Add(new CommandBinding(wallRun.UpdateLockedRotation, rotation.UpdateLockedRotation));vault.Add(new CommandBinding(wallRun.Vault, delegate() { vault.Go(true); }));wallRun.Predictor = predictor;wallRun.Add(new Binding(wallRun.Height, player.Character.Height));wallRun.Add(new Binding(wallRun.JumpSpeed, player.Character.JumpSpeed));wallRun.Add(new Binding(wallRun.MaxSpeed, player.Character.MaxSpeed));wallRun.Add(new TwoWayBinding(rotation.Rotation, wallRun.Rotation));wallRun.Add(new TwoWayBinding(player.Character.AllowUncrouch, wallRun.AllowUncrouch));wallRun.Add(new TwoWayBinding(player.Character.HasTraction, wallRun.HasTraction));wallRun.Add(new Binding(wallRun.LastWallJump, jump.LastWallJump));wallRun.Add(new Binding(player.Character.LastSupportedSpeed, wallRun.LastSupportedSpeed));player.Add(new Binding(player.Character.WallRunState, wallRun.CurrentState));input.Bind(rollKickSlide.RollKickButton, settings.RollKick);rollKickSlide.Add(new Binding(rollKickSlide.EnableCrouch, player.EnableCrouch));rollKickSlide.Add(new Binding(rollKickSlide.Rotation, rotation.Rotation));rollKickSlide.Add(new Binding(rollKickSlide.IsSwimming, player.Character.IsSwimming));rollKickSlide.Add(new Binding(rollKickSlide.IsSupported, player.Character.IsSupported));rollKickSlide.Add(new Binding(rollKickSlide.FloorPosition, floor));rollKickSlide.Add(new Binding(rollKickSlide.Height, player.Character.Height));rollKickSlide.Add(new Binding(rollKickSlide.MaxSpeed, player.Character.MaxSpeed));rollKickSlide.Add(new Binding(rollKickSlide.JumpSpeed, player.Character.JumpSpeed));rollKickSlide.Add(new Binding(rollKickSlide.SupportVelocity, player.Character.SupportVelocity));rollKickSlide.Add(new TwoWayBinding(wallRun.EnableEnhancedWallRun, rollKickSlide.EnableEnhancedRollSlide));rollKickSlide.Add(new TwoWayBinding(player.Character.AllowUncrouch, rollKickSlide.AllowUncrouch));rollKickSlide.Add(new TwoWayBinding(player.Character.Crouched, rollKickSlide.Crouched));rollKickSlide.Add(new TwoWayBinding(player.Character.EnableWalking, rollKickSlide.EnableWalking));rollKickSlide.Add(new TwoWayBinding(player.Character.LinearVelocity, rollKickSlide.LinearVelocity));rollKickSlide.Add(new TwoWayBinding(transform.Position, rollKickSlide.Position));rollKickSlide.Predictor = predictor;rollKickSlide.Bind(model);rollKickSlide.VoxelTools = voxelTools;rollKickSlide.Add(new CommandBinding(rollKickSlide.DeactivateWallRun, (Action)wallRun.Deactivate));rollKickSlide.Add(new CommandBinding(rollKickSlide.Footstep, footsteps.Footstep));

        I ran into tons of problems. I created binding cycles that caused infinite loops. I found out that initialization order is often important, and initialization is a nightmare with data binding, with some properties getting initialized multiple times as bindings are added.

        When it came time to add animation, I found that data binding made it difficult and non-intuitive to animate between two states. And this isn't just me. Watch this Netflix talk which gushes about how great React is before explaining how they have to turn it off any time they run an animation.

        I too realized the power of turning a binding on or off, so I added a new field:class Binding{ public bool Enabled;}

        Unfortunately, this defeated the purpose of data binding. I wanted to get rid of UI state, and this code actually added some. How can I eliminate this state?

        I know! Data binding!class Binding{ public Property Enabled = new Property { Value = true };}

        Yes, I really did try this briefly. It was bindings all the way down. I soon realized how crazy it was.

        How can we improve on data binding? Try making your UI actually functional and stateless. dear imgui is a great example of this. Separate behavior and state as much as possible. Avoid techniques that make it easy to create state. It should be a pain for you to create state.

        Conclusion

        There are many, many more embarrassing mistakes to discuss. I discovered another "creative" method to avoid globals. For some time I was obsessed with closures. I designed an "entity" "component" "system" that was anything but. I tried to multithread a voxel engine by sprinkling locks everywhere.

        Here's the takeaway:

evanofsky

Allow me to regale you with an exciting tale: the birth of a janky dialogue and voice system.

I have a JSON file with all the localized strings in my game, like this:{ "danger": "Danger", "level": "Level %d", ...}
A preprocessor takes this and generates a header file with integer constants for each string, like this:namespace strings{ const int danger = 0; const int level = 1; // ...}
At runtime, it loads the JSON file and hooks up the integer IDs to localized strings. A function called "_" takes an integer ID and returns the corresponding localized string. I use it like this:draw_string(_(strings::danger), position);
This all worked (and still works) pretty well for UI strings. Not so much for dialogue.

To write dialogue, I had to come up with a unique ID for each line, then add it to the strings file, like this:{ "hello_penelope": "Hello! I am Penelope.", "nice_meet_you": "Nice to meet you.", ...}
Yes, the preprocessor generated a new integer ID in the header file every time I added a line of dialogue. Gross.

I construct dialogue trees in Dialogger. With this setup, I had to use IDs like "hello_penelope" rather than actual English strings. Also gross.


A better way
I keep the string system, but extend it to support "dynamic" strings loaded at runtime that do not have integer IDs in the header file.

Now I can write plain English in the dialogue trees. The preprocessor goes through all of them and extracts the strings into a separate JSON file, using the SHA-1 hash of each string for its ID. Once everything is loaded, I discard all string IDs in favor of integer IDs.

I couldn't find a simple straightforward SHA-1 implementation that worked on plain C strings, so here's one for you.

The point of all this is: I now have a single JSON file containing all the dialogue in the game. Ripe for automation...

Speak and spell

Penelope is an AI character. I'm using text-to-speech for her voice, at least for now. I don't want to integrate a text-to-speech engine in the game; that's way too much work. And I don't want to manually export WAVs from a text-to-speech program. Also too much work.

I create a free IBM Bluemix account. They have a dead simple text-to-speech API: make an HTTP request with basic HTTP authentication, get a WAV file back.

I write an 82-line Python script that goes through all the dialogue strings and makes an HTTP request for each one. It keeps track of which strings have previously been voiced, to facilitate incremental updates.

Now I have a folder of WAV files, each one named after a SHA-1 hash. I'm using Wwise for audio, so the next step requires a bit of manual involvement. I drag all the WAVs into the project and batch create events for them.

GMoAul2.png

Now when I display a dialogue string, I just have to look up the SHA-1 hash and play the audio event. Easy.

Disaster strikes
I don't hear anything. All signs indicate the audio is playing correctly, but nothing comes out of my speakers.

I look at one of the audio files in Wwise.

n1Nybro.png

Looks like the file is corrupted. I play the WAV in a number of different programs. Some play it fine, others don't play it at all.

I edit my text-to-speech script to use Python's wave library to load the WAV file after downloading it from IBM. Sure enough, the library doesn't know what to make of it.

Too lazy to care, I edit the wave library in-place in my Python distribution. YOLO.

After a bit of printf debugging, I pinpoint the issue. The WAV format is based on RIFF, a binary format which breaks the file into "chunks". According to Wikipedia, the format of each chunk is as follows:


  • 4 bytes: an ASCII identifier for this chunk (examples are "fmt " and "data"; note the space in "fmt ").
  • 4 bytes: an unsigned, little-endian 32-bit integer with the length of this chunk (except this field itself and the chunk identifier).
  • variable-sized field: the chunk data itself, of the size given in the previous field.
  • a pad byte, if the chunk's length is not even.

    Turns out, IBM's text-to-speech API generates streaming WAV files, which means it sets the "length" field to 0. Some WAV players can handle it, while others choke. Wwise falls in the latter category.

    Fortunately, I can easily figure out the chunk length based on the file size, modify it using the wave library, and write it back out to the WAV file. Like so.

    Problem solved. Wwise is happy. Next I set up some Wwise callbacks to detect the current volume of Penelope's voice, and when she's done speaking.

    Here's the result, along with some rope physics in the background being destroyed by the wonky framerate caused by my GIF recorder:

    ncc5StD.gif

    If you want to hear it, check out the IBM text-to-speech demo here.

    Thanks for reading!

    Mirrored on my blog

evanofsky

The game industry hit Peak Advice Blog a while ago. Every day I [s]read[/s] skim ten articles telling me how to live.

Fear not! I would never give you useful advice. This series is about me writing bad code and you laughing at my pain.

First Contact
Say you have some voxels which occasionally get modified. You regenerate their geometry like so:voxel.Regenerate();
Because you are a masochist, you want to do this on a separate thread.


Rather than redesign your engine, you simply spawn a worker thread and give it a list of voxels to process. Easy in C#:Queue workQueue;static void worker(){ while (true) { Voxel v = workQueue.Dequeue(); lock (v) { v.Regenerate(); } }}new Thread(new ThreadStart(worker)).Start();// And awaaaaaaay we go!workQueue.Enqueue(voxel);
The

[font=consolas]lock[/font]

signals to other threads "hey, I'm using this". If the other threads also acquire locks before using the object, then only one thread will access it at a time.


Kaboom! It crashes when thread A dequeues an item at the exact moment when thread B is enqueueing one. You need a lock on the queue as well.

This is Hard and Boring and Slow
Turns out, without a lock you can't trust even a single boolean variable to act sane between threads. (Not entirely true. For wizards, there are atomic operations and something about fences. Out of scope here!)

Sprinkling locks everywhere is tedious, error-prone, and terrible for performance. Sadly, you need to rethink your engine from the ground up with threads in mind.

The Right Way
Why am I even including this? Ugh.

You read a few articles on modern AAA engines, where you find a diagram like this:

aOGgGRgl.png

This is the job graph from Destiny. AAA engines split their workload into "jobs" or "fibers". Some jobs depend on others. The graph has bottlenecks that split the work into phases. Within phase 1, tons of jobs execute in parallel, but all of them must finish before phase 2 starts.

With jobs, you wouldn't have to lock individual pieces of data. The dependency graph ensures that jobs run in the right order, and that nothing runs in parallel unless you're okay with it. You also don't have to think about individual threads -- a scheduler delegates jobs to a pool of threads.

Here's another diagram you stumble across:

XqMOUyQl.png

This shows the workload of the GPU and each CPU over time as the game renders a single frame. The goal is to fill all those holes so you use every bit of available compute power at maximum efficiency.

The Quick and Dirty Way
In a brief flash of clarity, you realize that you are not Bungie. You check your bank account, which sadly reports a number slightly lower than $500 million.

You recall the Pareto Principle, also known as the "80/20 rule". You decide to write 80% of a decent architecture for only 20% of the work.

You start with a typical game loop:while (true){ // Process window and input events SDL_PumpEvents(); SDL_Event sdl_event; while (SDL_PollEvent(&sdl_event)) { // ... } physics_step(); game_logic(); render(); // Present! SDL_GL_SwapWindow(window);}
[size=2]Side note: a while back you also switched to C++. Masochism level up.


What can you move off the main thread? If you touch OpenGL or anything within a mile of the windowing system from another thread, the universe explodes. For now, you keep graphics and input (

[font=consolas]SDL_PollEvent[/font]

) on the main thread.

That leaves physics and game logic. You give each its own thread. Since you need to spawn/modify/query physics entities in game logic, no other physics can happen while you're in a game logic update. The rest of the time, the physics thread can work in the background.

Sounds like a perfect case for a lock:std::mutex physics_mutex;void physics_loop(){ while (true) { std::lock_guard lock(physics_mutex); physics_step(); }}void game_logic_loop(){ while (true) { { std::lock_guard lock(physics_mutex); game_logic(); } render(); }}
In this setup, your

[font=consolas]render()[/font]

function can't read or write any physics data for fear of explosions. No problem! In fact, that limitation might be considered a feature. However, the

[font=consolas]render()[/font]

function also can't make any OpenGL calls since it's not on the main thread.


You re-watch the Destiny GDC presentation and notice a lot of talk about "data extraction". In a nutshell, Destiny executes game logic, then extracts data from the game state and lines it up for a huge fan-out array of render threads to process efficiently.

That's essentially what your

[font=consolas]render()[/font]

function will do: go through the game state, generate graphics commands, and queue them up. In your case, you only have one render thread to execute those commands. It might look like this:enum class RenderOp{ LoadMesh, FreeMesh, LoadTexture, FreeTexture, DrawMesh, Clear, // etc.};BlockingQueue render_queue;void main_loop(){ while (true) { RenderOp op = render_queue.read(); switch (op) { case LoadMesh: int count = render_queue.read(); Vec3* vertices = render_queue.read(count); // etc... break; case FreeMesh: // etc... } }}
It's a graphics virtual machine. You could give it a pretentious name like GLVM.


Now in your

[font=consolas]render()[/font]

function, you just write commands to the queue:void render(){ render_queue.write(RenderOp::Clear); for (MeshRenderer& mesh : mesh_renderers) { render_queue.write(RenderOp::DrawMesh); render_queue.write(mesh.id); // etc. } render_queue.write(RenderOp::Swap);}
This will work, but it's not the best. You have to lock the queue every time you read from or write to it. That's slow. Also, you need to somehow get input data from the main thread to the game logic thread. It doesn't make sense to have queues going both directions.


Instead, you allocate two copies of everything. Now, the game logic thread can work on one copy, while the main thread works on the other. When both threads are done, they swap.

EnchantedLavishDairycow.gif

Now you only have to use a lock once per frame, during the swap. Once the threads are synced, the swap operation is just two pointer reassignments.

Furthermore, you can keep the render command lists allocated between frames, which is great for performance. To clear one, just reset the pointer to the start of the list. Now you don't have to worry about implementing a queue with a ring buffer. It's just an array.

Our Bright and Glorious Future
For wizards, this is all wrong, because graphics drivers do command queueing anyway, and jobs and fibers are the right way. This is like Baby's First Threaded Renderer. But it's simple, it gets you thinking in terms of data flow between threads, and if you eventually end up needing a job system, you're already halfway there.

This setup might also make the switch to Vulkan easier. If you keep all your data extraction in one place and make it read-only, it should be trivial to split into multiple threads, each with their own render queue.

You can see a poorly-commented cross-platform implementation of this idea here. Potentially useful parts include the SDL loop, GLVM, and the swapper.

If you enjoyed this article, try these:

evanofsky

Friday 21:15
Fifteen minutes after the theme announcement, my friend Ben Homan walks through my front door. Not really my front door, I'm just a subletter. But this is a first. Normally he ignores our instructions to walk in without knocking. The first time, he texted me from the driveway.

21:30
Jesse Kooner walks in, also unannounced, bearing frozen pizza. Before he can even kick his shoes off, I loudly explain the theme: a never-before-seen tie between "growing" and "two-button controls".

21:45
Jesse has no laptop. I dig out an old one from my closet. I plug it in and start working on a few Windows updates. 72 to be exact.
Meanwhile, we decide which technology to use. Jesse's less code-focused skillset leads him to prefer Unity, while Ben wants to use the weekend as an opportunity to become more familiar with Node.js. We decide on Node.js. Jesse will provide creative input and artwork.

22:30
My roommates, a brother and sister, arrive home from an apparently underwhelming Christmas light show. The concept of a game jam is foreign to them, but they're good sports. We spend a half hour playing QWOP with them.

23:00
Our design parameters:


  • Multiplayer. Otherwise, what's the point of Node.js?
  • Probably 2D due to the limited timeframe. Although Jesse is more comfortable working in 3D.

    Jesse originally suggested doing this competition a few weeks ago, when he wanted to create a mash-up of "Cookie Clicker" and a tower defense game. He resurrects the idea now, only halfway joking.


    Ben likes the idea of a multiplayer vine growing game. I'm partial to a text-based social game about growing a social media brand. No one commits to anything yet.
    Ben is not a big gamer, so I pull up a few famous HTML5 games on his laptop. Cursors.io. Agar.io. 2048. This last one interests me in particular, as it involves growing numbers.

    23:15
    I'm pitching Ben and Jesse on a multiplayer version of 2048. I envision a free-roaming world filled with numbered tiles to collect. Instead of collapsing numbers together against the edges of the board, players would find walls and structures within the world to collapse their numbers against.

    23:30


    • How can two or more games of 2048 occur simultaneously on the same board? In normal 2048, the player controls all tiles on the board. We decide to give each player a unique color, and allow them to assimilate unclaimed, grayed-out tiles.
    • What happens when tiles from two players meet? We decide that whoever moves first gets to own the resulting collapsed tile from a move.
    • How do players traverse through the world? We toss around the idea of procedural generation, but eventually decide on hand-crafted, linear levels linked together via portals.
    • This raises the question: how do the portals work? And what happens to a player's tiles once they exit a level?

      A2M1Lhkl.jpg


      Top right corner: our confidence that we'll actually finish the thing.


      Saturday 00:30
      We've eaten two pizzas. We have a Git repository and a Slack instance which is ultimately only used to test out amusing Slackbot responses.
      Although the game is 2D, we decide on a 3D art style with an orthographic projection, to allow Jesse to use his skills in Maya LT, which he promptly installs on my old laptop.
      Ben works on the server with Node.js, while I start on the client with Three.js.

      02:30
      Ben heads home first, then Jesse. I pass out on my bedroom floor.


      4ama9So.gif


      You can see we're already struggling with the name.


      08:30
      I wake to find Ben working in the kitchen. He spends the morning building boiler plate for the server, while I work out some Three.js details.

      13:30
      I return from a run just in time to catch Jesse pulling up with donuts. After lunch, he and I spend the next few hours working out a pipeline between Maya and Three.js.

      16:00
      I tweet our first screenshot, featuring colorized instances of Jesse's tree model displayed in a horribly distorted orthographic projection.
      dC6nAJil.png

      18:00
      Without the server API to code against, I run out of things to do on the client. Ben finishes the data model, but he has trouble conceptualizing the rules for player movement. I haul my laptop over to indulge in some good old-fashioned pair programming.

      19:00
      Break for dinner. Burritos. Jokes.

      21:00
      Jesse continues modelling. With the basic API done, I start working to make the client consume it. Ben works on an image loader. We want to design the levels in GIMP.

      23:00
      Ben takes off. I've got player movement, animations, and tile numbers done.

      Sunday 00:30
      Multiplayer works. Jesse and I play a few games against each other. It's fun! It's a game! We work out some issues with the level loading code and try to get an interesting level loaded.

      02:00
      Problem: it's pretty easy for one player to gain the upper hand and quickly assimilate all the tiles on the board, making it impossible for other players to move and grow.

      02:30
      Solution! Players should only control tiles within a certain radius of their "center". Outlier tiles are grayed out, free to be picked up by other players.

      03:15
      Fix implemented. Jesse heads home and I turn in.

      09:15
      Tweet another screenshot before heading to church.
      YyFiTBdl.png

      At this point, I'm having an existential crisis about the name. I fire off a few panicked texts about it.

      12:30
      Ben and I are back to the grindstone. Jesse arrives with sandwiches and more donuts! Ben adds a cool username feature, but we eventually axe it to keep things simple.

      15:00
      Tweaks and bug fixes all day. Ben works on the level format, while Jesse lays out some levels in GIMP.

      17:00
      More polish. I put in Jesse's cloud models and a "connecting" spinner.

      19:00
      We finally brainstorm a name: Tile Risers. Jesse whips up a logo in Maya. We go through seven iterations before everything lines up in our janky export pipeline.

      20:00
      I spin up a Digital Ocean droplet, allocate an S3 bucket, commit the production URLs, and start filling out the submission form.

      21:00
      Time's up! Fortunately, we have another hour to submit. I later found out I misread the rules, and we actually had a whole extra 24 hours. At any rate, we were done. I commit two small bug fixes after submission, which is within guidelines.

      Conclusion
      d479nw8l.jpg3M0E3DB.gif

evanofsky
Developers hate him!

We'll cover some standard tips and tricks here, but we're not really interested in those. We're looking for the One Weird Trick to rule them all. Hopefully each trick we encounter brings us closer to coding Mecca.

In the beginning
The first video game I ever wrote was called Ninja Wars.
ninjawars.png
Yes, that is an HTML table of images. I changed the src attribute to move stuff around.

The top of the Javascript file looked like this:var x = 314;var y = 8;var prevy= 1;var prevx= 1;var prevsw= 0;var row= 304;var endrow= 142;var sword= 296;var yrow = 0;var yendrow = 186;var I = 0;var e = 0;var counter = 0;var found = 0;var esword = 26;var eprevsw = 8;var bluehealth = 40;var redhealth = 40;var n = 0;var you = 'ninja';var bullet = 'sword';var enemy = 'enemy';var ebullet = 'enemysword';var besieged = 0;var siegecount = 0;var esiegecount = 0;var ebesieged = 0;var healthcount = 0;var player = 0;var starcount = 0;var infortress = false;var prevyou = you;var einfortress = false;var prevenemy = enemy;var previmg = "";var prevbullet= "";var eprevbullet= "";var randnum = 0;var randnum2 = 0;var randnum3 = 0;var randnum4 = 0;var buildcount = 0;var characters = new Array(4);characters = ['ninja','tank','heli','builder'];var bullets = new Array(3);bullets = ['sword','bullet','5cal','sword'];var echaracters = new Array(3);echaracters = ['enemy','tank2','eheli','ebuilder'];var ebullets = new Array(3);ebullets = ['enemysword','bullet2','e5cal','enemysword'];var health = new Array(4);health = [40,30,20,10];var prevorb = 0;var prevnum = 0;
Hopefully this looks somewhat familiar, and I'm not the only one to start out writing code like this. Regardless, this debacle demonstrates our first trick:

Trick #1: globals are evil

We don't even know why they're evil, we just intuitively know.

edit: I should clarify that I'm no longer against globals. But that's getting ahead of ourselves.

Pretty soon we learn about objects. We can group our variables:class Ninja{ int x, y; int previousX, previousY; int health = 100;}class Sword{ int x, y; int previousX, previousY; int sharpness = 9000;}
We can even use inheritance to avoid copy-pasting:class Movable{ int x, y; int previousX, previousY;}class Ninja : public Movable{ int health = 100;}class Sword : public Movable{ int sharpness = 9000;}
Inheritance is nice! Nice enough to serve as our next trick:

Trick #2: object-oriented programming

Object-oriented is so great that it forms the core of many classic games, including Doom 3. Which, coincidentally, is open source.
Doom 3 loves inheritance. Don't believe me? Here's a small subset of its class hierarchy:idClass idEntity idAnimatedEntity idWeapon idAFEntity_Base idAFEntity_ClawFourFingers idAFEntity_Vehicle idAFEntity_VehicleFourWheels idAFEntity_VehicleSixWheels idAFEntity_Gibbable idAFEntity_WithAttachedHead idActor idPlayer idAI
Imagine you're an employee at id Software. This inheritance hierarchy works great for a few months. Then, one fateful Monday, disaster strikes. The boss comes in and says "Hey, change of plans. The player is now a car."

Look at idPlayer and idAFEntity_VehicleFourWheels in the hierarchy. Big Problem #1: we need to move a lot of code.

Big Problem #2: the boss comes to his senses and calls off the "player is a car" idea. Instead, we're adding turrets to everything. The car is becoming a Halo Warthog, and the player is getting a giant turret mounted on his back.

As lazy programmers, we decide to use inheritance again to avoid copy-pasting. But look at the hierarchy. Where can we put the turret code? The only ancestor shared by idPlayer and idAFEntity_VehicleFourWheels is idAFEntity_Base.

We'll probably put the code in idAFEntity_Base and add a boolean flag calledturret_is_active. We'll only set it true for the car and the player. This works, but the terrible, logical conclusion is that our base classes end up loaded with tons of cruft. Here's the source code for idEntity.

Go ahead, scroll through it. You don't have to read it all.

https://github.com/id-Software/DOOM-3-BFG/blob/9c37079c16015fc58de29d3de366e0d93dc11f8a/neo/d3xp/Entity.h#L163

The point is, that's a lot of code. Notice how every single entity -- down to the last piece of physics debris -- has a concept of a team, and of getting killed. Clearly not ideal.

If you're a Unity developer, you already know the solution: components! Here's what they look like:

unity.jpg

Rather than inheriting functionality, Unity entities are just bags of components. This solves our earlier turret problem easily: just add a turret component to the player and car entities.

Here's what Doom 3 might look like if it used components:idPlayer idTransform idHealth idAnimatedModel idAnimator idRigidBody idBipedalCharacterController idPlayerControlleridAFEntity_VehicleFourWheels idTransform idAnimatedModel idRigidBody idFourWheelController...
What have we learned?

Trick #3: in general, favor composition over inheritance

Take a moment to review the tricks we've covered so far: global variables bad, objects good, components better.

You won't believe what happens next!

Let's take a small detour into the world of low-level performance with a very simple question: which function is faster?double a(double x){ return Math.sqrt(x);}static double[] data;double b(int x){ return data[x];}
We'll hand-wave a lot of complexity away and just assume that these two functions eventually compile down to one x86 instruction each. Function a will probably compile to sqrtps, and function b might compile to something like lea("load effective address").
sqrtps takes about 14 CPU cycles on a modern Intel processor, according to Intel's manual. What about lea?

The answer is "it's complicated". It depends on where we load data from.Registers ~40 per core, sort of 0 cyclesL1 32KB per core 64B line 4 cyclesL2 256KB per core 64B line 11 cyclesL3 6MB 64B line 40-75 cyclesMain memory 8GB 4KB page 100-300 cycles
That last number is important. 100-300 cycles to hit main memory! This means in any given situation, our biggest bottleneck is probably memory access. And from the looks of it, we can improve this by using L1, L2, and L3 cache more often. How do we do that?

Let's return to Doom 3 for a real-life example. Here's the Doom 3 update loop:for ( idEntity* ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ){ if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { ent->GetPhysics()->UpdateTime( time ); continue; } timer_singlethink.Clear(); timer_singlethink.Start(); RunEntityThink( *ent, cmdMgr ); timer_singlethink.Stop(); ms = timer_singlethink.Milliseconds(); if ( ms >= g_timeentities.GetFloat() ) Printf( "%d: entity '%s': %.1f ms\n", time, ent->name.c_str(), ms ); num++;}
From an object-oriented perspective, this code is pretty clean and generic. I'm assuming RunEntityThink calls some virtual Think() method where we could do just about anything. Very extensible.

Uh oh, here comes the boss again. He has some questions.

  • What's executing? Er... sorry boss, it depends on what entities are active at the time. We don't really know.
  • In what order is it executing? No idea. We add and remove entities to the list at random times during gameplay.
  • How can we parallelize this? Gee boss, that's a tough one. Since the entities execute randomly, they may be accessing each other's state. If we split them up between threads, who knows what might happen.

    In short:

    shrug.gif
    But wait, there's more! If we look closely, we see the entities are stored in a linked list. Here's how that might look in memory:

    linked-list.png

    This makes our L1, L2, and L3 cache very sad. When we access the first item in the list, the cache thinks, "hey, I bet the next thing they'll want is nearby", and it pulls in the next 64 bytes after our initial memory access. But then we immediately jump to a completely different location in memory, and the cache has to wipe out those 64 bytes and pull in new data from RAM.

    Trick #4: line up data in memory for huge performance gains
    Like this:for (int i = 0; i < rigid_bodies.length; i++) rigid_bodies.update();for (int i = 0; i < ai_controllers.length; i++) ai_controllers.update();for (int i = 0; i < animated_models.length; i++) animated_models.update();// ...
    An object-oriented programmer might be infuriated at this. It's not generic enough! But look, now we're iterating over a set of contiguous arrays (not arrays of pointers, mind you). Here's what it looks like in memory:

    components.png

    Everything is all lined up in order. The cache is happy. As a side bonus, this version allows us to answer all those pesky questions the boss had. We know what's executing and in what order, and it looks much easier to parallelize.

    edit: Don't get too hung up on cache optimization. There's a lot more to it than just throwing everything in an array. I only bring it up to prove a point, and I'm only qualified to give the basic introduction. Check out the links at the bottom for more info.

    Time to wrap this up and get to the point. What's the One Weird Trick? What do tricks 1-4 (and many more) all have in common?

    The One Weird Trick: data first, not code first
    Why were globals so evil (trick #1)? Because they allowed us to get away with lazy data design.

    Why did objects help us (trick #2)? Because they helped us organize our data better.

    Why did components help us even more (trick #3)? Because they modeled our data better by matching the structure of reality better.

    Even the CPU likes it when we organize our data correctly (trick #4). No really, what is the trick actually
    Let's break it down in practical terms. Here's a representation of a typical Unity-like component-based game design.

    uml.png

    Each component is an object. It has some state variables listed at the top, and then some methods to do stuff with those variables. This is a well-designed object-oriented system, so the variables are private. The only code that can access them is the object's own methods. This is called "encapsulation".

    uml-encapsulation.png

    Each object has a certain amount of complexity. But fear not! OOP promises that as long as we keep the state private, that complexity will stay encapsulated within the object, and won't spread to the other objects.

    Unfortunately, this is a lie.

    throne-of-lies.jpg

    Sometimes, we need a function to access two or three objects. We end up either splitting the function between those objects, or writing a bunch of getters and setters so our function can access what it needs. Neither solution is very satisfying.

    Here is the truth. Some things cannot be represented as objects very well. I propose an alternate paradigm, one which represents every program perfectly:

    data-flow.png

    Once we separate process from data, things start to make more sense.

    Object-oriented helps us write good code because it encourages us to encapsulate complexity (i.e. state). But it forces us to do so in a certain way. Why not instead encapsulate like this, if it makes sense within our problem space?

    data-flow-encapsulation.png

    In summary, design data structures to match your specific problem. Don't shoehorn a single concept into a bunch of separate, encapsulated objects.

    Next, write functions that leave the smallest possible footprint on that data. If possible, write pure stateless functions.
    That's the trick.

    Conclusion
    If this struck a chord with you, it's because I stole most of it. Get it straight from the source:
evanofsky
The big secret of our industry is, we don't actually enjoy making games. We slave away in obscurity for years in anticipation of one glorious day.

Not release day, no. The day we can finally write a postmortem full of pretentious anecdotes, bad jokes, and unsolicited advice.

Well I just finished a game, and doggone it, I am going to exercise my inalienable rights as a developer.

The formatting on this article came out a little wonky. Read here for a better formatted version.



Things to do when making a game



Ancient gamedev postmortem traditions mandate that this section be titled "what went right". Unfortunately, the game was so shockingly good and so many things went right that a full overview would stretch on endlessly.

Instead you'll have to settle for this. These are some things I did that I recommend you do as well.

Come up with a good concept

I didn't do this one, actually. The original concept was a cartoony third-person game called Parkour Ninja. It changed every other week or so for the remainder of development. For a while the player had a pistol:
1wunul.jpg
And for a while, you could rip voxels apart and re-attach them:
p5zp3l.jpg9XAvHl.jpg

Almost everything got cut.

The final concept is not particularly unique. First-person parkour with a female protagonist has definitely been done before, and every third game on Steam uses voxels.

It worked in the end though, by combining familiar elements in a unique way, and by throwing in a weird, trippy, puzzle-y aesthetic. Every new idea steals from existing ideas.


In 2012 I released a short, strange, ugly, buggy alpha demo which nevertheless communicated the core ideas of parkour and mysterious voxels. Incredibly, Rock Paper Shotgun covered it. I doubt I would have stuck with the project without that affirmation.

Make it through Greenlight

I honestly have no idea how to replicate this feat. I don't know how it happened. Fortunately, Greenlight is much less daunting today than it was in April 2014.

I did almost nothing to promote the Greenlight page. I ran the campaign in tandem with a Kickstarter (more on that later), but almost all traffic came from Steam itself. Here's the embarassing trailer I used for both Greenlight and Kickstarter:



The game was greenlit in 16 days as part of a bundle of 75 other games, even though it hadn't reached the top 100 yet.

cBNID9Sl.jpg

As you'll see later, if you want to make a living developing PC games, you have to get through Greenlight.

Iterate the controls

My character controller article goes into much more detail on this, but basically, take however much time you plan to spend on the controls, and then double it.

Use MIDI knobs to control game variables. Explore the game space. Use offline processing to bullet-proof your character controller.

This point applies to all games, even those that don't have a character. The player's action and the game's reaction are arguably the most important aspects of a game, because they are unique to the medium.

Design your graphics carefully

Lemma has been pretty ugly for most of its life. I got lucky with a few textures in the 2012 alpha, particularly the stone texture which features heavily in the final game. But mostly I just slapped textures on haphazardly. Here are some perfectly good textures applied in the worst possible way:

vJPWAl.jpg

At the time, I knew something was wrong with this scene, but I couldn't put my finger on it. Allow me now, with the benefit of hindsight, to put my finger all over it.

  • Nowhere to climb. This is a claustrophobic indoor scene set inside some sort of derelict vessel. It belongs in Bioshock, not a parkour game.
  • Too busy. The textures are incredibly loud and detailed while the voxels are huge, flat, and boring.
  • No composition. Nothing draws my attention or invites me to explore. The shapes are all uninspired boxes.
  • Abysmal lighting and colors. If I recall correctly, I randomly placed the red point light on the left on a whim.

    Compare to this shot from the final game:
    mPXRXevl.jpg
    Still some rough edges, but not overly painful.

    Here's what I learned to get from point A to point B:

    • If you're like me, make up for your lacking art skills with code. God rays, SSAO, and particle effects worked wonders for me. And turn on mip-mapping for gosh sake.
    • Form and composition are more important than detailed textures. You can make a beautiful scene with just a few carefully placed shapes.
    • If you're trying to convey a massive sense of scale, your forms should have interesting features at every scale. A single, giant, featureless cube won't inspire awe. Neither will a giant cube with a detail pattern.
    • Colors and lighting make or break scenes. I probably spent as much time picking (and re-picking) colors as I did building voxels.

      Support all the things

      At the very least, add proper gamepad support. For me, Oculus Rift support was a huge selling point and a ton of fun for YouTubers.



      Things like sparse options menus, missing gamepad support, and shoddy VR implementations enrage gamers, especially PC gamers. There's a reason TotalBiscuit starts every video with a look at the options menu.

      I threw in every option I could think of, and almost every option requested by players. Y axis inversion, gamma, FOV, gamepad bindings, a framerate limiter, you name it.

      Get involved with the community

      I almost lost it In January 2014. Shut in my apartment for days on end, stuck in a difficult rut in production, I was going insane.

      Thankfully, Columbus has a budding game development scene. I rented a desk from a local gaming incubator. The mere act of driving to work and existing around other humans got me through the winter. As an added bonus, I gained a ton of playtesters!

      yKbs9yJl.jpg

      Every month I attend a local game development meetup. As a solo developer, it's the only time I get to talk openly about the topic that consumes 90% of my life. Seeing the same people every month and catching up on their progress is incredibly rewarding.
      For the rest of the month, there's Twitter!

      Do your own marketing

      If you're like me, your marketing budget is $0.

      On launch day, I spammed announcements to all of Lemma's accumulated fan base via Twitter, Facebook, IndieDB, GameJolt, Kickstarter, Steam Greenlight, YouTube, and an email list.

      I spent several days collecting contact info by hand for various press and YouTubers. Whenever possible, I automated the process with Python and Javascript. Some resources I used:

      • Video Game Caster
      • Video Game Journaliser
      • Top 100 YouTubers
      • Bolo - useful for digging up journalist email addresses

        I pulled everything into a Google Docs spreadsheet and ran a mail merge on it two weeks before the launch. Somewhere around 400 Steam keys ended up being activated.

        YouTube ended up bringing the most traffic. Over 400 videos have been uploaded to date, totalling over 5 million views, mostly thanks to three huge videos posted by jacksepticeye.

        Ship your localization strings in plain text

        Lemma uses Excel files for localization. I use a third-party library to read them, which makes the code pretty simple.
        This ended up being a great decision, because foreign players step up with their own volunteer translations. They can edit the Excel files in place and see the results immediately in-game.

        Things to never do, ever



        Run a Kickstarter

        As Greenlight becomes easier to conquer, Kickstarter becomes exponentially more difficult. Backers have been burned too many times by now, and everyone sets their goals much lower than the amount they need to deliver on their promises.

        I ran a failed Kickstarter for Lemma in March 2014. Originally, I planned to abandon the game if the Kickstarter failed. Then the Greenlight went through and I decided to cut back the budget, take some contract work, and do some budgeted art items (namely the character model) myself.

        Running a Kickstarter takes too much time away from development. My advice is to find another way to fund your game if at all possible.

        Write a pretentious story

        If you're making Deus Ex, feel free to go wild with gritty lore and philosophical questions about trans-humanism. But if you're making Flappy Bird, you can get away with maybe a 10 second cut scene, tops. Know how much story your game can "afford".

        The story of Lemma features quantum mechanics, the Philadelphia Experiment, life and death choices, infidelity, betrayal, and jealousy. All this crammed into 50 optionally collectible notes in a game about parkour.

        The story tries to do too much. When all these conflicting ideas combine, they blur together into a jumbled mess that neutralizes the impact of each individual idea.

        When it comes to story, do one thing, and do it well.

        Suddenly switch from linear to non-linear design half-way through
        I planned this from the beginning, actually. The first half of the game is linear so I can introduce mechanics one at a time. The player knows everything by the second half, so the game opens up into a non-linear cornucopia of levels that review the things you've learned so far.

        This is a pretty good pattern as far as pacing, but the linear to non-linear transition confuses players. The whole first half teaches you that there's one way to "win", then suddenly, you're dropped out in the cold and left to your own devices with a completely incomprehensible world map.

        AQK8Ohil.jpg
        [size=2]Wait, what?

        I did this because I thought, "this game is about exploration, it needs to be more non-linear". But all of the alpha releases were completely linear and not a single player complained about it. In fact, many of them commented that they enjoyed how each individual level could be cleared in many different ways.

        The takeaway is, there are tons of ways to make your game feel more non-linear than it is, without building a confusing tangle of interconnected levels.

        Design bad puzzles

        My worst puzzles break the game rules. If you have to write a custom script that manually pokes the game state when the player solves the puzzle, stop and re-think your life decisions.

        I'm always worried that my puzzles are too easy and that players will breeze through them too quickly, but in reality it doesn't take much to slow players down.

        Often, the simple act of exploring a 3D space is enough of a puzzle. Games like this are a continuous conversation between level designer and player. It's enough of a challenge for the player to parse what the level designer is saying.

        Throw in unnecessary enemies

        Enemies have been a part of Lemma since day one. I love watching players encounter them for the first time, because they're truly terrifying. That small taste of horror shakes things up and fits perfectly into the pacing.

        But after the novelty wears off, enemies become annoying and redundant. There's no combat; your interaction with them is always the same: run away.

        My goal was always to integrate enemies seamlessly with the environment. In parkour, the environment is already your biggest enemy and your most powerful tool, so it makes sense. Unfortunately, I only came up with one enemy that came anywhere close to achieving this goal: a sort of tower that detaches from the environment and falls on you.

        In hindsight, I should have been more confident in the core gameplay and remove the enemies to focus on better level design.

        Spend time on an unnecessary level editor

        Some games benefit hugely from a level editor. Heck, Garry's Mod is a level editor. If you're making that type of game, more power to you.

        If you're making a mostly linear, story-driven singleplayer game, a level editor doesn't make much sense. Everyone says "oh cool, there's a level editor", creates a few cubes, and then completely forgets about it.

        Again: do one thing, and do it well.

        Start a hobby project and transition it to professional

        Hobby game development is like building a tower of bricks. You don't know how tall it's going to be, you just keep stacking bricks. Each brick represents something that happens to interest you at the time. Branching dialogue? Sure, stack it on there. A pistol? Why not. Pretentious story? Check-a-mundo.

        Professional game development is like sculpting. You start with a certain amount of raw materials: time, money, motivation, player attention, etc. You plan out a rough idea of your sculpture, then you start chiselling. The size of the sculpture is irrelevant if you put your chisel in exactly the right spot.

        The two paradigms are incompatible. If you're a hobbyist looking to make the switch, consider starting fresh with a new project. Results Steam
        The second spike in these graphs is mostly due to jacksepticeye's Let's Play videos.

        Results



        Steam
        du652mrl.png
        Sales: 3,171

        ZfYb7X2l.png

        Gross revenue before 30% cut: $43,554

        Max simultaneous players: 63

        2Pw99lGl.png

        Demo downloads: 10,126

        iLSlHTjl.png

        Demo conversions: 277 (2.7% conversion rate)

        [size=1]t3BNTHKl.png

        Max simultaneous demo players: 53

        ve3U0JDl.png

        Key activations: 483

        Positive reviews: 77 (91%)

        Negative reviews: 7

        Refunds: 68

        2TPchSCl.png

        itch.io

        bqNKYkDl.png

        Demo downloads: 1,896

        Sales: 46

        Gross revenue before 5% cut: $701

        Humble widget (direct website sales)

        aaxRlnDl.png

        Sales: 37

        dINxlhwl.png

        Gross revenue before 5% cut: $557

        IndieGameStand

        Sales: 4

        Gross revenue before 30% cut: $57

        IndieDB

        Demo downloads: 1,388
        Piracy
        Lemma offers the option to anonymously upload analytics. 5,732 out of 13,410 demo downloaders (43%) actually opened the game and opted in to the analytics program.

        A total of 7,310 pirated copies of the game have submitted analytics data to my server. Assuming 43% of pirates opt in to the analytics, I estimate about 17,000 people have pirated the game, for a piracy rate of 82%.

        The worst part about piracy is that torrents cannot be updated, which means YouTube is full of footage of old, outdated builds.

        Conclusion
        a1orQJ0l.jpg
evanofsky

PR-o-matic

Lemma is finally coming to Steam on May 12. Check out the new trailer:



For the first time ever, I shelled out for Adobe Premiere rather than hacking something together in Movie Maker and OpenShot. I didn't use any of the fancy features, but it was worth it just to avoid dealing with crashes all the time. Although it still did crash (it's Adobe after all).

Ashton Morris did a number on the trailer audio. I had something entirely different all ready to go before he came in with something way better.

I've been collecting contact info for some time. I mostly just went through the following lists, pulling out info for people who might be interested in my game:

  • Video Game Caster
  • Video Game Journaliser
  • Top 100 YouTubers
  • Indie YouTubers I follow or who have already covered the game

    Video Game Caster lists emails right there on the page. I pulled them out using a jQuery one-liner in the Chrome web console. I noticed a lot of them seemed to be inactive, so I used BeautifulSoup to query each YouTube page and determine when their last two videos were uploaded.

    I spent today blasting out emails. I forked over $25 to increase the daily mail quota for a Google Sheets mail merge add-on, only to find out that a) the quota did not actually increase, and b) you can write your own mail merge in about 20 lines of Javascript. What a scam.

    In Sheets, just hit Tools -> Script editor. Here's my script, I was blown away by this:[code=js:0]var EMAIL_SENT = "Yes";function onOpen(){ var spreadsheet = SpreadsheetApp.getActive(); var menuItems = [ {name: 'Go', functionName: 'mail_merge'} ]; spreadsheet.addMenu('Mail merge', menuItems);}function mail_merge(){ var draft = GmailApp.getDraftMessages()[0]; var subject = draft.getSubject(); var template_html = draft.getBody(); var template = draft.getPlainBody(); var sheet = SpreadsheetApp.getActiveSheet(); var data = sheet.getDataRange().getValues(); var column_map = {}; for (var i = 0; i < data[0].length; i++) { column_map[data[0]] = i; } for (var i = 1; i < data.length; i++) { var row = data; var email = row[column_map.Email]; if (email) { var sent = row[column_map.Sent]; if (sent != EMAIL_SENT) { var message = template; var message_html = template_html; for (var column in column_map) { message = message.replace('{{' + column + '}}', row[column_map[column]]); message_html = message_html.replace('{{' + column + '}}', row[column_map[column]]); } GmailApp.sendEmail(email, subject, message, { htmlBody: message_html }); Logger.log(email); sheet.getRange(i + 1, column_map.Sent + 1).setValue(EMAIL_SENT); // Make sure the cell is updated right away in case the script is interrupted SpreadsheetApp.flush(); } } }}
    It marks rows that have been sent to by putting "Yes" in the "Sent" column. It pulls the email address from the "Email" column. Those are the only two hard-coded values. I wrote the email template as a draft in Gmail. The outgoing messages all appear in your Gmail "sent" folder.

    The only downside is that Gmail also has a 100 email per day quota. In my scramble to get these emails out, I had to pay for and set up Google Apps for Work on my domain to increase the quota to 1500 per day. Turns out, the quota still didn't increase, but between my personal Gmail and the Google Apps account, I was able to hit most of my contact list. I'll get the rest tomorrow.

    I had no idea PR would be so much fun! It's just like programming!

    Just kidding, it's still horrible.

    Thanks for reading!
evanofsky
Let's say that, like so many of us, you want to make a surreal voxel-based first-person parkour game. You're trying to figure out a production schedule. What will take the longest? Graphics? Sound? Level design? I bet it will be the character controller. And I bet it will take 4 1/2 years. Why?

  • In running/jumping games, player movement is paramount. It takes forever to nail the right feeling.
  • Each game is a unique snowflake. You will not find an article explaining how to design the controls for your specific game. You're flying blind.

    That said, each game offers a few transferrable bits of wisdom. Here's my story.

    Make a character

    You're a programmer, but one time you were able to suppress the gag reflex while using GIMP, so you're pretty much an artist too. You can draw a player character.
    3qqRJqkl.jpg
    That's certainly... a drawing. So the player is an anthropomorphized cylinder? Well, we've seen worse.
    If this character has any flaw, it's that he's too exciting and interesting. Can you make him a little more boring and generic? What if you use MakeHuman? It literally generates human characters from a template.
    0uvQADll.png

    Much better. But there's just one problem: this is a first-person game, so when players look down, they can see their own nose:
    jsAgDvnl.png

    Also, the "pectoral musculature" slider is a tad high, and players are getting confused about their gender.
    You end up switching to a female character. Because why not?
    Now for the nose problem. You can't remove the entire head, because a headless shadow might be somewhat disconcerting. What if you just remove the face?

    Z70Rhl.jpg

    [size=2]Perfect.



    (Eventually you revamp the model, hire an animator, and use separate models, one sans head, for the first-person view and shadow renderer. But none of that is entertaining.)

    Make it move

    You're using a great physics engine (seriously, it's quite good) that comes with a simple character controller. It looks like this:
    C3ZMMiOl.png
    The character is a cylinder floating above the ground, supported by a single raycast. This way, the cylinder can clear a small obstacle, and once the raycast hits it, the whole apparatus jumps on top.
    Since the game world is made of voxels, you quickly run into this problem:

    RDdpOgXl.png

    Tons of players get stuck this way in your first alpha release. Rather than spend time on an elegant solution, you brute-force it:
    SuQjLaQl.png

    Despite this, people still get stuck. You resort to a collision handler that actually pushes the character away from anything that could cause problems. You also interpolate the vertical position to smooth out the camera when traversing uneven voxels:
    9c7IGmzl.png

    Make it unrealistic

    In an attempt to model reality accurately, the game has no air control at this point. When you originally made this decision, you somehow forgot that the game is about an imaginary cube world.
    Thankfully, after listening to player feedback, you have a change of heart. In the real world, traceurs have many control dimensions (namely, their muscles) that enable precise jumps. Video games have exactly one button. Air control is only fair.

    Make it fun

    Since parkour is about momentum, you want the character to take several seconds to reach max speed. Which is fine, except that low acceleration makes small adjustments difficult. The first step takes forever, and the character feels like a semi truck.
    Your solution uses different accelerations depending on the current speed. The final speed curve looks like this:
    QorUs73l.png

    This solves half the problem, but players can still use the mouse to quickly whip the camera around 90+ degrees, which resets their speed back to zero.
    You experiment with a few hacks, but eventually settle on a solution using the dot product. It's basically a measure of the angle between two vectors multiplied by their magnitude. (Here's a quick interactive demo.)
    You use a dot product to find out how much side-to-side momentum the character has. If they're facing perpendicular to the direction of their momentum, the dot product will be large. You use that to increase the acceleration. Long story short, turning no longer burns momentum.

    Make it slippery

    There are other ways to lose momentum, like running into a brick wall. You try to mitigate this with low friction physics materials, but angling yourself into a wall will always slow you down:

    WideeyedAdolescentIndianhare.gif

    You are inspired by a blog post by Mike Bithell on this topic. You use three raycasts and some cross product magic to figure out a velocity that will slide along the wall.

    YoungKlutzyHalibut.gif

    Later on, you discover another annoyance. Your wonderful voxel engine sometimes helpfully constructs voxels like this:
    7CMrj0Sl.png

    There's a seam between the two adjacent blocks due to floating point error. When the character moves flush with the wall and tries to jump upward, it hits the seam and immediately stops.
    The solution is brain-dead simple: change the cylinder to a capsule. Yes, it really does take you 4 years to figure this out.

    Make it forgiving

    At first, players just don't understand the movement mechanics. They think they can't get from point A to point B, until you tap them on the shoulder and explain they have to do XYZ. You suspect this is because your tutorial is actually a placebo at this point.
    Eventually, the tutorial gets pretty good. Everyone understands the movement capabilities, and they can figure out which moves to use. But now they have a new problem: they fail in the twitchy execution and timing details of their plans.
    The worst culprit is a single infamous jump in the tutorial. It tries to teach players how to grab ledges because it's too long to cross with a normal jump.
    T0wgMJ3l.png

    Players fail two or three times before you tell them to "button-mash", which helps them nail the timing through sheer brute-force. Interestingly, as soon as they make this one jump, they have no trouble completing future jumps without button-mashing. For a while, you arrogantly conclude that people are just stupid.
    Part of the problem is still the tutorial: you ask players to make a leap of faith and perform a move they've never seen before. They have no idea what the character will do or how long it will take. So you add another, earlier tutorial that lets players try out the ledge grab in a safe space.
    But the frustration of perfect timing remains. The solution is two-fold:

    • Let players jump for a split second after they walk off an edge.
    • Let them hold buttons instead of tapping at the right moment.


      1aNvzAql.png

      To the surprise of no one but you, this makes the game a lot less frustrating and a lot more fun.

      Make it look good

      Over the course of development, you stumble on a few animation tricks. With enough nifty procedural animation, maybe people won't notice your shoddy weight painting and texture work!

      • Attach the camera position to the character's head bone, but use a separate root bone to control camera rotation. This eliminates weird rotations when blending between animations.
      • Speaking of which, use a quadratic curve to blend between animations rather than straight linear.
      • Also, don't use linear matrix interpolation. Instead use quaternion interpolation.
      • Remember the dot product from earlier, for calculating side-to-side momentum? Use that to make the character and camera lean when turning at speed.
      • Run the character bone transforms through filters for nice effects like tilting the character's head when looking up and down.
      • Plant the character's feet and play a little foot-shuffling animation when turning in place.

        (For a much more eloquent and in-depth look at procedural animation, check out David Rosen's GDC talk.)

        Conclusion

        rJk4nhel.jpg

        Budget an extraordinary amount of time for your character controller. Make it special and unique. And if you're me, prepare to be wrong most of the time.
        Lemma is set to release in May. The entire game engine is on GitHub. If you enjoyed this article, try these:
evanofsky

Screenshot Saturday 213

It's the end of February and this game is supposed to be content-complete. In a sense, it actually is. All the levels are done. Twenty in all. I thought this month would never end!

fxPzauyl.jpg

Just so you know, there are sixty of those lights and I had to hook up each one individually. It fell just barely beneath the "worth it to automate" threshold.

Don't look too closely at this next one, it's a bit spoilery.

07ZR5M2l.jpg

All that remains is to fill out a few story elements and wrap this thing up with some semblance of a satisfying ending.
I actually made some very interesting improvements to the parkour code in the past few weeks, but I'll save it for the upcoming character controller article.

For now, that's all I got! Thanks for reading.

Mirrored on my blog
evanofsky
This is not a tutorial. It's a story. A Voxel Odyssey.
The story starts with 19 year old me in a dorm room next to the Ohio State stadium. I don't have the repo from this stage of development (SVN at the time), but I remember the process clearly.

9Ob8Tlal.jpg

[size=2]Photo by Kristen Sutton
XNA 4 comes out in September 2010. I immediately dive in. This turns out to be a poor life decision.
For some reason, one of the very first things I implement is motion blur. I think this is Lemma's first screenshot, although at this point, it's a cartoony third-person game called "Parkour Ninja":

uPbWnEjl.jpg

[size=2]Such motion blur
I skip past the initial naive implementation of spawning a cube for each voxel cell. My first move is to iterate over these individual cells and combine them into larger boxes using run-length encoding.
[color=rgb(85,85,85)][font='PT Serif']

LCxK6yNl.png7WBNCTFl.pngeQscXX2l.png

[/font][/color]
Performance is already a problem even at small scales. I'm re-optimizing the entire scene every time I make an edit. Obviously, my next move is to only optimize the parts I'm editing.
This turns out to be difficult. Take this example:
EIGGYmql.png
I add a cube at the top of this stack. To optimize this into a single box, I have to search all the way to the bottom of the stack to find the beginning of the large box, then add 1 to its height and delete my little cube addition.
To speed this process up, I allocate a 3D array representing the entire scene. Each cell stores a pointer to the box it's a part of. Now I can query the cells immediately adjacent to my new addition and quickly get a pointer to the large box next to it.
Removals are the next challenge. My first idea is to split the box back into individual cells, then run the optimizer on them again.
[color=rgb(85,85,85)][font='PT Serif']

IZOODYFl.png8rHP5vgl.png4tonGAul.pngDHBh5gml.png

[/font][/color]
This turns out to be horribly slow. I soon realize that rather than splitting the box into individual cells, I can skip a few steps and split it into "sub-boxes". I still run the optimization algorithm afterward, but I can make its life easier.
I am thrilled to get this demo running on my roommate's Xbox 360. It fails to impress the girls in the neighboring suite.



Goodbye Xbox
I quickly run into more issues. The CLR's floating point performance is absolutely abysmal on Xbox 360. The physics engine breaks down and cries after spawning 10 boxes or so. I decide to target PCs instead.

Textures

I render scenes by copying, stretching, and scaling a single cube model. Slapping a texture on this cube turns out to be a horrible idea that looks something like this:
[color=rgb(85,85,85)][font='PT Serif']

Jk5UD9ml.png

[/font][/color]
To avoid texture stretchiness, I eventually write a shader to generate UVs based on the position and normal of each vertex. Here's the final version for reference:float2x2 UVScaleRotation;float2 UVOffset;float2 CalculateUVs(float3 pos, float3 normal){ float diff = length(pos * normal) * 2; float2 uv = float2(diff + pos.x + (pos.z * normal.x), diff - pos.y + (pos.z * normal.y)); return mul(uv, UVScaleRotation) + UVOffset;}
Instancing
Next, another performance crisis. Somehow I realize that doing a whole draw call for each and every box in a scene is a Bad Idea(TM). So I take the obvious step and... use hardware instancing. Yes.
Incredibly, I'm actually proud of my work so far. Somewhere around this time I also discover my lifelong love of terrible music.


[size=2]Local multiplayer? What is this game?

Improved level format
At this point, I'm saving and loading levels via .NET's XML serialization. Apparently XML is still a good idea in 2010. The voxel format is simply a 3D array represented as an XML string of ASCII 1s and 0s. Every time I load a level, I have to re-optimize the entire scene. I solve this by storing the boxes themselves in the level data as a base64 encoded int array. Much better.

Per-surface rendering
I start building larger levels and run into another graphics performance problem. The engine is simply pushing too many triangles. In a complex scene, a significant chunk of boxes are surrounded on all sides by other boxes, completely hidden. But I'm still rendering them.
I solve this problem by breaking each box into its individual faces. I actually iterate across the whole surface to determine what parts (if any) are visible. Shockingly, this turns out to be terrifically slow. I eventually mitigate the issue by caching surface data in the level file.
I render all these surfaces via... drum roll... instancing. Yes, really. I open Blender, create a 1x1 quad, export it, and instance the heck out of that thing. These are dark times. But I'm finally able to render some larger landscapes:

eVnERl.jpg

Physics
Time to see some cool physics. I now have two kinds of voxel entities: the static voxel, represented in the physics engine as a series of static boxes, and the dynamic voxel, represented as a single physics entity with a compound collision volume constructed of multiple boxes (I should plug the incredible BEPUPhysics for making this possible). It works surprisingly well:


Around this time I also switch to a deferred renderer, which is why I spawn an unreasonable number of lights at the end of that video.

Chopping down trees
Now it's time to take physics to the next level. My goal is simple: I want the player to be able to cut down a tree and actually see it fall over, unlike Minecraft.
This lofty dream is basically a graph problem, where each box is a node connected to its adjacent neighbors. When I empty out a voxel cell, I need a fast way to determine whether I just partitioned the graph or not.
So I add an adjacency list to the box class. Again, shockingly, calculating adjacency turns out to be a huge bottleneck. I later cache the adjacency data in the level file, which eventually balloons to several megabytes.
Now every time the player empties out a voxel cell, I do a breadth-first search through the entire scene, marking boxes as I go. This allows me to identify "islands" created by the removal of the voxel cell. I can then spawn a new physics entity for that island and break it off from the main scene.
I eventually come up with the idea of "permanent" boxes, which allows me to stop the search whenever I encounter a box that cannot be deleted.
I design a new enemy to showcase the new physics capabilities. I also test the limits of awkwardness and social norms by narrating gameplay footage in a dorm room full of people studying.



Chunks
Around this time I learn about broadphase collision detection. My engine is scattering thousands of static boxes around the world, which makes it difficult for BEPUPhysics' broadphase to eliminate collision candidates. At the same time, it's becoming obvious that rendering the entire world in a single draw call is a bad idea.
I fix both of these issues by splitting the world into chunks. Each chunk has a static triangle mesh for physics, and a vertex buffer with basically the same data for rendering. Since I have to regenerate both of these meshes every time a chunk is modified, the chunk size can't be too large. Also, smaller chunks allow for more accurate frustum culling.
At the same time, the chunks can't be too small or the draw call count will explode. After some careful tuning I eventually settle on 80x80x80 chunks.
[color=rgb(85,85,85)][font='PT Serif']

pk302l.jpg

[/font][/color]

Partial vertex buffer updates
This is probably the low point.
By now, I am incredibly proud of my "loosely coupled" architecture. I have a Voxel class and a DynamicModel class which know nothing about each other, and a ListBinding between the two which magically transforms a list of Boxes into a vertex buffer.
Somehow, probably through questionable use of the .NET Timer class, I locate a bottleneck: re-sending an entire vertex buffer to the GPU for every voxel mutation is a bad idea. Fortunately, XNA lets me update parts of the vertex buffer individually.
Unfortunately, with all the surface culling I do, I can't tell where to write in the vertex buffer when updating a random box. Also, how to shoe-horn this solution into my gorgeous cathedral architecture.
This conundrum occurs during the "dictionary-happy" phase of my career. Yes. The ListBinding now maintains a mapping that indicates the vertex indices allocated for each box. Now I can reach into the vertex buffer and change things without re-sending the whole buffer! And the voxel engine proper still knows nothing about it.
This turned out to never really work reliably.
Multithreading
I lied earlier, this is probably the low point.
Voxel mutations cause noticeable stutters by now. With no performance data to speak of, I decide that multithreading is the answer. Specifically, the worst kind of multithreading.
I spawn a worker thread, sprinkle some locks all over the place, et voila! It's multithreaded. It gains perhaps a few milliseconds before the main thread hits an unforeseen mystical code path and the menu somehow manages to acquire a lock on the physics data.
I am ashamed to admit that I never got around to correcting this colossal architectural faux pas.
[color=rgb(85,85,85)][font='PT Serif']

PNrpC5y.jpg

[/font][/color]

Large Object Heap
I'm now building large enough levels to run into memory issues. Turns out, the .NET runtime allocates monstrous 80x80x80 arrays differently than your average object. I write more about this here.
Long story short, the garbage collector doesn't like to clean up large objects. I end up writing a custom "allocator" that hands out 3D arrays from a common pool. Later, I realize most of the arrays are 90% empty, so I break each chunk into 10x10x10 "sub-chunks" to further reduce memory pressure.
This episode is one of many which explain my present-day distaste for memory-managed languages in game development.

Graduation
I graduate and work at a mobile game studio for the next year. The engine doesn't improve much during this time, but I start to learn that almost everything I know about programming is wrong and incomplete.

4Icq0tMl.jpg
[size=2]One of the many "offices" I've worked in over the years
I leave my job in February 2014 and continue hacking the engine. By now it's over 30k LOC and I am morally and spiritually unable to start over on it.

Goodbye allocations
With my newfound awareness of the .NET heap, I realize that my vertex arrays for physics and rendering are also probably landing in the Large Object Heap. Worse, I am reallocating arrays every time they change size, even if only to add a single vertex.
I genericize my Large Object Heap allocator and shove the vertex data in there. Then, rather than allocating arrays at exactly the size I need, I round up to the next power of 2. This cuts the number of allocations and makes it possible for my allocator to reuse arrays more often.

Goodbye cathedral
I finally throw out the "loosely coupled" ListBinding system and pull the vertex generation code into the voxel engine itself. The resulting speed boost is enough for me to go back to re-sending entire vertex buffers rather than faffing about with partial updates.

Goodbye index buffer
Up to this point, I've been maintaining an index buffer alongside the vertex buffer. In a much overdue stroke of "genius", I realize that since none of the vertices are welded, the index buffer is just a constantly repeating pattern, which is in fact the same for every voxel.
I replace the individual index buffers with a single, global buffer which gets allocated to the nearest power of 2 whenever more indices are needed.

Bit packing and compression
Many numbers in the level data format are guaranteed to fall in the 0-255 range. My friend decides to pack these numbers more efficiently into the integer array. He writes about it here.
I also pull in third party library #27 (SharpZipLib) and start zipping the level files. These changes cut the file size to under 30% of the original.

Goodbye UV optimization
I've been storing a huge amount of surface data like this:class Box{ public struct Surface { public int MinU, MaxU; public int MinV, MaxV; } public Surface[] Surfaces = new Surface[] { new Surface(), // PositiveX new Surface(), // NegativeX new Surface(), // PositiveY new Surface(), // NegativeY new Surface(), // PositiveZ new Surface(), // NegativeZ };}
I do this so that I can resize surfaces that are partially hidden, like this:
[color=rgb(85,85,85)][font='PT Serif']

rbYoDF6l.pngJhxpH2Dl.png

[/font][/color]
At some point in the vertex buffer overhaul, I realize that performance-wise, the physics engine doesn't care what size the surface is.
I use this fact to speed up mesh generation. I generate 8 vertices for the corners of each box, then copy them where they need to go in the vertex buffers.
Really, the graphics engine doesn't care much about the size of the surface either, aside from fill rate. What matters is whether the surface is there or not.
With this in mind, I kill the UV optimization code and store the surfaces in memory and in the level file like this:class Box{ public int Surfaces;}
The bits of the int are boolean flags for each surface. Yes, I could do it in a byte. Actually, maybe I should do that. Anyway, this simplifies my level loading and saving code, cuts my file sizes down to about 512kb on average, and drastically reduces memory usage. Axing the UV optimization routine also speeds up mutations.

Conclusion
[color=rgb(85,85,85)][font='PT Serif']

FJnrfthl.jpg

[/font][/color]

Clearly, this article is mostly useless if you're interested in writing your own voxel engine. The final result is far from perfect. I just want to share the petty drama of my past four and a half years. I for one thoroughly enjoy reading about other people's struggles. Maybe that's weird.
Lemma is set to release May 2015. The entire game engine is on GitHub. If you enjoyed this article, try these:
evanofsky

Screenshot Saturday 211

Last Saturday we had the Short North gallery hop. Hundreds of people came through our gallery to see art. The guys helped me set up the Oculus and a projector on the wall.

Sometimes I had to go out and pull people in, but most of the time, there was a line. My favorite customer by far was this kid:

6xdPxl9l.jpg
He jumped right in and played like a pro. The mother (recording video) was super supportive and excited.

At past expos, I dropped players into a bespoke demo level that skipped a lot of story and jumped straight into the tutorial. In December, I revamped the "real" first level (called "Rain") to be more like the demo level. If your game can't entertain people in two minutes at an expo, what's going to keep them playing if they buy the game?

After the gallery hop, I checked my Git logs. "Rain" was present in the game without a major overhaul for the past two years. I rebuilt every other level. Time to throw Rain in the trash.

For something that has received constant attention and iteration for two years, I was surprised how quickly I replaced it. Here's Rain 2.0:

W3CuFjol.jpg
It accomplishes all the same goals, teaches the same mechanics, and introduces the same (optional) story elements, but it's much more simple, streamlined, and just fun.

Rain 1.0 was the first level I ever made. At that stage I didn't entirely know what the game was. I had no level design vocabulary. I didn't even know how big the level should be or where it fit into the overall experience. I patched those details in later.

For Rain 2.0, I knew every goal from the start. I made decisions easily by asking which option best fit those goals. For once, my design tasks aligned in the correct order. As a result, I replaced two years of iteration in a single long work day.

Of course it's not perfect. Playtesting and iteration will reveal potential improvements, but it's already miles ahead of the old level. As a side note, here's a fun glitch I discovered during the redesign:

AdoredNarrowEquestrian.gif

But that's not all! Oh no, that was just Monday. I built another level in the "aqua" biome, bringing the count to 18 out of 20.



rJk4nhel.jpg
One more aqua level, then the ending level, and it's a wrap. The rest of development will be tweaking, polishing, and bugfixing, which at least for me is much easier than pulling creative content out of thin air. Hang in there. Thanks for reading!

Mirrored on my blog
evanofsky
I finished last week's map. It has some spinny things.

CompetentAgitatedIchneumonfly.gif

Then I made this week's map.

XAfbrRdl.jpg

h1TLRq9l.jpg

Who knew purple and green could look so... not terrible?

Anyway, this puts me ahead of schedule. There are three levels remaining. My goal is to for the game to be playable from start to finish by the end of February. It's ambitious, but I'm confident I can do it!

Today I took a break from level design to do some hardcore coding for the first time in a while. It was a breath of fresh air, which definitely reinforces the realization that I'm a programmer first and foremost. I optimized a ton of stuff, cut the level data size in half, and killed a metric crap-ton of memory leaks, but I'm way too tired to write about it right now. Maybe later!

That's it for this week. Thanks for reading.

Mirrored on my blog
evanofsky
This week was crazy productive. I finished last week's level, finished another level, which looks like this:

9xCowOAl.jpg

...which also included some story-related writing and scripting, and actually started working on NEXT week's level, which looks like this:

mPXRXevl.jpg
I seem to be on a purple streak lately. Actually, purple may rise unintentionally to be the most prominent color in the game. Also, this last level is apparently a subconscious ode to Monument Valley.

Amidst all this I'm constantly tweaking and fixing things, which isn't particularly exciting. However, this week did bring one interesting story: a playtester reported their graphics card overheated. Apparently, the poorly designed card couldn't run at 100% utilization for any significant stretch of time.

The playtester asked for a framerate limiting feature to prevent the card overheating. So now, I cap framerate at 120 FPS, a number which is adjustable via a slider in the options menu. Details like this take up an increasingly large proportion of development time as the project nears completion.

That's it for this week. Thanks for reading!

Mirrored on my blog
evanofsky
This past weekend I participated in the CivicHacks "Game Jam for Good". The goal was to raise awareness of the global water crisis and ultimately promote PackH2O, a Columbus-based startup that designs water backpacks for developing water-stressed regions.

The jam lasted 48 hours. My entry is called "Achilles".

3XsRRVV.png

Achilles is a multiplayer text-based simulation. You the player must manage a village in a third-world country experiencing a water crisis. You can play it here (bit.ly/waterjamachilles) assuming I haven't stopped paying for the cloud server.

It's incredibly depressing. Really, only bad things can happen. I had plans to add pregnancy and childbirth, but ran out of time. Here's a video of it in action:



The core idea was this: to convince people of the effectiveness of the PackH2O backpack, a promotional game should put players in the shoes of the people who will use it. And I think I accomplished that. In the game, the backpack allows people to carry twice as much water. The player experiences first-hand through gameplay how useful the backpack is.

Despite the game's inherent awfulness, it won an "honorable mention", which actually turned out to be good for a $500 gift card. smile.png You can check out the other winning games on the CivicHacks website.

Technology
On the backend, I wrote Achilles in Python using Flask as a web server framework (highly recommended).
I used gevent for greenlets, which made it very easy to write time-based procedures for each character. For example, here's how a man builds a hut:if village['build_material'] > 0: village['build_material'] -= 1 notify(world, village['id']) state['state'] = 'building' notify(world, man['id']) gevent.sleep(world_seconds(world, 60 * 60 * 17)) village['huts'] += 1 notify(world, village['id']) man['state'] = None notify(world, man['id']) send(village['id'], { 'event': '{0} finished building a hut.'.format(man['name']) })else: send(village['id'], { 'event': 'Not enough build material for a hut.' })
The "notify" and "send" functions send JSON objects to the relevant clients over WebSockets.
On the frontend, I only used jQuery (no plugins) and Mustache.js for templating.
If you're interested, check out the code here on Github.

Compression-based programming
I used this game jam as an opportunity to try out compression-based programming as advocated by the excellent Casey Muratori. The idea is, instead of spending time upfront designing a complex cathedral-like architecture, you should write straightforward, ugly, even repetitive code (copying and pasting). When it's done, you go back and "compress" the code into something nicer via refactoring.
Of course, some things are straightforward enough that you can compress them as you go. For example I always knew there would be a "send" function.
Casey says one of the big problems with "normal" programming is that you assume you know everything at the beginning of a project, when in reality, you often don't. Compression-based programming has you architecting things only after you've seen the whole picture.
After a weekend of experimentation, I think this pattern of coding results in plain, boring, easy to understand code, which is definitely a good thing. Normally I come up with some super fancy way to write everything very elegantly, which creates more work than it's worth in the end.
Another paradigm Casey rails against is object-oriented programming, so I also tried writing everything procedurally. The result was again boring but simple and easy.
The biggest win was separating state from behavior. On both the client and server, all the state lived in a single object, parts of which were operated on by various procedures.
Normally, I often link state and behavior so closely that they're impossible to separate. Every time I write a closure (probably my favorite bad habit), I squirrel away an opaque bit of state inextricably tied to an anonymous bit of behavior.
Time to wrap this up: you should try coding in a boring, straightforward, state-separated-from-behavior, procedural style. The result is a breath of fresh air in a world of increasingly clever object models and cathedral architectures.

Screenshot Saturday 208

This week's level is not quite done yet, but I have an excuse! Power was out at the incubator for two days, and the internet didn't come up fully until just yesterday.

Still, the level should be done some time this weekend and is already looking pretty good.

a1orQJ0l.jpg

I finalized the promotional graphics and published the game to Steam in "coming soon" mode. I'm incredibly grateful to Sam Gebhardt for contributing his Hollywood artistic talent!

XgTJOPVl.jpg
I updated the website to feature Sam's artwork and shot a brand new trailer:



The 5 second cuts didn't turn out to be as good of an idea as I hoped, but it works for now.

Progress continues. Lord willing I will finish this game with or without power!

Mirrored on my blog: Achilles

Mirrored on my blog: Screenshot Saturday 208
evanofsky
Records continue to be broken. This week's map was actually done on Wednesday!

Although most of Lemma is a strange hybrid of natural and alien-looking architecture, my design calls for a few "industrial / man-made" themed maps. For story reasons, and also because I just want to parkour through a skyscraper.

So on Monday I asked Twitter this question:
[quote]
Would people be upset if I do a few levels in the visual style of Mirror's Edge? Would that be tribute or rip-off?[/quote]

Answers varied, but unfortunately I had already started working on it, and it turned out so awesome that I had to keep going. At this point I don't care if people think it's a rip-off.

IkZJ96Yl.jpg

MediocrePleasantDoe.gif

z1vgb12l.jpg

Reinforcing the notion that 90% of my dev time is spent picking colors, I also revamped the skybox on Valley.

0D7MKtvl.jpg

Some friends at imaekgames recommended that I get the Steam page for Lemma up as soon as possible to ensure a spot in the "Coming Soon" list, so I started crafting some assets:

JnspJgzl.png

Currently working with an artist to redesign the main capsule (top left), but I'm pretty happy with the other assets. Here's what the page looks like so far:

sZW2hk0l.png

So yeah, crazy busy. This game might actually get done on time.

That's it for this week, thanks for reading!

Mirrored on my blog
evanofsky
I moved my office into an incubator / art gallery this week.

S43weETl.jpg
The move is mostly for my own sanity. Turns out, working alone in your apartment for 9 months isn't the most fun in the world. It's a Herculean effort just to stay motivated. I also lost all semblance of a disciplined sleep schedule.

Productivity has been great since the move, and I'm back on a normal sleep schedule. Having people around is great, even if I mostly tune them out to focus on work (sorry guys).

In keeping with the production schedule, I finished the last frost level today. Unfortunately its massive size doesn't lend itself too well to screenshots. I tried my best:

laASo0Il.jpg
I also completed the dialogue and cutscene for the first major player decision in the story. Sorry, no screenshots. It isn't much to brag about, but spoilers are spoilers.

Lastly, I updated the visuals for the movement prediction / block creation mechanic. Someone pointed out a while back that the old visuals made it difficult to distinguish overlapping shapes. Here's how it used to look (I always love sneering at old screenshots):

F7q5RQzl.png
And here's the new effect with edge highlights:

Q3vlpifl.jpg
I could have used a texture, but I ended up doing it in the shader based on UV coordinates:const float radius = 0.15f;float2 diff = float2(min(max(uv.x, radius), 1.0f - radius), min(max(uv.y, radius), 1.0f - radius)) - uv;float highlight = 0.6f + (5.0f * (diff.x * diff.x + diff.y * diff.y) / radius);
I seem to recall reading a way to do it with only arithmetic primitives, but I couldn't remember it or figure it out.

A bunch of other stuff also happened that I won't bother listing. If you are for some reason interested in nitty gritty details, the Steam beta tester group has extensive changelogs.

That's it for this week! Thanks for reading.

Mirrored on my blog
evanofsky
For the first time in the history of Lemma, I'm actually keeping up with my self-assigned pace of one new level per week.

These past two weeks I made two more frost levels. The plan calls for one more frost level, then it's on to the other two biomes.

Clicky for giffy

fST31FIl.jpg
Both of these levels have interesting quirks and unique features. They're probably too tough right now, but I'm scheduling plenty of time to playtest and sand down the sharp edges.

For me, the biggest challenge is to start with a blank slate and form new shapes out of nothing. It's much easier to playtest an existing design and think of ways to improve it. So I'm getting all the hard work out of the way first.

As I build these levels I finally get to implement story elements which have languished on the drawing board until now. I already have code for very simple cutscenes, and one of the five endings is basically done already.
[color=rgb(85,85,85)][font='PT Serif']


LlzjmA7l.jpg

[/font][/color]
I continue to solve old, long-standing gameplay annoyances.

Normally in Lemma, you can build floors by rolling or sliding off an edge, causing a blue platform to appear beneath you.

FeistyReflectingClownanemonefish.gif

The problem is, you can spam this move and build an infinitely long blue platform. I originally prevented this with a strict rule: you can't build a platform if you're already standing on a blue surface. This rule gets the job done, but it's clunky, unintuitive, and difficult to explain to players.

I thought about another bothersome exploit that used to plague Lemma. In older versions, you could get in a corner and climb up indefinitely by jumping between the two perpendicular walls. The clunky solution was to nerf the wall-jump, which was admittedly ridiculous. But the over-powered wall-jump was fun.

InformalCrazyBagworm.gif

So I kept the crazy wall-jump and wrote code to specifically detect the corner case (ha). I kept a running counter of spammy corner jumps. After a certain number, it disables wall-jumping in that area.

Now, the proper solution is to design a game with mechanics so elegant and simple that this whole situation never arises in the first place. But it's way too late for me to do that now, so we're stuck with this. Surprisingly, it doesn't bother people at all. Experienced gamers immediately try to exploit the wall-jump, see that it doesn't work, and move on. The rest of the game is unaffected, and I can keep my insane OP wall-jump.

Given the success of this exploit patch, I decided to do something similar to fix the issue with building floors. Now, when you try to build a floor on a blue surface, I do a breadth-first search to find solid ground. You can build on blue surfaces as long as you don't stray too far from solid ground.

Just like the wall-jump, the code is actually much more complicated now, but it boils down to this: you can always build floors unless you're trying to exploit.

And now for some random technical oddities. First, a quick tip if you're developing a game from scratch on Windows: make sure to handle DPI scaling properly. Lemma boots in fullscreen borderless window mode, and if the DPI scale setting is above 100%, Windows automatically scales the entire game window, which pushes most of it offscreen.

Here's a Gist that features an app manifest which will disable auto DPI scaling for your game.

In other news, I stumbled on the TextBelt library this week, which allows you to send SMS messages from a server for free. It's a Node project, but the concept is simple enough to easily replicate in your server framework of choice: blast emails to every single mobile provider gateway, knowing that only one provider owns the phone number and will allow the message through.

I imagine it would be fairly easy to optimize if the gateways send back email errors. At any rate, I'm tempted to take a weekend, write a fun SMS-based party game using this library, and try it out on some friends.

That's it for this week! Thanks for reading.

Mirrored on my blog
evanofsky

Screenshot Saturday 203

Big update this week!

My voxel renderer now has the capability to overlay everything with any texture I want. I'm using it on a new set of interconnected winter levels. This way I don't have to manually come up with a frosty version of each texture.

SaneNaturalBasenji.gif

Without giving away too much, this week I built a new system that has implications for both puzzle solving and movement mechanics.

NippyDecentGrizzlybear.gif

I also went back to several levels and fiddled with lighting again. Basically 90% of my development time is spent adjusting colors. Before / after:

kVbufnwl.jpg

Clearly, the old version relied heavily on bloom. I'm trying to avoid that a bit more now. Bloom is like crack cocaine to game developers.

Other random things:

  • When you walk off an edge, there is now a split second of forgiveness during which you can still jump. Just filing down another edge to make player movement less frustrating.
  • I finally killed an old glitch that subconciously annoyed me for years. Lemma has "bullet time", and up until this week it stuttered noticeably when running in slow-motion, despite maintaining a high framerate. I peeked into the BEPUPhysics source code and realized it runs on a fixed timestep with an accumulator. It updates at 60 FPS regardless of the actual framerate. So when I changed the time scale, that 60 FPS dropped to 30 FPS. But no more! I now scale BEPUPhysics' target framerate as well, and the result is silky smooth slow motion.
  • I liked using Jekyll for my blog so much, I also migrated the Lemma website, and updated it in the process. The site was already hosted on S3 so half the work was already done.

    That's it for this week. Thanks for reading!

    Mirrored on my blog
evanofsky

Screenshot Saturday 202

I've come to several realizations this week.

  1. Lemma is going to be good, but not great. I have to accept my limitations and finish the thing to the best of my ability.
  2. Lemma is more of an experience than a traditional video game.

With these two ideas in mind, I am focusing the next few months on making Lemma the least frustrating, most enjoyable experience I can.

So I'm trying to come up with puzzles that seem difficult but are actually simple to solve. The wonderful Monument Valley did a great job of this.

yCXCT6Yl.jpg


Fun fact: that's actually a public domain composite image of the dark side of the moon. I wrote a custom "sky decal" shader to paste it up there while still cooperating with the distance fog.

DOk6XTal.jpg

I kind of want to live there.

So that's where I'm at. I've been stressing a lot lately but I think I can actually finish the game in April. Whether I think I can or not... I have to.

That's it for this week. Thanks for reading!

Mirrored on my blog
evanofsky
Lots of stuff going on this week.

New dev blog

First off, my website got a much-needed overhaul. The horrible slowness of Wordpress.com was driving me nuts, so I switched to a custom-built site.
I used Jekyll, which is a static site generator. It spits out a bunch of HTML files which you can upload to a server, as opposed to Wordpress, which generates fresh HTML every time someone loads your page.
Advantages:

  • Absolute control. You can customize the site theme without dealing with mountains of horrible PHP. And I can finally post HTML5 videos.
  • You can host your website on Amazon S3, which is about 50 cents per month, 10x faster than a normal server, and pretty much guaranteed to never go down. Wordpress absolutely crawls in comparison.
  • Perfect for coders, because you can write articles in raw HTML or Markdown in your favorite text editor, and you can track your site in a Git repository.

    Disadvantages:

    • No easy way to offer email subscriptions to readers. RSS is easy to set up though.
    • Jekyll is written in Ruby and thus has a bunch of annoying dependencies to install. Still, the whole process is definitely easier than setting up a fresh Wordpress install.

      Migrating the comments to Disqus was super easy. I use s3_website to deploy the site to S3. It only updates diff'd files, so it only takes a few seconds in most cases.
      Overall I recommend it if you're a coder.

      New app

      I got hired this summer to do a mobile game as part of a franchise tie-in. The game is finally out today for free on Android.
      Here's some (raw, uncut) gameplay footage:



      grepr patch

      grepr has garnered a modest but promising amount of attention. Most notably, iDubbbzTV had some great things to say about it. (warning: language)


      I mostly agreed with his bigger complaints, so I made a few tweaks:

      • The terminal menu is now more simple and straightforward to operate
      • The enemy AWK drone is now larger and easier to spot
      • The third level, where the enemy AWK first appears, is now greatly simplified
      • Some glitchiness pertaining to the data node collision volume is now fixed

        More importantly, the game now runs on Linux! It's 64-bit only at the moment, but everything seems to run just fine. If the game glitches on you, make sure you have libsdl2-2.0.0 installed.

        New screens

        Amidst all this craziness you might think I've abandoned Lemma. And you'd be wrong. Here are some fresh new screens:
        prjvBqRl.jpg

        LimpDismalDowitcher.gif

        I also fleshed out a whole ton of writing:
        B3zvn27CEAAws8E.png
        So yeah, lots of stuff happening. Stay tuned.
        That's it for this week. Thanks for reading!
        Mirrored on my blog
evanofsky

Screenshot Saturday 199

"Level design for days" has been my motto for several months now, and this week is no different.

Behold, new challenge levels! These are timed, bite-sized maps with simple goals that can be completed in under a minute. They're the kind of things you can create in the level editor and share on Steam.

Amtg3RSl.jpg

IGcnXcul.jpg

xqtlymDl.jpg

I need to find a new texture for that garish green material.

Lots of other things are happening, but they're more like a million tiny updates rather than a few conveniently screenshot-worthy ones. So that's it for this week! Thanks for reading.

Mirrored on my blog
evanofsky

grepr - 7DFPS 2014

I survived 7DFPS, barely. Here are some fascinating statistics:

  • Days to create an FPS: 7
  • Hours spent: 93
  • Levels built: 5
  • Lines of code written: 2313
  • Hours to spare before deadline: 2
  • Functioning brain cells remaining: approximately 4

    I'm happy with the result, though. Jack did a great job on the audio as usual, although at the last minute I had to throw in some clunky placeholder sfxr sounds. Blame me for those! Maybe we'll replace them later.

    Here's the game pitch:

    You are a "grepper" - a remote operator hired to collect data from abandoned cities using an AWK drone. At a time when data serves as currency, a competitor appears on the scene with an identical drone. Find her before she finds you.

    For the AWK drone, moving and shooting are the same. It attaches to walls, floors, and ceilings, then launches explosively to its next target. Anything in the way will have its whole day ruined.

    Here's a video and some screenshots:



    qRZKpQMl.png

    YIrxKfrl.png

    6MYjbvUl.png

    Download it here for Mac or PC and let me know what you think! And make sure to check out all the other great 7DFPS games.

    Mirrored on my blog
evanofsky
This past weekend I exhibited Lemma at the Ohio Game Dev Expo. It was an awesome time. Extra Life raised over $9,000 for charity (yes, it is in fact over 9000).

The Oculus Rift was a huge hit!

vmkkgYel.jpg

I worked on various improvements right up to the expo. First, some new textures for moving platforms and doors:

YloSUlPl.jpg

This is the first texture I've created that has any kind of directional meaning. The reason is that until this week, I had no control over how the textures mapped to the voxels. It was all procedural. So if I put an arrow graphic in a texture, there was no guarantee which way it would face.

Now I have editor controls to at least rotate and offset the textures, so I can have more meaningful texture design.

Next up, I wanted the menu to be more visually interesting to attract people to the booth, so I spruced it up a bit.

iDgwjHil.jpg

Both before and after the expo, I fixed a ton of issues with the existing levels. I especially reworked Forest quite a bit (once again... sigh...).

IM4VOwcl.jpg

Every time I do it, it gets a thousand times better. At some point I have to draw a line.

I'm also still plugging away at some exciting new levels. Sneak preview:

5GotVrxl.jpg

That's it for this week. Thanks for reading!

Mirrored on my blog
evanofsky
[color=rgb(85,85,85)][font=Arial]

With a headline like that, what could possibly go wrong?

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

In this article I present an idea that may help both sides of GamerGate survive the ordeal. Simply this: negative movements are counter-productive. Positive movements affect change.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

What is GamerGate if not a negative reaction? You could say it's in favor of journalistic integrity, but it's more accurate to say that it's against journalistic deceit, despite the efforts of some GamerGate supporters to redefine the movement in positive terms. It can't escape its reactionary negative roots.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

The other side suffers from the same problem. Those journalists and organizations identified by GamerGate as "Social Justice Warriors" mounted the attack over the past few years in articles and other media which condemn gamer culture and promote an "us vs. them" mentality more frequently than they promote diversity.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

By now, everyone has picked a side. Bi-partisan discussion is rare. How do we solve this? By reframing everything in positive terms.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

I turn to an example from outside the industry. I think Christianity is on the tail end of a decades-long struggle with the issue of LGBTQ individuals. Many Christians changed their interpretation of the Bible to allow for alternate sexualities and genders. No problem there. However, some Christians simply cannot read that into the Bible no matter how hard they try, and they cannot abandon their faith. For them, there are two options:

[/font][/color]

  1. Openly condemn, alienate, and otherwise crusade against the LGBTQ community. Essentially, become a bigot.
  2. Respectfully show the LGBTQ community what Christian love is supposed to look like without interfering with their lives. Yes, you can do this while still maintaining a belief that their sexual/gender orientation is sinful.

[color=rgb(85,85,85)][font=Arial]

Just for a second, put yourself in the shoes of a hardcore, born-and-bred, farm-raised conservative Christian. You are deathly afraid of the "gay agenda", a scourge which will rampage across your home country and destroy everything you love. How hard would it be for you to choose option 2? Yet many Christians do this.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

Of course you know the Christians who choose option 1. Westboro Baptist Church is the most horrific example. How effective were they? I haven't heard from them in years. They generated an immense amount of vitriol, probably without changing a single person's mind. More than anything, they damaged Christianity by driving LGBTQ individuals away from other, more welcoming churches.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

The point is, negative movements don't work. They're actually counter-productive. Most Christians figured this out in the example I just gave. We in the gaming community have not figured this out yet.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

The reason is, it's much easier to be against something than to be for something. The tiniest injustice on either side of GamerGate instantly mobilizes a massive social media army, because it's easy to get outraged and click "Retweet". Both sides of GamerGate exhibit the lowest form of slacktivism.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

So, "wat do?"

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

If you are pro-GamerGate: I don't know if your movement can be salvaged, which is a shame. You've already tried to turn the focus toward positive things like integrity and respecting consumers, but GamerGate will always be a negative reaction against something at its core. Heck, #NotYourShield has a negative right there in the name. If you can, abandon ship and start a new campaign focused on the positive things you find important. Forget the half of the game industry that seems to hate you. Prove them wrong, not by posting a video showing how nice you are, but by forgetting the conflict and doing something positive that fulfills your goals, regardless of what people think. Maybe make a game!

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

If you are anti-GamerGate: Stop attacking; you're only throwing fuel on the fire. What does that mean in practical terms? This will sound incredibly controversial (steady... let me explain before you crucify me), but don't talk so much about harassment, prejudice, and the like. Instead focus on the great positive things women and other minorities are doing in this industry.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

Charlie Brooker covers the topic of mass murders in a brilliant 2009 segment of Newswipe.

[/font][/color]

[color=rgb(85,85,85)][font=Arial]

He quotes Dr. Park Dietz, the famous forensic psychiatrist:

[/font][/color][quote]
[font=Georgia]

We've had twenty years of mass murderers, throughout which I have repeatedly told CNN and our other media, "if you don't want to propagate more mass murders, don't start the story with sirens blaring.

[/font]
[font=Georgia]

Don't have photographs of the killer.

[/font]
[font=Georgia]

Don't make this 24/7 coverage.

[/font]
[font=Georgia]

Do everything you can not to make the body count the lead story, not to make the killer some kind of anti-hero.

[/font]
[font=Georgia]

Do localize this story to the affected community, and make it as boring as possible in every other market.

[/font]
[font=Georgia]

Because every time we have intense saturation coverage of a mass murder, we expect to see one or two more within a week."

[/font][/quote]

[color=rgb(85,85,85)][font=Arial]

I think it's not a huge stretch to extend this advice from mass murders to harassment. Of course we all need to be aware that it happens, but focusing on the harassment itself only invites further attacks. Instead, focus on how much these minorities have accomplished, and only mention harassment in passing. They should not be admired for being a minority who puts up with a lot of hatred. They want to be admired for what they do, period. And there's plenty to admire, so let's focus on that.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

For further reading on the subject of antagonism, tribalism, and politics, I leave you with a wonderful in-depth article written by a libertarian-ish Jewish psychiatrist entitled I Can Tolerate Anything Except the Outgroup.

[/font][/color]
[color=rgb(85,85,85)][font=Arial]

Mirrored on my blog

[/font][/color]