Pepperoni Slayer
This was the final project for my high school AP Computer Science class.
You can see a short
youtube video about it here
TLDR
It's another 3d engine, but using more naive approaches. These approaches
are actually slightly more intuitive to understand though so we might get
into some details about it.
It also has a pretty fun wave survival game built around it. I'm quite
proud of how actually playable it is.
The engine
Pepperoni slayer can draw quadrilaterals in any orientation in theory,
however they are limited to cardinal directions for collision purposes
(oh boy is the collision bad, I forgot about that).
Rendering works on a very simple principle. An object takes up a certain
portion of your fov, which we can calculate using inverse trigonometric
functions.
Projecting 1 point
For simplicity's sake, let's first imagine that the camera only every
faces in one direction and that we are in 2 dimensions.
When we are projecting, we are trying to find out where this point should
sit on our screen. The way this algorithm works is that the point is some
angle theta away from the "looking axis" of the camera.
We then have to simply find the ratio of this angle to the angle of the
field of view.
This angle should then be multiplied by the number of pixels that makes
up HALF of that axis's length on the screen. This is how far away the
point should be drawn on that axis from the center of the screen.
We then just do this for the y axis as well. And voila! We have projected
from 3d to 2d.
Rotating the camera (yaw) is simply done by rotating the world by the
negative of the cameras yaw, projecting, and then rotating back.
For some reason I couldn't figure out pitch. I think it was a problem
with the projection. So I ended up doing a pseudo pitching algorithm
which ended up looking very wonky at extreme angles, so the player's
pitch is limited to like 45 degrees in the actual game.
Problems encountered
Points behind the screen
Points behind the screen get mangled as per usual with projection.
I didn't think of a clever solution like I did in demise, instead,
I developed a sub optimal solution and moved on, like a true engineer.
I figured I could get around this by simply not projecting points that
were behind the camera, however this led to problems with quadrilaterals
which were both in front of and behind the camera.
My (very crappy) solution was to split a quad up into many points
(around 40 if I recall correctly), and project each of those
individually. Then points behind the camera could be thrown out and
there would still be a large amount of points in the wall to be
drawn on the screen.
Rendering Order
This was one of the hardest problems I tried to solve with this project.
If you read the demise section, you learned all about the BSP which is
a fancy way to render objects in the correct order on screen. There was
no such thing for this project.
I thought this would be pretty easy. How do you tell which object should
be drawn on top of the others on screen? Easy - it's just the one that
is closest.
While this is a simple solution, it hides a much more complex beast,
determining which object is closest.
Pretty much the way that I did this was to choose a point in the center
of the quad to be drawn, and use that to determine the distance of the
entire quad to the camera.
So I took all the quads, threw them into a big ArrayList, then sorted it
every frame.
This slowed the machine down considerably to say the least. And it didn't even work properly most of the time!
I didn't ever solve it not working properly, but I did fix the speed
somewhat.
Since this was my first CS class, I remembered a bubble sorting algorithm
that we learned. I knew that it was slow, but what if instead of doing
an entire bubble sort every frame, we just did 1 iteration of it?
By just doing one iteration of the bubble sort per frame, we reduce
complexity to O(n), while sacrificing accuracy.
It gets sorted eventually, not quickly.
Collision
I had to think of a way for the player to collide with a wall in 3d.
Pretty much, the way it works in this game is if the player is a
certain distance away from the wall in one axis, it reverts the
player's speed in that direction.
This works well enough, but what if a player enters a wall head on.
All of a sudden our current model will trap the player inside the wall.
Since they are within that distance of the wall on it's normal axis,
it will reject their speed in the direction of that normal axis. This
means the player will be moving in the direction of the wall until
they leave its hitbox.
One solution to this is to add rejection to the player when they run
into the "corner" of the wall. Pepperoni slayer goes about this
by basically creating a cylindrical hitbox at the corners.
So the hitbox ends up being like this.
In retrospect this is not a terrible solution, but it could definitely
be better.
Reflection
This project is super cool. I still play it from time to time just
to see if my skills are up to par. The projection algorithm was naive,
but completely original, which is definitely okay. It helped me a lot
in understanding more complicated algorithms in the future.
I'd say not bad for a first big project.