Projects – Wrong Cave! | William WuSkip to main content
Home / Projects / Wrong Cave!

Wrong Cave!

Wide aerial view of hero vs. boss

Introduction

In Spring 2024, I took the course CSE 125 (Software System Design and Implementation) with Professor Geoff Voelker. This course centers on a single quarter-long team project: the creation of a distributed real-time 3D multiplayer game in just 10 weeks, culminating in a live demonstration for an audience of department faculty, course alumni, other students, and the public.

CSE 125 course logo

As CSE 125 is offered only once a year and space is limited to about 30 students, the application process prioritizes demonstrated commitment and problem-solving ability. (See the course overview for more details.) Thus, I'm honored to have been a part of the course and I consider it to be one of my most significant experiences at UC San Diego.

Students in the course were separated into five groups. Each group had six developers and one artist who worked together on their own unique game from concept to implementation. As shown on the 2024 groups page, every game ended up turning into something distinctive and interesting:

  • Asymmetric party crafting game (my team's!)
  • Rhythm-based Soulslike boss fight
  • Asymmetric maze dungeon crawler
  • Exploration/collection/stealth game
  • Character ability-based capture-the-flag/domination shooter
Collage of screenshots from each of the five games

The groups page contains links to each group's website and live demo recording. From the thumbnails alone, it's clear that the only limit was our imagination!

My team

I was fortunate to have the following amazing individuals on my team:

all of whom were supremely talented and a pleasure to work with. Please visit our group page to see our weekly reports, screenshots, demo video, and live server (plus a showcase of Sean's outstanding web dev skills).

Our game

Wrong Cave! title art

Wrong Cave! is a two-stage asymmetric couch co-op crafting game in which a team of heroes tries to escape a cave and fight off the Mushroom King who lives there. In the first stage, the heroes frantically craft weapons and armor using resources collected from around the cave, while a small mushroom minion (controlled by another player) sneaks around and sabotages their efforts.

Two hero players gathering resourcesTwo hero players gathering resources far away

In the second stage, the king breaks free (controlled by whoever played the minion) and tries to destroy the heroes or the minecart they need to escape; the heroes must protect themselves and the minecart using the weapons they have acquired.

Hero perspective of the mushroom king emerging from its enclosureMushroom king standing in front of a minecart labelled "Heroes' escape"

The game runs in web browsers, much like the many .io browser games in recent years, so there is no need to install anything else to play it. A live instance was supposed to run on the CSE 125 server, but it currently seems to be shut down.

We aimed to make the hero experience feel frantic and chaotic—hero players must keep track of several different crafting recipes, throw and chase items around the cave, and try to avoid the minion's traps. On the other hand, the boss experience is one of being sneaky, powerful, and in control.

Some of our chief inspirations for the game concept and major design elements included:

  • Overcooked for its gameplay mechanics and chaotic feel (hence our working title of Undercooked). Our game could perhaps be summarized as "Overcooked with a boss fight."
  • Among Us for its asymmetric roles and sabotage mechanics. Though it wasn't a specific inspiration, our game's aesthetic also features goofy, cartoonish visuals.
  • Games in the Soulslike genre for their large, powerful bosses and highly difficult combat. This became less important as our ideas evolved and we focused more on the crafting aspect of the game (as well as its goofiness), but its influence is still present in the design of our boss character.
  • Web games like Krunker.io for their simple interface/HUD style and lobby system.

Tools and technologies

  • TypeScript: Primary source code language.
    • Our choice of making a web game meant JavaScript and TypeScript were our best options. For large projects like this one, the static type checking that TypeScript provides is crucial for catching mistakes.
    • Most groups over the years have used the classic combination of C++ and OpenGL. I believe one group so far has used Rust.
  • cannon-es: Physics engine.
    • Using this library gave us rigid body motion basically for free, but it came with many caveats that required workarounds. For example, the engine only checks for collisions at an object's current location each tick, not along the line segment from its previous to its current location.
    • I think that ultimately, we still saved time by using the library instead of rolling our own physics, but it would have been a cool experience to write a custom physics engine too.
  • WebGL API: For rendering 3D graphics in the browser.
    • Our earliest decision as a team was to use WebGL so that our game could be played in browsers on any platform, and having this chance to learn about WebGL was the main reason I joined this group. Fortunately, I and several others already had experience with OpenGL, which is very similar to WebGL—the latter just has a few more limitations.
    • We found the WebGL2 Fundamentals website to be pretty helpful. It has tutorials that walk through a large number of topics with plenty of examples.
  • glMatrix: Vector and matrix math utility library.
    • Another downside of using cannon-es was that it came with its own implementations of vectors, matrices, and quaternions which were not type-compatible with glMatrix. Since glMatrix offered many operations (particularly ones relevant to graphics) that cannon-es didn't, we often had to convert between the two types which led to some inconsistencies.
  • Express and ws: Network communication via the WebSocket API.
    • In general, large multiplayer games often use custom protocols based on UDP connections to reduce bandwidth usage. Because we use a set of locally connected machines with high bandwidth and low latency for the final demo in this course, students can just use TCP connections and less compressed/optimized data structures for transmitting game states.
    • For our group in particular, since we were making a web game, UDP wasn't an option to begin with (browsers only allow TCP). We also got a lot of functionality basically for free from JS/TS—for example, JSON data marshalling is built into the language.
  • Other APIs: Web Audio API for sounds, HTML/CSS for user interface.
    • As with some other parts of the stack, we got these for free because all modern browsers provide them.
  • Development tools: npm for package management, esbuild for bundling (very fast), ESLint for code linting, Prettier for code formatting, and of course Git for version control.
    • The linting was pretty minimal because some members of the team didn't like using a linter. I basically learned JS/TS with the recommended ruleset so don't find it obtrusive, but we ended up turning off most rules to save time for others who weren't accustomed.
  • Blender: 3D modeling and texturing.
    • We exported our models as glTF files to make them easy to process—this format is designed to work well with WebGL and OpenGL.

Our journey

For a more in-depth chronicle of our game development journey, please see our project specification, weekly reports, and final review on our group page. I'll summarize our weekly progress here:

  • Week 1: We held a long meeting to nail down some game design elements.

    • The core gameplay loop remained basically the same for the rest of the quarter: three players craft weapons on a timer while a fourth player sabotages them, then the fourth becomes a boss and they battle each other.
    • We knew from the beginning that we wanted to use WebGL so the game could be played in browsers, even if this meant sacrificing some performance.
    • We left several elements (aesthetics, story, certain mechanics such as exactly how crafting would work, etc.) intentionally undecided, to return to them later; we needed to investigate things like JavaScript physics libraries and the performance implications of dynamic lighting first.
    Long whiteboard covered with multicolored notes written in marker
    Notes from our meeting.
  • Week 2: Concept art; basic physics simulation on the server; camera set up and rendering physics objects and static textured meshes on the client.

    Eight humanoid characters drawn in a cartoony style, each with a unique color and facial expressionDetailed fish model and textured cubes on a black background with white dots

    Character concept art and a fish model that we used for graphics testing.

  • Week 3: More concept art; discussion of server code architecture/logic organization; exploration of various lighting and shading models (point lights with shadows, cel shading).

    Textured cubes with physically incorrect shadows surrounded by white wireframes on a black background with white dots

    Wireframes showing the shapes of physics objects being simulated by the server.

  • Week 4: Processing player inputs and translating to movement; shader pipeline to allow postprocessing steps; first-person camera and freecam mode; placeholder models; basic particle system; team social.

    • This was the first key milestone of the project: a client that sends inputs to a server, which responds with an updated game state that the client renders.
    Placeholder map model in white, rotated incorrectly against a black background with small miscellaneous objects in frontPlaceholder map model lit by orange and green colored lights that cast shadows against it, on a black background; a white cylinder wireframe covers some of the image

    Placeholder map model (it was rotated incorrectly for a while) and shadow casting + first-person testing.

  • Week 5: Collision detection through the physics engine; 2D outline and cel shaders; the first of many object models.

    Placeholder map model displayed with red outlines, partly obscured by a shower of red particlesScreenshot of the first player model, colored blue, in the Blender modeling software

    Outline shader (drawing in red for visual contrast) and the first player model being created in Blender.

  • Week 6: Picking up items; parsing triangle mesh collider from object file (used for generating the map's collision mesh).

    Testing the map collision mesh on Sean's YouTube channel.

  • Week 7: Support for multiple players; throwing items; recipe crafting system; revamped player movement system; revamped map collider parsing (using boxes instead of a triangle mesh because the latter didn't work very well); boss minion character; lots of new models.

    • Our team morale was the lowest around this point in the quarter. It seemed like every week, we discovered more new tasks than we completed old ones, and the game was still more of a sandbox engine than a game where each player has a goal and a way to win or lose.
    • I think that early on, we didn't know enough about the tools and game elements yet to properly plan out our requirements, so having this experience seems helpful for avoiding that kind of situation in the future.
    Multiple players stuck on a rampBlue player model smoothly lit from below

    Multiple players stuck on a ramp due to a physics bug, smooth shading instead of two-tone shading, and throwing items.

  • Week 8: Sound effects; crafting stations and resource spawners; boss sabotage mechanics (blinding and setting traps); lighting fixes; revamped particle system; first playtests with most of the team.

    • Half of the team got sick during this week, sadly. But we pulled through!
    • As every game developer knows, playtests were hugely helpful for identifying high-priority issues that intruded on the player experience. We continued playtesting and tuning often in the run-up to the demo.
    Room with large number of items and orange light in the backgroundScreenshot from a playtest with multiple playersScreenshot from a playtest with multiple playersScreenshot from a playtest with multiple players

    Stress testing with a large number of items and three screenshots from a playtest with multiple players.

  • Week 9: Game stages and win conditions; combat system; in-game text rendering; user interface; final game map; even more models; simple animation system; more sound effects.

    • At this point, there was a way to win, so we finally had a true game.
    • The game itself had also become a platform—all our work on physics, graphics, networking, controls, game objects, etc. created foundational infrastructure on which we could quickly build and iterate new features and interactions.
    • For example, we thought that a combat system would take multiple weeks to build, so we were planning to first implement a simpler (from the players' perspective) version which was essentially a clicking contest between heroes and boss. However, we really wanted to push for real in-game combat, and it actually turned out to be relatively easy to achieve thanks to the aforementioned platform. That is, instead of needing to build it from the ground up, we could just add on to existing systems like item interaction.
    Red text floating in space for testingPiles of chairs, knives, and iron oreNew map model consisting of rocky gray walls and floor, with wooden beams supporting some entrances; the center is lit by an orange light

    Testing in-game text, piles of chairs and other items caused by a bug with restarting the game, and the glorious new map model.

  • Week 10: Big boss character; full two-stage game sequence; polished UI, lobby, and lighting; ability to correctly restart the game; in-game crafting recipe guides; particles, camera shake, and other "juicy" effects; many small features, fixes, optimizations, and quality-of-life changes; final demo!

    • As tends to happen in these situations, the majority of this work occurred in the 24 hours before the demo (nearly 200 of our ~700 commits!). We didn't even procrastinate, having made pretty steady progress all quarter, but there was just that much left to do.
    • The final demo went smoothly and we got to show off most of the interesting mechanics we created, as well as some of our debugging features. And of course, one or two known bugs that remained. It was a great finale to a great quarter.
    User interface: Four players stand in center of screen; "Game Logo" is displayed prominently above them; a list of players and color selector is shown in panel on right side; a list of settings sliders is shown in panel on left sideBlue player standing in front of a hole lit by green light while holding iron ore; a workbench stands to the left in the corner of the chamberFirst-person view of the player holding a pickaxe while standing in front of an iron ore deposit

    Gameplay images from one of the final versions before the demo. We chose to leave the "Game Logo" text in place.

Demo day

Below are some images and GIFs taken from our demo livestream, which is also available on YouTube: full stream (our segment begins at 22:00 following Dr. Voelker's introductory remarks) and edited playlist.

Gameplay being projected onto large auditorium screen while players sit at computers onstage with their backs to the audienceCloseup of players onstage during the demo with their backs to the audienceSide angle view of players talking and smiling onstage while the audience claps in the background

Views of the stage from our demo livestream. Each group did two runs of their game, inviting two people from the audience to play.

First-person gameplay screenshot of the Mushroom King attacking the player, who is holding a bow, while a green hero stands to the leftFirst-person gameplay screenshot of the minion being slapped by a blue hero, with a red vignette laid over the screenGameplay screenshot of an aerial view of blue, green, and yellow heroes running through the cave
Gameplay screenshots from our demo.
Gameplay recording from hero perspective: Hero picks up wood and throws it into furnace; furnace ejects iron ingot, which bounces away and falls down a hole; hero follows down the hole but the ingot is picked up by the minionGameplay recording from hero perspective: Hero throws wood onto anvil, which ejects a sword; hero picks up the sword and walks awayGameplay recording from hero perspective: Hero glimpses the Mushroom King in a large room, then jumps over a hole and throws string onto a workbench; workbench ejects a bow; hero picks up the bow and begins launching donuts at the Mushroom KingPlayers talk and laugh, sitting at computers on stage, while audience claps in the background

Segments from the first run of our demo, where we showed the hero perspective.

Gameplay recording from minion perspective: Minion watches heroes throw items at a workbench, which ejects a bow; minion and heroes chase after the bow and a blue hero picks it up, then throws it further away; minion follows the bow and picks it up, then runs away with itGameplay recording from minion perspective: Minion follows a yellow hero up a slope and runs through a hallway; a blue hero follows the minion into a room and slaps it, causing it to freeze in placeGameplay recording from minion perspective: Minion watches a blue hero throw a bow through a doorway; a colorful bow is thrown back outGameplay recording from king perspective: The king enters a large chamber where several heroes are waiting; the king repeatedly launches mushrooms at the heroes, one of whom is holding a bow and is launching donuts back; a third hero enters behind the king holding a sword and begins attacking the king

Segments from the second run of our demo, where we showed the boss perspective.

Takeaways

  • The importance of task tracking: We mainly tracked our work informally by discussing what we had done/were going to do at our weekly meetings. I think we all wanted to focus on getting things done with minimal overhead, especially with no official "person in charge." But on several occasions, we accidentally did duplicate work or forgot about an important task. Some other groups used GitHub Issues and I also use them a lot personally, so such a system could have helped us save some time.
    • Incidentally, we also didn't use Git branches much. Early on this made sense because we were working on low-level building blocks of the project that couldn't easily be separated. Later, though, it possibly might have helped make things cleaner.
  • The importance of being present: I made an effort to show up to every meeting in-person that I could. But I don't mean to call out anyone who missed a couple meetings; this point is more about the effort part, and I think all of my team members similarly tried their best to make ourselves a cohesive unit. That meant being responsive to messages, contributing ideas, asking questions, and listening to feedback, because we all were passionate about the project and had the same goal. And the best parts of the quarter to me were the times when we were together as a team, whether hammering out some new code at a meeting or getting food at the local mall.
  • The importance of making well-reasoned product decisions: From the start of the project, our discussions often revolved around the experience we wanted to create for players and ways to achieve that experience. (The Art of Game Design by Jesse Schell describes this kind of approach.) We generated many, many different ideas over the course of 10 weeks, but we always made sure to examine them through that lens (and some others, such as "is it feasible to implement?" or "will it impact performance?"). I think this approach helped make our game feel more cohesive, as everything in it was made intentionally with a higher purpose in mind.
  • The importance of cleaning up after yourself: WebGL and OpenGL have a lot of different types of buffers and bindings. One common and hard-to-debug issue that we encountered was forgetting to unbind/clean up everything at the end of a function or block. If something is left behind and is still there during the next WebGL operation, it's likely to create some kind of invalid state and you get a nonspecific warning or error.

© 2024 William Wu

Site designed and implemented by me. Built with Vite and React, hosted with GitHub Pages. Privacy policies