In 2016, a team of Computer Science students including myself, Ian Lim, Ben Wagner and Jerry Quintero created a multiplayer Virtual Reality game for our CSIT Senior Seminar class at the University of Nebraska at Kearney. If you’re unfamiliar with the device, the HTC Vive is a set of hardware for playing games in Virtual Reality that includes a Head-Mounted Display (HMD), two controllers and two sensors. This setup allows the player to walk around and interact with the in-game environment. If you haven't seen a Vive before, check out this video to gain a better understanding:
As one might imagine, Virtual Reality hardware gave us a large assortment of options for our project and allowed us to make an immersive and exhilarating experience that (I hope) was memorable for those that played it.
Narrowing Down Concepts
As with any new project, the first step is to develop an idea. Since Virtual Reality is such a versatile tool, finding an idea we could all get behind was crutial. We narrowed down our options to a small list of potential games:
This game would allow the player to step in the shoes of a Private Investigator. The player would investigate crime scenes and look for clues. They’d use their controllers to operate various investigation tools (magnifying glass, finger print duster, black-light, etc.) in the pursuit of a criminal.
For this game, one player would use the Vive, while up to four other players would use regular controllers and a monitor to play. This would be considered an Asymmetric Multiplayer game since the role the Vive user would have in the game is drastically different from the role of the traditional players. The game would be some sort of party game, pitting the players against each other.
This game would focus on scares. Virtual Reality is an incredible medium known for its immersion, so any horror game on the platform would automatically become more terrifying. This game would be a bit more linear, providing less gameplay and more scripted events, giving a “Halloween Haunted House” vibe.
After narrowing down our options, we decided to go with the Multiplayer Party Game concept. Once we had chosen our basic concept, we needed to flesh out the idea. By figuring out the exact mechanics and components of the game, we determined what parts needed to be created and in what order.
Our model for the game was to pit the Vive player against the traditional players in a game of survival. The game will take place inside a labyrinth filled with treasure. The traditional players would be searching the maze for treasure, while the Vive player would play as some sort of monster that defends the labyrinth from intruders. As the traditional players hunt for treasure, the Vive player would be hunting the traditional players. When the Vive player catches them, they’ll be eliminated from the game.
As for the traditional players, each treasure they get will count as a point for them. To make things more complex, the traditional players will be competing with each other to gain the most treasure. At any point in the game, the traditional players can exit the maze. When they do, they keep whatever points they’ve accumulated for final scoring. This means that if one player was to exit after getting 2 points, another player could stay behind to try and get a total of 3 points before exiting. If, however, a player was to be caught by the monster, they’d lose all their points and would be unable to win. With all of this laid out, we knew what parts of the game first required our attention.
My first individual task for development was to code the movement of the Vive player. While coding movement and controls for a player in a traditional, non-VR game may seem straight-forward, doing so for a VR game is a bit more complex. Regular video game controllers have been around for a long time. As games have continued to evolve over the last quarter-century or more, standards have been found for controls and movement. For example, if a person familiar with playing video games were to pick up a controller to play a traditional, non-VR game they’ve never tried before, they can safely assume that the left thumbstick on the controller will move their player around.
But because VR is still so new, no standards or status quos have been set yet for controlling a character in a VR environment. This being the case, I had to develop a controller scheme that allows new players to easily navigate the environment. Since the maze they’ll play in will be larger than any reasonable play-space set aside for the Vive, the player will not be able to simply walk through the maze. To give them the ability to navigate the full maze, I’ve implemented a script that allows the player to move in the direction they point with their controller. When a trigger is pulled on one of the Vive controllers, the script finds the vector between the player’s head and the controller and then moves the player in the direction of that vector. When the controller is farther away, the player will move faster. When it is closer, they move slower. This allows the Vive player to have fine-tuned control over their movement, giving them the ability to use stealth and finesse.
With movement taken care of, it’s now time to work on the other controls of the Vive. Originally, I planned to add collision handling. This would make sure that the Vive player would not be able to walk through walls. After brainstorming, we decided to sidestep this problem by making the Vive player a ghost in the maze. As a ghost, the Vive player will be able to walk through walls to aid in catching the traditional players. This also allows the Vive player to hide in walls to jump out and catch unsuspecting victims by surprise.
With that obstacle out of the way, it was time to give the ghost the ability to grab players. To do this, I added two spheres to the game attached to each of the Vive player’s hands. These spheres will act as triggers that, on colliding with a traditional player, will check if the pad-button is pressed. If the button is pressed and the sphere is colliding with the player, then the player will be eliminated from the game. These spheres will act as place holders until I import Ben’s 3D hand models.
Trouble With Shaders
While toying with our current game idea and exploring other option for the game, we decided it would be a cool feature to make the ghost invisible to the traditional, couch-sitting players. After deciding our original idea was perhaps too complex, we also changed the game concept so that the couch players are now on a team and work together to survive, rather than compete to look for treasure. They would also be given guns with one bullet each to defend themselves from the ghost. In order to see the ghost, the couch-players will each be given a flashlight that will allow them to see the ghost only when it is inside the cone of light. To produce such an effect, I realized this meant I had to learn as much as I could about shaders.
Shaders are like graphical scripts written in CG/HLSL. These are used to calculate the exact look of each vertex along a material. Shaders can be used not only to affect the color of reflected light, but also affect the perceived position of each vertex. When a material is applied to an object in Unity and told to use the written shader, the object’s visuals will follow the rules set by the CG/HLSL code. Unfortunately, CG/HLSL functions quite a bit differently than regular programming languages, meaning there’s a bit of a learning curve. The language does not have the large amount of functions available to most programming languages, so creating my own shader required a high understanding of mathmatics and 3-dimensional geometrics. For better or worse, this task was a bit outside my comfort zone. After studying up on shaders for hours, I hit a road block. Apparently the task that I was trying to accomplish with the shader was more advanced than a normal, beginner-level problem. I could tell this was going to take some time.
The Joy of Music
To take a break and step back from my shader problems, I decided to work on the music for the game. After messing around in GarageBand for a while (an admittedly guilty pleasure of mine), I created a simple, song for the game. With the song, I hoped to capture the intensity that the game will hopefully invoke while still giving it an “arcade-like” sound. In all honesty, it sounded like any royalty-free techno song you could find online, but it was nice taking a break from the gritty math of the project to focus on something more “right-brained”.
After continually searching for similar problems to my invisible ghost shader, I eventually was able to find someone who made a shader that allowed objects to become visible when they were close to a target object. After following along with his work, I was able to create a similar effect, except for my shader, I used a slew of complicated equations to determine if the ghost was inside a triangle set by three distinct points. At the end of a near all-nighter, I had (much to my surprise) achieved the desired outcome. I had created a shader that allowed the vertices of the ghost’s material to only render if they are within the bounds of the flashlight’s cone of light. This shader does not take into account the y-coordinate (height) of the ghost or light source. Instead, it only evaluates the x and z-coordinates since the couch players will be looking at the scene from top-down.
So, all of my individual components were complete. The only thing left was to see how the game worked with everybody’s pieces put together in one build. This is an important aspect of game development when working with a team. Miscommunications and assumptions can at times cause disconnects between the components of the end product. That’s why it's important to communicate frequently and openly with teammates to minimize any problems in compatibility between aspects of the game down the road.
After putting everything together, we found a few things that needed tweaking. The biggest tweak that I had to make was to my shader. Unfortunately, it proved much harder than anticipated to make the shader work with more than one flashlight area. Moreover, the shader had to scale it’s operations with the amount of players currently in-game. If there were to be only two traditional players and one Vive player playing, for instance, the shader would break because it would look for the two triangles of the missing players as well and throw an exception. This also meant that the shader had to tell when a player has been eliminated so that it did not search for a flashlight cone that did not exist. After some major changes to the code, I got it working with multiple players.
One bug I had ran into during this was that after the ghost eliminated a player, the player’s flashlight cone would sometimes still remain, making the ghost still visible for no logical reason. To fix this, I changed the code so that when the ghost player grabs a couch player, the couch player is teleported way outside the map before being destroyed, thereby avoiding any visual glitches all-together. While this was admittedly a hacky fix, it did the job.
In the end, I’m incredibly satisfied with how the project turned out! Making a fully functioning VR game that could support 2 - 5 players was an incredibly rewarding experience. Not only did it work, but I was surprised it was actually a really fun game to gather around with your friends to play. Admittedly, there were some kinks and bugs, but overall, it was playable and entertaining. If you've ever been interested in developing games, I'd highly encourage you to look into Unity. It's a great tool for anyone wishing to try their hand at development.
Click here if you'd like to see my original blog post on the project, or read any of my teammates' posts.