Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Deceiver

A topic by Helvetica Scenario created Dec 01, 2015 Views: 14,280 Replies: 115
Viewing posts 1 to 99
(97 edits) (+4)

Latest update: Jan 16




Premise

Launch your spider drone at walls, ceilings, and enemy heads in this philosophical shooter set at the end of the world. PC/Mac/Linux/PS4.

Prototype

The game is very loosely based on a prototype called "grepr", which I made for 7DFPS 2014. Play it on itch.io.

New engine, open source

I built the prototype in Unity, which worked great. But now I'm so sick of Unity crashes and performance issues due to C#'s memory management that I decided to build this game from the ground up in C++, targeting PC, Mac, Linux, and hopefully PS4.

The latest source and assets are available here: https://github.com/etodd/deceiver

I retain copyright on the assets, but the code is MIT licensed.

Website

deceivergame.com

(+1)

I've followed Lemma from pretty early on and it ended up amazing.

Your new Game is shaping up to look just as great!

Thanks! I'm trying to take all the lessons learned from Lemma (mostly what not to do :P) and put them in the new game. Completely different theme, but lots of similarities.

(+1)

Grepr was already amazing. I look forward to this.

(+1)

Stream: For those interested in coding, I've been streaming a bit on Twitch, although not at regular times. Whenever something comes up that I think would be interesting to watch, I stream it. Mostly C++ code. Here: twitch.tv/emt1337

Dishonored: I played this game for the first time last week. Accidentally beat the whole thing in a single 8-hour play session. I had completely forgotten about the game, and now I come to the obvious realization that my game is basically the Blink ability. Not a bad thing, certainly.

I learned some things about stealth. My AI bots now follow predictable patrol routes. And footsteps now emit sound which I visualize in-game like so:

But mostly, Dishonored just made me wish I was making a normal FPS with a human player character rather than a spiderbot. So, the big reveal is:

Humanoids: you can now switch back and forth between humanoid and spiderbot.

I always planned to do something like this, but it was going to be more of a "possess this character" thing. But I think it needs to play a more central role, so I think you'll be able to switch back and forth at will. We'll see. The controls will be exactly like Lemma. I've already got rough jumps, wall-jumps, mantles, wall-running, a decent acceleration curve, a non-horrible placeholder running animation, and wall sliding. All this was pretty straightforward since I've done it before.

Here's the tricky thing about humanoids: they can walk even on pink surfaces which are inaccessible to spiderbots. But spiderbots can climb on walls and ceilings. Several movement puzzles immediately come to mind.

Level design: still struggling with this. My goal is to bring a demo to GDC and find a world-class level designer willing to collaborate.

In the meantime, I decided to steal a design from Dishonored and use it to test the humanoid controls. See if you can place it. There are two Sentinels guarding the entrance...

I also removed edge detection on the out-of-range (pink) geometry. It cuts out some unnecessary noise, but also makes it harder to see stuff in the distance. We'll see if I keep it. Visuals are very much subject to change.

Engine stuff: The armature component now supports multiple blended animation layers. Silky smooth motion. I'm also nearly done with a major refactor which will enable saving/loading games. My goal is to basically fread a bunch of data into memory, hit "go" on the simulation, and have it work. Pretty much that boils down to: IDs everywhere rather than pointers.

Looking ahead: Been reading up on behavior trees. AI is a major shortcoming in my games, it's always been simple FSMs. I'd like to level up this area of my development expertise.

(+1)

Grepr was great and this is looking really solid already :)

Thanks, by the way uhhhhhhhh can I beta test your game pretty please

(+1)

uhhhhhh ya. Will reach out early next year :)

(+1)

Ludum Dare happened this past weekend. A few friends and I made a browser-based multiplayer version of 2048:

Play it here on itch, and vote for our entry! Please? :)

Just a quick update for today. Implemented the Poor Man's Volumetric Lighting™ method used in Lemma:

The algorithm creates a lot of noise, but thankfully most of it gets masked by the film grain.

Source here

(+1)

That lighting looks slick, and also, is that 2048 mixed with Scrabble?

(+1)

Thanks. Haha I think maybe the best way to describe it would be "2048 MMO roguelite". For a while we thought about calling it something like that.

(+1)

Happy new year! I'm busy experimenting with more appropriated architecture.

(+1)

Looks good ;) The first new screenshot makes me think of Petra in Jordan...

Will you write new "Poor man" articles? The ones you did regarding Lemma were quite interesting to read.

Well spotted. Yeah it's basically Petra. :)

Yes, new articles are on the way! I just like to wait until after the fact before I write about things, so I can address them with the benefit of hindsight. But the series will definitely continue!

(+1)

Wow, I'm loving the looks of this one. Those last architectural shots are really something. Keep it up

(+1)

Thanks. Yeah, no plans to quit any time soon. ;)

New name [?]

To date, I have thrown out and rewritten the design doc and story seven times. This latest revision has lasted longer than the others, so I'm hopeful that it's the last.

"MK-ZEBRA" was originally a reference to MKUltra. The story started out in the vein of paranoia fiction, but now it's drifted toward something hopefully a little less cliche. The current working title is "The Yearning". Let me know if you hate it. I have not yet encountered any major concerns with the new title.

New humanoid model

I modeled, rigged, and animated a new placeholder humanoid model. It'll do for now.

Beneath a certain health threshold, the helmet opens to expose the head. It's not too visually distinctive. It'll probably get redesigned.

I'm using this to experiment with a "last-hitting" mechanic, as in a MOBA. You can't headshot an enemy until one of your ally units damages it.

Parkour

A while back I realized that I'm not quite done exploring the parkour mechanics in Lemma. There will be some parkour elements.

I'm taking the best bits from Lemma, refining them even more, and cutting out the garbage. With the new codebase, it's actually possible to build up momentum above the max speed, whereas Lemma killed your extra momentum whenever possible.

Here's the slide move filling in some transient yellow floor tiles:

Turrets, projectiles, particles

Things shoot each other. Lights flash. Sparks fly.

The red areas visualize the view range of enemy units. I hijacked the deferred point light renderer to overwrite the albedo color for those areas, rather than modifying the lighting buffer.

Also, film grain is gone.

Soren

This is a work-in-progress character. She's an AI who thinks she's a person, currently voiced by a text-to-speech program. The diamond frame grows and shrinks to match her voice.

Here's my amateur pixel art for her spritesheet:

Open source

The entire repo is now up on GitHub (URL will likely change soon due to name change). I'm retaining copyright on the assets, but they're available for download, and all the code is MIT licensed.

(+1)

As a non-English speaker, I preferred the title "MK-Zebra" because it has a more "high-tech" sounding connotation and I think it fits better with the game visuals. "The Yearning" logo & meaning makes me think of a more "artistic" game but I guess that if there's a story, it's justified ;)

Anyway, it still looks like a good solid project. Keep us updated :)

(1 edit) (+1)

i agree. mk-zebra seems to fit the aesthetic of the game better (at least, the aesthetic of what has been shown in this devlog)


really loving the look of this game by the way!

Thanks for the feedback guys! I think you're right, MK-ZEBRA fits the visual theme a bit better...

The logo is still up in the air; it could definitely convey that high-tech aesthetic more effectively. I do want to stick with the stark black and white though. It worked pretty well for the Lemma capsule art. And I am actually going for a bit of an "artistic" feel (headshots aside), so, glad that comes across. :)

(1 edit)

Train Jam

I was lucky enough to snag a Train Jam ticket this year! Got to meet a ton of lovely people, hang out and talk shop, and of course play some of the amazing games they made.

Here's my entry, Haunted Heist, a 3D browser game. Rob a haunted mansion!

Roscoe

I got hired to make a secret game embedded in a website footer. Check it out. :)

Right, on to the real stuff!

Design overhaul

The past couple months, I've been overhauling gameplay, going in some wild directions. The following is a synopsis of my "design" process (emphasis on the scare quotes).

Goals

The game has two main aspects. At any given time, you will be either a) parkour-exploring cool environments, or b) facing off against another player online.

Target emotions for parkour-land: freedom, delight, curiosity, relaxation. Basically the same goals Lemma had, or should have had. I'm not too worried about this mode; I've done it before.

Target emotions for multiplayer-land: tension, fear, triumph. This is new territory, and will constitute the focus of the rest of this post. I want to replicate and improve the experience of playing a one-on-one, sniper-only, one-shot-kill round of Call of Duty. Maybe the players only take 5 shots the whole game, but they're constantly trying to predict each other's moves and out-maneuver the opponent.

Detour into MOBA-land

In an attempt to improve on the CoD sniper idea, I tried to identify the things that make it interesting. I thought maybe the long-term player interaction differentiated it from your typical free-for-all CoD frenzy. The longer you're alive, the more interesting your interaction with the enemy becomes. As soon as you die, everything is reset and lost. I wanted to facilitate more methods of player interaction that didn't involve death.

I don't know much about MOBAs, but I think they have a lot of non-player-death interaction. Players are generally alive for a long time; dying is a big deal. And plenty of successful players go through a whole game without killing another player.

So I tried to identify compelling MOBA elements that could be transplanted into my game without too much fuss. I added auto-spawning minions, turrets, and big, granular health bars.

Only minions could attack turrets. Players could kill minions with a headshot, but only once the minion's health dropped beneath a threshold, which opened their helmet. This forced players to stay behind their minions and try to last-hit enemies.

Players could still kill each other with one or two hits, but it became a risky proposition to venture into enemy territory. I decided to visualize view ranges around enemy units to make it clear exactly where an enemy would start attacking. A bit like fog of war.

I hoped that all of this would give players more ways to interact with each other, albeit indirectly, killing minions rather than each other.

Progression

Again, I don't know much about MOBAs; I just can't get into them. But they clearly work for people. I wanted to extract the fun from them. I played a few (admittedly short) games of LoL, and more importantly, collected some feedback from MOBA players. I discovered there is no single feature that defines a MOBA, but one feature seemed at once pivotal and easy to transplant: player progression. In a MOBA, everyone starts each game on a (hopefully) level playing field, and becomes more powerful as the game progresses.

Mainly, they progress by leveling up their abilities. I decided to steal this, along with the idea of a team score.

Here I also experimented with colors, trying to brighten everything and make it more appealing, like a MOBA.

Killing enemy minions and players gave credits, which could be spent on abilities. Kill the enemy player five times to win. Easy. Ship it!

Problems

At this point, everything has at least 50-100 HP. I kept player attacks pretty powerful, but minions take around 30 seconds to wear each other's health down. In true MOBA fashion, they just stand still attacking until somebody dies. This works fine in a top-down RTS-like setting, but not in a "visceral" first-person game.

In general, my detour into MOBA-land was missing some of my target emotions, namely tension and fear. Giving players five lives, turning everything into bullet sponges, and brightening the graphics all worked to lower the tension. I could see that with some polish, the MOBA elements would accomplish the goal of long-term player interaction, but at the expense of everything else.

Backpedaling

I'm now back to the original prototype: you get one life. I think this is more impactful than an arbitrary score number at the top of screen.

I'm also back (way back) to darker colors. The vibrant, beautiful colors I love still have a place in parkour mode, but here the colors need to be dark and maybe even discomforting.

I also switched the reticle from a diamond to an inverted triangle, for several reasons. First, triangles are aggressive, especially inverted triangles. I noticed just that small change evokes a much more hostile feeling than the relatively peaceful diamond shape. Second, triangles and non-square angles are a theme I want to explore in this game, since my last game was all squares. I keep finding squares I've added instinctively and converting them to triangles.

Salvaged ideas

My goal now is to facilitate long-term player interaction without sacrificing tension. I'm keeping the "fog of war" idea, and even making it more central. Map control is one element I noticed was critical to MOBAs. I'm now experimenting with having players place proximity sensors around the map which "capture" areas. Maybe a bit like Splatoon.

Abilities and player progression are also staying, although I'm simplifying them so that only one ability can be equipped at a time.

Wrapping up

All this has not been very productive in terms of features making it into the final product, but I see it as a necessary step of my patent-pending "design by trial-and-error" process. The key is to have a set of design goals which you can use to judge potential ideas. Some of these crazy MOBA ideas work toward my goals, and so remain, while others get cut. Previously, my design goals have been, "make something I like".

I went to a great GDC talk about the narrative design of Dragon Age Inquisition's DLC. The writers picked movie references for their story; one was Captain America Winter Soldier; the other escapes me at the moment. But these two movies gave the whole team familiar references from which they could make decisions on every little detail of the game.

That's what I'm trying to do. I don't have any movie references for the story yet, but I'm looking. Unfortunately my movie literacy is pretty abysmal. :)

Design epiphany

Until recently, I thought of the game as a series of alternating levels: PvP level, parkour level, PvP level, parkour level...

That's a lot of content. It makes a lot more sense to design each level to work equally well in both contexts. First, you play the level facing off against another online player. The game overrides the level's color scheme and render settings to achieve the feeling of tension I want, and to communicate critical information about the game state.

Then you play the same level in parkour mode, where each level can look and feel wildly different.

It's not a new idea, plenty of games do this. The tricky part is tightening up the design so that each element in mode X serves a corresponding purpose in mode Y.

Custom nav meshes

For reasons not yet clear, I find myself in need of a bot-friendly method of navigating around a level.

Now, I already have a traditional nav mesh for bipedal bots, thanks to Recast. It's super nice and can even be modified at runtime.

But this bot needs to be able to shoot itself around the level, attaching to walls and ceilings like the player does. I need a different kind of navigation mesh for this.

Here's the plan:

Task 1. Sprinkle points across all the surfaces in the level.
Task 2. Do a bunch of raycasts to determine which points connect to each other.

Of course this is pretty compute intensive, so I do it at build time.

Surface parameterization

I try to code up task #1 live on Twitch, resulting in this:

Eh, it's a bit off. The problem is, I want the points to be regularly spaced in a nice grid on each surface. This is closer to what I want:

Here's the process I end up with:

1. Loop through each triangle in the scene.
2. For each triangle, calculate two "basis" vectors for the grid on the triangle.
3. Use a standard triangle rasterizer to generate all the points on the grid, projecting each one back into 3D space.

The end result works pretty well, although I'm still struggling with thin triangles slipping through the cracks of the grid, and with sampling around the edges of triangles. Here's a shot of a particularly bad configuration resulting in a lot of missed points:

Adjacency calculation

Next, I connect each point with up to 48 of its closest neighbors, like this:

Here you can see the sparse point sampling completely missed the walkway. Not good. I'll probably revisit this problem at some point.

It takes about 5 minutes to generate and connect ~4000 points. The raycasts are really slow. I end up splitting the level mesh into chunks, which speeds up the raycasts immensely. The whole process takes less than 30 seconds now.

Most of the points in the graph are maxed out at the 48-neighbor limit. The connectedness of the graph is insane.

Pathfinding

I code up a quick implementation of A* and run it.

Turns out, when each point in a graph has 48 neighbors, the computational complexity of A* explodes. Even a path of only 2 or 3 hops takes a good 30 ms to calculate. Granted, it's unoptimized, and I could also try another algorithm entirely, but I suspect any algorithm would struggle with this graph. The good news is, since the connectivity is so high, and since points can connect to each other across long distances, I probably won't see paths longer than 3 or 4 hops in practice.

I end up putting A* on a separate thread. Similar to the threaded renderer, I communicate with the AI thread via a simple bytecode protocol written to a pair of ring buffers. Results are returned via callbacks.

Double whammy update!

Lead indicators

From the very first prototype, it's been difficult to hit small moving targets (like heads). For a while I tried setting the player speed super high, which mostly worked, but you still had to lead your targets. Finally I buckled down and implemented lead indicators, which show you exactly where to shoot. You can see it proceeding ahead of the minion in this gif:

It's pretty satisfying to aim out into empty space and see everything line up as you fly toward the target.

That progress bar and "DETECTED" warning are part of the new sensor system. If you linger too long in an area claimed by the enemy, they'll get alerted of your exact location.

Glitches

I keep encountering glitches which make the game look way cooler. I would like to find a way to incorporate a glitchy aesthetic into the game somehow.

AI player

This still needs a ton of work, but I'm making progress. The navigation graph is pretty much done, and I'm now able to display what the bot is seeing:

I'm currently using two behavior trees running in parallel, one high-level and one low-level, plus a super-low-level system responsible for aiming and shooting.

Level design envy

I imported a cool map somebody made for Quake 3, just to see what it would look like. Now I kind of want to hire the guy:

New test map

For now though, I have a new, non-copyright-infringing test map in the works.

Blender SSAO

Speaking of level design, someone on the stream the other day told me that Blender now supports SSAO in the editor view. Hit "N" in the 3D view to open the property pane, then turn on ambient occlusion:

Control points

I wanted to imbue the sensor mechanic with more utility and depth, so I added "control points". Every 60 seconds, you receive a credit bonus based on the number of control points within range of your sensors. They look like this:

They're very unassuming, and there are no attendant UI elements, so right now they're pretty confusing. I'll eventually think of a good way to make them more obvious.

This shot also shows the new compass, which is less cool but more functional. The red and blue triangles point toward the respective spawn points of each team. I kind of miss the old compass rose though. We'll see if this one sticks around.

(1 edit)

Friend/foe colors

This has bugged me for awhile. I use red in the UI to convey "bad"/"enemy". I also had two team colors: red vs blue. It was pretty confusing when you were on the red team.

Now there are no red or blue teams. It's just "friend" (blue) or "foe" (red). Here you can see both players are blue on their own screens:

Crawling is back

I uncommented it again. This time I turned the speed down pretty slow; it's not meant to be a major part of gameplay. It just alleviates some of the frustration of being rooted in one spot, and it gives you something to do while waiting for the movement cooldown. The AI player also has some rudimentary crawling code. It also exhibits inaccuracy when trying to hit targets.

Sensor system

Currently, placing sensors is an ability that comes equipped by default. It's on a 20-second cooldown timer, which is frustrating. You have to keep track of it in the back of your mind, and it just feels arbitrary. Today I realized it makes much more sense to just charge a certain number of credits to spawn a sensor. If you place it near an uncontested control point, it will eventually pay for itself.

I'm considering another idea with the sensor system, which is to make players invincible when they are within range of their own sensors. To damage an enemy in their safe zone, you would first have to destroy their sensors, which is a risky proposition.

Maybe there's an expensive ability later on that cancels the invincibility. This could be something that both players save up for in the late game.

Parkour

The parkour code is 75% done. I started to re-implement animations from Lemma. My test level is also starting to look half-decent.

Third-person

When I've shown the game to new players, it usually takes a good five minutes before they even vaguely understand what's going on. I think the biggest point of confusion is: "what exactly am I?"

The game is so strange that they have no point of reference. If you're making an FPS or a platformer, people are familiar. But in this game, the combination of vector graphics and strange movement is a lot to take in at first.

