Loading
Group Project
Beaver Quest

Beaver Quest is a 2D top-down puzzle-adventure game built in Unity (C#) using the Universal Render Pipeline. Players guide a beaver through tile-based levels, carrying colored cubes onto matching pressure plates to open gates and solve puzzles. For this team capstone project (TGP1 Team 13), I served as the gameplay systems and tools programmer, building the core gameplay framework, tooling, and editor workflows.

Gameplay trailer

Beaver Quests Official Gameplay Trailer

Core contributions

I implemented the following systems and editor tools to support level design and asset integration:

  • Quest system: A ScriptableObject-based pipeline supporting sequential quest chains, automatic scene-level quest loading, and configurable delays.
  • Scene management: Asynchronous additive loading and unloading with event callbacks (Request, Loading, Complete) and an animated loading widget.
  • Puzzle tools: Color-matched pressure plates and cubes with instance-level IDs, allowing designers to set up multi-gate puzzles directly in the Inspector.
  • HUD widgets: Multi-page tutorial popups, context-sensitive interaction prompts, an async loading bar, and a tile-placement preview.
  • Post-processing and materials: Parallax background layers with per-layer scroll rates, an interactable highlight system, and custom URP Shader Graph shaders for emission, outlines, and bloom.
  • Supporting modules: An event-driven audio manager with mixer routing, camera zoom controls, a controller possession model, and saved user settings.
  • Dynamic sorting: Y-axis sprite sorting responding to collider events to ensure correct visual layering during item pickup and placement.

System architecture

All modules communicate through a static event bus. Domain-specific event classes (such as SceneEvent, QuestManagerEvent, and PlateEvent) expose Action<T> delegates. Handlers subscribe on initialization and unsubscribe on destruction, keeping modules decoupled.

graph TD
    subgraph PersistenceScene["Persistence Scene (Never Unloaded)"]
        Bootstrap["Bootstrap"]
        SM["SceneManager"]
     QM["QuestManager"]
        AM["AudioManager"]
        CM["CameraManager"]
        PP["PostProcessView"]
        TM["TileMapManager"]
        CTRL["ControllerManager"]
        SET["SettingManager"]
   LW["LoadingWidget"]
    end

    subgraph LevelScene["Level Scene (Additive Loaded)"]
        META["SceneMetaView<br/>(Quest assets, flags)"]
        PV["PlayerView"]
        CUBE["CubeView<br/>(CubeColor)"]
        PLATE["PlateView<br/>(PlateID, AcceptCubeColor)"]
        WALL["WallView<br/>(PlateID match)"]
        PORTAL["PortalView"]
        POPUP["PopUpWidget<br/>(Tutorial pages)"]
    end

    Bootstrap -->|SceneRequestTransferEvent| SM
    SM -->|SceneLoadCompleteEvent| QM
    SM -->|SceneLoadCompleteEvent| PP
    SM -->|SceneLoadCompleteEvent| TM
    SM -->|SceneLoadingEvent| LW
    QM -->|Reads quest list from| META
    CUBE -->|PlateTriggeredEvent| PLATE
    PLATE -->|PlateTriggeredEvent| WALL
    PV -->|PlayerLayDownCubeEvent| CUBE
    CTRL -->|Possess / Unpossess| PV
    CM -->|CameraZoomEvent| PP

Gameplay and tooling systems

Quest system

The quest system uses a Quest ScriptableObject base class. Each quest type (such as PickUpWoodQuest) overrides StartQuest() to listen for specific gameplay events and CompleteQuest() to announce completion. The QuestProcessHandler listens for QuestCompleteEvent and advances to the next step.

Every level contains a SceneMetaView component that lists quest assets in order. When SceneLoadCompleteEvent fires, QuestLoadHandler reads the list and populates QuestManager, letting designers build quest sequences without touching C# code.

Quest and tutorial ScriptableObject assets
Quest and tutorial ScriptableObject assets configured as standalone project assets
Example quest prefab for level designers
A quest prefab configured in the Inspector with title, delay, and target conditions
0:00
/0:00
Quest and tutorial systems chaining sequential goals and displaying tutorial dialogues

Tutorial popup widget

PopUpWidgetView accepts a collection of MultiPagePopUpWidgetViewContext objects containing sprite references and localized text. Designers configure tutorial slides in the Inspector with live preview in OnValidate(). Once the player pages through to the final slide, the widget fires PopUpWidgetReachLastPageEvent, which completes tutorial-bound quests.

PopUp widget for tutorial
Multi-page popup widget with per-page text and illustrations

Puzzle mechanics and tools

Puzzles use two core components: PlateView (pressure plate) and CubeView (movable cube). Each plate exposes plateId and acceptCubeColor, while cubes specify cubeColor. When a cube lands on a plate, PlateTriggeredHandler verifies color matching. If colors match, the plate triggers and broadcasts PlateTriggeredEvent, signalling matching wall blocks to retract.

An IsHeld flag on CubeView prevents plates from triggering while a cube is still carried by the player, preventing accidental activations while walking over plates.

Plate and cube matching
Pressure plate and cube pairing configured through inspector properties
0:00
/0:00
Context prompt and cube placement preview indicating valid grid cells
0:00
/0:00
Dynamic sprite sorting adjusting order based on Y-position during pickup and drop actions

URP materials and post-processing

I wrote custom URP Shader Graph shaders for emission, transparency, outlines, and bloom. PostProcessView coordinates background parallax layers, where each LayerView defines its own displacement rates and opacity curves. The object highlight system swaps materials using PostProcessDataSo, a ScriptableObject storing highlight and base material pairs.

URP Shader Graph nodes
URP Shader Graph graph with nodes for emission, opacity, and outline generation
URP bloom and opacity
Bloom and opacity effects applied through URP materials on puzzle objects
Object highlight effect
Interactable object highlight driven by material swapping
0:00
/0:00
URP material effects reacting to gameplay states
2D lighting in scene
Scene-level 2D lighting across tile-based levels

Camera control and background parallax

CameraView tracks the player with configurable offset, zoom speed, bounding limits, and an optional reset setting. PostProcessView drives background parallax, scrolling layers at different rates based on zoom level to establish depth. The camera can also pan to key world events (such as obstacle clearance) before returning to the player.

0:00
/0:00
Camera zoom and parallax layer movement

Scene management and loading widget

SceneManagerView manages asynchronous additive scene transitions using coroutines. LoadWidgetView listens to the scene transition events, reporting progress, level name, and current loading stage before fading out.

0:00
/0:00
Loading widget showing progress during scene transitions

Settings system

The settings panel controls master, sound effects, and music volumes alongside camera reset toggles. Values persist in a SettingsData asset and route to the AudioMixer at runtime, playing confirmation audio clips when sliders move.

Settings UI
Audio settings and camera toggles with persistent state

Design decisions

ScriptableObject-driven data

Quests, audio configurations, post-process parameters, and scene metadata are defined in ScriptableObjects. Designers can adjust and chain content in the Unity Inspector without editing scripts.

Event bus decoupling

Modules interact through static Action<T> delegates grouped into domain-specific event classes. Handlers register on setup and unregister on teardown, avoiding direct cross-module references and keeping subsystems easy to isolate.

View and handler separation

MonoBehaviour View scripts delegate business logic to plain C# handler classes. For instance, QuestManagerView delegates to QuestLoadHandler and QuestProcessHandler, keeping Unity lifecycle methods light and business logic testable.

Inspector-first workflows

Pressure plates, cubes, and quest nodes expose their identifiers and matching conditions as inspector fields, letting designers build puzzle stages without custom code.

Additive scene separation

A persistent scene hosts central managers (audio, camera, quests, scenes, and settings), while puzzle levels load additively. Each level scene’s SceneMetaView supplies local quest definitions.