Project: Meteora

A first person, roguelike-horde shooter

Project Breakdown

  • Solo - 7 weeks, halftime

  • Created in Unreal Engine 5

  • All scripts & systems are created by me

  • Assets used:

    • Blockout Tools plugin by Dimitry Karphukhin

    • Chaotic Skies by Velarion

    • Low Poly Nature by Vertex Rage Studio

    • Low Poly Sci-fi corridor by PolyArt3D

    • Sci-fi Weapon Pack by GrayBite Studio

Goals

  • To create an addictive gameplay loop with clear player progression and side objectives*

  • Creating replay value through roguelike elements, such as randomizing weapon spawn locations and a shop that randomizes items & stats

  • Making a visually pleasing low-poly environment with differing “visuals” for the sake of a good player mental map

Summary


Meteora is a systemically heavy project, being a roguelike horde shooter, taking inspiration from the likes of Black Ops Zombies and the player upgrades found in Risk of Rain 2 items.

Set in a low-poly environment featuring a sci-fi lab along with a close by village that have fallen under the corporate terror of hostile robots that have gone rogue.

The focus of the project is primarily on scripting & system design, accomodated by an addicting gameplay loop, and built on a Figure-8 style level for good flow.

Noteworthy systems:

  • Economy

  • Upgradable stats

  • Unlockable weapons

  • Randomized shop system

  • Spawn algorithm & round based logic

  • Player controller & visual feedbacking

Software used

  • Unreal Engine 5.6

  • Miro

  • Krita

Playthrough


Early stage of weapon swap

Middle stage of weapon swap

Gameplay systems


As previously stated, Project: Meteora is fairly ambitious on the scripting side for a solo project, but I will cover the most important ones here:

Weapon showcase with their respective secondary functions


sHOP

The shop is the biggest contributor to the games’ replayability and keeps it exciting to come back for more runs to see how powerful you can become. It is also vital to interact with in order to become strong enough for the boss at the end of each run!

  • How it works:

    You add a row in the Item data table → Give it an icon, Item Index, name, description and stats you want the player to get upon purchasing the item.

    Upon interacting with the shop, the player spends their money, the shop will randomly select a row from the data table, and whichever item the player chooses, they will get its stats applied to them, and the shop closes. Transaction complete.

Code embed

Round system

  • The game runs on a round based system, what that entails is that every round gets an increasing amount of enemies, as well as incrementing their stats.

    • Enemies get more health, move speed and damage with each passing round. At first I considered giving them more money upon death, but decided not to, as higher rounds offers you more enemies you will get an increase to the economy regardless.

  • It also works in a way where the enemies don’t have the exact same stats, what I mean is that some run faster and some have more hp than others.

    • It makes it feel more dynamic to play and keeps the player on their toes, as you aren’t 100% prepared for what is coming at you. Some shooters will teach you that some enemies will always die in 2 bullets, but that can become mundane when you always know the outcome. That is something I wish to avoid.

prototyping


  • Early stages of Meteora went something like: what if I take two of the most addictive kinds of shooter games I know and combine them? After that I just experimented with a simple AI, ramped up the tempo as the game progressed through spawns and enemy stat increments, then tried to make an economy around that.

    • That’s where I got the idea of items and making a randomized shop, for the player to keep up and have meaningful progression throughout the game.

  • The absolute first thing I created however was the weapon swapping system. I had never made a system before where you changed weapons in a traditional FPS manner, so it was very exciting to tinker with.

    • After I figured out the visual swapping, I discovered the magic of using Bytes and enumerations. I got working on different gun logic, single bullet for pistol, buckshot for the shotgun and sphere trace for the sword.

      • I experimented further with weapon functionality and wanted to make a “Specials” system, where each weapon had a cool secondary use for different use cases. The shotgun and pistol secondaries are quite standard, but I’m very happy with how the defensive stance on the sword turned out. It just feels super cool to block incoming damage at any given moment and it’s my personal favorite Special upgrade to buy in a run!

Player mechanics

  • The base player script is fairly simple, consisting of:

  • A sprint mechanic

  • Different upgradable weapons

    • Shooting, reloading and a special secondary function per respective weapon.

      • Pistol has Single-fire as default, Full-auto as special upgrade

      • Shotgun has Buckshot as default, Slug rounds as special upgrade

      • Sword has a melee range attack as default, defensive stance that blocks all damage as special upgrade

  • Variables such as money and ammo reserves

    • This puts an in-game focus on resource management and economy

How easy it is to add more items to the shop:

  • The reason for the shop containing random items:

    Knowing exactly where to go on the map only to buy the items you wanted, would become stale and make the overall game flow worse. But making the shop random also keeps the player balanced, and won’t let them stack a single upgrade type that will just ruin the game balance. Sure, you can still roll for that and get it by chance, but then that run would feel special and exciting because you got lucky with the shop. I think that’s a win-win system.

    • I did however intend from the start to have different shops containing only certain types of items (offensive, defensive etc.) but it felt like a stretch goal I didn’t want to prioritize for the moment being.


Shop interaction