Another problem is the fact that at any given time, you can't aim at half the screen because it's blocked by the wall you are attached to. It makes sense, but it's confusing to a new player in first-person because they don't even realize they're attached to a wall.

A third-person camera alleviates some of these issues. I realized that as far as gameplay, nothing is really lost in the switch to third-person, except for the perennial problem of what to do when the camera is inside a wall. Right now, I'm just culling everything in the way.

Ideally, I would still render blocking geometry, but with low opacity. In fact I'll probably do that eventually. This works for now though.

I've always had a janky third-person option (as seen in the previous devlog), so this was just a matter of cleaning it up and making it work for real.

Ability overhaul

Here are my gameplay goals:

  • Create tension by making it possible to be killed at any point in the game.
  • Encourage a lifecycle of progression through the early/mid/late game without destroying the tension.
  • Minimize the number of mechanics and wring everything possible out of them by making them interact with each other.

I'm using an ability system with upgrades to accomplish goal #2 (progression). I immediately thought of a bunch of crappy abilities like stealth, skip cooldown, invincibility, etc. You would buy these upgrades at your spawn, then switch between them in the heat of battle.

The problem is, these abilities obliterate goal #3 (interaction between mechanics). So you press a button to become invincible for a few seconds; that can become an interesting idea (see TF2) but on its own there's nothing else for it to interact with.

These abilities do nothing but augment your ability to kill the other player, which isn't very interesting. There's much more potential for interaction if instead you augment the environment.

I already had a bit of this, since you could spawn a minion by capturing a certain point. But that doesn't really allow player expression; it's just a checklist for the player to fill out. Capture points A, B, and C.

All this to say: I turned the minion spawn mechanic into an ability. I actually cut it down to three abilities: spawn sensor, spawn teleporter, spawn minion. And there are no cooldowns, it's just a flat fee for each item spawned.

Here's the new menu that pops up automatically at your spawn point. Hold one of three buttons to upgrade that ability.

Sensors and minions are mostly done, but I haven't even started teleporters.

Lots of work to do!

Hey the game looks pretty cool. Will you be needing any music for it? If so you can check out my portfolio here www.sbeastmusic.com/portfolio

(1 edit)

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.

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.

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:


New terminal system

Here's the new workflow for each level:

  • Spawn into the level as a humanoid.
  • (Explore and find stuff, not done yet)
  • Activate a terminal, which looks like this:
  • Have a conversation with Penelope (main AI character previously called Soren), who starts the matchmaking process.
  • While chatting and waiting, you can still parkour around the level.
  • Once Penelope finds a match, she connects you with the other player and reloads the level in PvP mode.

New health system

Previously, health worked as follows: you started with 1 HP, and you could "capture" health powerups to get to 3 HP total. If someone hit you just right, it was still an instant kill. But most shots bounced off and subtracted 1 HP from you.

Now, you can only damage a player if you have the same or higher HP. If there's no chance to damage a player, you'll see a flashing "danger" sign. I also got rid of the instant kills; they felt more like random chance than skill.

Previously, when a player captured a health powerup, it remained theirs until you damaged them, resetting the powerup to neutral. At that point, they could recapture it to get their HP back, or you could capture it and get an edge.

Now, you can capture a health powerup even if it belongs to an enemy. You steal their HP. This encourages you to set up protections around your health powerups. Also, once you get to full health, you can't capture any more health powerups. This encourages you to run on low health so you can steal enemy HP.

This system makes things crazy if you happen to get into a fight near a health powerup. You'll start out at the same HP until player A damages player B, at which point player B is forced to capture the powerup before they can do any more damage. It gets pretty hectic.

In the early game, this sort of back-and-forth is exactly what I want, but I can see it dragging on in the late game. So I will probably design an expensive mechanic (available only in the late game) that allows you to "freeze" powerups in some way, to force the game to end in a reasonable amount of time.

Control point UI

Control points now have icons that change color depending on their owner. Here you can see one point inside my sensor zone, and one outside:

Each captured point earns 2 credits every 15 seconds. The UI shows this "credit delta" beneath your total credits (+2 in the above screenshot).

Teleporters

I started out giving two possible actions for each ability. Hold the ability key (e.g. 1, 2, or 3) to spawn the item (e.g. a teleporter). Tap the key to execute an action with those items (e.g. teleport).

I ended up combining the two. Hold the key to simultaneously spawn a new teleporter and teleport to an existing one.


The particle shader needs some work. From some angles it looks like flying macaroni noodles.

Roll animation

Look, I did an animation!


There is no fall damage, but if you fall too far, you'll instantly lose all momentum and completely stop for a second. You can roll at just the right time to maintain and even increase your momentum.

New menus

I worked on some menu animations.


Notes / matchmaking

I planned out three systems for parkour mode. Notes are done:


Matchmaking is also done. One more system to go.

Todo

  • Balance and adjust PvP gameplay
  • AI programming
  • Level design
  • Writing
I'm headed to New York City this summer to teach people how to make games at MakeSchool. My hope is that when I return, the game will be in a playable state and I'll have some funding. At that point I'll probably start looking for some help on this project.

I'll skip all the boring stuff I did this week... writing dialogue, fixing build errors on Mac, incrementally improving the parkour code, refactoring a bunch of templates out of existence, fixing rare AI crashes...

Just a few things of note:

Level design revisited

I resurrected this level and re-worked it in light of new design decisions. It now works pretty well in both PvP mode and parkour mode.

Stealth upgrade

In order to encourage you to plan for the long game, I must make it more difficult to kill your enemy at the beginning of the game. However, in order to make you feel vulnerable, you still need to die in a few hits.

This led me to the stealth mechanic: you're invisible while planted on surfaces that you "own".

Now you're safer, but you're cowering in your safe zones.

In the late game, I want things to get more hectic, more lethal, and less safe. Easy solution: pay X credits to upgrade your sensor ability and disable the enemy's stealth. This was easy to implement, so I did.

There are two problems with this design. First, when someone disables your stealth, you have no idea what happened. Your stealth just stops working, and now you're frustrated and confused. A UI element could solve this, but that's extra work, extra visual noise, and you still might miss it.

Second, it's not a "fun" upgrade. When I look at the upgrade menu, removing an element of the game looks unappealing, even if it's a good tactical choice. I want an upgrade for me, not a downgrade for my enemy. Maybe it's just semantics, but it makes a big difference emotionally.

Then I realized that you the player already have power to disable your enemy's stealth: you can spawn a minion to destroy their sensors. It's way more fun to spawn a dude that shoots lasers than it is to just buy an upgrade.

TL;DR: I trashed the stealth upgrade.

More health revisions

Previously I described how much fun it was to battle an enemy near a health powerup. You have to constantly switch between attacking your opponent and re-capturing the health powerup, depending on how much health you both have.

This is made possible by two important details: first, when you are damaged, the game resets your nearest owned health powerup to a neutral state, giving you a chance to quickly regain your health by recapturing it. And second, players can steal health powerups owned by an enemy.

This design has a problem too, though: once you are damaged, you immediately retreat to the nearest health pickup, since you can't do damage anymore. Your enemy follows close behind. Once you're both at the powerup, neither of you has an incentive to leave. You can always gain an upper hand by re-capturing the powerup. The game will end there, after an indeterminate period of back-and-forth.

The game should not end with the first encounter.

Here's where I'm at now: first, when you are damaged, your most distant health powerup resets. This forces you to make a significant retreat, probably back within your safe zone where you have an advantage. Second: in the early game, you can't steal enemy health. You have to buy an expensive upgrade.

This upgrade is much more appealing because it allows you to gain a huge advantage very quickly. It also splits the game into four different modes, depending on which of the two players have the upgrade. And it makes the game end more quickly as time expires.

Containment fields

Now there's another problem. In the early game, it's pretty easy to escape danger. You might be frustrated if an enemy slips through your fingers, but no worries, it's early.

However, in the late game, it's still just as easy to escape, making the game drag on endlessly.

The first thing I thought of was a "stun" ability. Pay X credits and now you can hit a button to temporarily freeze the enemy in place.

More design problems. First, it's frustratingly difficult to hit a button at the right time while frantically bouncing around chasing someone. Second, there's no way for the enemy to counter. Maybe they buy a counter-upgrade that makes them un-stunnable?

That is soooooo boring.

Here's what I'm experimenting with: you buy an upgrade that adds "containment fields" to your minions.

It's hard to see, but there's a transparent field around that minion. You can pass through it freely, while the enemy cannot. This accomplishes the goal of corralling your enemy, but is more interesting than a stun ability for two reasons: first, the field is mobile because minions walk around. Second, the enemy can counter it by killing the minion (which happens to be a lot of fun).

I've only playtested these mechanics against my not-very-smart-yet AI, but I'm hoping to do some real playtesting this weekend. In the meantime, there's plenty of level design, animation, and writing to do.

Yesterday I set up itch.io refinery, which is basically itch's equivalent of Steam Pipe. It does binary diffs so you can upload new builds in seconds. I can now generate download keys for beta testers. I highly recommend this system for anyone who does any kind of beta testing. It's nearly as powerful as Steam Pipe, but only takes about 5 minutes to set up.

Still a lot of bug fixing, UI polish, parkour tweaking, etc. going on right now. But I built another level!

I think creating and exploring new spaces is a major part of why we make games, at least for me. This place didn't exist yesterday!

As usual, colors were difficult to get right. It doesn't help that there is absolutely ZERO correlation between the colors I choose in Blender and the ones I get in-game.

Gamepad aim assist

This is something I didn't want to get creative with. It's a commonly solved problem, and I'd rather steal someone else's solution. I found a YouTube video explaining how aim assist works in Call of Duty. There are two factors.

First, when you are aiming at a target, your look sensitivity slows way down. So if you are swinging your crosshair over a target, it will linger on it ever so slightly. I actually put it down to 60% of the normal speed. It's hard to see in this gif, but it's there:

Second, when you are moving relative to your target, the game will adjust for that movement. It doesn't "lock on", it just offsets your input by the relative motion between you and the target.

This one can get really obvious, especially if you don't move the camera at all. It will just keep tracking forever, making it feel like a cheap aimbot. To avoid this, the tracking only kicks in if you are moving. It won't track a moving target when you're at rest.

I also put an inactivity timer in so that if you aren't actively aiming the camera, it won't track. In this gif, it doesn't track at first, but then I twitch the joystick and it tracks for another half second before going inactive again.

Footstep effect

This is just a nifty thing that shows where you've been.

Playtesting

I got some much-needed playtesting last week. (thank you COGG!) Splitscreen multiplayer is actually a lot of fun. I of course wrote down a ton of issues that I was able to correct this week. The hardest problem, as expected, is teaching people how to play. The tutorial was awful and I knew it, but after seeing people play I got some ideas to revamp it. That will probably happen 3-4 more times before it ships.

Overworld level transition

I'm planning on 16 levels. You have to beat two opponents to unlock the next level. Opponent 1 must be beaten on the level you're currently at. For opponent 2, the match-making system will choose a random level from those you've unlocked so far. I think it's important to replay maps in a multiplayer game.

Anyway, I want to show your progress through the game, so I made a simple overworld transition screen that shows how many levels you've unlocked, how many you have left, and which map you're about to play.

The loading time and black screens are all fake. It actually loads 15-20 MB scenes in under 100ms.

I'm not sure what to do for level names. Right now it's just "level1", "level2"... but something more creative would be nice. The guys who make Brigador just stole an album track list and used that for their level names. Brilliant idea if I could find the right album.

(1 edit)

Cooldown upgrade

I had an upgrade that sped up the movement cooldown by 25%. This is a quantitative change, not a qualitative one. I think that makes for a lame upgrade. Now I'm experimenting with a new upgrade: add 1 to your max HP. This is also a quantitative change, but it's significant enough that it changes the game. Of course it has to be quite expensive.

AI

I've been optimizing and bug-fixing the AI. I've been having trouble with the triangle rasterizer. I want it to distribute points evenly across all surfaces, but sometimes it doesn't do that.

Actually fixing the problem sounded like too much work, so I first deployed some mitigation measures. The biggest issue was the following scenario:

  • AI bot tries to pathfind from point A to point B
  • AI system tries to find the navigation graph node closest to point A
  • The nav graph is missing nodes near point A, so it turns out the closest node is actually on the other side of a wall
  • AI system happily calculates a path from that node
  • AI bot gets stuck trying to get to the other side of the wall

I "solved" this by also storing a normal for every node. In the scenario above, the system will now ignore the bad node since it's facing the wrong way.

This duct-tape patch still wasn't enough, so I also went and actually fixed the rasterizer. It now works on all but the very thinnest of triangles. I think I'm okay with it.

Even with a perfect nav graph, the AI can still get stuck due to the low sample rate. I put in more mitigation measures:

  • Normally the bot tries to crawl toward the next nav node before shooting toward it. I made it stop if any further movement would obscure the next node behind an obstacle.
  • If the bot can't see the next node, it might be a bit off from the node it's currently supposed to be at. I made it crawl toward it in this case.
  • If all else fails, the bot goes into "panic" mode after a few seconds, swinging the camera around and shooting to the first suitable target location.

I did a lot of work on the behavior trees as well. They keep falling into loops, where behavior X gets triggered, executes successfully, but for whatever reason doesn't actually do anything to change the situation, and so it gets triggered again the next time around.

The bot actually has three loops, two of which run in parallel at any given time. This is another place where repetition can creep in, because the two parallel loops can preempt each other in certain situations. You're running behavior A, behavior B interrupts it, but as soon as it's done, behavior A starts up again. If the two behaviors are at odds (going different directions for example), the loop will repeat forever.

Right now each behavior has a hard-coded priority index, but I think I might need to alter it randomly, or decrease it every time the behavior runs, in order to avoid situations like this.

Goodbye teleporters, hello rocket pods

Teleporters were just not fun. Plain and simple.

Also, getting caught in an enemy sensor field was not scary enough. The enemy gets points for it, and they might be able to track you down better, but they might not even be looking for you at that moment. You end up with "alarm fatigue", walking around constantly being tracked without worrying about it.

So I trashed the teleporters and replaced them with single-use rocket pods. Each rocket is dirt cheap and takes 1 second to spawn. I'm expecting people to build arrays of these things.

Here's a gif. Ignore the cramped UI, it's a lot less crowded in 1080p.

Right now the rockets just head straight for the target without regard for the environment, but I'll probably have to put some simple obstacle avoidance in.

Abstraction and the Fourth Wall

Did some more playtesting and got feedback from a fellow gamedev (thanks Steve!). He brought up a very good point. The game has a lot of arbitrary numbers with no physical counterpart: hit points, credits, etc.

Originally, every game element was explained by the story. Over time things have become more abstract. Which is fine, except that it's more difficult and less impactful to think about "credits" than something more concrete like "ammo".

The reason for the abstraction is that I plan to break the fourth wall pretty severely in this game. In fact, at the moment, there really is no fourth wall. Everything is unapologetically arbitrary and "video gamey".

My friend made me realize that in order to break the fourth wall, you must first establish it. Otherwise you're just non-fiction.

So I'm backtracking a bit and bringing in some of the fiction elements that previously played a larger role. This way, there will actually be a fourth wall for me to break later in the game.

I'm starting with simple stuff. Instead of "HP", it's "armor". Instead of "earning credits" by "capturing control points", it's "collecting energy" from "energy ports". I made this a little more concrete with a simple animation showing your sensor pod collecting the energy:

I have a lot of world-building and backstory built up for this game, but I dropped it in favor of more self-aware, fourth-wall-breaking stuff. I didn't want anything in the way of the mechanics and character arc. But now I'm realizing that world-building is super important for immersion.

Invincibility

The other major feedback I got was that the health/invincibility mechanic was confusing and frustrating. It made no sense that just because someone has higher HP than you, you can't damage them at all.

I need some kind of mechanic for this though. I don't trust players to run away and live to fight another day. And I don't want games to end in 2 hits.

Here's what I'm trying now:

  • When you get hit, you are shielded for 8 seconds
  • If someone hits your shield, the shield goes down, but they become stunned and unable to move for 3 seconds
  • If you are shielded and hit someone, your shield also goes down
Here's what it looks like in action. I show the remaining shield amount as a progress bar. The "SH" is the first two letters of the world "SHIELD".

Hopefully this slows things down and encourages people to buy upgrades to gain an advantage.

I leave for SF on Sunday and then NYC a week later. Work will continue, but updates will probably slow down a bit.

Anyway, here's a funky color scheme I liked because it felt a bit 70s

Separating abilities from upgrades

Previously it worked like this. There were three abilities: Sensor, Rocket Pod, and Minion. Each ability had two levels. You had to buy level 1 before buying level 2. You started out with Sensor level 1 already purchased, since sensors are how you collect energy.

I realized that the level 2 upgrades were completely unrelated to the level 1 abilities. Furthermore, enforcing a certain order of upgrades limits the number of meaningful choices you have. And having the player start out with an ability by default is like making a choice for them; there should be many viable ways to purchase upgrades.

I flattened out the leveling system, so now you can buy any upgrade as soon as you can afford it. And if you don't want to buy the Sensor upgrade, you can buy something else and rely on other ways to get energy.

I also changed "Sensors" to "Collectors". Makes more sense within the new fiction.

Containment field changes

That brings me to containment fields. I made them work both ways, so you can use them to keep an enemy in or out, depending on the situation. I'm just calling them "force fields" now.

Anyway, previously you would buy an upgrade that would add these force fields to your minions. The problem is, minions are cheap. I wanted them cheap because they're super fun and cool, but this made it easy to suddenly fill the level with a confusing smear of transparent spheres. I decided to make force fields another spawn ability, just like collectors, rockets, and minions.

Here's the new spawnable force field. I'll redesign the model later.

New problem: I now need to map four spawn abilities on to three gamepad buttons: X, Y, and B. I already tried the "press Y to switch active ability" system, and hated it. I really like the three buttons, not least because three is a major theme of the game.

I decided to limit the number of spawn abilities you can purchase to three. I map the abilities to buttons in the order you purchase them. I thought this would be confusing but it's actually not bad. We'll see if it sticks.

More balancing

I realized that minions were seriously over-powered, because they were the only way to destroy enemy collectors. If your enemy captures all or most of the energy ports on the level, you have to buy the minion upgrade to re-capture them.

I made it so that you can destroy collectors on your own. They're pretty tough to hit, so the minions are still really helpful. But to make the minions even more useful, I made them attack the enemy, where previously they would only attack enemy collectors. I had to slow down the projectile and the attack cooldown, and add a cooldown UI bar. We'll see if all that sticks as well.

A* optimization

A* was taking up to 20 seconds in some cases, causing the AI player to sit still in the middle of combat and think really hard. Turns out, I didn't have an efficient way of determining if a node was already in the A* search queue or not. It was just a linear O(n) search. I already had a data structure to store per-node metadata, so I added a boolean flag indicating whether the node was already in the queue, and that brought the worst-case A* search time down to 1 second.

