Tilberinn

Final School Project

A wave-based survival first person shooter game built in our custom engine

Tilberinn

Tilberinn is my final school project, a wave-based survival first person shooter game developed in our custom engine.

The player must fend off endless, progressively harder waves of enemies while managing resources and upgrading their guns and abilities.

My role included gameplay systems, such as the weapons system, interactions and tools.

Weapon System

The weapon system uses an inheritance hierarchy to keep code organized and reusable. The base class PlayerWeapon defines the interface all weapons share, with a PrimaryAction() function for firing guns, throwing grenades, or swinging melee weapons.

Specialized weapon categories inherit from PlayerWeapon — PlayerGun, PlayerGrenade, and PlayerMeleeWeapon. PlayerGun further branches into HandGun, Rifle, and Shotgun to handle weapon-specific logic.

All gun behavior is driven by two key structs: WeaponStats (fire rate, damage, recoil, spread) and GunSpread (dynamic spread calculation and decay). This design allows for easy addition of new weapons and categories while maintaining consistent behavior across the system. You can upgrade the weapons in game by collecting resources and spending them at the upgrade station. I used decorator patterns to implement the upgrade system.

View Weapon Structs

struct GunSpread
{
    void Update(const float aSpreadRadius);
    void EmitSpreadEvent() const;

    float Min = 16.0f;
    float BaseSpreadMultiplier = 0.f;
    float CurrentSpreadMultiplier = 0.f;
    float Current = 16.0f;
};

struct WeaponStats
{
    float FireRate = 0.f;
    unsigned ReserveSize = 0;
    unsigned BulletsInReserve = 0;
    unsigned MagazineSize = 0;
    unsigned BulletsInMagazine = 0;

    float BaseBulletDamage = 10.f;
    float BulletPenetrationMultiplier = 1.f;
    unsigned BulletsPerShot = 1;
    
    float DropOffDamage = 0.f;
    float MinDropOffRange = 0;
    float MaxDropOffRange = 0;

    float MaxRaycastRange = 0;
    float SpreadRadius = 0.f;
    float SpreadBias = 1.f;

    float AimDownSightSpreadMultiplier = 0.5f;
    float AimDownSightFOVMultiplier = 0.85f;
    float AimDownSightSpeedMultiplier = 0.5f;

    float RecoilPitch = 2.f;

    bool ExplodeOnImpact = false;
    float ExplodeRadius = 100.f;
    float ExplodeDamage = 150.f;

    bool IsAuto = false;

    float HeadshotMultiplier = 1.f;
    float LimbMultiplier = 1.f;
    float TorsoMultiplier = 1.f;

    uint8_t AimSound = 0;
    uint8_t FireSound = 0;
    uint8_t EquipSound = 0;
    uint8_t ReloadSound = 0;
    uint8_t ReloadEndSound = 0;
    uint8_t QuickReloadSound = 0;
    uint8_t ReloadBulletSound = 0;
};
                                

Level Sequence Export and Engine Integration

Our level designers create the level in Unreal Engine and export it, which we then import into our custom engine.

I extended the export so it also writes values from a LevelSequenceActor. It exports keyframes that can contain transforms, floats, and events such as audio, VFX, and animations.

This system is used for both videos shown here. In the Event video, the lights, VFX, camera shake, and audio are all authored in Unreal Level Sequencer and then exported into the engine.