Project Summary Submission

Inspiration

Our project has several direct inspirations, from its artstyle to our game design.

  1. Artstyle - We were inspired by classic arcade games, and thus our world and enemies were created to emulate the classic 8-16bit era. Our biggest inspirations were Doom, Zombies Ate My Neighbors, and Night of the Consumers.
  2. Gameplay - We were heavily inspired by games like Call of Duty's zombies gamemode. Our 180 degree playing field, dedicated zones for players, and post-wave shop were inspired by Dead and Buried, one of the first VR games I ever played (and what hooked me on VR all those years ago.)

Our name “Black Friday” was inspired by the chaotic Black Friday shopping, as the bastion you are defending is a supermarket.

What it does

Our project launches players into a wave-based shooting defense game. Monsters spawn each “wave” in order to attack the player. There are multiple different enemy types, each with their own behaviors and attacks.

Players are equipped with a weapon and a shield. The weapons have limited ammo, fire rate, and magazine capacity, but you can upgrade your weapon at the end of every round. Weapon upgrades also allow you to transform your gun into a new gun type - you start off with a relatively underpowered pistol, but can upgrade into a shotgun, then an alien rifle.

Players can dodge attacks by moving their avatar around, or they can summon a shield to deflect all attacks. The shield can easily pull you out of danger, but it has limited energy. Shield energy is expended when it’s raised and when it takes damage - in this way, players are encouraged to time their shield activations properly in order to best conserve their shield energy.

Players must defeat all the enemies in a wave without dying. We have If a player dies, you lose a life and are automatically respawned. If you run out of lives, it’s game over! In the case of multiplayer, players share a pool of lives.

There are 12 waves which progressively get harder and introduce more enemies as the game goes on. There are 5 unique enemies, and we periodically introduce harder versions of the same enemy in certain waves. Depending on a players luck, a Horizon Worlds logo has a chance of floating across the battlefield. Shooting it will grant players an extra life.

The last resource players have access to is the post-wave shop. From the shop, you can replenish your shield energy, buy extra lives, and upgrade your weapon using currency. You earn currency from shooting and defeating enemies.

How we built it

All music, programming, art, essentially everything in this world was hand-built from scratch. No generative AI was utilized for any part of our production, including our music and concept art. No asset packs, store assets, or pre-built assets were used in the game.

We used several tools to create this experience, this section will go into detail about each tool.

HZW Creation Client

We utilized the MHCP desktop editor for placing all assets, firing particles, and composing our scene. This was all “trimesh” or non-primitive building.

Programming

All programming was done in Typescript via the desktop editor. We also leverage some custom built tooling to process assets into forms that we could use in Meta Horizon Worlds.

GIT

For collaboration, we utilized git to share code between our programmers extensively implementing traditional git flows with the horizon editor.

Blender

Our environment and weapon models were created from scratch using Blender. Blender was also used to craft low-poly collision meshes for optimized performance and more control over navmesh baking.

Textures/Materials

  1. Aseprite was used to translate our concept art into stylised aliens and to create their animations.
  2. Weapon+World textures/materials were created in Aseprite and Medibang Paint Pro

Concept Art/World Photo

Our enemy concepts and world photo were drawn using Photoshop.

Music/SFX

All music was created by our composer using Ableton.

SFX were created in BFXR, Audacity, and FL Studio.

Challenges we ran into

We ran into many, many challenges when developing this game!

This was the first game we ever created for Horizon Worlds.

We only had one teammate who was familiar with Horizon Worlds. For the rest of our team, we had never really experimented with HZW. This meant that we had to basically guess at what kind of game we could make that would be impressive, fulfill the contest submission requirements, and most importantly, actually work given platform limitations. We are familiar with building on UGC platforms and are always prepared for the worst when it comes to having to rapidly pivot our ideas in a short timeframe. And pivot we did!

Game Ideation

Our first idea was to create a racing game. However, after hopping around worlds a lot, we noticed that there weren’t many compelling vehicle games. Players tended to lag behind vehicles, which visually wasn’t great for us. Lastly, we weren’t convinced we could provide a great mobile experience given how vehicle controls were looking. We then pivoted to the wave shooter idea. We believed that a wave shooter would be a great type of game to start off with - a classic, arcadey type of game that could showcase our art and programming skills, without feeling like we had to “scope up” to be impressive.

Animating enemies

Our first hurdle with the wave shooter was that we absolutely needed to have expressive enemies. We originally started by 3D modeling concepts, but realized that HZW does not really support expressive, keyframed animation just yet. We also realized that we could rapidly hit a polygon limit with how many enemies we wanted to have on the screen at once. The move then was to get back to the basics - 8-16 bit enemies, animated on a spritesheet. This way, we could have many expressive enemies at once. This completely overhauled our artstyle as well (we were originally aiming for something that looked more realistic.)