ECONOMY DESIGN

  • This is also my first project where I’ve made a bigger scoped economy!

  • Enemies give the player 75 gold on death. In the early game, players have to consider progressing on the map through doors, restocking ammo or hitting the shop.

    • It can feel scarce at first, but as they go through the run they will find themselves with more and more disposable income. And you will never find yourself with “too much money without anything to spend it on” like certain other games do, which just feels like a suffering from success type of moment.

  • The player can buy the following in a run:

    • Doors: 500-1 000 gold depending on how deep they are in the level

    • Ammo refill: 250 gold

    • Spin the shop: 750 gold

    • Special weapon upgrade: 2 500 gold

      • I think the special upgrade is at a perfect price point. The upgrades feels strong enough to be compared to 3 shop purchases, but its impact feels greater, given they are active effects and not just passive ones. I think it gives the player a heavy decision, to save up the money for a big upgrade, or incrementally get stronger through more, but smaller stat increments.

        I’m super happy with how that balancing turned out!


lEVEL OVERVIEW



Level Start

1

2

3

Courtyard

Courtyard weapon spawn

4

5

6

7

8

8

9

Village

facility entrance

Facility weapon spawn

Facility open area

Altar room

Boss room

Level design


Different zones of the map, underlined by the Figure-8 design philosophy


Environmental spacing

Every good horde shooter is deliberate with its space, how tight should important areas be? Where does it become more open? What does the player have to consider when going from one place to another? Having answers to these questions in the level makes for an engaging play space.

I think I managed to create varying layouts using these questions as a pillar of the level design. The 2 main areas, the Facility and the Courtyard heavily uses these fundamentals and makes for engaging moment-to-moment gameplay.

During playtests, people were caught in the tighter areas when they were playing greedy or too aggressively, which is exactly what I wanted to see. Returning playtesters would become better at analyzing the more unforgiving spaces, and would choose alternate routes/become better at clearing out the rougher areas before proceeding.

This player behavior shows to me that it’s a map that can be learned and mastered by players, and it is incredibly gratifying to see as the level designer.

Example of tight space situation

The map is created on a Figure-8 foundation and divided in to distinct visual zones to help the player create a mental map, as well as creating good flow and helping the player to not get lost.

  • What I mean by Figure-8 is that the map sort of "loops” on itself in its layout. If the player just walks along a path, the level will eventually lead them back to the initial location.

    • The reason I chose this design approach is to create flow and make controlled funnel points, where the player can expect more enemies per square meter. Certain areas become more risky to traverse due to the tight spaces, but also allow the player to quickly dispatch many enemies at once. It allows for moments where split second decisions matter, the quicker route will get you from A to B faster, but you’re also risking getting caught in a tight space.

      It’s what makes the level design feel engaging to play, the player is being tested on their judgement to choose the right path in any given moment. It feels good to overcome and is naturally integrated in the rule of “30-seconds of fun”.

    • The zones aren’t purely for visuals however, they help control the map flow through spawn points that only become active once the player has entered the respective zone.

Green = Generally safe

Yellow = Generally risky

Red = Generally dangerous

Using the space to funnel enemies for easy kills

Zones


sPAWN system

  • As previously mentioned, the map is split up in zones that control which spawn points are active.

    • Not only is this good for removing the downtime of having to wait for an enemy across the map that just spawned in, it also makes it so the game doesn’t softlock. Without this system in place, enemies would spawn in locked rooms, and if the player can’t afford to buy the door, they can’t kill the enemy and progress through the rounds.

  • The script is more or less just a Byte variable that updates whenever the player moves between trigger boxes. It then checks which spawn point actors are overlapping with the zone, and allows for those points to become active until the player moves through another trigger box.

    • This same setup also makes it super easy to build upon the different zones in a visual manner, for example if you wish to update a post process volume, fog density or the overall brightness without tinkering with a bunch of lights, you can just update those settings with an enumerator switch in blueprints.

Blockout of boss room and level sequence

Finished boss room with glass floor breaking

To make an ending for a roguelike


  • I wasn’t originally planning to make an ending for this project, I just had the idea to make an endless survival experience. But that became kind of dull, as having infinitely scaling enemies became a death sentence in the end regardless of skill or items possessed.

    So the next step would be to try a bigger, more intimidating foe, which you could try your item build and skill towards. It also just becomes a natural goal and something to prepare for on subsequent runs, and maybe you want to get the satisfaction of killing the ending boss without any items what so ever?

    • I also just think walking away from the experience with a victory feels better than walking away because you grew tired of the game and wanted to do something else with your time

  • I decided to keep the boss low scope in mechanics as it was at the end of the project and it was unexpected that I would work on something like this. I think it works decently as a spectacle thing to end the game with, especially when he jumps down from the roof and breaks the floor to reveal a room underneath.

    • If I had planned more for the boss from the start, I probably would have given him more attacks and more advanced behavior. Make him more of a boss fight and less of a stat checker.

Boss fight


Reflections


This project was an absolute blast to work on, and very much opened my eyes to more technical level design and system design.

If I had to estimate I think it ended up being 70% of the time spent on scripting/making systems and the remaining 30% on creating the level. In spite of the time split, I still think the level ended up being super cool and definitely fulfilled my vision of a fitting horde shooter map, with its Figure-8 layout.

The only thing I wish I did more of was level interactables, the doors are cool and add to the progression as well as economy of the game, but It would’ve been cool to showcase more stuff in the level to make it feel more alive.