Jupiter_Hadley: thanks for giving it a shot! It's "grepr start", in case anyone else has trouble.
Recent community posts
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:
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.
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.
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:
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.
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:
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.
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.
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.
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.
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.
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.
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.
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...
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.
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.
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).
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.
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.
After posting last week's devlog, I realized the HUD was a bit of a trainwreck, so I redesigned it.
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.
I added an "active armor" ability that temporarily grants you invincibility and reflects any incoming damage onto the attacker.
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.
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!
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.
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.
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...
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.
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.
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.
- 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.
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.
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.
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.
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 :)
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:
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
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:
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.
You can now kill minions from above. I haven't done anything to align the animation yet.
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.
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.
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:
This is the third map you'll discover, if you count the title screen. Which I do.
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".
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.
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.
I've been fleshing out the first few levels. The first one also doubles as the title screen:
Rope climbing and swinging is in, although still a bit WIPpy.
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.
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.
Happy Thanksgiving! :)
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.
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.
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.
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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!
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.
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.
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.
Ian has been working hard on new maps. Check it:
I think they're pretty nifty.
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
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...
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.
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.
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.
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.
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:
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.
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.
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.
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.
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.
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.
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)
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.
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.
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.
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.