Just a quick update today!

Ponos recolor

I recolored this map (now called "Ponos"). I will always try to squeeze more purple into a scene.

AI improvements

The AI still has a long ways to go, but it now beats me on the regular, which is a good sign. Now, on the rare occasions when it encounters a bad nav path, i.e. one that is in the nav graph but cannot actually be traversed, it removes the path from the graph. This prevents loops where the AI gets stuck repeatedly trying the same invalid path.

Rocket obstacle avoidance

The rockets now use four "whisker" raycasts to avoid obstacles while they track their target. Makes them a much better value proposition since they're less likely to end up in a wall.

Energy bonus changes

Previously you would earn energy bonuses for things like damaging the enemy, killing one of their minions, etc. These bonuses just coalesced out of thin air. Made no sense. Now I actually steal the energy from the enemy and give it to you.

Armor pickup particles

Armor pickups are the most "gamey" thing in the game. There's no physical explanation or real-world analogue for them. A friend suggested making them look a bit more magical and powerup-esque. Thus:

Other stuff's going on, but nothing screenshot-friendly. Lots of writing.

(1 edit)

Moros

This is a new level I'm working on. It also includes a simple water shader. Water is basically "out of bounds" at the moment. Maybe later I'll do something else with it.

Triangular particles

This was driving me nuts. Everything in the game was triangular, except for the particles, which were diamonds. Most particle systems assume particles are quads, so to make a triangle I'd have to use a texture. Well, I refuse to use textures in this game. LONG LIVE VECTOR GRAPHICS!

TL;DR: I wrote a custom particle system that creates triangles instead of quads. All is right in the world.

Movement cooldown skip

This is an experiment to enable high-level play. It works like an active reload system. The cursor flashes briefly and you have a chance to skip the cooldown if you aim quickly enough and nail the timing. It can be fun to chain a combo together and get a rhythm going.

Writing

In my last game, I tried to use branching dialogue to provide choices to the player. I wanted every choice to mean something. I also wanted to avoid one-sided conversations with lots of reading in between player choices. Unfortunately I ended up arbitrarily sprinkling choices into each conversation that amounted to "BE A JERK? (Y/N)"

The theory was, I would keep track of your level of jerkiness, and unlock different dialogue options accordingly. The reality is, I never did that, and anyway, simplistic morality systems aren't all that interesting.

Now I'm doing simple dialogue branches based on in-game actions. For example, when the player loses a match, Penelope consoles them before moving on with her normal conversation.

All that to say: the writing is moving forward. It's better than the last game. More interesting character traits, less pretentious drivel. But still a little bit of that.

NYU Playtest Thursday

I had the privilege of attending the playtest event at NYU Game Center on Thursday. Got to see Ian Snyder's new game. Didn't realize who it was until afterward.

I arrived late but did manage to playtest one game and have someone play mine. The game was not really their type, but they had a lot of useful insight. Looking forward to future events!

One nice thing about NYC is the steady stream of gamedev events. There's so much going on! I'm trying to take advantage of my time here as much as possible.

Tile Risers

As a side note, a mid-sized YouTuber picked up Tile Risers this week, bringing a sustained number of simultaneous players to the game for the first time. A few free game sites have also picked it up. Right now there are at least 10 people playing at any given time, which is enough to make it interesting. Check it out here!

This week was a lot of future planning and tangential work surrounding the game. I did manage to do a couple things though.

More gamepad tweaks

One problem with the gamepad aim assist discussed earlier was that it would actually adjust your aim away from the target in some cases. I put in a simple check to prevent that. It would also adjust the camera far too noticeably if you were aiming nearly straight up or straight down. Another check fixed that issue.

Even so, some players are still struggling with the gamepad controls. Today I tried a cubic response curve rather than the quadratic one I've been using, but it felt too wild. Then I found this incredibly detailed article about gamepad acceleration in various popular FPS titles.

Turns out that in addition to a response curve, they all apply some form of acceleration. So in Call of Duty, one of the twitchier titles, even if you mash the stick all the way, it takes 0.2 seconds for the rotation to reach full speed. Halo actually employs two acceleration curves: fast for the first 0.2 seconds, then a slower buildup of speed through the 1 second mark.

For now I went with the Call of Duty acceleration model. I also decreased the FOV from 80 to 70 degrees (FOV slider is on the todo list). We'll see how it fares in playtesting.

Rating system

The rating system determines your standing on the leaderboard. Right now, the only two factors that matter are a) whether you won, and b) how much energy you had left over. Could maybe add more scoring incentives later.

SDL

GLFW's gamepad support was simply broken, so I switched to SDL quite some time ago. I've been battling it ever since. On Mac, the fullscreen support just doesn't work. Granted, fullscreen on Mac is totally bonkers. I'm not sure what the expected behavior should be, but I'm pretty sure what I have now is not it.

On Windows, it works fine except on my laptop, where the DPI is 120 instead of 96. I've tried every combination of calling / not calling SetProcessDPIAware(), asking for fullscreen / windowed / borderless fullscreen window, etc. It's 100% a tossup as to what window resolution you end up with. I'll keep fighting it, but if anyone has any SDL tips, I'd love to hear them!

Hey Evan, the game is coming along really well. Are you in need of any music for it? If so you can check out my portfolio here www.sbeastmusic.com/portfolio

Hey Scott! Glad you like the game. :) Yeah I'm not super sure about music at this point. I checked out your portfolio a while back, it's pretty great. I'll keep ya in mind when the time comes for music.

LLC

After doing business under my own name for two years, I finally registered Helvetica Scenario, LLC in Ohio. The name is an awesome reference I've been wanting to use for a while.

What finally won me over was the discovery that helveticascenar.io was up for grabs. (currently just redirects to my personal website)

Sony application

The whole purpose of forming an LLC was to be able to join the PS4 developer program. I applied this week and am waiting to hear back. Fingers crossed.

Reticle revamp

A few testers played for a good five minutes before realizing the triangle at the center of the screen was a reticle.

I revamped the design a bit. It has to communicate FIVE different states without getting too visually noisy.

Buy period

Also in response to tester feedback, there is now a 10 second buy period at the start of each match, where movement is disabled, allowing time to buy upgrades.

Custom map support

I'm now shipping the level importer executable alongside the game executable. This will allow players to put .blend files in a special folder, run the importer executable, and play their map in the game.

The main motivator behind this feature is actually the possibility of facilitating other map makers working on the game before release. More on this later, hopefully.

Force field update

I encountered an issue with the force field: player A can spawn a force field around player B's base. If player B does not yet have the necessary upgrades to destroy the force field, they can never return to their base.

Right now I'm solving this by putting a limited lifetime of 15 seconds on the force fields. The idea is, it runs on "battery". If you place a force field near an energy port, it will last indefinitely. Then all I have to do is ensure there are no energy ports near the bases.

We'll see how well this works in practice. Force fields are definitely not as well integrated as the other mechanics; they probably need more time in the oven.

Soteria design

"Soteria" is the tutorial level. Like in most games, it has received the most attention. I'm still redesigning the scripts and triggers, rewriting the text, and rearranging the level geometry.

Back when I was doing gamedev part time, I had a hard time starting work on large features, because I only had blocks of a few hours when I really needed much longer periods of focus.

I'm experiencing that same problem again; there are a number of things I just can't start without a solid 8 hours to think about it.

Fortunately, I only have three weeks left in NYC, and there's a ton of minor stuff to do in the meantime. I'm also taking advantage of the many gamedev events here. The game has received a ton of playtesting in the past few weeks. One playtester, after listening to me explain the game, said "That's... kind of stupid." The game seems to really resonate with people.

On Wednesday I was able to show the game at a tiny IGDA meetup at a venue that was way too fancy for the occasion.

Scan lines

One playtester suggested that the game needed more "texture". I said goodbye to film grain a long time ago, but I decided to experiment again, this time with scan lines.

Version 1 looked like this. Vertical lines every other pixel. I used a weird blend mode which darkened the scene via multiplication, and brightened it via addition. It was odd.

(Click to see full res)

Version 2 incorporated some suggestions from Twitter: switch to horizontal lines and animate it. I also changed the density to 1 line every 4 pixels.

Then Matt Gallais, art director on Deus Ex Mankind Divided (woah), suggested I modulate the intensity based on depth, making it a sort of "scan line fog".

Pretty nice, but still a few issues to solve.

  • I dropped the weird alpha blending and switched to white lines with additive blending.
  • The lines were sampled via nearest-neighbor. This caused jitters once I started animating the lines. I did some manual filtering to smooth this out.
  • The filtering looked great in most cases, but caused the whole screen to flash noticeably sometimes, especially on TVs. I'm still experimenting with this, but for now I'm biasing the sampling towards one pixel rather than mixing the pixels evenly. Seems to be working okay so far.
Here's how it looks now:

Design changes

Playtesting revealed that no one knew how to collect energy, so I decided to merge the armor pickups and energy ports. Now you earn energy based on how many armor pickups you own.

Force fields suddenly become more useful, because armor pickups can now power them indefinitely. You can capture an armor pickup, then set up a force field to protect it. Only an enemy minion can destroy the force field.

Another somewhat big change was the way players bounce off each other after doing damage. Previously, the camera instantly swung around to face the drone's newly reflected trajectory. Now I keep the camera facing the original direction. I also fudge the reflection vector so that the player bounces a reasonable distance away. This helps prevent the awkward scenario where two drones are right next to each other.

Logo

Someone suggested that the pyramid symbol made them think of the Illuminati, or a dollar bill. I mostly picked it as a fairly direct interpretation of "yearning".

Right now I'm experimenting with flipping it upside down:

This was a nifty but nearly unreadable idea:

Someone suggested that I hire a graphic designer, but I am a) too cheap, and b) too much of a control freak. So I'll just keep iterating until I find something acceptable. This was basically the process with the last game as well.

Dash

I have this system that grays out everything behind the surface you're currently attached to. I do this because you can't shoot backward through the surface; it doesn't make physical sense. However, a significant number of players (probably 50%) get confused when they aim at the wall they're currently attached to, and they can't shoot there.

So I added a "dash" move. When you aim at the attached surface, the reticle changes to a chevron, indicating that you will dash a short distance. This gif demonstrates:

It still takes some getting used to, but at least it avoids the frustration of pressing a button and seeing nothing happen.

The only problem is, I'm not sure what to do with the spider-bot's legs during the dash. Right now they just sort of fold up a bit. No one has questioned it so far, so for now I'll just leave it as-is.

Danger indicator

Players often charge into battle with only 1 hit point, not realizing that they were in danger of losing the entire game in a single hit. Losing a match should not be an unpleasant surprise.

Also, 3 total hit points is a bit low. I wanted 3 because there are 3 of everything in this game, but 4 is more ideal.

I solved these issues by increasing the player health to 4 hit points and hiding the first hit point in the UI. So you have a base health of 1 hit point, but you don't see it in the UI. At the start of each match, you'll see a flashing red empty health indicator, ensuring that the first thing you'll do is look for health.

If you get down to 1 base hit point, you'll also see a flashing danger indicator when an enemy is in range. You can see this at the end of this gif, along with a few other new features. Players now leave trails of tracer particles, making them easier to follow. You can also see what happens when you damage a player and bounce off them; it's much less jarring and disorienting.

Splitscreen menu

The menu for setting up a splitscreen match used to be pretty bad. Each player had their own screen. You would press A to join and switch teams. You had to constantly glance over to the other screens to see which team everyone was on. I got this setup from Call of Duty, but it doesn't really make sense for local multiplayer.

There's a standard solution to this problem. Not sure why it took me this long to switch to it.

Not pretty, but much more functional.

Summer Expo

On Thusrday I got to show the game at Playcrafting NYC's Summer Expo. There were apparently 110 games, over 800 people, and most importantly, free pizza.

It was tough to draw people with only a laptop and some business cards, but a decent number played and really enjoyed the game. However it really hit me that this game's appeal is much more narrow than my last game. A typical conversation went like this:

Person: "This looks cool, what is it?"
Me: "A 3D shooter. Want to play?"
Person: *turns away* "Look, a mobile game!"

"Core" gamers (sigh) absolutely loved the game. Not sure how to feel about it. I would love to make highly accessible games that anyone can play. I would especially like to make games that appeal to both men and women, because doing that almost always results in a better game.

I don't have enough skill for that yet. So for now I'll rationalize by pointing out that the genre of indie 3D shooters is in pretty bad shape right now. The only quality, innovative titles I can think of at the moment are by David Pittman and Terri Vellmann. Plus, there's the tired old saying: "make the game you want to play".

Whatever it takes to make you feel okay about yourself, right?

Anyway, the other takeaway from this expo is something I've been thinking about for a while. Namely, it's nearly impossible to explain the game to people. Being able to explain your game in a sentence is super important. I've been working on it since day one, and I still can't do it, because it's basically two games mashed into one.

So I'm removing one of the two games. No more parkour.

I realized that the parkour mechanics have nothing to do with the rest of the game. In fact, it's so disconnected that I can pretty cleanly remove it without changing much. The important part is telling the story, and I can do that via other means.

The plan is to go back to the roots of the original "grepr" prototype, which had a nifty terminal emulator between levels, where the (very miminal) story unfolded. I won't be going full terminal this time (it needs to be gamepad accessible). It should be fun to come up with a neat seventies-ish UI though.

Small update this week.

Parkour mode completely gone

Like I promised last week, this entire mode is now gone. Cutting big features like this used to hurt, but I've somewhat retrained myself to view it as a good thing now. It's probably just Stockholm Syndrome.

The only problem is that parkour mode was super colorful, and without it I'm left with an extremely restricted color palette. The visuals feel more cohesive now, but I would like to find some way to incorporate those colors again in a way that fits in with the rest of the game.

Look sensitivity, Y axis inversion

This is one of the most important features of any FPS. Can't believe it took me this long to add it, but there it is.

Playtesting

The game is testing much better at this point. For most of its life, the game has been super confusing to people. They only played for one round before quitting. I think the main changes that made it more intuitive were:

  • Scan line fog - improved readability and depth perception
  • Aim assist
  • "Tutorial" (really just starting on a small, simple map with no special abilities)
Whoops, no screenshots this week! Here's a gif:


Crawl navigation graph

So far the nav graph has only contained data for shooting from point to point. The AI crawling was basically fake. I wanted the nav graph to allow the AI to crawl intelligently.

I started by simply connecting points together within a certain radius. I had some bugs at first, but you can see what I was going for:

Next, I wanted to do some raycasts to ensure there were no obstacles between points before connecting them.

For co-planar or concave surfaces, a single straight-line raycast works fine. For convex surfaces where the AI bot needs to crawl around a corner, two raycasts are necessary. This image illustrates the case I was planning for:

Here the AI bot would be crawling horizontally around the triangular shape. It should be able to crawl around two of the corners, but the third corner is blocked by another piece of geometry.

When connecting two points on different surfaces, I ended up calculating the intersection line between the two surfaces, picking the closest point on that line, and doing two raycasts from that point to each of the two candidate connecting points.

Here's a screenshot of the results inside the triangular geometry. The points on the obstructed corner on the right are not connected.

Here's how it looks when it's all working.

Corner cases

The movement code works by placing the player a small distance away from the surface and raycasting toward that surface. I realized you could leak through the geometry by shooting and landing very close to a sharp angle like the one beneath this ramp:

Here, the attachment point could be flush with the floor while the player's position is actually inside the ramp.

I've been beveling and removing these problematic angles, but I realized the code needs to handle this case because it's possible for players to create arbitrary geometry by spawning force fields. The fix is thankfully pretty simple. I just do a few dot products and nudge the player away from the corner.

Crawl glitch

This issue has been looming since the beginning of the crawl code. It happens when you're on a corner and you try to move adjacent to both surfaces:

Here we're trying to move upward, but the movement vector gets clamped against the surface we're currently on. The code transitions you to the other surface, which then clamps your movement vector in the opposite direction. You end up jittering back and forth between the two surfaces. This can also happen with concave corners.

I solved this by preventing the player from transitioning to a new surface if the new clamped movement vector would send the player back toward the original surface. Now it just slides smoothly along the edge.

Sniper

Not much to say here. I added a sniper ability. It increases your range and allows you to attack without moving. Each bullet costs 10 energy, and your position is immediately revealed to the enemy after firing. It's particularly potent when combined with force fields.

I'm back in Ohio! And back to full-time development. Yay.

New drone model

Somebody on the stream pointed out that the drone had three legs, while its body had five sides. This was a holdover from the original model I made for grepr back in 2014.

Fear not, the error has been rectified!

New title screen / logo

GDEX is coming up, and they emailed me asking for assets to put on their website. I realized I needed to figure out the logo and come up with a reasonable title screen to attract people at the expo.

Here was my first quick attempt:

Serviceable, but boring. I also wasn't happy with the composition. I looked at a bunch of main menus in video games and realized that a lot of them put the menu items right in the center of the screen. This frees up the frame to allow for a more interesting composition:

I had to mess with the scan lines and tone them down a bit to reduce flickering. I tried using just a regular texture sampler to create the effect, but that actually flickered even more than my math-based solution.

One thing I'm still not sure about is the way the scan lines interact with the UI. The scan lines are modulated by depth, and the UI renderer doesn't write to the depth buffer at all, which means that you can see faint geometric outlines in the UI. This was completely accidental, and I kind of like it because it's almost like transparency, but I'm not sure if I should keep it or not. It would be a relatively easy fix.

Here's the background without the menu in the way:

Control points and energy indicator

Last night I was thinking that the levels needed more interactive objects or "goals" in them. There were only the health pickups, which also passively give you energy. I also didn't like the hard cap on energy collection; since you can only own up to three health pickups, you can only earn energy so quickly. I was also worried about a new use case I wanted to support, which is the possibility of more than 2 teams. Each team needs its own spawn point.

I solved all of these problems by allowing players to capture each other's spawn points (or "bases"). There are two reasons why you might capture a base: 1) to earn more energy, and 2) to gain another location from which you can buy upgrades.

Each map now has 3 or 4 bases on it. If there are only two teams playing, the other bases start out neutral.

Here I am capturing a base. It's a non-cancelable operation. I also added a timer indicator in the lower left to show when your passive energy gets applied every 15 seconds.

Ian Cuslidge

Ian is working on the game for at least the next month, doing some level design and general consultation. Say hi to Ian! He's already put together some interesting new designs.

Movement cooldown

There have been nothing but complaints about the movement cooldown. Ian suggested replacing it with a "charge" system similar to Tracer's "blink" ability in Overwatch.

I thought this was a good idea. So now, instead of a single cooldown, the player has three "charges", each with a separate cooldown. Here you can see me burning through all three charges in quick succession, leaving me vulnerable at the end.

I also nearly doubled the crawl speed. Another common complaint.

