Crypt Survivors
Developed for and placed first in the Toronto Film School spring 2025 game jam.
Crypt Survivors is a fast-paced 2D action roguelike with melee, ranged, and magic combat. Players strategically select attacks, manage resources, and survive waves of enemies while collecting upgrades through a weighted reward system.
Key technical highlights:
- Directional Combat System: Player attack direction is calculated using the mouse position and Unity's Camera.ScreenToWorldPoint, dynamically determining attack angles for melee, ranged, and spell attacks.
- Weighted Upgrade System: The RewardUI script defines upgrades via a modular Upgrade class containing a name, description, button color, weight for random selection, and an applyUpgrade action. This allows for both player stats and attack style modifications.
- Randomized Reward Selection: At runtime, 3 upgrades are selected using weighted random selection to ensure varied gameplay and strategic choices.
- Dynamic UI Integration: Upgrade buttons display contextual information including name, description, and category-specific colors. Clicking a button applies the upgrade immediately and resumes gameplay.
- Flexible Attack and Spell Modifiers: Upgrades include increasing damage, attack speed, projectile behavior, spell size, or completely changing attack types (e.g., switching to spin slash or burst fire) while handling stacking and conditional upgrades automatically.
- Player Feedback and Sound Integration: UI interactions trigger sound events, giving immediate auditory feedback for upgrade selection.
My Role
I led development of all gameplay systems for Crypt Survivors, including:
- Directional combat system with melee, bow, and magic attacks
- Weighted reward and upgrade system
- Projectile mechanics and spell scaling (Fireball, LightningBolt, IceFan)
- Input handling and dynamic spawning logic for attacks
The focus was on creating responsive, satisfying combat while allowing players to feel meaningful progression through upgrades.
Gameplay Highlights
Directional Melee Combat
Sword attacks in Crypt Survivors are designed to respond directly to mouse direction, allowing the player to aim slashes, spins, or thrusts dynamically. Each attack type is handled with a combination of instantiation, rotation, and scale adjustments based on player stats and combat modifiers.
Why it’s done this way
- Directional melee allows precise, skill-based combat, giving players control over swing direction.
- Separating
PlayerCombatandSwordscripts ensures input vs. animation logic is modular and maintainable. - Cooldowns and attack speed scaling allow player stats to directly influence responsiveness, giving a sense of progression.
How it works
-
FireSword():
- Calculates mouse direction.
- Computes spawn position offset from player.
- Applies rotation depending on slash type.
- Adjusts size and collider to match scaling stats.
- Instantiates the sword prefab.
-
Sword.cs:
- Animates slash over time for Slash/Spin types.
- Moves the sword linearly for Thrust type.
- Handles collisions for different enemy types.
- Plays sound effects for each hit type.
Code Snippets
FireSword()in PlayerCombat.cs – calculates direction, spawn, rotation, and initializes the sword.Update()in Sword.cs – rotates the sword through the sweep angle and handles destruction.
Bow Mechanics
The bow system in Crypt Survivors allows players to engage at range with precision, offering multiple firing modes and projectile behaviors.
Why it’s done this way
- Supports both precision shots and area spread attacks for variety in combat.
- Separate
PlayerBowandArrowscripts for modularity and maintainability. - Cooldown and draw time mechanics create a sense of skill-based timing.
How it works
-
FireArrow():
- Calculates direction based on mouse or controller aim.
- Spawns arrow prefab with speed, rotation, and damage values.
- Supports multiple modes: single shot, spread (multiple arrows at slight angles), and burst.
-
Arrow.cs:
- Moves arrow forward each frame based on speed.
- Detects collisions with enemies or environment.
- Handles piercing or bouncing logic for advanced arrow types.
- Optional particle/sound effects on hit.
Code Snippets
FireArrow()in PlayerBow.cs – handles aiming, spawning, and arrow type selection.Update()in Arrow.cs – moves the arrow and manages collisions/damage.
Magic Spells
Players can cast a variety of magic spells, each with distinct behaviors and effects. Spells scale dynamically based on player stats, allowing meaningful progression.
Why it’s done this way
- Provides variety in combat styles: ranged, area, and directional spells.
- Allows scaling with player stats (damage, size, casting speed) for progression.
- Separate spell scripts (
Fireball.cs,LightningBolt.cs,IceShard.cs) keep behavior modular and maintainable. - Audio and visual feedback reinforces impact and helps communicate spell mechanics.
How it works
-
Fireball:
- Travels forward at a set speed and explodes on impact.
- Collider activates after a short delay to avoid self-hit.
- Damage varies by enemy type; sound and explosion prefab triggered on hit.
-
LightningBolt:
- Instant effect with very short duration.
- Deals variable damage based on enemy type.
- Plays lightning and hit sounds; collider scales with spell size.
-
IceFan (IceShard):
- Fires multiple shards in a fan pattern with slight angular offsets.
- Shards move forward, scale with player stats, and detect collisions with enemies and walls.
- Plays ice sound effects on impact or block.
Code Snippets
CastSpell()in PlayerCombat.cs – chooses spell type, calculates direction, spawns prefab, and initializes spell.Init()in Fireball.cs, LightningBolt.cs, IceShard.cs – handles scaling, movement, collision, and audio.CastIceFan()– rotates vectors to create a fan of shards, each with its own collision and visual effect.
Reward UI
After leveling up, players are presented with a selection of random upgrades. Each upgrade can enhance weapons, spells, or player stats, offering strategic choices and progression tailored to the player's playstyle.
Why it’s done this way
- Provides meaningful choices on level-up without overwhelming the player.
- Supports modular upgrades for melee, bow, spells, and player stats.
- Uses visual cues like button color and text to indicate type of upgrade.
- Includes smooth UI interactions and audio feedback for selecting upgrades.
Key Implementation Features
-
Lambda expressions:
Each upgrade's effect is defined inline using a
System.Action, which is a lambda function in C#. This allows you to say:applyUpgrade = () => GameManager.Instance.UpgradeMeleeDamage(3);
The() =>is the lambda operator: it defines a small function without a separate method. -
Weighted random selection:
Each upgrade has a
weight(a number ≥ 0). When picking upgrades, the system does the following:- Sum all weights:
totalWeight = sum(weights) - Pick a random number
rbetween 0 andtotalWeight - Iterate through upgrades, adding their weights cumulatively until
runningWeight ≥ r - Select that upgrade and remove it from the pool
Total weight = 1+3+6 = 10.
Generate random r = 4.
Cumulative weights: 1, 4, 10 → the second upgrade is picked because 4 ≤ cumulative weight 4. - Sum all weights:
-
Dynamic UI assignment:
Button colors, titles, descriptions, and click events are assigned at runtime using the chosen upgrades.
Audio feedback is also triggered using the
SoundCreatesystem when an upgrade is selected.
How it works in game
- When the player levels up, the game pauses (
Time.timeScale = 0). - The reward UI spawns and randomly selects 3 upgrades using weighted randomness.
- Each button shows the upgrade name, description, and a color-coded button indicating type (melee, bow, spell, or player).
- The player clicks a button to apply the upgrade; the lambda executes the corresponding function in
GameManager. - Game resumes immediately after selection.