Apocalypse
By Emma Spindler, Rohan Gholkar, Parker Patterson
Apocalypse is a game about shooting zombies with a laser. Our game is similar to the classic arcade game, Asteroid, but with a modern twist. Instead of flying through space and blasting asteroids, in Apocalypse, the user takes control of a motile, robotic laser turret and tries to shoot their way through the hordes of oncoming zombies.
Apocalypse is a game about shooting zombies with a laser. Our game is similar to the classic arcade game, Asteroid, but with a modern twist. Instead of flying through space and blasting asteroids, in Apocalypse, the user takes control of a motile, robotic laser turret and tries to shoot their way through the hordes of oncoming zombies. Unlike asteroids, these zombies have a taste for (machine) blood, so they actively seek out their target. In this sense, the enemies in our game are more similar to the zombies of Call of Duty’s famous zombie mode than they are to the lifeless rocks in Asteroid.
Our game incorporates several of the topics covered in Dr. Guy’s 5611 course: our laserbeam is implemented using a basic particle system; enemies move through the simulation according to flocking principles; firing the turret involves inverse kinematics; orienting laserbeam particles involves planning rotation; and, finally, we used squash and stretch to enhance the liveliness of enemies’ death animation. We would have liked to incorporate even more 5611 topics such as sound, 3D rendering, and improved animations for the user’s in-game agent, but, due to the condensed timeframe of the course, we did not have time to implement these features.
Among the 5611 topics we incorporated into Apocalypse, there are a few “key” algorithms that make our code function, including Eulerian integration, inverse kinematics, collision detection, planning rotation, and an algorithm for visually-pleasing squashing animations. With the exception of Eulerian integration and inverse kinematics, all algorithms used were designed by team members specifically for this project.
Eulerian integration is used to achieve realistic motion for the enemies as well as the user. Inverse kinematics algorithms were necessary to implement user-directed laser fire. A naive approach to collision detection was used initially, wherein the positions of the edges of the shapes in question were checked against one another. Later on, we settled on a better collision-detection algorithm involving our Vec2 library function distanceTo(). Our squashing algorithm modifies the positions of the enemy’s vertices exponentially, killing the enemy once it has been squashed flat.
Although a longer project timeframe would have allowed us to manifest a bit more creativity in our work, we are pleased with Apocalypse. Some things worked well and some things did not, but, ultimately, our group’s strong communication allowed us to overcome obstacles and arrive at satisfactory solutions. We consider the project to be a true team effort, but we also wanted to include this list of some of the individual contributions we made that we’re particularly proud of:
Rohan Gholkar
Dynamic laserbeam orientation
Planning rotation
Laser and cannon code
Inverse kinematics
Laserbeam visual design
User input handling
Emma Spindler
Enhanced visuals
Modified enemy flocking behavior
User death
Game over screen
Simulation reset
Parker Patterson
Enemy and Flock class framework
Enemy health and death system
Squash animation code
Game over screen
Simulation reset
One of the greatest barriers we faced in the early stages of development was that portions of our code had been developed independently and, in one portion, object-oriented paradigms were used, while, in the other portion, a more C-like programming style was used. These differences proved troublesome, as different members of our group had differing levels of fluency with object-oriented programming practices. Ultimately, we all agreed that the programming paradigm we used was less important than the fact that the simulation worked, so, rather than attempting to unify our programming paradigm, we kept it as-is. The intermingling of C-like and OO code lead to some confusion at times, but we all have a deeper understanding of the Java/Processing language as a result.
Many of our initial approaches to problems throughout the development of Apocalypse proved to be ineffectual and were later replaced with more sophisticated solutions. For example, initially, our cannon would fire a continuous stream of particles as long as the user held down on the mouse button. There were many things we liked about this approach, both aesthetically and practically, but, in the end, we decided to limit the number of shots to one per click. This decision was made to allow for more detailed rendering of the laserbeam particles as well as to simplify and speed up the simulation overall.
At one point during the development of the laserbeam particles, we tried to use Processing’s delay() function call to limit the number of laser particles on-screen, however, we were unable to find a satisfying way to use delay(), since it would pause the entire simulation whenever it was called. Finding a way to achieve delay in a simulation like ours was an interesting problem, and, ultimately we decided to sidestep it rather than address it directly, largely due to time constraints.
Another approach that was eventually scrapped was the use of traditional flocking behavior for enemy movement. Initially, our idea was that the enemies would be flying around the screen in swarms, irrespective of the user’s position. However, as we were implementing flocking behavior, we realized that we could modify the flocking code so that the enemies would be drawn to the user. After seeing the terrifying, slow crawl of the enemies (squares, at the time) toward the user’s position, we were inspired to change the rendering of the enemies from plain quads to zombies.
We’re quite proud of some of our solutions to the design challenges we encountered. We used inverse kinematics to inform the simulation as to the trajectory of new laserbeam particles. First, two vectors were created: one from the user position to the mouse position and another from the user position to the end of the cannon. By taking the inverse cosine of the dot product of the two vectors, we determined the angle the cannon needed to rotate in order to align with the mouse. The laser particles had their positions initialized at the end of the cannon and had a constant velocity that was parallel to the cannon. In order to give the laser particle a more laser-like appearance, we decided to make rectangles rather than circles. However, an issue came up regarding the orientation of rectangles. All of the rectangles had the same orientation despite their differing velocities. This was solved by using simple trigonometry to find the angle of each laser particle’s velocity. The rectangle was then rotated by this angle so its long edge is parallel to its velocity.
One strategy that ultimately worked in our favor was to give the enemy and flock classes their own update and draw methods. Once we all got on the same page about OOP, having the code organized this way was a real timesaver.
We were very happy with the way that we were able to modify flocking behavior to give our zombies their zombie-like movement. Initially, it was not our intention to draw the enemies toward the user, but once we realized how easy it was to implement this behavior using our existing flocking code, we decided to add it to the simulation, and it became an integral part of the game.
We hoped you’ve enjoyed learning a bit about our design process. It certainly was a satisfying way to test the new knowledge we gained in CSci 5611. We finish our report with a few comments about the computational bottlenecks in our simulation and a discussion of the limits to scaling our project up.
One of the most computation-heavy portions of our code is the setup function. The main slowdowns in this section are loading the images we used, and, perhaps more significantly, when the Flock’s constructor is called, we use a doubly nested loop to ensure that new enemies are not spawning on top of other enemies. We were unable to think of a more efficient way to perform this check.
There are some potentially computationally heavy function calls related to trigonometry and algebra, but we did our best to minimize the use of such functions, so, overall, they shouldn’t present much of a barrier to scaling the project up.
One theoretical bottleneck in the code is our collision detection. To check whether or not enemies have been hit by the laser, we iterate over every laser particle in the simulation for every enemy, each timestep. This part of the simulation runs in O(m * n), where m is the number of enemies, and n is the number of laser particles onscreen. Although we mitigated this bottleneck somewhat by reducing the number of particles on screen at a given time, we did not find a solution to the underlying problem of having to iterate through n particles for all m enemies.
UMN CSCi 5611 with Dr. Guy
Processing Flocking Example: https://processing.org/examples/flocking.html
Flocking tutorial: https://www.youtube.com/watch?v=dQw4w9WgXcQ
Vec2 Library (included in Code section)
my brain, my brain, and my brain
Referenced Joints.pde for inverse kinematics
Zombie: https://www.kissclipart.com/zombie-cartoon-clipart-zombie-cartoon-drawing-oe9bsi/
Picture on the top of the site: https://www.aquiziam.com/is-a-zombie-apocalypse-really-possible/
Special thanks to Chris Kauffman