More health revisions

Another problem was the fact that the game started players with no health. This gives them something to do right away, namely run around the level capturing health, but if players ran into each other early on it was usually game over much sooner than my target match time of 10 minutes.

At first, I tried starting the player with the closest health pickups to their spawn already captured. This made them less vulnerable in the early game, but without any health to collect, players would start looking for each other immediately, again ending the game sooner than I'd like.

I ended up increasing the health cap from 4 to 6. I also start players with 3 "free" health points, i.e. they are not tied to a physical health pickup on the map. This means that once you lose those points, you can't get them back unless you steal the enemy's health. Basically, the overall amount of health in the game goes down over time, which is exactly what I need.

I think this is about the 8th version of the health system. We'll see if this one sticks.

Sanding down sharp edges

This is a constant process, but I thought I'd share one simple example.

Normally, the ability spawn buttons are edge-triggered. Meaning, if you hold down the button, it won't keep spawning. You have to release and press the button again. This means that if you happen to press the button while it is deactivated (for example, while you are in mid-air), the button will not recognize your input. However, you might press the button just before it becomes active, maybe just before landing on a new surface for example. The button will never register your input because it's edge-triggered. This is frustrating.

I put in some code specifically to handle this case. Things are less frustrating. The end.

AI work

The AI can now spawn rockets and force fields. It has a very basic concept of saving up money for upgrades, and it compares the priority of each purchase with the possibility of future upgrades.

I also noticed the AI making jerky crawling movements, so I made the nav mesh generator connect diagonal points as well as adjacent ones:

Health overhaul v9

The problem is, matches still end far too quickly. For some reason I thought of the regenerating health mechanic in many modern shooters, and how people complain that it encourages you to cower behind walls for a few seconds instead of running into the action. That behavior is exactly what I need in this game!

I took out the kludgy shield-and-stun mechanic and replaced it with a single special health point that regenerates after 8 seconds. It works well enough that I'm fairly confident it will exist in the final game in some form.

Another problem Ian pointed out was a lack of variation between early-game and late-game play. I addressed this issue with a band-aid patch for now by separating the abilities into two tiers: cheap, defensive, early-game abilities and more expensive, dangerous, late-game abilities.

New maps

Ian has been working hard on new maps. Check it:

I think they're pretty nifty.

Teleporter redux

I brought back the teleporter as one of the tier one abilities. It works a bit different now; it almost instantly teleports you to the farthest friendly control point. Useful for escaping dangerous situations. If you spawn minions, they can also make use of your teleporters.

Radial dead zone

Drew Van Camp commented in my stream, posting a link to a great article about thumbstick dead zones. I was previously using a square dead zone, but no more! Gamepad controls feel even better now.

Story overhaul v10

The story is the only thing that's been through more revisions than the health system. I started looking for a new story when I threw out the parkour elements. When I say "new story", I only mean a different way of communicating the same idea that has been constant since almost the very beginning. As I've mentioned before, Story v10™ returns to the roots of the 2014 7DFPS prototype, which brings us to...

Overworld

The story now unfolds mainly in the overworld. The story is closely tied to the overworld interface, so I decided to flesh it out and reassure myself that I can make it do everything the story needs.

I thought about the interface for about a week and came up with a three tab system:

When a tab is "minimized", it will show a compact summary of its contents, and a notification will flash if something needs your attention.

The overworld will be a separate lightweight management game. You'll be capturing zones, managing resources, etc. You'll be able to deploy a drone to automatically capture zones, but when an opponent intervenes, you'll have to decide whether to risk more energy and manually pilot the drone, or chalk it up to a loss and sacrifice the drone.

The zones are simple shapes built out of triangles, and though you can't see it in the gif above because there are no buildings, the zones recolor all the geometry inside them. I do this by modeling the zones as volumes, like this:

Everything inside the volume will change color with the zone. I use some simple depth sorting and culling flags to pull this off without a stencil buffer.

The zones generate energy over time. Bigger zones generate more energy, and there's a bonus for capturing multiple zones in a chain.

AI

Despite all these new features, most of my time has actually been spent improving the AI. It now handles force fields correctly and can even snipe sometimes. It mostly works, but there's still a lot to do. Once I have a decent baseline implementation, I'll move on to creating multiple AI personalities.

The tab UI was pretty fun to write. Here's a dev stream recording of the whole process.

Level design

Ian's cranking out some great work. I tested one of his new maps in a 2v2 configuration for the first time ever. Almost everything worked on the first try!

Improved culling

This is still a work in progress, but I'm trying to make the geometry culling effect less intrusive and confusing. Previously I was using a culling volume of two spheres, which basically removed anything that could possibly obstruct the player's view. I realized that might be overkill in some cases, because you could actually see a lot of things that would be obstructed from first-person view.

After a ton of experimentation, I'm now trying out a view-aligned cylinder, with some special behavior to handle geometry behind and in front of the surface you're currently attached to.

Messages UI

Work continues on story-related elements of the interface.

Supersampled edge detection

Notice a difference between the first screenshot and the others?

I actually like jaggies. I know, I'm weird. However this game is going for more of a vector-based aesthetic than a pixel one, so they have to go. The only way I could think to do it right is by supersampling. My implementation is super basic and probably not optimal.

I still do lighting and everything else at 1x resolution. Only the edge detection shader runs at 2x. My frame time went from 4ms to 7ms. There's probably still a lot of low-hanging optimization fruit I haven't touched yet.

Tons of other stuff is happening, mostly just gameplay tweaks and bug fixes. But we'll skip that for now.

(+1)

Played and loved grepr! This game looks just promising.

Thanks! Glad you enjoyed it. :)

Overworld

Here's how it works right now. First you spend a "hack kit" to gain access to a zone. It will start out being owned by someone else.

Next you deploy a drone to capture it. It will attempt to "auto-capture" the zone for up to 30 seconds. If the "zone's owner" interferes (i.e. the matchmaking server finds an online opponent to face you), then you have 10 seconds to accept their challenge and play a match against them. If you ignore the challenge, you'll lose the drone you spent.

Likewise, people can capture your zones at any time, and you'll have the option to interfere. The more zones you own, the more energy you will passively collect, even while you're not playing.

The overworld UI no longer fits nicely in a 300px gif, so here's a full screenshot:

The above is what the UI looks like when you're a member of a group, which allows you to play 2v2 matches.

New maps

2v2 matches require new maps in addition to a number of changes and new features. Ian knocked out a few, including this one:

And I finally finished this one from forever ago:

I'm showing the game at the Independents' Day festival this weekend, so I should have more playtesting data to work with soon.

(+1)

This game is really stunning! Interested to see how it develops. How long has the current version since you switched to your own engine been in development for? :O

Thanks! :) It's been about 18 months with the new engine, give or take.

Quick update: I am working on two massive projects that aren't ideal for screenshots, hence the radio silence.

One project is netcode. So far I've got a headless server building on Linux, a virtual connection going over UDP, and some nice serialization / bitpacking tools. My code borrows heavily from libyojimbo, which is part of the gafferongames networking article series.

The other project is porting the game to PS4. I finally got the dev kit and SDK set up; it was surprisingly straightforward. So far everything is building except for the one source file which contained all the OpenGL code. I'm slowly translating those GL calls to their PS4 equivalents. The PS4 API is pretty sophisticated and a bit daunting, but the documentation is fine, and overall it's nice to work with so far.

I'll probably continue to write some gameplay code to keep from going insane with low-level graphics and network stuff. I'd like to add more abilities, starting with grenades.

Netcode

Work on the netcode continues. It provides reliable ordered messaging under conditions of up to 25% packet loss, and it can now serialize the world state over the network. Bitpacking and zlib compression keeps the bandwidth down. The next project is delta compression and client-side interpolation. After everything is synced properly and "spectator mode" is effectively complete, then comes the tricky part: client-side prediction.

PS4

The PS4 port requires me to translate all the shaders from GLSL to Sony's proprietary shader language. I'm putting this on hold for now to focus on other features. If you're a PS4 programmer or you know someone who is, contact me! I would love to hire someone to work on this aspect. It's a very straightforward port.

Ability overhaul

A few things bothered me about the ability system. One is that because you could only spawn objects at your current position, it did not afford much room for creative expression. It was less a question of when and where to use abilities, and more only a question of when.

Furthermore, at least one of the abilities (sniper) worked differently enough that it broke the pattern of the other abilities. You entered "sniper mode", in which your next move fired a bullet instead of moving your drone.

I decided to switch all the abilities to this system. So now, you press a button switch to a certain ability, then hit the primary fire button to use it. If it's a spawn ability, the object spawns where you were aiming.

Decoy

This is a new ability that spawns a decoy drone, which confuses the enemy's UI and aggros all their AI units. Needs tweaking, but I think it'll stay in. Here's a VOD showing the development process for the decoy.

Health v10

I've always loved games with lots of health. I used to run custom Halo matches with snipers, infinite grenades, and health cranked up to 400%. No one liked it.

This game has been a further exploration into this tense, high stakes, no-respawn type of game, in the vein of Counter-Strike and Rainbow Six: Siege (which I have been enjoying immensely lately). The problem is, those games are team-based where mine is largely 1v1.

I've been trying to artificially inflate the playtime with more health. When it comes down to it, if your goal is to destroy the enemy, it's deeply unsatisfying to abandon a fight unless you're about to die. This leads to matches with one quick fight, as opposed to the multiple, varied skirmishes I'm going for.

So I'm going the more traditional route. One hit point, one shield hit point, and a small number of respawns. Yes, it's a compromise, but ultimately the goal I'm shooting for is an emotion, and this new system gets closer to that goal than the old one.

In order to make the respawns still feel meaningful, I plan to make them affect the metagame. Each drone you waste costs money, and maybe if you win a match, you get to take your opponent's leftover drones.

Rush mode

Another factor that encouraged overly quick matches was the gametype: so far it's been only deathmatch. I also realized that deathmatch modes never reach the same level of "tactical thinking" achieved in modes like CTF. So now I'm experimenting with a "rush" mode, which should be familiar if you've ever played Battlefield:

This is your basic attack/defend game type. The defender has to keep the objectives safe for a certain period of time. We'll see how it goes.

(+1)

Wow, I just discovered this devlog, and I'm totally stoked about your rendering style!! Also pretty impressed you decided to write your own C++ code in lieu of Unity.

Oh, and I was just playing Haunted Heist... very charming. Super satisfying audio!

-Zach

Thanks for the kind words. :) Yeah, don't do it my way... stick with Unity... it's better for your sanity. :)

(1 edit)

Another design overhaul

It bugs me to have two modes of gameplay (overworld and PvP) so explicitly delineated and separate. It makes the game lean too far toward the multiplayer category and makes the singleplayer story aspects feel tacked-on. I want the two to be truly integrated in a meaningful way.

So instead of abruptly kicking you out to the overworld, I'm just going to pull up the UI right there in-game while you wander around. This means that I'm cautiously inching back toward the old exploration mode. Which is great because I've been dying to somehow get my colors back in.

It will be more integrated than it used to be. Previously the game would completely reload the level, setting the meshes to be colored or B&W depending on the mode. Now it's just a render flag which I can toggle at any time.

So the idea is, you'll be in exploration mode, you'll enter a new area, find a control panel, and start capturing the area. You might capture the zone before the owner can defend it, or they might spawn in right away and stop you.

There's still a lot to think about, especially what happens if you lose. I think I want some form of permadeath for the player character.

Netcode

Progress continues. First I got positions and orientations synced in a naive, brute-force manner. Then I implemented delta compression so that only moving objects are sent every frame. My first try was less than perfect:

But I got it working. At this point the client was basically in spectator mode; there was no player input.

After synchronizing a few player configuration variables like usernames etc., I started sending player input from the client to the server. It's important that I don't have the server just trust whatever position the client says the player is at; a system like that would be hacked almost instantly.

I was surprised to see how accurate dead-reckoning on the server was, even with low float precision. I'm currently using dead-reckoning, plus the server accepts the client's exact position as canonical if it's within a small tolerance of where the server thinks it should be.

Here's how it works in practice. The white triangle shows where the server thinks the player is.

There are still tons of issues to tackle, but it's looking feasible. I also tested the game with my linux VM in NYC. It survived its first contact with real-world internet conditions, so that's encouraging.

Here's a dev stream recording of some of these netcode features being coded.

New culling effect

If you look at the old culling effect, you can see the inside of the geometry you're currently crawling on. I turned off back-face culling to make this work, and it looked fine. However, you could often rotate the camera and see all the backfaces of the outside of the level, which looked tacky. Plus, disabling back-face culling entails a significant performance hit.

So I re-enabled back-face culling and I black out everything you shouldn't be able to see with a cylinder. Works surprisingly well.

(1 edit)

GDEX

Got to run a booth at GDEX this past Saturday. Also gave a talk called Thirteen Years of Bad Game Code (full article coming soon). Made some new friends, caught up with old ones, had a great time. No pictures, sorry. I don't believe in pictures.

A ton of changes happened around GDEX. Some happened between the two days of GDEX, because I woke up in the middle of the night to write code for four hours.

Dash

Sometimes it's possible to aim beneath the surface you are currently attached to, like this:

The laws of geometry dictate that you can't shoot to the location you're aiming at without passing through the surface you're attached to.

Previously in this scenario, if you pulled the trigger, the drone would always dash, which keeps you stuck to your current surface, but might slide you closer to your goal.

This was confusing to people, so now it's only possible to dash if the place you're aiming is roughly co-planar with you. Otherwise, as you can see above, it just won't let you go.

Reticle

On a similar note, the reticle was also behaving weirdly. Consider the case of the player on the right in the screenshot below:

They're aiming at an enemy drone, but there's nothing behind them but empty space. Previously, the reticle would have been red, preventing them from going. I have to be very careful to prevent drones from flying off into space, which would definitely happen in this case if they hit the drone and the drone died.

However, since we're attached to the same surface as the enemy drone, we can dash and hit the drone without ever detaching from the surface.

It gets even more complicated though. Since the game is third-person, we actually have to do two raycasts. First we raycast from the camera straight through the reticle. Then we raycast from the drone to the position we got from the first raycast. Most of the time these line up in a way that makes sense to the player, but sometimes it can get confusing.

Long story short, the reticle code has like 17 special cases now. It's insane but it feels great. Now, if you aim at an enemy and pull the trigger, something is guaranteed to happen. This was not always true.

Shields

I've experimented with how and when to display shields from almost the beginning. For a while they were just a ghostly white outline, then they were a solid transparent color. More recently I added a fresnel effect. After adding animations I think they're finally good enough.

Culling effect

I spent a few hours trying to solve the problem of seeing through map geometry. It's a huge challenge because there's no way for the GPU to know whether a given pixel is "inside" or "outside" level geometry.

I tried doing a bunch of raycasts and generating clipping planes from those, but I encountered artifacts that would require sampling every single triangle in a certain radius around the camera and create a clipping plane for each one. Which is feasible for my low-poly maps, just not something I'm ready to tackle right now.

Batteries

I didn't know what to call these for a long time, but I think they're batteries. Anyway, they used to confer health, but I took that away in favor of the current, simplified health system. They felt less compelling ever since.

Now they also function as sensors, meaning they provide stealth and detect enemies.

It's more fun to battle over one of these now, because one of you might become invisible at any time.

Netcode

Last Friday at 4am the netcode entered a somewhat functional state. I was streaming at the time, so I sent the build to someone watching the stream and we were able to "play" together (sort of)!

I've since fixed most of the glitches in that video. There were also some issues with the reliable messaging, which I would have never discovered except that my laptop seems to be having major Wifi issues. It started dropping packets left and right. I increased some buffers, timeouts, and the extent of the sequence numbering, and it seems pretty solid now.

This specific game is challenging from a netcode perspective, because the main mechanic involves player characters bouncing off each other. The problem is, no two players experience exactly the same game state because of lag.

So I'm running the movement code on both the client and server. If things line up well enough, the server will accept the player's exact position as canonical. However, if things aren't exactly the same on the client and server (due to lag for example), things can get out of sync, and then the client has to awkwardly snap to where the server says they should be. I'd like to avoid that as much as possible.

There are a number of things I'm doing to address this. First I implemented lag compensation as described by Valve. As a drone flies through the air, the server rewinds the state of the world to 45 ms ago, or whatever the round-trip time is for that particular client. In other words, it rewinds to the state of the world as it appears to that client. Then it checks for collisions against this older state, rather than the current one. Time travel, basically.

Lag compensation isn't perfect though, and this particular mechanic (bouncing off players) is incredibly sensitive to minor deviations. If you raycast against a sphere at a slightly different position, the resulting reflection angle may vary wildly. To solve this, I'm quantizing the normal from this raycast, meaning I have a table of 18 vectors distributed around a sphere, and I pick the closest one to the normal where we actually hit the sphere.

But wait! It gets even more complicated. After choosing a reflection vector, it might turn out that we can't actually bounce that direction. For example, it might send the drone flying into space. I solved this problem a long time ago by doing a randomized series of raycasts starting with the original reflection vector and slowly expanding outward, stopping when a viable candidate is found.

"Randomized" does not sync well over a network, so that had to go. I'm now using a pre-determined table of possible reflection angles.

This still isn't working perfectly. The collision positions sync up to within 0.03m and the quantized normals sync up great, but somehow the code still chooses a different reflection vector between the client and server about 25% of the time, causing disorienting hitches. Still actively researching this.

Grenades

These came out just a smidge too bouncy at first:

They still need a lot of work, but I like where they're going. They function both as grenades and mines, so if no enemies are near, they just attach to a surface and wait.

Language

You may have noticed something weird about the screenshots in this post...

So the story is, yesterday I experienced either a stroke of inspiration, or just a regular stroke. Not sure which. At any rate, I spent the next four hours "translating" every string in the game to an imaginary language.

I blame Anthony Burgess. I recently read A Clockwork Orange and became enamored with the idea of fictional languages that are just close enough to English to be decyphered by an average English reader. I'm not sure if it's right for me though.

In his foreword, Burgess said Nadsat was born of his cowardice, created to obfuscate the pornographic nature of his novel. Hiding your choice of words behind a language barrier does smack of squeamishness, but I like the idea for two reasons. First, it's difficult to write a believable world that differs vastly from our own, yet features most of the same words and phrases.

Second, I find the added cognitive load of deciphering alien words draws me in to fictional worlds. At first I laughed at certain Nadsat words ("eggiwegg"?). This happens with any foreign language. My brain adjusted, and by the end of the novel I felt I could pass as a native speaker. That's a powerful tool to engender empathy.

Another argument against: I don't have the time or expertise to develop a pseudo-English language. So far, in this "trial run", it's basically a bastardized, poorly-understood version of Middle English.

I may consult with some friends who know more about Middle English. Or I might drop the idea completely.

(2 edits)

New edge rendering system

