For the last few decades, humans have been sending images out into space in the hopes of communicating with intelligent life. A nearby alien race has intercepted one of these images - a photo of a cow - and have determined that the creature is a fantastic being of the gods. Seeking the source of this image, the aliens come across a dangerous, chaotic planet known as Earth, and they quickly discover that these mystical beasts are real and that there are dozens of them helplessly wandering this dangerous planet. In order to save these stunning creatures, the aliens have decided to bring as many cows as possible home in their mothership.
The player takes on the role of one of the aliens, and is tasked with gathering as many cows as possible. Since the scouting ship’s interior is too small for the cows to be held inside, the player must rely on their gravitational tractor beam to hold the cows until they can be safely deposited in the mothership.
Controls: WASD moves your ship, and the arrow keys move the camera.
Goal: Find the mothership, and bring all the cows on the map to it in the shortest possible time. But don't bring home haybales!
The final score is based on the speed at which all of the cows are collected and whether or not the player collected any unwanted hay bales that are also scattered around the map.
Most physics libraries were not designed in a way that would work for our purposes. ReactPhysics3D and Nvidia's PhysX were the better options I found. But PhysX was not a cross-platform solution, and I had issues building and linking ReactPhysics3D.
I eventually decided it would be best to implement physics myself. Please note, I DO NOT RECOMMEND THIS FOR FUTURE PROJECTS. A library is a much more stable and sustainable solution. I only did this because I have implemented 2D physics engines in the past.
My main source for this engine was my old AP Physics notes. I implemented a per-frame net force for each object, which then was calculated into acceleration and velocity and then cleared again at the end of each update loop. I also implemented simplified versions of magnetism, gravity, friction/drag, and elastic collisions for cylinders and spheres.
PROBLEM: Objects clipped through or hovered over the ground depending on their size and scale due to their model centers.
SOLUTION: Added a function to find the height of the model based on the highest vertex position. Translated the model up by half its height, essentially changing the position point to the base of the model
PROBLEM: Jittering from continued collision.
SOLUTION for ground collisions: Snapped object position to ground height when close to or under ground, and did not apply gravity unless above the ground by a small margin.
SOLUTION for object-to-object collisions: Initially applied a normal force equal to the incoming force such that velocity was zero, but this made objects stuck in place.
Tried pushing objects out the way they came in (ie in the negative velocity direction), but doing this with precision involved a significant amount of computation.
Pushed objects out in the closest "out" direction. This worked reasonably well, but there was still a small amount of jitter, and objects could get stuck between overlapping obstacles like trees.
Finally, made objects bounce off obstacles after colliding, which made it difficult to jitter or get stuck, and it fit better with the silly tone of the game anyway.
PROBLEM: Objects could jump through one another when moving at high speeds.
SOLUTION: Debated using ray-traced collisions, but I also knew that reducing overall processing lag would help, so I focused on that first. As predicted, the reduced lag from my quadtree and Anthony's view frustum culling raised the frame rate, thus lowering the per-frame move distance at high speeds and eliminating the issue.
This data structure is used to optimize collision detection by grouping nearby objects into branches of a tree that subdivides the world into four sections, then subdivides those subdivisions, and so on as needed to organize objects.
I used this gamedev.net tutorial when designing the quadtree. Although this tutorial is for octrees, quadtrees follow a similar algorithm. However, I hit a few snags as a result. Firstly, the tutorial is in C#, which looked enough like C++ to trick me into thinking I could write my quadtree a similar way, but had a few key features (most notably memory management) that required extensive revision. In addition, the tutorial example code wasn't very well designed, and omitted or duplicated significant portions of necessary computation. Overall, it was helpful in understanding the basics, but did not prove to be enough to get my code working, and I did not manage to successfully debug the quadtree before we turned in this project.
PROBLEM: The quadtree sometimes got stuck in an infinite build loop.
SOLUTION: Ensured that each build step was called in the correct order, instead of placing recursive checks as presented in the tutorial.
PROBLEM: Objects on node edges did not collide with anything outside of the node.
SOLUTION: Realized that radius had to be taken into account when sorting objects into quadtree.
PROBLEM: Duplicated behavior for objects on node edges.
SOLUTION: Realized that collision detection for being "within" a note was not the same as simple intersection -- quadtrees require an object to be completely within a node's boundaries. Changed the algorithm computing to accommodate this.
PROBLEM: Quadtree deleted the player near the edges of the map.
SOLUTION: See above -- the player had to be contained within the quadtree, but the player's collision radius was very large to accommodate its gravitational range. Added 50 units of space to each side of the quadtree.
PROBLEM: Seemingly-random portions of the tree never collided properly.
SOLUTION: Added a function that printed the whole tree, including bounds, objects. Fixed some very well-hidden typos. Fixed the way the bitflag keeping track of active nodes was set. And after many, many hours of further debugging, it still didn't collide properly.
View frustum culling was implemented to reduce the amount of lag and make the game playable on our machines that have no discrete graphics cards. With it enabled, only the objects in the camera view is drawn.
PROBLEM: Even with View Frustum Culling implemented, having the camera face a certain way with many trees would still have a somewhat slow rendering scene.
SOLUTION: Reducing the cull distance in front of the camera (-z direction) helped reduce this lag in most cases.
PROBLEM: Pulling the culling distance closer to the camera resulted in distracting drawing and culling of objects on the edge of the culling.
SOLUTION: Reducing the ambient light in the fragment shader kept the light at the center of the scene roughly the same while making the area around the player more dark the farther away from the player. This increased darkness scaled by distance made the sudden rendering and culling of trees significantly less noticeable.
Shadow mapping uses the difference in depth between a fragment from a light source's point of view and the same fragment's spatial location to determine whether that fragment is shaded. This is an easy and efficient way of obtaining real-time shadows.
PROBLEM: Our base code was designed in a way that made using FBOs very difficult to use.
SOLUTION: Restructured rendering system, pulling each rendering pass out into a separate function, and each step within those passes into separate functions.
PROBLEM: Lights pointed directly down did not show shadows.
SOLUTION: Offsetting the lookat position by 0.1 pixels in the x and z coordinates fixed this issue.
PROBLEM: The shadows were being rendered too dark, changing the color of the objects drastically when coming into close proximity. This made the object shadows very distracting when approaching them.
SOLUTION: Moving the shade variable in the fragment shader that creates the shadow color to be multiplied with the ambient light component instead of the overall light color made this issue much less noticeable.
Our base lighting shader was a Blinn-Phong algorithm with distance-based falloff. Zoe referenced both our 471 notes for this.
PROBLEM: The falloff function was originally designed for a static camera. There was a glitch where a tiny dot of specular shine could appear where it wasn't supposed to when the camera was rotated.
SOLUTION: This was a small detail and we didn't have time to fix it, so instead we significantly lowered the amount of specular used to make it less noticeable.
The tree and cow model are found online, but the UFO and mother ship model are created using Autodesk Maya. The cow was also re-rigged by hand in Maya.
When animating models, there are a few different ways of doing so. Rather than using mesh deforming or hierarchical modeling, we had to do it a different way.
PROBLEM: Cross-platform development does not make importing external libraries (Assimp) easy.
SOLUTION: Instead of using Assimp or hierarchical modeling for animations, instead we hand-rigged our cow model and exported multiple .obj files of different key frames for its walking animation. Each obj mesh is saved to an array and when the animation is needed, it increments and cycles through the array every 3rd frame of game time before drawing.
Our particle system is used for creating the sparking effect when the UFO would crash into the walls.
PROBLEM: Every tutorial online was incompatible/outdated for our game implementation.
SOLUTION: We implemented a lab from a previous course, changed some parameters and the shape of the particles to resemble sparks, and only spawned particles after collision with the trees in the game.
Dealing with sound in our game adds a lot more lag, though it is necessary for our game feel and experience.
PROBLEM: External libraries and cross-platform development do not work well together, and there is no other easy way of audio support.
SOLUTION: Manipulation of the CMake files allowed MacOS and Linux to work with our external library (irrKlang) and allowed us to play sound effects and background music.
PROBLEM: Our game's mood does not seem compliant with our "goofy and silly" theme.
SOLUTION: We added bouncy sound effects on collisions as well as a cartoony background music.
Our GUI is implemented using dearimgui, a simple immediate graphical user interface library.
PROBLEM: The player does not know how many uncollected cows are on the map.
SOLUTION: We added a cow counter in the UI so the player isn't spending so long wandering around the map.
PROBLEM: The sense of urgency is not prevalent in our game. It was too easy and relaxed.
SOLUTION: In order to reward players who collect the cows quickly, we implemented a stopwatch which counts up while the game is playing. This stopwatch is used to tally the score at the end: the higher the stopwatch time is, the lower the score multiplier.
To allow for more expandability in our game, we implemented a map editor that reads in a map png using stbi_load from stb_image.h. This function creates an array of the individual RGB values for each pixel, which is then mapped to the center of the object in world space.
PROBLEM: Our play testing results show that our map is too small.
SOLUTION: We created a map editor to allow multiple sizes of maps to be played on and experiment with size and map design.
PROBLEM: Too many trees were spawning too close to each other.
SOLUTION: Since the map editor reads pixel by pixel and draws objects with their center at the pixel, we applied a filter to the tree line image to only show every other pixel to space them out.
PROBLEM: Cows are spawning inside our trees.
SOLUTION: Rather than randomly placing each cow, we added support in the map editor to read a color for individual cows to allow us to hand place them in areas we want them to be. This also gives us the ability to change the number of cows in each level place them in small herds.
Player and camera movement aren't exactly a real-time technology, but they're very important to the feel and playability of a game, so I'm documenting my solutions here for future groups.
PROBLEM: Required a third person camera perspective.
SOLUTION: Shifted camera 10 units behind the player character. Changed arrow-key controls to the pitch and yaw of the camera, which rotate the camera around the character. Added separate WASD controls to move the character itself.
PROBLEM: Player movement was not aligned with third person camera perspective. (ie. If you rotated the camera to the left, "forward" could become "right".)
SOLUTION: Found the vector from the camera to the player and projected it into the xz plane as the forward vector for the player. Crossed this with the up vector for a leftward vector. Used the forward and left (and their negated backward and right) vectors to coordinate character movement with the camera's perspective.
PROBLEM: Player and camera movement required repeated button-tapping.
SOLUTION: Rather than handle movement in the key callbacks, we set booleans for whether each key was pressed in the callbacks and handled the player movement in the update loop.
PROBLEM: Player movement started and stopped abruptly.
SOLUTION: Made key presses add force to the player via the physics engine, rather than affect position directly.