Project Info - OMEGUN

OMEGUN is a heavily altered version of the FPS template found here. The project includes two levels that have been made with newly added weapons and enemies. Combat has been completely overhauled into a doom-like run and gun shooter. Here you will see how my encounter design constantly puts players in unique and interesting situations.

Scripting

Though the majority of OMEGUN's script is provided by the template, many edits and several major additions have been made in service of OMEGUN's core gameplay philosophy of skillful movement and potent weapons. Most notably, a trigger system has been added and the checkpoint system has been almost completely redone. An exhaustive list of non-trivial additions (i.e. more than just changing a variable in a blueprint) is shown below:

Main Takeaways

Template Changes

Level Design Enemies Weapons

Spawners

spawner

The Spawners are responsible for enemy placement in OMEGUN. Though the implementation itself is fairly basic, the scripting has a few features made to accommodate other parts of the game. For instance, both the spawn delay and tracker for when the particle effect is complete use timelines instead of Delay nodes, which allow the spawning process to be interrupted in case the player dies as more enemies are in the process of spawning. You may notice additional aspects of the spawner's design mentioned in the other sections of this document.

spawner-text

For additional clarity on the end of level design, the spawner shows its display name and the actor class it is set to spawn. The name of the spawned actor is written in the color the particle effect will be when the spawning occurs. When making the level, I've given each spawner instance a name that describes when its actor spawns, such as "Intro First Wave". These small quality of life changes make it considerably easier to make edits to the level while requiring practically no time at all to script.

Trigger System

All map triggers in OMEGUN are set up using a TriggerManager actor. The TriggerManager is made so that all setup can be done outside of blueprints. Triggers are added to the TriggerManager via an array of "TriggerData" a user can add to. The TriggerData structure holds two variables: An actor and a TriggerType enum that tells the TriggerManager what fires the actor's trigger. The TriggerManager only activates when all of its triggers are fired. TriggerType can have one of three values: Overlap, which fires if the player overlaps the given actor, In Area, which acts like overlap but "unfires" if the player leaves the overlapped actor before its TriggerManager is activated, and Death, which fires when the given actor is destroyed. Once all triggers added to the TriggerManager are fired, the TriggerManager will find all actors with a tag matching the "Tag to Fire" variable and call the Trigger function from the Triggerable interface.

trigger-binding

Triggers are implemented by binding events to the default Event Dispatchers Unreal gives to every actor. Specifically, it uses the On Actor Begin Overlap, On Actor End Overlap, and On Destroyed events. Because triggers are implemented this way, any actor can act as a trigger, from a simple Trigger Volume to the game's enemies. Actors such as doors or pickups that already check for player overlap can also be used for Overlap and In Area triggers, reducing the number of actors needed in the level.

Most Death triggers are added during runtime when the relevant enemy spawns. Each spawner has a "Add Death Trigger to" field which accepts a TriggerManager. When the spawner spawns its actor, it is added to the TriggerManager as a death trigger. TriggerManagers only check if they should be activated after one of its triggers have been fired, so it is safe to have an "empty" TriggerManager waiting for its death triggers to spawn.

Checkpoints

Originally, dying in this template had very little impact; The player would simply be teleported to their last checkpoint with full health to finish off whatever encounter killed them, not unlike the respawn system of games like Borderlands or Bioshock. This is a pretty disappointing outcome for a challenging encounter (even Borderlands forces you to restart Boss fights in particular if everyone dies!), especially in a game with no currency to lose such as mine. Thus, I have completely revamped the respawn system into the more traditional style of making the player restart the encounter that killed them. The game resets to whatever state it was in when the checkpoint was reached.

Every actor that must be reset upon player death, save for the player themselves, implements the Respawn On Reset interface. These actors include health pickups, ammo pickups, keycards, map triggers, and spawners not activated by map triggers. When the player interacts with any of these actors, they are added to a "Used Since Checkpoint" array stored in the player blueprint. When the player dies, the Respawn Reset function from the Respawn On Reset Interface is called on every actor in the Used Since Checkpoint array. The Used Since Checkpoint array is cleared whenever the player dies or reaches a new checkpoint.

In addition to implementing the Respawn On Reset interface, actors must be specially made to reset when interacted with. Pickups and keycards, for instance, become invisible and have their collision turned off instead of being destroyed when grabbed by the player.

trigger-reset trigger-reset2 trigger-reset3

Map triggers require special attention, as many actors activated by the trigger must be deactivated when the trigger is reset. To this end, all triggerable actors, with the exception of spawners, have their effects reversed if triggered post-activation. Such actors are doors and jump pads. Doors, for example, will unlock and open if triggered. If the door is already open when it is triggered, it will instead close and lock when triggered. While this simplifies code by not requiring an "untrigger" function, it can also be useful outside the context of level resetting: A door can be triggered once to let the player into a room and triggered a second time to lock them in that same room.

If a TriggerManager has even one of its Triggers fired, it is added to the Used Since Respawn array. The TriggerManager is also added when it is activated (Add Unique is used to prevent the TriggerManager from being added more than once!). In the latter case, the TriggerManager turns on a Retrigger Toggles variable. If Retrigger Toggles is true when a TriggerManager is reset, it will trigger all actors connected to it that are not spawners, reversing the effects of it being activated before the player's death.

A limitation of the TriggerManager related to resetting is that a TriggerManager cannot perform a "partial" reset; When a TriggerManager is reset, all of its fired triggers are always cleared without exception. This prevents a TriggerManager from working properly when a Checkpoint is encountered before all of its triggers are fired. This becomes a problem in Dire Spire when the player must backtrack after collecting the yellow key, as there are overlaps that must trigger enemy spawns, but only after the yellow key is collected and a checkpoint between the player and overlaps is reached.

To make this work, a special trigger volume that can have its collision toggled via triggers was made. Grabbing the yellow key activates these overlaps. If the yellow key is grabbed but the player dies before reaching the checkpoint, the relevant TriggerManager is reset and the overlaps lose the collision they just gained. If the player reaches the checkpoint, the TriggerManager does not reset on player death and the overlaps will keep their collision.

OMEGUN Alt Fire

The OMEGUN's alt fire causes its lasers to home in on the closest target that the player is aiming towards. Although Unreal's Projectile component makes implementing the movement of the projectile simple, the scripting for the target acquisition is not as simple as one might guess.

laser1 laser2

The projectile uses both a sphere-cast and a raycast to search for enemies, both starting from the player component used to position projectiles and moving in the direction the player is aiming. The sphere-cast is used for detecting the enemy to home in on. This collision type is used to create a wide detection radius for acquiring targets, as a simple raycast would fail to find targets the player isn't precisely aiming at. The sphere shape also causes the check to prefer targets closer to the center of the player's aim.

The sphere-cast uses the TraceForObjects function to search specifically for enemies. Unfortunately (And quite annoyingly!), the template does not have enemies parented under a single class, so many casting attempts would be needed if the visibility channel were used for the sphere-cast. Instead, a raycast is performed before the sphere-cast to search for obstacles. The raycast's hit location is used as the endpoint for the sphere-cast to prevent it from going through walls.