I was talking to a friend at our local gamedev meetup who's also doing a game with vector graphics. Rather than a traditional edge-detection shader, he renders the full scene normally, then renders the whole scene again, but this time with the fill mode set to render lines, and using a modified version of each model which only contains the edges.

I realized this was way simpler than the edge-detection I was doing, and it would allow me to do real MSAA on the edges. So now my importer executable generates a separate index buffer for each mesh, which only contains sharp edges. It uses the same vertex buffer. This system has two positive side-effects: first, because I disable culling when rendering edges, even if a wall gets culled out for visibility purposes, you can still see the outline of it. Second, I can now control which edges get highlighted directly in Blender.

Here's a glitch screenshot I took while developing this feature, which of course looked much better than the intended outcome:

And here it is working correctly:

The MSAA is still not perfect. I think the reason is that the depth buffer is not multisampled (and that's because it's a deferred renderer). I have to copy the depth buffer into the multisample FBO, taking the furthest depth sample from the closest ~4 pixels, to ensure that all 2-4 pixels of the line will clear the depth test.

Here's a direct comparison of the edge rendering techniques I've tried so far:

Netcode

If you've been following along, you know the game has these batteries dangling from physics-enabled chains:

Previously, I had to sync the position and orientation of every chain link across the network. When they were in motion, they constituted the majority of bandwidth. I tried serializing the state of the physics constraints at load time, then only syncing the position of the battery. The result is not perfect, but acceptable for a purely aesthetic feature. Bandwidth is now down to 100-200kbps in the worst case scenario.

Last time I talked about the difficult problem of drones attacking and bouncing off each other. I finally realized it's impractical to perfectly synchronize client and server via deterministic simulation. The server and client need to wait for some form of communication to occur between them before proceeding in a given direction.

Here's what I came up with:

  • Client runs all movement code locally.
  • Client detects that it hit an enemy and executes the code to bounce off them. Client notifies the server which direction it bounced.
  • Server is also running movement code locally. There are two possibilities: either it detects the hit before receiving the client message, or after.
  • Most likely, the server will receive the client message first. It caches the message and waits for up to 0.1 seconds for the server-side movement code to confirm the hit. If it never hits anything, the server gives up and sends the drone bouncing off in the direction the client said it went.
  • The process is similar if the server detects a hit before receiving a client message. The server caches the resulting bounce direction and waits for 0.1 seconds for the client message. If it receives the message, it executes the bounce according to the client's wishes. Otherwise the server forgets anything ever happened.

Physics chains and drone bouncing were the tough netcode challenges. The rest has been pretty straightforward:

  • The game calculates and displays the point you need to shoot at to hit a target. Previously this only worked in local games because on clients, the physics engine is overridden by the netcode, so it doesn't have any velocity values. I'm now calculating those missing velocities on the client by comparing consecutive state frames received from the server.
  • All abilities now work properly in networked games. The main challenge here was the ability to spawn minions. I have to sync the current animation, and the current timestamp inside that animation, for each minion.
  • Sparks, effects, explosions, controller rumble, camera shake, and other hit events are now synced across the network.
  • Shockwaves are now client-side only. Previously they were regular game entities, which meant their creation and deletion had to be synced across the network to ensure IDs lined up properly. Now they're just client-side effects, as they should be.

Bolter

This ability allows you to shoot bolts similar to the ones shot by minions. It's interesting because all the abilities are tied to the three-jump movement cooldown system. Spawn three minions and you'll have to wait for the cooldown before jumping again. The bolter is the only ability with no cooldown whatsoever. It's meant to be a rapid-fire weapon limited only by your energy resources.

Improved decoys

Decoy v1 was just a bad design. You could spawn a decoy in an obscure corner of the map, then run around the whole map without being spotted by enemies, minions, sensors, or anything else. If a decoy was active, you were basically invisible.

Now the decoy must be visible before it will confuse an enemy unit. If the decoy is hidden in a corner, it has no effect.

There is one exception: if you plant a decoy in view of an enemy sensor, that sensor will continually alert the enemy, "HEY! THEY'RE OVER HERE!" even if the decoy is across the map, and you're sitting right next to them.

Teleporter gone again

The teleporter has been added in and removed twice now. It's just not fun. Dear Future Evan: if you are tempted to bring back the teleporter a third time, DON'T DO IT.

Netcode

Until now, the netcode operated under the assumption that nothing happens until all clients are connected. Everyone receives the map data at once, and the reliable messaging always starts on sequence 0.

I need clients to be able to join games already in progress. The new process looks like this:

  • Client spams the server with connect packets.
  • Once the server receives this packet, it saves the current sequence ID as the starting sequence for that client. The client needs to receive every message starting with that ID.
  • The client receives these messages, but does not process them yet. It stores them in a buffer.
  • Meanwhile, the server is also sending map data via an entirely separate reliable messaging channel. Normally, all reliable messages are sent to every client, but the map data is only sent to this specific client.
  • Once all the map data has been transferred, the client processes the messages it cued up while loading the map, and notifies the server that it's done loading.
  • The client is now caught up to the latest sequence ID, and things proceed normally.
There are a ton of tiny but critical implementation details that come together to make this work. My first prototype worked okay in perfect network conditions, but quickly fell apart under packet loss. After a few hours of bug fixing, the connection process now works even under 25% packet loss.

Minion attack animation

Previously, when Minions attacked, they would just stare at you until a bolt projectile suddenly materialized out of their forehead. But no more! Now it materializes out of their hand. This changes everything.

New character model

Parkour mode is back, and with it, a new player model.

(actually it's just a modified version of the player model from my last game)

The neat thing is that it lines up perfectly with the Minion model, so I can play every animation on both models.

Slide attack

I'm experimenting with some interactions between the player and minions in parkour mode. So now you can slide into a minion to take him out:

Of course you have to gain a certain amount of momentum to make the attack effective. I'm still tweaking the movement code to make this feel good. One problem with my last game was that you could reach top speed just by holding W, and there was no canonical way to accelerate past that (although speedrunners found a number of exploits). I'm trying to fix that in this game.

Minion melee attack

Minions became awkward when the player got too close; they were still firing projectiles as if the player was far away. Yesterday I added a melee attack:

That's it for now.

Happy Thanksgiving! :)

Terminal

Each level starts with the player navigating to the top of the map and hacking into a terminal, which switches the game into spider drone mode. The goal is then to capture the map, exit the terminal, and move on. At any point, the current "owner" of the map may spawn in to defend their property. In reality, the game will try to matchmake you with someone who owns that map; it might be different every time.

Here's a very WIPpy concept of how the terminals will work.

In-game map view

Previously, the overworld was a separate map that had to be loaded, with everything that entails. I needed it to be accessible from anywhere in the game, so I had to refactor to make it remain in memory in the background.

Just wanted to pop my head in here to say that I finally finished the world's most complicated porta-potty:

(+1)

Hey, I've been checking back every now and then to see any new updates, and the game is coming along really well!

This week I worked on the primary method of transportation between levels: trams!

I've always had a thing for trams in video games. They evoke a feeling of progress and meaning.

I didn't know if they would even be possible at first; Bullet physics does not allow dynamic rigid bodies with triangle mesh shapes. It has to be convex, which would prevent the player from entering the tram.

Here's what I did in the end:

  • Created a box-shaped dynamic rigid body for the tram
  • Disabled collision between this body and the player
  • Created a static rigid body with a triangle mesh for the actual tram collision shape
  • Parented the static body to the dynamic one so that my engine automatically updates its position to match the dynamic body

Amazingly, it all worked.

This was the first version, trams 1.0:

Then I made the tram runners smarter, so they could accelerate, decelerate, and follow paths:

Finally I tweaked the model and added glass and animated doors.

That's it for this week. Will probably go back and work on the spider drone half of the game next week.

This week I've been catching up with tweaks and bug fixes after the last big feature push. Most of the core game loop is functional now, even in networked mode, although some sharp edges still need to be sanded down.

New stuff: there are now two levels with trams, and they connect to each other. It works surprisingly well. Here's the tram on the new level:

There are also collectibles now. These provide much-needed resources and give you an incentive to explore.

That's it for this week.

Cracking

It wouldn't be a cyberpunk game without hacking of some sort. The idea here is to slow you down when entering a game to allow more time for matchmaking.

I used Beautiful Soup to scrape 64 4x4 Sudoku puzzles from a website, then I randomly rotate the digits and flip the board to generate more puzzles. It's a fun little mini-game.

Dock

I've been fleshing out the first few levels. The first one also doubles as the title screen:

Tarzan

Rope climbing and swinging is in, although still a bit WIPpy.

New map

This is the third map you'll discover, if you count the title screen. Which I do.

Parkour animations

These work exactly the way they did in my last game. While climbing a ledge, the player physics body moves straight up, and then straight forward in a jerky fashion. While this is happening, I offset the model and camera so that the climbing animation stays rooted at the same position even though the player entity is moving. After the animation is done, I blend everything back together so the model and physics body are in the same position again. I believe this is similar to something in UE4 called "root motion".

Animated characters

I started out thinking this game would work the same as my last in terms of story. Branching dialogue choices in a simple text-based system, plus random notes scattered throughout the levels.

This week I finally realized a few things:

  • I mainly play action games. This is an action game. Things happen in action games. Reading text is not a great fit.
  • None of my favorite games have branching dialogue. You choose a story branch by performing an action, not selecting it from a menu.
  • Games are most compelling when gameplay and story coexist and complement each other. That's difficult to do when they're totally separate. Pre-rendered cutscenes, or worse, animatics, foster a clear delineation between gameplay and story. The best games do everything in-engine, preferably without taking control away from the player. In an action game, heavy amounts of text lead to the same problem.
  • I now have enough modeling and animation experience to pull off fully animated characters if I take a lot of artistic license and stick to a stylized look.
In light of all that, I'm fully removing the text message system. You will now search out and talk to different characters throughout the game. The sailor above took me about two days to model, animate, and script. It's a slow process, but the end result is so much better than seeing a new message notification in the corner.

Friends! I need your feedback.

I am considering renaming the game once again. "The Yearning" is still a good fit, but it's confusing, pretentious, and easy to forget. It's also impossible to infer anything about the game from the name alone.

I'm considering renaming it to "Skirr".

"Skirr" is the name of the city in which the game takes place. Reasons I like this name:

  • It means "to flee". The game is about fleeing the earth to escape an apocalypse.
  • It sounds like "scurry", which evokes the creepy-crawly nature of the spider-bots.
  • It doesn't have much competition on Google.
  • To me it sounds like an action/adventure title.
  • Taking inspiration from Astroneer, "SKIRR" in all caps looks acceptable and could help draw attention.

Reasons I don't like it:

  • Confusing spelling. People who hear the title spoken might think it's spelled something like "Scur".
  • Confusing pronunciation. People who read the title might think it's pronounced something like "Skeer".

What say you? Too confusing still?

Another title I thought about for a while is "Caligula", after a play by Albert Camus. "Caligula" is the name of the refuge planet in the game. Unfortunately there's already a "Caligula" game in development.

Apologies for so many rebrands, but I would rather nail the title than stay shackled to a bad one in order to minimize confusion. Besides, No One Knows About Your Game.

In other news, the dock is finally finished:

(+1)

Personally I think The Yearning was good enough, although SKIRR sounds more original and customised.

As long as the gameplay, visuals and sound are good, I don't think the name matters too much to be honest.

(1 edit)

Life stuff

I had my first anxiety attack on Tuesday! Feels like I've completed a gamedev rite of passage. I've been relaxing and hanging out with my family this week to try and get healthy again. Feeling much better now. Here's what got done before the break:

Hobo

This guy was supposed to look ragged, but his outfit was based on the ridiculously photogenic homeless man so it ended up very stylish actually.

He's one of the first NPCs you'll meet. He just talks to himself.

Aerial kills

You can now kill minions from above. I haven't done anything to align the animation yet.

Behind-the-scenes work

Lots of bug fixes and small changes. I refactored the scripting system so that scripts can be executed on both the client and server in networked games. But the biggest time sinks (and of course the biggest overall challenges for this project) are the AI and netcode. I'll still be doing fun story stuff and character models through the end of February for a vertical slice to show at GDC. After that, it's time to dive in to network infrastructure and a completely new AI system.

Animations

I keep adding animations one by one. At one point, Assimp decided to optimize the root bone of the player model out of existence. This obviously caused some problems.

(+1)

Projectile client-side prediction

All moving projectiles in the game are normal entities tracked via the usual interpolated transform sync system. This is fine for AI characters shooting at you, but it's incredibly frustrating when you are shooting projectiles. You have to wait for a network round-trip before the projectile shows up.

I often test netcode on localhost, where there is no lag. Since this feature is heavily dependent on lag, I took some time during the stream this past Friday to implement a buffer that simulates network lag.

I cranked the lag up to 300ms total round-trip time and fired some projectiles. The first problem was that, since the server took 150ms to register my "fire projectile" command, my target might have moved by the time the projectile got to it.

The solution works like this on the server:

  • Rewind the world 150ms to the point where the player fired
  • Step forward in increments of 1/60th of a second until we reach the present, checking for obstacles along the way
  • Spawn the projectile at the final position
  • If a target was hit during this process, delete the projectile and apply any damage effects

To the player, there is still a 300ms delay before anything happens, but the projectile will pop into existence 20 feet out, where it would have been if there were no lag. This makes it easier to aim, but it's still annoying to have no immediate visual feedback when you fire.

I thought about spawning the projectile on the client. The problem is, projectiles are entities, and the entity system is controlled by the server. If I spawned projectiles on the client, IDs would get out of sync and things would explode.

So instead, I made a new system for fake projectiles, totally separate from the entities. Actually "system" is too strong a word, it's just an array of structs. These fake projectiles live for up to half a second, and the client removes them in order as soon as the server spawns a real projectile.

Here's the end result running with over 300ms of lag:

Shops

You can now buy stuff at these special locations known as "shops".

Locke has a number of greetings he can give, which will have accompanying animations. I'm really starting to enjoy animation work! Actually had a blast making this:

Also, this thing is now over 50,000 lines of code

Health v11

I added an extra shield hitpoint, so it takes three total hits to kill a player. I'm hoping this will encourage players to use abilities rather than only attacking each other directly.

Master server

I have the beginnings of a master server, which facilitates connections between players and servers. It's pretty basic at this point, but it works. I'll continue to expand this in the future with authentication and matchmaking.

AI

I threw out the behavior tree system, which badly needed refactoring anyway. Now I'm working on a system which will record player inputs into a database, then search through the database for specific scenarios and play back the correct actions. It's maybe halfway to a rough draft working prototype.

Clouds

Just another cool thing I wanted to pull in from my last game. They're animated and actually cast shadows on the environment.

GDC and PAX East

I'll be present for the first two days of GDC, plus out of nowhere I got an opportunity to show at a booth at PAX East! Beyond excited :)

Another rebrand

This is the last time, I promise. The game is called Deceiver.

Easier to remember, and a surprisingly uncontested name within video games at least.

Some people know the reasoning behind the name, but unfortunately it's extremely spoilery.

Rain

This system does raycasts to determine how far each raindrop should fall. Unfortunately, my particle system (and most particle systems I believe) requires that particles be removed in the same order they were added, meaning all particles must have the same lifetime. To get around this, I store each particle's individual lifetime in a vertex attribute and clip the particle out of existence in the pixel shader if the particle is too old.

The system keeps a cache of raycasts around each camera, updating the cache over time at a rate of about 1000 raycasts per second. As an improvement over my last rain system, it can also "fill in" missing particles when the camera moves or teleports too quickly for the normal spawn rate to keep up with.

Miscellaneous

  • Added a network lag simulator and made more changes to further harden the netcode. One example: the server was using the client's running average RTT to rewind the world when doing collision detection for that client. Problem is, if the player sends a "jump" command that gets dropped by the network and resent later, the timing of that command is out of sync on the server and client, and it will only be resolved when the player lands. So now the server uses the sequence ID of the command to calculate and store an RTT value for rewinding purposes, which remains constant until the player lands.
  • For the longest time I was bothered by the game's performance on my laptop, which has a GTX 950M capable of running Rocket League on decent settings at over 60 FPS. For a while this made me doubt a little bit my ability to write performant shaders. Turns out, my game was being relegated to the Intel integrated graphics chip. Tweaking the nVidia settings brought performance to over 150 FPS. Yay!
  • Upgraded to VS 2017. I think it's an improvement from 2015 overall. Certainly the install experience was much better.
  • The local/online multiplayer menu system is maybe half done. Multiplayer might be completely done soon. I am considering an Early Access release to stress test the netcode and collect gameplay feedback.
Showed the game to a few publishers at GDC last week. Headed to PAX East this weekend. Stay tuned.

PAX East

I had an opportunity to show the game as part of the Playcrafting booth at PAX East. It was fun getting to meet a lot of cool people, but I realized that you get back what you put in when it comes to expos and shows. It's difficult to attract interest when you're part of a larger booth and have no signage.

Attract mode

To help attract attention at PAX, I wrote up a quick and dirty "attract mode". The game can now record matches and automatically play them back after 60 seconds of inactivity. As soon as someone touches a controller, the game goes back to the main menu. It was very straightforward to write; I just record the network packets and play them back in order. Unfortunately I couldn't get anyone to play a match with me before PAX, so the feature went unused. But it will come in handy in the future!

UTF-8 support

This was a lot easier than I expected. Essentially the only code that needs to think about multi-byte codepoints is the text rendering code.

This was necessary because I wanted...

Playstation button prompts

The game now displays different button prompts for each player, depending on what kind of controller they plugged in, if any. It also instantly switches to keyboard prompts if you touch the mouse. My last game always showed controller prompts as long as a controller was plugged in, and that proved to be confusing to some players. Besides, this is a feature that impressed me in The Witcher 3, so I decided to steal it.

Clipping v4 (?)

Still experimenting with new camera clipping techniques. If anyone knows any of the graphics programmers who worked on For Honor, I would love to know how they do their effect.

In this version, I'm rendering backfaces the same as camera-facing tris, but in pure black. I'm working on removing the white outlines inside the black areas. If I can also fix a few weird clipping situations that block the camera view, I think the effect will finally be complete.

Hacking improvements

The sudoku hacking game had one flaw, which is that sometimes it gave you a number that could belong in a currently unsolvable portion of the puzzle. To fix this, I made it calculate the number of possibilities for each cell and give you the one with the least number of possible values.

AI

I scrapped the old behavior tree system a while back, so the AI players have been sitting idle since then. I'm finally making significant progress on a new system which will be able to seamlessly playback recordings of player moves, mixed with ad-libbed AI actions. The recording system is done, and now I'm working on the AI actions. After that, I'll work on the playback system which will select which recordings to use in each situation.

Having AI players in the game again revealed to me a major issue with the gameplay...

Skill shots

