Tuesday, April 8, 2014

Blob game

I implemented about a quarter of a 'blob game', and it was an honest nightmare.

A 'blob game', in this context,  is a game built on a mass-aggregate physics system. You control a 'blob' made of constrained point masses (in this case an icosahedron formed of rigid rods and spheres) and maneuver that through an obstacle course  made of point masses, rods, cables, springs, spheres, and maybe also planes if you're feeling ambitious. In addition, the spec called for a level editor of some description, an AI controlled enemy, and a collection system.

I have the character controller. This project was a disaster.

My collisions and constraints only work by the tiniest possible margin, after consuming four times the time they should have to implement. I tried to implement my own version of the collisions algorithm from memory, which went very poorly and produced all manner of strange glitches. I eventually gave up on that and implemented the one in the book very directly. This solved about half of my problems. After sinking three hours into a bug stemming from an improperly-constructed constructor, and another few hours flipping signs basically at random, collisions worked. (A handful of the signs in the examples I was working from were wrong, and another handful were broken due to disparities in my existing code and the existing code in the sample. For example, my code stores mass directly, and calculates inverse mass, rather than storing inverse mass and calculating mass.)

Getting hard constraints (rods) working was a similar process of sign flipping and blind exploration. It's actually a very satisfying way to work when something does finally work, although it's not as efficient as really knowing what you're doing going in. For a while my rods were acting like a sort of strange spring, where they would stop two objects coming too close, but if they ever separated beyond their desired distance the rod would actually push them farther apart forever. Another permutation just made the balls group up tightly as soon as they were destabilized from their desired distance. A combination of these two malfunctions resulted in a stable rod, which is what I'm still using.

Once I had rods working properly, making the blob character controller only took a few minutes. Unfortunately, those were the last few minutes I had, so there isn't terribly much more completed.

My strangest bug is as-yet unfixed. If a collision occurs that has a velocity below a specific threshold, my interpenetration solver would push it at exponentially scaling speeds in the wrong direction, sending the unfortunate point mass to {-infinity, -infinity, -infinity} over the course of the next half dozen frames. My best guess is that it's a floating-point rounding error on a near-zero divisor somewhere, but I couldn't find anything like that, and after a few hours of searchign I gave up. I ended up disabling the interpenetration code entirely, and, surprisingly, it worked fine. At the speeds that objects in this simulation move, the interpenetration code does nearly nothing, and the velocity solver is more than capable of handling collision resolution entirely on it's own. "Comment out the whole function that's screwing up and pray" is never the best solution to a problem, and it's very rarely a functional one. But sometimes you get away with things.


Tuesday, February 4, 2014

Physics Force Accumulation

I've been working on a solar system simulation.

It went okay. The basics are all in place, I can add bodies, and add gravitational forces per-body. (Right now things are only attracted to the sun, because those are the only non-negligible forces in the system, but multiple forces are fully supported.)

The devil's in the details, though.

The numbers involved are too large and require too much precision for my Vector data type, which uses floats. I've done what I can in that department by choosing good units, my distances are in Light Minutes, my masses are in 10^24 Grams. However, my gravitational constant is wrong, and no amount of tinkering to compensate for errors makes it work right. I can find values where planets in a certain size range orbit more or less properly, but lighter planets than that range take off into space, and heavier planets develop super-elliptical slingshot orbits that would destroy a real planet. (As you see in the video, Jupiter's orbit takes it through the surface of the sun at four or five times intended heliocentric velocity.) I almost think there's a formula wrong somewhere, but I can't find it, and fortunately this one problem is in a very minor part of the spec. 

All my auxiliary features are in place; I can zoom on planets, and dynamically add planets, and display particle trails for orbits. I can draw debug info, although I can't figure out how to draw it at a planet's location, or at any size smaller than 48pt. I can reset the simulation freely.

Here's a video of it in action, although Youtube mangled it pretty badly, so you'll have to squint at the smaller planets.


Monday, September 30, 2013

SpaceWar Jam

Boyd and I were told to make a copy of an old game called SpaceWar in an an afternoon.

Boyd basically had it done for a past project, so it went pretty smoothly. Most of the code I wrote was pretty trivial, but I'm proud of how my starfield generator ended up looking.

I can't think of anything I'd do differently next time. This project went almost perfectly smoothly.
http://boydpellettproduction.blogspot.com/ is my partner's blog.
http:prof.johnpile.com is my professor's website.

Monday, September 2, 2013

Networking in Source

The Source Engine, and by extension Team Fortress 2 and company, handle networking over UDP.

There is an authoritative server that handles all game logic, physics simulation, etc. It runs at a fixed rate of either 30 or 66 ticks per second, although this number can be changed. Not every tick is sent to clients as a snapshot, each client has setting to keep the data sent to them within their bandwidth restrictions to avoid dropped packets. In practice, this means that a client gets about 20 updates per second, which is clearly a good bit lower than the frame rate of the client. To avoid the game looking choppy, the client doesn't render in real-time. Instead, it renders at a (configurable) 100ms delay behind the game state it has actually received, and interpolates between the two snapshots on either side of that delay point. (100ms allows for one dropped snapshot at 20 snapshots per second without a stutter, but doesn't introduce input lag that is noticeable to most players.)

Clients send input to the server in smaller snapshots containing only player input, at a higher rate. Latency, or Ping, is the amount of time it takes for a client to take an input snapshot, send it to the server, and receive a game snapshot with that input taken into account. Ping delays are inevitable, and there is nothing that can really be done to prevent them. However, the engine has a simple trick to disguise them from the client, greatly increasing playability. Each client machine runs the game simulation for the player and things they control in real-time, letting players move with zero latency. Of course, the server is still authoritative, and that means that sometimes the local player position is wrong. Every time a new snapshot is received, the local simulation is corrected, either instantly or over the course of a few frames.

This isn't good enough to do quality hit detection, though. Because it's so important, the server keeps a log of player positions for one second, and whenever a hit is called for (i.e. a weapon is shot) it estimates what time the shot actually occurred, based on the client's specific latency and interpolation, and 'rewinds' the server to check the hit, then applies it retroactively to the current gamestate. Essentially, this means that you don't have to lead an enemy with a hitscan weapon if you have high ping.