My initial idea involved navigating a 4-dimensional world (4 spatial dimensions), inspired by Flatland and Portal. The new gameplay mechanic isn't shooting portals, but rather shifting your position Ana/Kata (the positive/negative 4D directions). I didn't plan on any shooting or NPC interaction, instead focusing on navigation-based puzzles, perhaps with collectible objects of some sort to motivate complete exploration. I spent a fair bit of time thinking about how one could display spatial information for such an environment, and I'm really excited to pursue this idea at some point in the future (when I have greater time and greater skills). For this competition, I decided to scale things back a bit.
), and I've done small combinations of these demos, but to make an FPS game I'd have to combine a lot of these effects. And so it began. To set the scene: the player stands on a barren landscape; in the distance is an otherworldly maze-like structure with a central chamber within it. There is an object in the chamber that the player decides to reach.
Challenges and Resolutions
1. Generating a World Map.
I decided to use a voxel-based layout for simplicity; then the world map data could be stored as arrays of numbers. Simple enough, but for an "interestingly large" world containing 1,000 to 10,000 cubes, the renderer was working at about 1 frame per second. By merging geometry data, I could create a single mesh for each of the material types, which increased the rendering speed dramatically, but negatively impacted collision detection calculation time (discussed below). Also, I replaced the cube geometries used for window-style objects (which includes most of the external walls) by plane geometries, leading to a small performance gain and also a small complexity increase -- I had to also include information about whether each planar wall was east/west, north/south, or up/down.
2. The Goal.
I wanted the goal area to be visible from the beginning, and to be visually interesting enough that the player wants to reach it and will explore the area to do so. Thus I wanted to make a lot of the walls to be translucent, so that a large amount of the structure's layout is visible to the player, encouraging them that there is a straightforward path to the goal.
I imported a star-shaped model created in Blender and set it to spinning to catch the player's eye. If I had more time, I would have (read as: "When I continue working on this project at a later date, I will...") added some bright particle effects (e.g. sparks shooting off the star) and perhaps some sunbeams (volumetric lighting, a.k.a. "god rays") shining down on the object from very far above.
3. Interesting Tiles.
I decided to create "force-field" walls -- mostly transparent but with an animated texture that resembled glowing electric charges. This lent itself nicely to the feel I was going for. Unfortunately, looking at translucent objects through other translucent objects creates problems for the renderer, which you can see if you look closely enough... so don't :)
4. Collision Detection.
I stored references to all "solid" meshes in an array. First, I used bounding spheres to determine which cubes to perform detailed collision detection on. This worked well originally when there were a lot of small individual cubes, but after mergining the geometric information to increase rendering speed, this calculation became useless and was removed. For the detailed collision detection, Three.js includes ray collision checking against the faces of a mesh, so I typically create a ray from the center of the player mesh to each vertex and use these to check for collision. It's not terribly accurate, but it works. With the increased complexity of the meshes after merging the geometric data from the cubes, this algorithm becomes really inefficient as we check for intersections between rays and mesh faces that are very far away. There has to be a better way, but that's a problem for another day. And finally on this topic, originally I was going to use a cylinder-shaped mesh for collision detection. (Sometimes a cube can get "caught" on one of its corners when rotating near a wall), but unfortunately the calculations using a bounding cylinder were too slow (too many vertices!), and so I had to revert to using a bounding cube.
I used very simple velocity/gravity mathematics to handle this, but for lower frame rates (even using the delta time between frames!)
the maximum height of the jump was less. (I'm sure this can be explained somehow as the errors inherent in a discrete approximation of a nonlinear continuous process.) Thus I increased the initial velocity when jumping, so that the player can jump over a single block even when the game runs at 15 frames per second. As a reault, the jump may seem overpowered for those of you with 60 frames per second rendering speed.
6. Game Over.
What happens when the player reaches the goal? In the 7DFPS version, nothing (read as: "your satisfaction must come from within"). I have an idea, however, for an interesting twist which I will incorporate into a polished future version of this game.
This was a lot of fun, I learned a few new things along the way (like pointer lock controls), and I'm inspired to keep working on this program even after the competition is finished. What more could you ask for?
Try it out online: 3JS FPS