I realized that the core shooting mechanic was a bit rock-paper-scissorsy, because it always takes the same number of hits to kill. Three hits is also too many; it feels tiresome, especially if the other player is trying to get away. The possibility of getting one-shotted as constant fear adds some thrill to the game. At the same time, I don't want a single dumb mistake to result in death, especially at the hands of a non-player character.

With that in mind, I'm bringing back an old concept I tried a while back: skill shots. Your shot can take away 1 hit point or all 3, depending on how good it is. I think it's especially satisfying to get a 1-hit kill because you don't bounce off at all.

HUD redesign

After posting last week's devlog, I realized the HUD was a bit of a trainwreck, so I redesigned it.

Before:

After:

Camera clipping effect

Last week I mentioned a few more changes I wanted to make to the camera clipping effect. These are now done, and after over a year of tweaking, I finally consider this effect good enough to ship.

The effect works by filling all the backfaces with a special G-buffer tag that causes later post-processing effects to block out any glowing edges and scan lines with a pure black color. The only downside is that the effect requires all environment geometry to be convex. I think it's a small price to pay for perfectly correct results with almost no performance penalty.

Active armor

I added an "active armor" ability that temporarily grants you invincibility and reflects any incoming damage onto the attacker.

Assault mode

Since October 2016, the main focus of the game has been Rush mode, where the attacking team must capture two control points by setting off a timed hacking process.

There were a few problems with this mode:

  • The game is designed for 1v1, possibly 2v2 or 3v3 at the most. Rush games work best with large teams. In a 1v1 Rush game, the players often just take turns capturing one control point, then the other.
  • Sitting still and holding X to hack a control point is not very exciting.
  • The control points don't really interact with any other systems in the game. At best, they encourage you to camp in a single spot and wait.
I'm replacing Rush with Assault to solve these problems. It's simple: attackers must destroy the core. Defenders must hold the attackers back for a certain time, or exhaust their resources.

The core consists of a number of modules which must be individually destroyed:

All turrets must be destroyed before the core becomes vulnerable. Turrets work like this:

I also realized that minions are important enough that they should be a part of every match, rather than being available as an optional ability. So now, they spawn automatically over time.

Where do they spawn from? Well, I wanted to give more reason for players to capture batteries other than just gaining more energy. So now, they also function as spawn points. You can choose to spawn from any battery you own.

This mode takes the game back toward the earlier MOBA experiments, but without some of the failed aspects (large bullet-sponge health bars, last-hitting).

Sniping tweaks

Your shield now goes down while sniping. High risk, high reward!

Active armor tweaks

You can now reflect incoming bolts, increasing their speed and damage. Useful for fighting turrets.

Misc

There are tons of other changes to talk about. One example is the "toggle zoom" option. Instead of holding RMB to zoom in, you can click it to toggle the zoom. I think unsexy accessibility options like this are super important. The new AI system is also still under construction. I'll be showing the game at Vector conference next weekend, so hopefully I'll get a lot of feedback to move forward with.

Vector conf

Had the opportunity to speak and show the game at Vector conference at Eastern Kentucky University. Got some playtesters and some great feedback.

I had the privilege of meeting Nathan Fouts from Mommy's Best Games. His was one of the blogs that convinced me of the viability of going indie back in 2009-2010. He played the game and enjoyed it but listed a lot of stuff he hated, which is exactly the kind of feedback I need. One problem he mentioned has plagued the game for years...

Dash combo

Up until now, I applied a shader that darkened everything behind the surface you're currently attached to, like this:

Notice the sharp line across the structure in the top left. This indicates that you can't shoot yourself anywhere in the darkened area, because you'd have to clip through the surface you're currently attached to. This always confuses everyone. I have to explain it to every new player.

Nathan suggested instead to automatically zip the player to the edge of the surface where the targeting line is clear, then launch them toward the intended target. So I got rid of the darkening shader and implemented his suggestion. Here's how it looks in slow motion:

That battery hangs below the floor level, but the game still lets me hit it by automatically dashing forward to the edge before launching at the target.

It seems asinine and nitpicky, but tweaks like this add up and affect players' subconscious impression of the game.

Rockets and decoys cut
Decoys were never really fun, and sensors accomplish basically the same thing (take aggro off the player). Rockets were really cool but not useful. Then I added grenades, which fill a similar role to rockets since they wait for enemies to approach before exploding.
New core design

Destroying six separate modules turned out to be tedious, so I lowered the number to three. People were also confused why they the core was invulnerable until the turrets were destroyed, so I put a force field around it which disappears once the core becomes vulnerable.
Force field changes
Previously, force fields had a short battery life. You could place one near a battery to increase its life, but it would still last less than a minute. The spherical shield itself was invulnerable, but there was a "generator" object inside the field that could be destroyed. If you happened to be inside the field when it was created, you could easily destroy the generator. Minions could also walk through the field to get inside and destroy the generator.
The problem is, now there are a lot more minions in a typical game, since they spawn automatically. I wanted force fields to be big, expensive, and important, but they don't last long with so many minions around. Plus, making them exclusively vulnerable to minions felt a little too rock-paper-scissory.
Force fields now sport an indefinite lifetime and a large amount of health, second only to the turrets. They can now be worn down from the inside or outside via minions, bolts, sniper bullets, grenades, or plain old drone attacks. And they're much more expensive.
Server optimization
The most CPU-intensive task in the game right now is actually ragdoll simulation. If more than 5 minions die at the same time, framerate drops from 300 to 60 or less. Ragdolls are strictly cosmetic, so I disabled them on the server to avoid CPU spikes. During normal gameplay, the server now puts one CPU core at 20-40% utilization, although AI might drive that number higher.
Project status and future
I decided not to teach this summer and instead subsist on money left over from last year. That means there will have to be a Kickstarter and/or Early Access release sometime around fall.
Audio
This is the biggest thing missing for any kind of release. Jack has been excited to work on this project for years now, but is currently crunching on LawBreakers. I started filling out Wwise audio events and spreadsheets in preparation for him to join the project this summer / fall.

Overworld redesign



Until now, I've been designing each level to function equally well for first-person running/jumping/climbing, and third-person spider bot PvP combat. I realized it would be much better to design separate levels for parkour and PvP, so now the plan is to have 9 parkour levels, each with a PvP map attached to it. While I was revamping the level structure I decided to redesign the overworld a bit as well. Still very WIP.

New tutorial



The PvP game has changed so much recently, and I kept shoehorning the new gameplay into a tutorial map originally designed for a different type of game. Finally I decided to start over from scratch.

Promo art



Someone asked me to design a retro cabinet for the game, so they could put it in their VR arcade. I jumped at the opportunity because I'll be showing the game at a few expos this summer and fall, and I need promotional artwork. The image above is all rendered in-engine, which I now realize is not going to work for a high-resolution physical sign. The new plan is to render something in Blender and trace over it with vector art.

Tweaks

Most of my work is on small details right now. Above is a development stream where I added animation to all the menus in the game, then added a footstep animation to the wallrun tutorial, then moved some blocking IO calls to the AI thread. All important changes that noticeably improve the game, but not exactly revolutionary.

(+1)

Dot pattern

I had a bug in my AI, so I turned on my nav mesh debug renderer to see what was going on.



Sure enough, there was a bug where faces were not rasterized if they were almost horizontal but not quite. You can see a conspicuous gap in the screenshot above.

After fixing the bug, I thought the nav mesh looked cool enough to stay enabled in release mode. I cleaned it up by adding transparency and antialiasing:



Finally, I wrote a shader to make the dots fade out over distance:



Promo art

Last week I tried to use in-engine screenshots to create high-res promotional artwork. I realized that wasn't going to work, so I set about making Blender imitate my rendering style:



Of course Blender is so amazing that it was pretty easy. Freestyle edge rendering let me choose which edges to highlight, and a halo material worked for the stars.

I imported a few more models from the game, threw in a quick particle system, arranged them into a scene, and slapped the logo on it:



The colors seemed a little garish, so I tried another color scheme:



Everyone on Twitter liked the pink version better, but I really wanted to get rid of the black background behind the logo. I also enabled contour rendering to get a nice silhouette around the main character. Unfortunately, this also added a silhouette around each individual spark particle, which made them too distracting. I ended up putting the sparks on a separate render layer with no freestyle edge rendering, and then compositing the two layers together. Here's my final composite setup including an extremely simple bloom effect:



After a few more hours of posing and tweaking colors, I ended up with this:



Hopefully this will make for a nice banner at IndyPopCon.

Combat tweaks

As usual, I'm making tons of small but significant tweaks. Gameplay has changed a lot in response to a decent amount of playtesting recently. Weapons no longer take energy to fire; only building things incurs an energy cost. Energy also accrues more slowly, and upgrades cost more.

Here's 30 seconds of footage showing what the game is like now:

I ended up moving the main character into a separate render layer which allowed me to thicken the edges around her. I also added mist to give a sense of distance to the background, and a barrier in front of the character to give her some grounding. Also messed with the composition. Here's the new version:



New level

In the most recent redesign, I realized it would be prohibitively difficult to design maps that function equally well for both parkour/exploration and PvP drone combat. Now I'm designing different maps for different purposes. This is the first real map I've designed specifically for parkour:





Since I don't have to worry about things like balance and spawn points, I can focus on the spectacle and fun of just running around in the environment. Likewise, I can make smaller and tighter PvP maps without worrying about visuals too much.

New character

This guy has been planned for a long time, and now he finally has a model.



His name is Meursault, and he's a little nuts. I'm going to try animating and scripting the first encounter with him this upcoming week. Excited to see how it turns out.

WIP Cinematic

Been working on this cinematic where the player gets shot and drugged:



The player's model and Meursault's model are in separate blend files, so I created a "workspace" blend file with all the models linked in, so I can animate them together. Then I save the animations back out to their respective blend files, then line everything up in-game.

Settings menu



Finally fleshed out the settings menu. Almost every graphical effect can be turned off. All keys can be rebound, and the tutorial prompts and UI instantly update to reflect the change. In my last game, there was one menu for both keyboard and gamepad controls; if you plugged in a gamepad, you could only change the gamepad controls. One person was confused because they forgot they had a gamepad plugged in. Anyway, for this game I'm doing two separate menus. The gamepad menu is only visible if you plug in a gamepad. Also, other local players can change their own gamepad settings, but not the keyboard controls or any other settings.

Gameplay tweaks

I made a few changes in an attempt to move away from twitchy Call of Duty-esque gameplay. First, I halved the gamepad acceleration so that it takes 0.4 seconds to reach full speed. Then I slowed down the ADS zoom speed, and the speed at which drones and bolts fly. I also tweaked the energy rewards to encourage players to capture and hold batteries rather than aggressively attack all the time.

Also, it's a minor detail, but I'm experimenting with analog zoom; the degree to which you pull the trigger affects how far the camera zooms in. We'll see if people like it or not. Probably doesn't make too much of a difference.

Indy Popcon

Almost ready for Indy Popcon. Tweaked the promo art a bit more and had it printed out:



Along with this one:



Also put up a quick website: http://deceivergame.com

Indy Popcon

Pictures!







This was a lot of fun, but unfortunately I won't be going back next year. There were maybe ten games total; most of the con focused on unlicensed pop culture stuff and YouTube personalities. Most people were not interested in my game at all, but those who did sit down and play almost always went away with a huge grin on their face, so that was encouraging!

Gameplay recording

I recorded almost 400 MB of gameplay. This works by blasting all network packets from the server directly into a file, then playing them back in order. I can record an 8 minute play session in under 10 MB.



The only downside is that the network protocol does not capture camera angles, so I can only watch replays in this weird top-down view.

Here's some highlights:


Right player stalks left player from the top of a pillar


Left player captures a battery but immediately gets smashed


Right player gets overwhelmed by left player's minion army


Left player does a good job of staying behind their minions


Right player holds their own against minions, left player not so much

I got a ton of player feedback from Indy Popcon, which means I have a huge list of cool stuff to work on. A few notable changes already implemented:

Camera culling

Yes I'm still improving this. Previously I used a cone shape to cull geometry between the camera and the player's drone. The issue was that, if you backed up close to a wall, it would intersect with the narrow part of the cone, leaving you a tiny circle to see through. I switched from a cone to a paraboloid, which slightly improved the situation:



There was another issue as well. When transitioning from one surface to another, the player's rotation lerps smoothly, which is nice. The problem was that, all the culling planes were based on this lerped rotation, which resulted in a lot of popping and graphical artifacts during the lerp. Now the culling transitions immediately from one surface to the next, while the player model still lerps smoothly.

Cooldown tweaks



Previously, after a cooldown, all three of your jumps recharged instantly. Now they recharge individually, similar to Overwatch's Tracer, which was the original inspiration for this system. The difference with this new system is, the first jump takes a long time to recharge, while the other two recharge much faster. I want to give players an interesting choice here: do you immediately use the first jump because it's an emergency, or do you wait a split second longer to gain more future mobility?

That's all for now. Big changes in progress. Next milestone is GDEX in September.

Title screen redesign

The title screen also doubles as the first level in story mode, so it's super important. Here's how it used to look:



I didn't like how the dark colors contrasted with the white outlines, and the nighttime setting didn't mesh very well with the start of a journey. Also, the level had you moving to the left to progress, which felt surprisingly jarring since our brains associate left-to-right movement with progress. Finally, there wasn't much room to explore and mess around, and the tutorial was very linear.

So I flipped everything around to progress toward the right, changed the colors, opened up the layout a bit, and integrated the tutorial more seamlessly with the environment. Here's how it looks now:



Cinematic

The cinematic I've been working on is done, for now at least. It's a little over a minute long.



Unfortunately I realized it needs to happen in the third level rather than the second, which means I still have another cinematic to do for level 2 before this vertical slice is done. I'm excited for it though, along with all the other story stuff. I recently found a way to cut the story down to 9 levels, 2 of which are already done.

Kill cam highlighting

This is a minor but important quality of life improvement. The kill cam now highlights your killer when they're not directly visible. I basically just change the depth test to pass when the depth is greater, and then render the mesh with transparency. Easy peasy.

Upgrades to the upgrade system


Upgrades in this game have always had a certain risk/reward mechanic. They take a few seconds to purchase, during which your drone remains immobile and vulnerable. Most of the time this was fine because you had to be at a friendly base in order to upgrade, but sometimes the base got captured while you were upgrading, which kicked you out of the upgrade menu.

Someone at Indy Pop Con suggested an idea that would both add some visual interest and make players invulnerable while upgrading:



At first, I was stressing out about the edge cases and physics of flipping a drone around - what happens if two drones are attached to the base when it flips? What if an enemy drone flies through the gap into the void while the base is flipping?

Then I realized I had to fake the whole thing. Nothing actually moves at all; I disable the drone's collision and animate the model, but otherwise, the drone just sits there. No physics objects move at all. About half of this feature was implemented live on stream.

itch.io integration

I spent a day getting cURL building on Mac, Windows, and Linux, which allowed me to make HTTP requests. Now I'm using itch.io's API for login and authentication. Launch the game from the itch app, and my game receives a JWT via an environment variable. The game talks to the master server, which talks to itch, et voilà, it magically knows your name. Scary. Steam authentication will work basically the same way.

Virtual servers

Now that we have actual user profiles, it's finally time to let people connect to each other. Until now, I had half-assed plans for a matchmaking system similar to the one I use in campaign mode, but then I decided to try an idea I've been wanting to do since at least 2011. It goes like this.

I really miss community-run servers in games. It's hard to build a community for, say, Overwatch, without a dedicated place for people to meet. Even if it's not a community per se, some of my favorite memories happened in dedicated servers with custom game rules. That's gone now. These days it's a lot easier to pay some cloud provider and not have to worry about community-run servers falling out of date or distributing malware.

So the idea is: virtual dedicated servers. Anyone can create a dedicated server for free. It's just a database row that holds a name, a set of game rules, and a list of admins. When someone wants to play on this "server", a real dedicated server is allocated from the pool and set up with the custom game rules. It's like giving gamers their own little version of Amazon Web Services. Here it is in action.



Still a lot of work to do, especially on the server browser queries. I'd like to use a Reddit-like algorithm to keep popular servers at the top while still surfacing new configurations.

I'm using the fantastic SQLite for the database. Their build process combines all their source files into a single 6.8 MB C file. Super easy to set up.

Samsa

I made a robot doggie. His name is Samsa. He plays a fairly critical role in the story. Don't worry, nothing bad ever happens to dogs in video games and movies and literature.

I didn't like how he turned out first. He has to unfold into a sort of command center, which dictates certain design limitations:



At this point, the vibe he gave was more Stegosaurus than Golden Retriever. I asked Twitter how to make him cuter, and they told me to shorten and speed up the footsteps, add some Z-axis roll, and add an antenna. A lot of them also suggested "googly eyes", but that was a bit much for this game. I implemented everything else:



The antenna idea was brilliant because it's so easy and fun to animate and adds a ton of visual interest:



Damage buffering

One of the abilities in Deceiver is called "active armor". You hit a button, and any incoming damage gets reflected back at the attacker. Like so:



If you watch that gif closely, you can see that I react well in advance of the actual bolt impact. But what if it was really close? If you look in the top left of the gif, you can see the ping is over 200ms. If I reacted just in time from my perspective, I would be 200ms too late from the server's perspective.

To solve this problem, I created a buffer for all player damage. That means the server acknowledges incoming damage, but waits for a split second before actually applying it. And by a split second, I mean whatever the player's ping is, plus one frame for good measure.

Here's what that looks like. The bolt disappears and spawns a particle effect instantly, but the damage doesn't actually register until 200ms later:



Now I can react to the bolt right up to the moment the bolt impacts. Note that damage buffering is unnecessary and in fact detrimental between two players playing splitscreen on the same client!

Yes, this skews the game against attackers, but I think it's much more frustrating to get killed cheaply due to lag, as opposed to the minor annoyance of having your hits not register. Especially in a game where a lot of damage comes from non-player characters.

While developing this feature, I used a tool Ryan Evans recommended to me called clumsy, which simulates bad network conditions. Brain-dead simple to operate.

Lastly, I added pings to the scoreboard, so you can rage at players who have Comcast.



P.S. - Sound is coming. This is me being completely hyped out of my mind.

Team switcher

A ton of features have gone in recently to bring the game closer to a multiplayer demo. Previously, players chose teams before starting a match, but that didn't work for networked multiplayer. Also, there was no way to switch teams in the middle of a match. Until now!



Plus if you're a server admin, you can move other players to different teams as well.

Sniper ricochets

This is a little subtle and hard to see, but sniper bullets now work the same way as player movement: if the target survives the impact, the bullet ricochets. Sorry, I didn't have time to compile a trick-shot montage set to Breaking Benjamin.



IPv6

