LÖVE und PANZER - I Broke Everything
Over the past few weeks, development has slowed down a bit while both Colby and myself had several other obligations to deal with. While development slowed, it did not stop entirely and we did manage to squeeze out several new features and fixes, both big and small.
After I added multiplayer support to our game, I decided to crack open Blender and rescale our tank model to be appropriately sized. I jumped on Wikipedia and looked up the size of the hull on a Panzerkampfwagen IV and scaled our tank up to match the length. This of course broke everything.
With the tank now a realistic size, the camera was in the wrong place, the terrain was too small, and the bilinear interpolation no longer worked smoothly. The quickest fix for the camera was to simply multiply the offset position by the tank scalar. While this worked, it didn't quite feel right any more so we spent a bit of time trying different values until we found a camera position that was comfortable. We plan to go back to that at a later date but for now, it's fine.
To fix the interpolation, we came up with a somewhat simple solution that we plan to refine in the coming weeks. The problem with the bilinear interpolation was that the tiles were so tiny compared to the resized tank that there was just too much jitter. The tank felt like it was falling over every little bump instead of smoothly riding over them. What we ended up doing to resolve this was to grab the centres of the four tiles that lined up with the front and back of the tank treads and create a new square. This new square gives us an approximation of the tank's position on the terrain. We used those four tiles to derive a normal for our new square and applied that normal to our tank. This mostly works, but we plan on implementing a more precise method once we implement bounding boxes.
With the freshly scaled tank, it became quite obvious that the arbitrary speeds we were using to test with were no longer comfortable. A quick lookup on the World of Tanks tankapedia gave us the speed, rotation, and turret rotation values for the Panzer IV. Implementing those speeds made the tank feel much more realistic but it became very apparent that we needed to rethink how we were generating and managing terrain.
The original method we were using to generate terrain included generating the entire map at once and indexing every triangle into a winged edge data structure. Without the bilinear interpolation, the winged edge setup became unnecessary and we found it was also the biggest bottleneck in our generation. So we axed it. Actually, we axed the whole thing. It was decided that instead of working with triangles we would work with full tiles, and instead of generating the whole map, we'd chunk the data.
Chunking terrain seems like a fairly straightforward process, but it is surprisingly confusing to keep track off all the various coordinate systems. Each chunk is made up of 16x16 vertices, which creates 15x15 tiles. Coding late at night caused me to forget that 16/15 discrepency and boy was that frustrating. "Why is it saying I'm in the wrong chunk when I'm only on the 250th tile?!" When that finally sunk in, the rest was fairly straight forward. Counting from the southwest corner of the map, chunks are organized in a 2D array. Each chunk has 256 vertices organized in a linear fashion where each row has 16 vertices. We decided to use a 2D array for the chunk grid because the size of the map can be variable. The chunk sizes are fixed, so it is quicker to use a 1D array and use a bit of math to determine when you move to another chunk. All chunks use a local tile coordinate system for simplicity. What this means is you can check chunk , tile 57; or chunk , tile 57. With the new chunking system, generating a 256x256 tile map only takes a few seconds insead of nearly a minute like it did before.
One feature we have been wanting to implement was sub-model transforms. Our tank model is made up of several meshes, most of which are static. One particular mesh, the turret, we needed to rotate so a player is able to move forward while shooting in a different direction. To be able to do this, we needed to adjust our IQE loader so that each mesh holds extra data such as the mesh name so that we can gain access to individual meshes. We also added a turret rotation variable to the client and server to keep track of the raw data. Every frame we grab that rotation radian and use that value to apply a rotation matrix transform to the individual mesh. Being able to rotate the turrent made the game feel much better, but the real improvement came when we attached the camera to the turret when in sniper mode. Such a small change really improved the feeling fo our game. Now players are able to drive around and look behind them, et cetera.
The final small change we made was to allow players to change the name that appears above their name. This made our play testers quite happy to finally know exactly who they are chasing around, and who is chasing them.