LÖVE und PANZER
In my previous post, I discussed how Colby and myself initially set up a 3D environment in LÖVE, aptly named LÖVE3D. In this post, I will be discussing some of the tools we're building for this environment, and challenges we've come across while building them.
After making a dance video to show off the abilities of my IQE loader, we decided to start work on a game. We had a simple panzer model kicking around, so we decided to make a simple tank shooting game. We started by modifying the world we already built by removing the dancing Mikus and replacing them with our tank. We attached the camera to the tank's body and allowed a togglable first-person view.
With a basic Player object, we quickly got bored of the flat surface we were driving on. We had two options: design a world in Blender and load it up, or procedurally generate a world on the fly. We decided the latter option would both be more interesting and less time consuming, so we researched noise generation algorithms. After looking at a few algorithms, we decided to use Simplex. Luckily, there was a Lua Simplex implementation already made and licensed under MIT, perfect for our purposes.
Setting up a procedurally generated world was fairly straight forward. First we generated a flat plane of evenly spaced vertices, then ran the noise generator against each vertex to get a height value (Z-position). To tie the tank to the ground, we cast a ray from the tank's centre into the ground to determine the point of intersect, and set our Z-position to that point. This allowed the tank to drive on the ground, but the tank would also clip through the ground since we had no real direction other than forward. To quickly solve this, we used the intersecting triangle and nearest neighbour's normals to determine the tank's orientation (pitch, yaw, and roll). This generally worked, but the tank jittered a lot and showed off the shortcomings of our 3D camera.
To solve the tank jitter, we needed to rethink our interpolation method. The nearest-neighbour was producing poor results and a lot of errors. With a bit of research, what we found was a near-perfect solution: bilinear interpolation. Instead of determining which triangle we were on, we shifted to using full tiles. Instead of a nearest-neighbour, we grabbed the two closest adjacent squares. Interpolating with the new data, the tank was able to move around quite fluidly and without jitter.
What we originally thought to be an issue with the 3D camera we learned was actually an issue with our tank data. By writing a simple function that converts orientation to direction, the tank's data was corrected and the camera was totally fixed.
One feature Colby really wanted to add was online multiplayer. I had written some basic code about a year ago for just this purpose so it took very little effort to rework that into this project. The game server simply listens for commands from each client such as "I am now moving forward", "I am now turning left", "I have stopped turning", et cetera, and does server-side updates to keep track of where tanks are. The server then sends these same flags down to each client, and the client does its own updates. Every few seconds, the server sends a force-sync that corrects any deviation. Since the commands are not timestamped, there is a little bit of jitter when the force-sync is sent. For now, it works quite well.
We will be releasing all of our code when we feel it is ready to be seen by the public. So far we have released several bits of code that we are continuously updating:
- Lua IQE Loader - a pure Lua IQE data structure
- Cirno's Perfect Math Library - a pure Lua math library designed with 3D games in mind