Animating sprites turned out to be almost just as difficult.

While we thought were were being clever going to spritesheet animations, it turned out that HZW did not really support sprite animations that well. For example, there is absolutely no “gif” import. While we did have access to a “nyan cat” tutorial sprite animation, if we cloned the nyan cat a couple times, we actually got rate limited on our project and were then incapable of editing on that client for the rest of the day. Clearly there were going to be issues with the sprite animations, since we planned on having many enemies on the screen at once. We were using up some sort of budget when it came to having animated sprites - but we weren’t sure what. We did a ton of testing (and asked a lot of questions) about what could be blocking us. Ultimately, we discovered that HZW was limiting us on binding calls, which aimed to severely limit our gameplan. We were anxious that we wouldn’t be able to do this game idea at all, until we made several important discoveries.

First, AnimatedBindings allow for UI elements to update continuously with only a single binding call to start the animation. One limitation of AnimatedBindings, however, is that they’re restricted to only numbers and also smoothly interpolated between those numbers. What we really wanted was a step-function for our sprite sheets to flip between the frames of the animations. We then discovered that there was a way to specify a custom interpolation ranges in a fairly generic way, and it turns out that this enabled us to represent any kind of animated function we wanted, including the aforementioned step function.

This is when we encountered the repeat animation bug (B235), which blocked our ability to start looping an arbitrary animation sequence within the middle of a sprite sheet. Through collaboration with meta support, we were able to find a suitable workaround for us to utilize while a fix was being worked on.

One issue with this workaround was that it brought us closer to another limit related to bindings; the 1000 character serialization limit for AnimatedBinding updates. If you string together too many animations (say, in a SequenceAnimation) you run into a character limit for the serialization of the AnimatedBinding. As such, we had to make some compromises in our animation system with the tradeoff of consuming more binding calls. Luckily, while this did cut into our binding call limits, we were able to still keep it from going over that limit. The result is that we were able to stuff the screen with a ton of expressive, reactive enemies!

Time Limits

The time limit for developing this project was very strict. We actually started development pretty late (we only learned about the contest on the 3rd of November, and we had to kickstart the ideation machine/see who was available, so we really didn’t even get to start building until that weekend.) I feel like we navigated this time limit pretty well, given that all of this was brand new to us. Unfortunately, a couple of our enemies were left on the cutting room floor. The first enemy type was a flying bug enemy, and the 2nd enemy type was a projectile launching alien who would bombard the player with multiple shots. We had finished the concepts and sprite art for these cut enemies, but just weren’t able to program them in time. Maybe later!

Learning typescript

By far the biggest hurdle was utilizing typescript. While we have some experience in Typescript, we knew we would need to use it over the far more easy code blocks in order to create the most impressive/functional world we can. Our programming team had the biggest workload ahead of them by far, since artists could still create in programs they were familiar with. We hope you enjoy what were able to code up.

How we developed for mobile

  1. We created custom UI elements, including healthbars, shield bars etc. These are placed differently in-world based on platform.
  2. Mobile players get a few extra effects - when taking damage, a vignette appears. We have a “slimer” enemy who covers players screens in goo if you are hit by its projectile.
  3. Mobile players have easy access to our UIs.

Accomplishments that we're proud of

  • Scalable flip book animations - tons of expressive enemies!
  • Custom ai collision system
    • The built-in NavMeshAgents in Horizon Worlds have avoidance properties, but overall it is still possible for multiple agents to overlap. This behaviour was not desirable for our game, so instead we built our own custom enemy system.
    • Leveraging the power of TypeScript Interfaces, we were able to allow multiple enemy behaviours to be “embodied” by a one-size-fits-all EmbodiedEnemy, which behaves as a physical object that tries to reach the transform exported by its behaviour.
    • By making the EmbodiedEnemy physical, we are able to offload the task of preventing enemy overlap to the Horizon physics engine, which is perfectly optimized for the task.
  • Dynamic weapon system
  • Fully custom, original music.
  • It’s fun!!

What we learned

We learned that we can push ourselves, and the platform we are building on, to the limit. We are really happy with what we were able to create in only a couple weeks in a brand new ecosystem.

What's next for Black Friday

We currently call Rec Room our home, but we’d love to do more work over on HZW if players react positively to our game. We’d love to add some of the enemies we left on the cutting room floor as well as a bossfight. We’d like to add unlockables for players to grind towards earning, as well as more difficulty options. Overall, we feel a lot more confident with creating in HZW and would love to create more experiences for it sometime in the near future.

Built With

Share this project:

Updates