The master server and game server now listen on IPv6 as well as IPv4. If a client connects to the master server over IPv6, the master will return IPv6 addresses for all game servers. I suspect this may be a brittle solution that somehow breaks at some point, but it's good to have the plumbing done and know the code is future-proof. In related news, here's a great article I just read about what IPv6 could have been.

Minion pathing

Minions use a simple heuristic to determine which target to attack: whichever is closest. Now take a look at this map:



It has two floors stacked on top of each other. There's a turret on the upper floor. A minion who spawns on the floor directly beneath the turret will assume that the turret is the closest target, and kick off a pathfinding request to make its way there.

Unfortunately, the path to the turret turns out to be a roundabout excursion up a series of ramps, and will likely take the minion past other, closer targets. This is a problem. There's no way of knowing which target is actually closest without calculating a pathfinding solution for each and every one, which would be prohibitively expensive.

Instead, I decided to mark up the map with some extra pathfinding information. I added some special Blender objects which allow me to say "if you're inside area X, and you want to reach target Y, then you'll have to pass through point Z to get there". This metadata does not impact the actual pathfinding, it only improves the accuracy of the heuristic for choosing targets.

While I was messing around with minions, I also implemented a basic obstacle avoidance algorithm to keep them from running into each other and clumping together. The algorithm is this: if a minion is in front of you, turn to the right.

Another improvement I made is that if a minion attacks a player and then loses sight of them, they will advance to the last known position of the player and search for them before moving on to a new target.

In-game UI notifications

One piece of feedback I've received recently is that players have a hard time keeping track of the game state. How many batteries do I have? How many turrets do I have? Did the enemy just capture something of mine?

So now I'm highlighting batteries and turrets outside the player's range with colored icons. There are also icons for "under attack" and "lost", so you can easily see which things require your attention. If you happen to be looking away from the object that's under attack at the time, there's also a text notification.



Resolution switcher

I finally added settings for screen resolution, fullscreen/windowed, and v-sync. The settings menu is finally done.



Multiplayer demo

All of this is coming together to culminate in a multiplayer demo hopefully by the end of September. Prepare your bodies

(1 edit)

Smooth camera

Thanks to Twitch viewer RayMarch for this one. They suggested adding some lag and springiness to the camera so you can better see when your drone hits something.



This makes things much less confusing when you bounce off an enemy:



The camera locks up again when you're done dashing or flying, so you can still do precision aiming.

UI cleanup

After seeing the first gif above, someone pointed out the "danger" sign blocking your view of the spider drone. The UI has a number of status indicators that turn on and off depending on the player state. These were scattered along the central vertical axis. I standardized their size and position and made the stack up nicely to the right of the reticle.



Turret tweak

Also visible in the second gif above is the newly tweaked turret design. Previously the base of the turret was dark, meaning spider drones could not shoot or climb on it. This caused some issues, as you're trying to aim at the turret and suddenly get a big confusing X on the screen indicating you can't shoot there. The new turret base allows you to crawl on it while avoiding other gameplay issues by having the actual turret body hover a foot above the base.

Build ID overlay

You can also see the new build ID overlay in the screenshot above. This will help me debug issues when people submit screenshots, and hopefully prevent people from taking pre-alpha screenshots as final quality.

Staggered grid experiment

I tried staggering the grid points to create a triangular pattern rather than rectangular:



I found the rectangular pattern emphasized the level geometry more clearly, while the triangular pattern distracted from it. I ended up undoing this change.

Kicking

Server admins can now kick people. You can even kick someone playing on the same screen as you.



Progressive upgrade pricing

Each upgrade you purchase now increases the price of all future upgrades. This makes the order of purchase more important, and also allows me to price things relatively cheaply at the beginning, which gives the player more options. Previously you really could only choose between the four cheapest upgrades at first.

Map work

Revamped Office:



Cleaned up and updated Refinery:



Cleaned up and updated Plaza:



Also, I tested out how Samsa looks in-engine. :)

This week has been INSANE. I don't even know why. When productivity strikes, you don't ask questions. You just go with it.

Shotgun
We coded up a shotgun live on stream:


The result:



Full auto bolter

The bolter is now full auto:



Speed changes, 20% map shrinkage

I increased the drone crawl speed a bit, and then decreased some other numbers to make it even faster in comparison. I slowed down the fly speed, decreased the drone's maximum range, and shrunk all the maps by 20%. This makes it more viable to tactically change your position by crawling, where before, everything was so spread out and the crawl speed was so slow that you felt essentially rooted in place. It feels like a whole different game now.

Camera rotation clamping

I clamp the camera's rotation so that the player is never stuck aiming straight into the wall they are attached to. At first, I clamped it against the wall's plane, but then I realized it was okay to let the player aim into the wall a little bit, so I switched to a cone. If the player tries to rotate the camera so that the look vector is inside the cone, the game pushes the camera back.

There were a few problems with this system. First, I didn't realize the algorithm needed multiple iterations to keep the camera out of the cone. So sometimes you would see the camera jitter a bit over the course of a few frames. I corrected this by running multiple iterations in one frame.

Second and more importantly, the cone sometimes pushed the camera out in a way that changed the player's aim unexpectedly. When landing on a new surface, I would start the cone perpendicular to the camera, then slerp its quaternion toward the final rotation of the surface. Unfortunately, slerp doesn't always rotate in the most direct path, and that caused it to move the cone in weird ways.

The new system immediately sets the cone orthogonal to the surface. Instead of tweening the cone's rotation over time, it tweens the cone's size. The cone starts out at 0 degrees and slowly expands to 45 degrees. This pushes the camera in a very smooth and predictable way.

I still have to smooth out the cone's rotation when crawling between surfaces, so I switched from a quaternion slerp to a linear vector lerp which takes a more direct path to the desired rotation. The end result is much more smooth and never disrupts the player's aim.

Here you can see the system nudging the camera away from the wall after landing:



Drone repulsion

I've had a pretty major problem until now, which is that you could shoot at a drone, barely miss, and anti-climactically land right next to them, with your character models uselessly clipping through each other. Worse, it's incredibly difficult to hit someone once you're that close; the physics just don't line up.

I corrected this by adding "repulsion". If two drones are just crawling around and their shields bump into each other, one drone will take a hit and the other will go flying. Drones that just landed take precedence. If both drones are just crawling around, the one that's moving faster takes precedence.

This change helps introduce more space and movement between players. Interestingly, this extra flight does not take one of your three cooldowns like it normally would.

Extra drone upgrade

I had a hard time keeping track of how many drones I had left while playing. It was always a surprise when I lost a match by using up my drone stock. So now I display your remaining drones at all times in the UI. I also added a new upgrade which allows you to purchase extra drones for a relatively high price. These changes are designed to make people play more carefully.

noclip in replays

I've had a replay system for a while, but the camera has always been a bit weird. I added support for noclip, which will hopefully allow me to film some neat scenes for a trailer.



Server regions

Servers now know which region they are in, and the client asks you which region you're in, and the master server matches the two together.



Text emotes and chat

The UI for this is still a bit rough, but chat messages and text emotes (a la Rocket League) are in.



Misc

Players are now rewarded for damaging other players, even if they didn't get a kill. Servers will now kick you after a certain period of inactivity to prevent AFK players from clogging up matches. I've also made a ton of improvements to the netcode, making it more robust and improving the client-side prediction.

Objective labels

After adding text emotes last week, I realized how useful it is to be able to reference points on the map by name. To facilitate this, I added labels for batteries and turrets. Batteries are numbered, turrets have letters. These labels also appear in the event feed in the top right, so there are multiple ways to find out which objective requires your attention.



Shell casings

This was a no-brainer. The bolter, shotgun, and sniper now eject spent cartridges. They're purely aesthetic. The cartridges are sized differently for the different weapons.



eeeeeeeeeeeee i love it

Audio

Jack has been summoning incredible sounds out of the ether despite being one of the busier human beings I know. I spent most of this week hooking them up and experimenting with spatial audio. I was kindly given a trial copy of Wwise Reflect, which I immediately integrated. Unfortunately, the plugin didn't quite work for our use case. We're switching to a more standard reverb setup where the game scales different reverb presets from 0-1 depending on the results of a few raycasts.

Reverb is nice, but obstruction and occlusion is essential for gameplay. Obstruction is easy enough - one raycast is enough to determine whether a sound is blocked or not.

Occlusion is trickier. Let's say a sound is just barely obstructed by the edge of a wall. The sound would not be affected much, because the occlusion wouldn't be very high. In some sense, the sound just takes a slight detour around the obstruction, arriving at the listener barely distorted.

I'm approximating occlusion by estimating how far the sound would have to travel to reach the listener without penetrating any walls. If the sound is nearby but behind a large wall that we can't get around easily, the occlusion should be 100%.

To calculate this, I'm leveraging the AI nav mesh. When a sound source is obstructed, the audio system does a pathfind to determine how occluded it is. Here's a visualization:



Sound doesn't actually work this way, but the total path distance minus the straight-line distance is a good rough estimate of how far "out of its way" a sound will have to travel to reach the listener.

Map switcher



Server admins can now select which map will load next, even if the map is outside the server's normal rotation. They can also force the server to instantly switch maps.

(+1)

The Last of the Jaggies

It took me two and a half years, but I freakin' FINALLY murdered the last surviving jaggies.

I started out using a typical edge detection post-process effect for the glowing edges. Then for a while I was supersampling the edges. Then I switched to a new system based on GL_LINES, which finally gave me some nice anti-aliasing. As a refresher, here's the comparison:



This has worked pretty well for the most part, except for a few cases where I got Z-fighting between the solid geometry and the lines. It looked like this:



I tried to mitigate this by restoring the depth buffer with a shader that sampled multiple pixels, taking the farthest one, but it wasn't enough.

A lot of times it was more subtle than the above example. The anti-aliasing would get cut off by the depth buffer:



Long story short, turns out drawing lines on top of solid objects is a common problem in 3D modelling software, and thus OpenGL has a built-in solution called glPolygonOffset. You render the solid geometry with a specially calculated depth offset that takes the depth derivative into account. So polygons that have a larger depth differential get offset more.

Rendering the whole scene with this offset messed things up for my culling system, so I settled on drawing the scene once normally, then instead of restoring the depth buffer later for edge rendering, I render the whole scene again in a depth-only pass with the polygon offset enabled.

It works beautifully:





Shotgun kick

I'll leave you with this gif of a feature I threw in yesterday. The shotgun has a teensy bit of kick now.



It's a minor thing in the grand scheme, but it makes me happy.

(1 edit) (+1)

Cooldowns, again

Cooldowns have gone through the most iteration to date. Aside from the health system, movement system, reticle code, and story. :P

People were having a hard time equating the three pips below the reticle with the amount of "charges" they had left. The bolter made things more confusing both for the player and for the code, because it let your fire three bolts per charge. Then the shotgun came along, which required two whole charges to fire. Things were getting crazy.

I did away with all that and replaced it with an actual heat-based cooldown system. Looks like this:



You can also see the new muzzle flash effect in that gif.

Linux

I finally re-installed linux on my desktop, and wonder of wonders, the graphics drivers worked out of the box. That never happens. I was shocked. Here's the built-in open source AMD drivers running the game faster than Windows 10:



Spawn effects

Jack made a pretty awesome spawn sound which had basically zero accompanying visuals, so I added some:



Adding controllers on the fly

I never realized what a huge win this feature is. Especially at expos, it's great to be able to add and remove players on the fly as people come and go. Took maybe 50 lines of code. Totally worth it.

Friends, admins

I finally added some rudimentary friend functionality. You can create a server and mark it as "private", which means only friends are allowed to join. I also added the ability to mark others as server admins, which means they can change the server settings, switch maps on the fly, mess with teams, and kick people.

Tons of other stuff is going on in the background, including work on the first official trailer (woah)

Trailer
I spent a few weeks animating, capturing, and editing a trailer. I started out working on some story scenes:






Animation can be really tricky. Here's a problem I encountered with Blender's "Child Of" constraint:



I export the animation at 24 fps, but play it back at 60 fps. That causes the cigarette to shake around a little. Fortunately you don't really see the cigarette too closely in the shot I had planned.

At any rate, I talked to the wonderful M. Joshua who advised me to hold off on the story elements. The trailer isn't for the full release, only for a multiplayer alpha.

I did finish the trailer, and even managed to cram a teensy bit of story into it. OBS Studio wasn't able to record even 720p video at 60 fps without hitching, so I picked up an HDMI pass-through recording box and hooked it up to my laptop. Worked beautifully at 1080p 60 fps. This is the first time I've been able to make a 1080p trailer.

Generators

The Assault mode is a bit like League of Legends. Team A tries to destroy Team B's turrets. When people try this mode, they immediately want to know if they can repair their turrets. A repair tool has been on my to-do list for some time, but I wasn't excited about adding a whole new ability that accomplishes only one purpose.

Then I started thinking about Sensors. These have been in the game almost from day one, but they've never felt overpowered like everything else does. They detect and track enemy drones, and they create a stealth zone where you become invisible. That's it.

I decided to combine the repair tool idea with Sensors and ended up with Generators. In addition to the old sensor functionality, these also passively generate energy at a fairly good clip. They pay for themselves within 30 seconds or so. They also slowly heal any minions and turrets in range. I also buffed them up and gave them more health and a shield. You can spawn a ton of these near a turret to get a whole bunch of healing power.

Prepare for alpha

The multiplayer alpha is really close to release. Just need to polish a few things and deploy the servers. I tested my ability to run multiple instances on the same server. It looks like I could run about 6 games with 4 players each on a single t1.micro.

I added support for GameJolt login. Also added a crash reporter on Windows:



It also opens the GitHub issues page after uploading the crash dump.

I'm showing the game at GDEX this weekend, then at LexPlay next month.

GDEX

My friend Charles Dickens loaned me some massive TVs that made this booth a sight to behold:





Deceiver got two honorable mentions for something something art-related, and best of show. The weekend was a lot of fun, and there were a ton of great games. BOMBFEST absolutely stole the show!



One interesting insight was that the game played really really fast, much faster than previous builds. One one hand, that's great, people love the action, but on the other hand, I could tell people were getting tired playing the game. It's very demanding. Players would become completely absorbed for 10 minutes, then snap out of it and say "okay that's enough of that".

That was one thing I noticed about the best of show winner MageQuit. Despite offering a lot of PvP action, it actually plays pretty slowly and gives players a ton of downtime. I realized this design works great for local multiplayer games where you're mainly trying to hang out with friends anyway.

This ties in with something I read a while back about the success of PUBG. That game also has a ton of downtime, which is great for streamers because they need time to interact with their chat.

For now, I tempered the pacing a bit by increasing cooldown times. It's a stop-gap measure. I would like to find another way to facilitate those all-important peaks and valleys in pacing. I think larger maps and larger teams would be a great way to do that.

Website, Steam

I made some major improvements to the website on the advice of the illustrious Jessie Kooner.



I also submitted a Steam store page for approval from Valve so people can wishlist the game.

This leaves two items on the alpha release to-do list. First, I'm waiting on itch.io to enable OAuth support. Second, I need to buy and deploy some dedicated servers.

I wanted to release tomorrow, but it's looking like I'll have to push it to next week. At any rate, it's getting close!

Dedicated servers


I shopped around for a while but ended up back where I started: Digital Ocean. So, the reason you're not supposed to use VMs is because performance is unpredictable; you never know when someone else on the host is going to saturate the CPU with a massive cron job or something. Theoretically you should be shielded from that, but in reality, it's not great for realtime applications.

I personally can't yet say whether it's an issue or not. But I did purchase special "high CPU" instances, which guarantee that you get two dedicated physical cores. One instance each for US East, US West, and Europe. I'm running four server processes per core for now.



Also set up an extremely simple alerting system that sends me an email when a server process crashes, and when a client uploads a crash dump.



Dashboard

I added a simple dashboard to the master server that allows me to execute SQL commands and keep an eye on connected servers and clients.



The bulk of this feature was done on stream:


Spotting

Someone on stream suggested adding a "spot" feature which would allow players to call out targets for their team. I thought it was a pretty good idea. This feature was also done on stream:


Gifs

Needed some new gifs for the website, so I recorded a few.





The alpha is basically ready. I might have to launch without itch's OAuth support. We'll see.

The multiplayer alpha is live! Limited number of keys, check it out now: https://etodd.itch.io/deceiver

Here's the trailer:

(1 edit) (+1)

Really love the art style. Also, it blows my mind that you created the engine from the ground up. One day I hope I too will have the capability of accomplishing that feat. I'm a software engineering major in college currently, but have no where near the knowledge to comprehend the task of creating an engine such as yours.

Anyways, looks absolutely amazing!

Glad you like it. Keep at it, you'll definitely get there! This was the best I could do 13 years ago: 

(+1)

Just watched the trailer, looks really good!

Thanks :)

(+1)

Nice looking game, I really like this style.

Thanks, glad you like it!

(+1)

Nice looking game, I really like this style.

Playtest feedback

I've had a ton of incredibly useful playtest feedback the past two weeks. Here are some of the gameplay changes and additions I've made in response to feedback:

Team bases are now surrounded with indestructible force fields to prevent spawn camping.



My friend Zac Fierce gave some great feedback about abilities. Previously, you could carry three abilities, which were permanent. Abilities got mapped to X, Y, and B in the order they were purchased. You could "toggle" them on and off by pressing X/Y/B to select the ability, and then right trigger "fired" the ability. The ability automatically toggled itself off after firing.

One of the problems with this system was that it changed the behavior of the right trigger without telling you. If you switched to the shotgun and attemped to fire, you'd expect it to fire and then toggle back to the normal movement/jump ability. But if the shotgun wasn't ready to fire, you none of that would happen, which might leave you confused as to which mode you're in.

Zac suggested putting the movement ability on a separate button, but I didn't want to add complexity to the controls. There was also the problem that the reticle gives tons of feedback about where you can and can't jump, and of course if you have a weapon equipped, you can fire it anywhere you want. So I would need two reticle indicators, one to show whether you can jump, and one to show whether you can fire. I might still do this, but it will take some thought to pull it off without complicating things too much.

Here's what I ended up doing. Rather than toggling abilities on and off, I made it so that when you press Y, it switches to the Y ability and stays there until you select a different ability. I also cut the number of abilities down to two, so that I could map the movement/jump ability to X. I also now allow abilities to be replaced, so while you can only carry two at a time, you can buy as many of them as you can afford.

The upgrade menu also now lets you choose which slot you want the ability to go in, which solves another issue. When someone always buys the shotgun first, they build up muscle memory that says "Y is shotgun". If they change the order of buying, the old system would change their control bindings so that Y is something else. The new system lets the player choose their control bindings regardless of purchase order.

Here it is in action:



Another feature request I heard a lot was for a "capture the flag" game mode. So that's in now:



Another common complaint dealt with the camera. I've explained previously how I use a cone to nudge the camera away from whatever surface the player lands on, so that they're more likely to be aiming in a direction they can shoot toward next.

Unfortunately I've always kept this cone active, even after the player has landed on the surface. This prevented the player from looking directly back at the surface they were attached to. They would have to swing the camera around the cone. This was an easy fix. After landing on a surface, there's a half second where the cone does its thing, and then I disable it. Now you can swing the camera around any way you like without regard for level geometry.

Spotting was another requested feature. I already have text emotes in the game that allow players to shout out specific turrets or batteries, but it's much more intuitive to just aim at something, hit a button, and have everyone see it.



Assault needed a bit of balancing. It was weighted pretty heavily in favor of the defenders. I'm balancing this by giving the attackers a pretty hefty chunk of energy to start with, and also by adding time to the clock and giving the attackers extra lives for every turret they destroy.

Also, players on the attacker team used to have an individual pool of lives, which meant one player could run out of lives and get stuck spectating. I switched to a ticket system so that lives are shared across all players.

That takes care of gameplay changes for the most part. Of course I've been tweaking cooldowns, damage values, and other numbers like crazy. But that's not very interesting.

One thing everyone agrees is very interesting is authentication! I asked itch.io to add third-party OAuth support, and they did it! Now you can log in to the game using your web browser even if you don't have the itch app installed.



I also integrated the Steam SDK, which will also log you in automatically.



Some players were having issues initiating and maintaining a connection, so I made another pass at the netcode and added some redundancy and improved latency handling. Now I'm sending every message at least twice as a pre-emptive measure. Also, when packets arrive delayed or out of order, the game does a better job of fast-forwarding to catch up with the server.

Another common feature request was mouse support in the menus. This is mostly done now.



Tons of other minor improvements have also been made. Players can edit server settings in-game. The game now gives more feedback when you kill another player. There's now an FOV slider. The input system now respects your operating system's keyboard layout. The crash reporter system has allowed me to fix a number of crash bugs.

I'm also putting together some assets for the upcoming Kickstarter. Progress continues!

CTF tweaking

CTF turned out to be a bit too easy to score in. There was no incentive to capture the batteries, which takes a lot away from the game. So I added force fields around the flags. Now you have to beat down the force field before capturing the flag, which encourages you to earn more energy to improve your damage output and capture flags quicker.

Story mode

Started working on story mode again, beginning with the most important features, like shopping cart physics:



The story mode is basically Dishonored, except there are no people and all you do is climb.

For a long time, the first level has had a problem of not feeling like a real place with a purpose. It feels like everything was put there just for you, the player. I spent some time fleshing out the level and making it feel more like a city with a life of its own:





This is like, version 10 of this level. There's still a lot of work to do on it. I would like to add more NPCs.

Hello! It's been a while since the last update. I was out of the loop for a few days recovering from wisdom teeth removal. Also, while the past few weeks have been very productive, it's more like a thousand small improvements rather than two or three big ones. I'm writing a lot of commit messages like this:

Story mode changes

I was able to watch some brand new players get a little lost and confused in the Docks level I posted last time, and have since refined the level design to the point where my sister had no problem finding her way around. Some super dedicated playtesters in our Discord server also found a ton of bugs and exploits which I was happy to patch.

I also decided to include audio logs in the game, to give more time to explore the characters without having to build expensive cutscenes. Other small touches to add life to the Docks include this fire effect which didn't turn out quite right on the first try:

Drone combat tweaks

The active armor effect has been a problem for a while. It was too subtle. Just some sparkles around your drone. I finally did what I should have done months ago:

Back in August I posted about "drone repulsion". Basically, if two drones are crawling around and they bump into each other, the slower one takes damage and goes flying. The intent was to avoid the problem of shooting at a player, just barely missing, and landing anti-climatically right next to them, so close that your shields overlap. Drone repulsion ensures no two drones ever overlap.

The problem is when you're crawling along, suddenly bump into an enemy drone, and your drone goes flying 20 meters without any input on your part. It's jarring, disorienting, and not fun.

So I got rid of it and instead solved the problem of near-misses in a much more obvious way: use a sphere-cast rather than a raycast. Now if you're flying toward an enemy and any part of your shield touches their shield, that counts as a hit for you. Once you land and start crawling, no more drone repulsion.

Can't believe it took me this long to figure this out.

New map

This is Treatment Plant. Actually it's an old map by Ian Cuslidge, but it's been revamped, cleaned up and welded together to accommodate the new rendering system and game types.

Other news

Special thanks to Ryan Evans for pointing me in the right direction to get my game recognized by graphics drivers so that it runs on discrete GPUs rather than integrated Intel HD garbage. Here's the code in question:

#if _WIN32
extern "C"
{
    _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif

Also, look for a new entry in the "Poor Man's" series of game programming articles this coming Tuesday.

In the course of poring over old screenshots and gifs for said article, I dug up a couple neat images from the past 2.5 years of development:

(1 edit)

Seven

Seven is the character with the most screen-time in the game. And she's a no-nonsense badass. I knew I needed to dedicate
some time to her.

I started out last week by playing hair stylist. I wanted to take Overwatch's wavy hair and bring it into Deceiver's neon aesthetic.

Next I modeled some cyberpunk combat boots.

Then the rest of her outfit.

The last piece was a military ALICE pack, but I'll get to that later!

Rain audio

Logan whipped up a looping rain sound for me. Previously I've just ramped the volume up and down based on a system that calculates how much rain is falling near you. But this time I wanted to control not only the volume, but also the positioning. So if you're standing under an overhang, the
rain will sound like it's coming from outside.

I already had a grid of raycast results surrounding the camera to drive the particle system, so I just ran some weighted averages on that to figure out where to place the rain sound. I used a Wwise RTPC to set the spread to 100% when the sound is close to the camera, so if there's rain all around you, you'll hear it in both ears.

Reverb voxel

I spent a day and a half on this crazy voxel-based reverb system. When my level importer builds a level, it first chops the level up into chunks.
Then at the center of each chunk, it queries the AI navigation mesh and collects all the points that are "visible" from that chunk.

Then it rasterizes those points into three "shells", for close range, middle range, and far range geometry. Each shell is sort of like a spherical
black and white bitmap image. If the close range bitmap is 100% black, then that means we're in a pretty tight space.

The importer spits out three coefficients for each voxel chunk, representing the amount of close range, middle range, and far range reverb. After this it runs the whole voxel through a smoothing kernel and writes the result out to a file.

At runtime, whenever a sound plays or a sound emitter moves, it's just a quick lookup to figure out what reverb values to use.

I'd like to write more about this soon. Hopefully there will be another Poor Man's article on it. :)

Adaptive client-side interpolation delay

I watched a talk on Overwatch netcode:

Pretty much the only thing they mentioned that I wasn't doing already was the adaptive interpolation delay. Basically, if you have a reliable
connection (regardless of ping), the game will buffer incoming packets for a shorter amount of time, because it trusts that new packets will
come in on time. This reduces latency.

If your connection is dropping packets left and right, the game will start to jitter. Eventually it will decide to increase the interpolation buffer to smooth over those missing packets. I use a simple scoring algorithm with some basic hysteresis to prevent the client from switching back and forth all the time.

Of course, algorithms often do the wrong thing, so it's important to give people control whenever possible. Hence this menu option:

Store art

The character in the store art I've been using dates back to 2014... and it shows.

With Seven's model finally done, I decided to redesign the store art and focus it around her, since she's the most important character.

First I tried just adding her to the existing scene:

This worked okay, but I was never quite satisfied with this color scheme. I tried messing with the lighting.

At this point I started soliciting feedback from some fellow developers, and they all thought it looked confusing and muddy. The foreground and
background bled into each other too much.

I kept messing with the lighting.

Everyone wanted me to switch to cold background, warm foreground, so I tried that:

Eventually I realized the old 2014 model had to go. It wasn't up to par. I also had to spend some time in the Blender compositor to get ambient
occlusion to behave the way I wanted to. By the end I had seven render layers, some of which spit out multiple passes.

At this point I slapped the logo on top and realized it would never work. The logo is surrounded by a dark border, and it didn't stand out against
the dark background at all. So I switched back to a pink background, and also threw in some geometry from a level I've already completed.


You can see I also removed the scan lines from the logo. On their own, they were okay-ish, but I realized they were just too complex and I couldn't make them work with anything else.

I'm still not 100% happy with the colors on Seven; I think she might not stand out enough. But after two whole days of tweaking I think my time is better spent elsewhere. I've never had this much trouble with store art before.

The capsule art stands out pretty well, but I'm still worried about it.

The problem I've had with this game from the beginning is that it's essentially two different games in one, complete with two different art
styles. There's the exploration game with vibrant, feminine colors, and then there's the drone combat game with deep, masculine colors. Which
style do I use for marketing materials?

A fellow dev suggested doing a simple A/B test with Facebook ads, which I actually tried once a few years ago. The sample size for a $50 ad spend (on the order of 50 clicks) seemed to small for me to take action on. I dunno. It might be a good idea.

Improved map view

The map view before was too zoomed in to show the whole map at once, and it kept panning the camera around all over the place. Also, the UI was
unclear. I switched to a static camera that overlooks the whole map, and cleaned up the UI:

Cooldown netcode

The cooldown netcode has been a problem for a while. Previously it worked like this. The first time the player performed an action, the server
would add the necessary heat to the cooldown meter, but then instantly subtract the amount of heat that would be dissipated during the network
round-trip time (RTT) between the server and client, to ensure they stayed in sync.

When the client needs to add heat to the cooldown meter, it does so instantly and ignores the cooldown value coming from the server, simulating it locally until enough time has passed for the server to receive a packet and send one back. Then the client overwrites its local heat value with the one from the server. Most of the time, they're perfectly in sync and the handoff isn't noticeable.

However, the server only needs to compensate for lag one time. If you have multiple cooldowns stacking on top of each other, and you subtract the
RTT from each one, you'll get incorrect results. So the server only performed this lag compensation if the heat value was at zero.

The tricky part is, the bolter adds a very small amount of heat to the cooldown meter for each shot fired. It takes less than a third of a
second to dissipate the heat. Turned out, if your ping was high enough (i.e. the RTT was more than 150ms), the server would subtract enough
from the cooldown meter that by the time you fired the bolter again, the heat would be down to zero already, and the cycle would continue. You
could fire the bolter indefinitely.

Long story short, I made the client do lag compensation instead of the server. It takes the current heat value coming in from the server and subtracts the RTT no matter what. Much more simple and robust.

Grapple

As I've said before, the singleplayer portion of the game is basically Dishonored with no enemies and all you do is climb. Well, I wasn't
joking. The game now has Dishonored's Blink ability as a movement upgrade:

It uses the same cooldown system as the rest of the game, which I like because the cooldown is pretty quick the first two times, and slows down
after that.

Triangular minion head

A viewer on Twitch said I should try making the minion's head more triangular. I had no argument against it, since everything else in the game is
triangular. Now all my brand new promotional art is outdated :(

Force field sound obstruction

This was an easy win. I already have audio obstruction/occlusion code, it just wasn't taking force fields into account. It's pretty cool when
someone fires a weapon inside a force field and it sounds muffled and muted.

UI colors

I switched the UI from teal and pink to yellow and pink. Here's how the main menu looks now.

Working on some scenes for the trailer. Here's Seven and Samsa in "holographic cyberpunk space command center" mode:

He folds up into cyberdoggo form:

And Seven packs up to leave:

The backpack animation was incredibly tricky. It's not perfect, but I'm okay with it. I did it by duplicating the bones that affect the
backpack, then creating constraints that glue the new bones to the original ones. To put the backpack on, I just animate the influence of
these constraints from zero to one.

(2 edits)

Still working on the trailer. Here's a new shot from it:

It's coming together quicker than anticipated. Most of the footage is pretty close to final, and now it's just a matter of getting the audio together.

In the mean time, I realized while watching gameplay footage that the weapons were in desperate need of camera recoil. So I added some.

It's just velocity and acceleration. I overwrite the velocity when the gun fires, then the acceleration (or "gravity") brings it back down. Once the angle gets close to zero, I limit the velocity by a multiple of the current angle, so it slows down instead of slamming back to zero. Relevant code here.

Unfortunately this new feature means I now have to re-shoot several scenes in the trailer.

I got an opportunity to talk to Michael Cox, marketing guy from Crows Crows Crows. He's @DevMicco on Twitter, he has DMs open. Great guy with super helpful advice.

He told me to release the trailer, release the demo, push PR, everything, but don't launch Kickstarter. Instead funnel people into Discord and mailing lists. then launch Kickstarter when there's enough people to make it succeed on the first day.

That makes a lot of sense to me, so that's tentatively the plan I'm going with now.

In the meantime, we had another playtest session on Thursday and I came away with a bunch of balance tweaks and bug fixes, which I was able to knock out yesterday.

The most interesting news is, the game now has a Discord bot.

This thing allows you to indicate when you are available to play, and if anyone else is online or marked available during that time, you'll get a
notification. It also periodically blasts out statistics about who's playing.

I've been using vyte.in to organize playtest sessions, and it hasn't been great, so hopefully this will work better.

Initial Discord integration is done. You can now see what people are doing in-game.

And if you're in a game, you can invite a person or a whole channel to play with you, even if you're in a private server.

The invite updates the number of players in the server in real-time.

I also upgraded the bot to place LFG players in a special role and mention them specifically when people are playing.

I'm really impressed by the quality of Discord's software. Their APIs are well-designed. Everything they do is a pleasure to work with.

New map WIP

I decided the maximum number of players I want to support is 12, so the game can support 4 teams of 3 or 3 teams of 4. In light of that, I started working on a new map with enough room for so many players. This will be the largest map yet.

It's still a work in progress, but sometimes I'm tempted to leave parts purposely unfinished.

Presets
Some friends were kind enough to play a few local matches of Deceiver with me, and even though most of them were familiar with shooters, they were still confused. It didn't help that the game was in the middle of some of the design overhaul changes described below.

What I should have done was set the game to unlock all the abilities and disable the advanced stuff so we could all just shoot each other. The game does offer those settings, but I didn't want to sit around tweaking settings while they waited.

This is related to another problem I have with the servers online. Playtesters have created 77 different servers with different settings. It's impossible to see at a glance what the rules are for each server. Also, when creating a server, it's overwhelming to see all these options, and it encourages you to tweak all of them, which is usually not a great idea.

Presets are the solution I came up with. When you create a server, you can choose "Standard", "Arcade", or "Custom". Now you can see at a glance what to expect from a server. Arcade unlocks all abilities, and Custom lets you go crazy with any setting you want. The nice part is, these presets work with all three game modes. I can easily envision more presets like "Snipers Only", "No Shields", "Fast Cooldowns", etc.

Hardware cursor

Ever played a game where the cursor felt laggy and unresponsive? That was my game until just recently. Turns out, computers use a separate low-latency hardware path to render the cursor. I switched from my custom OpenGL cursor to a hardware cursor. Unfortunately, that meant my vector-based anti-aliased cursor mesh wouldn't work anymore.

I spent WAY too much time trying to pixel art a cool looking cursor, and ended up settling for a plain ol' crosshair. I may switch to the default system cursor, but I think this helps everything fit together better.

Design overhaul

I made a number of sweeping design changes. Below is a list, with the accompanying reasoning behind each change.

  • Remove ticket system from Assault. Assault is an attack/defend mode, and previously the defenders won by either running the clock out or exhausting the attackers' tickets (respawns). Now the ticket limit is gone. Reasoning: Originally, both teams actually had limited tickets, which made the game type devolve into just regular ol' team deathmatch. With only the attackers limited by tickets, it still meant the defenders were basically playing deathmatch. Receiving energy and deterring the enemy is enough of a reward for killing an enemy player, there's no need to attach a win condition to it. Also, the ticket system had to scale based on the number of players, so if a player joined or left mid-match, the number of tickets would change. Confusing UX.
  • Remove spawn selection. Previously you could spawn from any battery you captured, and by default you spawned at the battery nearest to where you last died. This is all gone now. You only have one spawn point. Reasoning: it's important to have downtime with lower intensity gameplay. Also, the decision of where to spawn was never interesting, and mostly just served to confuse newer players.
  • Make turrets auto-heal and decrease player damage against them. Reasoning: Once again, over the past year or so I forgot that the Assault game mode is basically a MOBA. Players are not supposed to fight turrets themselves. It's not interesting or fun. So now, it's mostly worthless to fight turrets.
  • Make ability re-purchases cost nothing. Once you've purchased an ability, you can now replace it with another one and later switch back to it free of charge. Previously you had to purchase it all over again. Reasoning: I want to encourage more diverse usage of different abilities. Also it feels more friendly to the player.
  • Make all abilities free to use. Previously, you would pay energy to buy an ability, and then some abilities also required energy to use. Now, energy is only used to buy abilities. Reasoning: it was always impossible to balance prices for moment-to-moment combat purchases against prices for longer time-frame purchases. If a high level ability costs 1000 energy to unlock, and a low-level ability costs 20 energy to use, that means you can spam the low-level ability 50 times. Also, you never got a sense of progression because you're always spending your progression currency on combat. The number never really goes up. Now the abilities are easy to balance: I just put separate cooldowns on them. And your progression is much easier to see, since your energy always goes up until you spend it on an ability.
  • Change the minion ability to a passive spawn rate boost. Previously you could use it to spawn minions at will. Now it just takes up an ability slot and increases the rate at which minions spawn from your captured batteries. Reasoning: the minion ability previously required no strategy. You could just show up at an enemy base and plop down 10 minions in as many seconds. Now you are more encouraged to capture and protect batteries, since they spawn minions.
  • Change DM and CTF modes to no longer spawn minions by default. Reasoning: the DM and CTF modes, which should be straightforward for anyone familiar with shooters, confused my friends who are familiar with shooters. These are the modes I use to introduce the game to new players, since there's much less to explain. But people are still confused, and one of the major confusion points is "who are all these people walking around?" It just makes the game feel unfocused and chaotic. I still think minions are a blast, and people love shooting them, so I'm keeping the minion boost ability in for these game modes. Instead of boosting the minion spawn rate, it enables them spawning at all.

Stealthed rectifiers
Rectifiers do two things: they heal stuff, and they create a field which provides stealth for your drone and alerts you to the presence of enemy drones.

Previously, rectifiers were pretty ineffective because they were easily destroyed. Now they're tougher because they're stealthed by default, so enemies can't see them unless they plop down a rectifier of their own.

Unfortunately, the healing particle effect I was using completely gave away the rectifier's position. It showed a trail of particles going from the rectifier to the object being healed. I changed the effect to use a sphere mesh instead. Had to modify the instancing system to allow per-instance colors.

Suicide minions

You can now launch a grenade at a friendly minion and it will attach to the minion's back, waiting for a hapless victim to wander near. While implementing this feature, I accidentally turned the minions into giant walking grenades: