Documentation:26-3002 Another Order: Procedural Poetry Funhouse
Introduction
Background
In the 1960s and 70s, Vancouver neighborhoods such as Kitsilano and Strathcona were home to many forward-thinking poets who challenged the fundamentals of what defined poetry. Judith Copithorne was one such figure. She was most well-known for her contributions in concrete poetry, where the visual form of the poem dictates the purpose of the poem itself.
Emerging technologies, such as Virtual Reality (VR) and Large Language Models (LLMs), present new opportunities to experiment with the visual representation of poetry in a three-dimensional modality. Inspired by the roots of concrete and procedural poetry, this project uses emerging technology to recontextualize poetry as an art form that can exist in a tangible space. The Emerging Media Lab aims to create an immersive experience in both VR and desktop for Creative Writing students that pays homage to the experimental work of Judith Copithorne and allows users to engage with procedural poetry in fun, unique ways.
Objective
The primary objectives of this project are to provide an educational and immersive experience for Creative Writing students to interact with concrete poetry and AI through experiential learning. In this term, the team was challenged to address the VR UX issues that were identified through various playtest sessions, create a desktop version of the experience, add an ending experience, reintegrate a Market room to replace the computationally expensive Maze room, as well as clean up the existing flow for a more holistic and polished end-to-end user experience.
Listed below are the various tasks the EML team has accomplished this term:
- Desktop Build Implementation: To make the project accessible to a broader audience, the team developed a Windows Desktop version this term.
- Opening LLM Choice: Users are now given three options to choose from in terms of AI use in the project: (1) Train a UBC-hosted LLM, (2) Use OpenAI’s LLM, or (3) Not use AI at all.
- Market Room Integration: To replace the computationally expensive Maze Room, the Market Room was designed and integrated into the project this term.
- End Experience Integration: To properly tie up the narrative journey of the user, an End Experience level was added with callbacks to the different activity rooms and reflection questions for the user.
- Tutorial Re-design: To better inform the user about foundational mechanics, the tutorial was re-designed to be more intuitive. The tutorial currently guides users through movement, camera rotation, word grabbing, and word collection.
- VR Character Movement: Improved navigation by having clearer controls and spatial guidance.
- Word Interaction Polish: Playtest feedback revealed that word movement was too sensitive. The word movement has since been adjusted to feel more intuitive.
- Reworked Attic Adaptive Lighting: A more deterministic model was added this term to make the sentiment analysis that affects lighting in the Attic faster and easier to edit.
- Attic Adaptive Audio: To enhance user immersion, adaptive audio was added to the attic as well.
- UI Updates: The cat companion dialogue, common word shelf, and music player interfaces were updated for enhanced readability and usability. The cat was also added to the Attic for user instruction. Instructional UI prompts were also added for the desktop build.
- Asset Design: Play testers reacted positively to the hand-drawn assets created for the previous iteration of the project. This term, the team further leaned into that visual style and incorporated more hand-drawn assets for the Lobby, Attic, and End Experience.
Format and Versioning
This project uses Unreal Engine 5.5.4, Meta Quest Virtual Reality Headsets, and integration with LLMs (OpenAI).
Setup and Deployment
Tools
This project uses the following:
- Unreal Engine 5.5.4
- Meta Quest SDK
- Custom Shape Button Plugin
- Meta XR Plugin
- Meta XR Platform Plugin
- Perforce
Set Up
Overall project set up is based on this guide for setting up Unreal Engine for Meta Quest VR. and this guide for setting up Visual Studio. Download the following:
- Unreal Engine Launcher: https://www.unrealengine.com/en-US/download and Unreal Engine 5.5.4
- Android Koala 2024.1.2 Patch 1 September 17, 2024: https://developer.android.com/studio/archive
- Java SE Development Kit 21.0.3: https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html
- Meta Horizon Link App (Desktop): https://www.meta.com/quest/setup/
- Meta Quest Developer Hub: https://developer.oculus.com/downloads/package/oculus-developer-hub-win
- Unreal Engine 5 Integration v78 (Meta XR Plugin): https://developer.oculus.com/downloads/package/unreal-engine-5-integration/
- Unreal Engine 5 Platform v78 (Meta XR Platform): https://developer.oculus.com/downloads/package/unreal-5-platform-sdk-plugin
- Install Visual Studio 2022 version 17.10
Visual Studio 2022
In the installer’s Workloads tab, under Desktop & Mobile, enable the following options:
- .NET desktop development
- Desktop development with C++
- .NET Multi-platform App UI development
In the installer’s Installation Details panel, expand Game development with C++ and enable the following options:
- C++ profiling tools
- C++ AddressSanitizer
- Windows 10 or 11 SDK (10.0.18362 or Newer) - USE version 10.0.22621.0.
- Unreal Engine installer
JDK 21.0.3
Run this "Java SE Development Kit 21.0.3" installer. Install into the default location (C://Program Files/Java/jdk-21).
Android Studio Koala
1. Run the installer
2. Open Android Studio and install required components
3. Relaunch Android Studio, click "More Actions" on the right menu
4. Select "SDK Manager"
5. Ignore the left menu
6. At bottom right, tick "Hide Obsolete Packages" and "Show Package Details"
7. Under "SDK Platform," tick Android SDK Platform 34 and 35
8. Under "SDK Tools," expand "Android SDK Build-Tools 37"
9. Tick 35.0.1
10. Expand "NDK (Side by Side)"
11. Tick 26.1.10909125 (We tried 27.2.12479018 with no luck)
12. Expand "Android SDK Command-line Tools"
13. Tick the Latest version (currently version 19)
14. Expand "CMake"
15. Tick 3.22.1
16. Also tick: Android Emulator, Android Emulator Hypervisor Driver, and Android SDK Platform-Tools
17. Apply and accept licenses
18. Close Android Studio
19. Reboot computer
JAVA_HOME
1. Press Start on Windows
2. Type "Environment Var..."
3. Open "Edit the system environment variables"
4. Click "Environment Variables"
5. Under User variables, locate "JAVA_HOME"
6. Change to C:\Program Files\Java\jdk-21
7. Press OK, and Apply
Meta Quest Mobile App
1. Install the Meta Quest mobile app
2. Follow the app instructions to connect your Quest
3. Tap the headset icon
4. Select your Quest
5. Go to Manage Device > Headset Settings > Developer Mode
6. Enable Developer Mode
Meta Quest Link Desktop App
1. Install
2. Launch
3. Update drivers if prompted
4. Set as default OpenXR runtime if prompted
5. Under Settings > General, enable Unknown Sources
6. Under Settings > Beta, enable Public Test Channel and Developer Runtime Features
7. Connect Quest to PC with USB
8. Follow linking instructions
9. Accept USB permissions on the Quest
10. On Quest: Settings > Advanced > Developer > enable all
Unreal Engine Android Setup
1. Locate you Unreal Engine 5.5.4 install folder
2. Run “UE_5.5\Engine\Extras\Android\SetupAndroid.bat”
Meta XR Plugins
1. Locate your project folder
2. Create "Plugins" folder
3. Unzip UnrealMetaXRPlugin.78.zip into Plugins
4. Confirm "MetaXR" folder exists
5. Unzip Unreal5PlatformSDKPlugin.78.zip into Plugins
6. Confirm "MetaXRPlatform" folder exists
7. Launch .uproject
Unreal Engine Plugins
1. In Unreal: Edit > Plugins
2. Under Installed/Virtual Reality, enable Meta XR
3. Under Installed/Online Platform, enable Meta XR Platform
4. Close Plugins window
Unreal Project Settings
1. Edit > Project Settings
2. Go to Platforms > Android
3. Minimum SDK: 32
4. Target SDK: 34
5. Install Location: auto
6. Orientation: Landscape
7. Package game data inside .apk: ticked
8. Resolve all red warnings
Unreal SDK Setup; SDKConfig
1. Project Settings > Platforms > Android SDK
2. SDK: C:/Users/name/AppData/Local/Android/Sdk
3. NDK: C:/Users/name/AppData/Local/Android/Sdk/ndk/26.1.10909125
4. Java: C:/Program Files/Java/jdk-21
5. SDK API Level: android-34
6. NDK API Level: android-34
Troubleshooting “Missing PoetryHouse Module”
A common error upon setting up the project and trying to run it for the first time is this:
The following modules are missing or built with a different engine version:
PoetryHouse
Would you like to rebuild them now?
To troubleshoot this problem, follow these steps:
- Open Perforce.
- In your Perforce workspace, right-click the Plugins folder.
- Select “Check Out...” and select “Ok” to the next two pop-ups.
- Find and open your PoetryHouse project folder.
- Delete these folders: .vs, Binaries, Intermediate, Saved, as well as the PoetryHouse.sln file.
- If the .vs folder is not showing up, on your file explorer click View > Show > tick Hidden Items
- Right-click PoetryHouse.uproject
- Select “Generate Visual Studio project files”
- If this option is not showing up, select “Show more options” first
- A new PoetryHouse.sln file will be created. Open this file with Visual Studio.
- Once the PoetryHouse.sln file opens in Visual Studio, right-click PoetryHouse under Games in the righthand Solution Explorer.
- Select Build.
- Wait until you see something like “Build Successful” in the Output.
The project should run properly now. Once it builds successfully, you can revert the Plugins folder by following these steps:
- Open Perforce.
- In your Perforce workspace, right-click the Plugins folder.
- Select “Revert files.”
Build
If set up correctly you should be able to build to both Android (VR) and Windows. Make sure to change the Game Mode in the starting level to the correct version before building. If building to VR, set the Game Mode Override in the ArbutusOutdoor level’s World Settings to VRGameMode. If building to Windows, use the BP_PCGameMode.
To build in Unreal: Platforms > [Choose your platform] > Package Project
Project Settings (Desktop)
To properly build to Windows Desktop, you will need to change the Project Settings before building:
- In Unreal: Edit > Project Settings
- Project > Description > Settings > Start in VR
- Disable/untick this option
- Engine > Enhanced Input > Default Mapping Contexts
- Remove all default mapping contexts by pressing the Trash button
- Add 4 input mapping contexts
- IMC_PCMovement
- IMC_PCWordMagnetBehaviour
- IMC_PCSettingsMenu
- IMC_PCBlackoutRoom
- Ensure they all have Priority: 0 and Add Immediately: ticked/enabled.
- Optional but nice: Disable all plugins related to Meta Quest as they will not be needed.
Note: When packaging to Windows, the team found that packaging a Development build worked best. If doing this, make sure to disable all screen messages in the game. A simple way to do this is by opening the ArbutusOutdoor level blueprint and connecting an Execute Console Command node with the command “DisableAllScreenMessages” from the BeginPlay node.
Demo (VR)
Add Build to Headset
Take the APK and use the Meta Quest Developer Hub to add it to the headset by dragging the file into the Meta Quest Developer Hub after your headset is connected. If your headset is not connecting to the developer hub, make sure it has Developer Mode enabled.
Run Build from Headset
To access the App hit the Meta button on the right controller to open the main menu. Open Unknown Sources on the small menu on the left hand side with 3 dots and 3 lines. Once inside Unknown Sources you should see PoetryHouse as an option to launch. To recenter the camera at the start of the experience hit the menu button on the left controller to open the settings menu and hit the Reset VR Camera button.
Demo (PC)
When Unreal is done packaging the project to Windows, locate the packaged build on your computer, open it, click the Windows folder, and inside the Windows folder, double-click PoetryHouse.exe
If it fails to open or run, right-click this .exe file and choose Run as Administrator.
Technical and Project Details
Main Functionalities Table
Here is a table of the main existing features at the end of the Summer 2026 term and their level of completion:
| No. | Task | Priority | Status |
| F1 | Cat Companion | Nice to Have | Complete |
| F2 | Audio Layer | Nice to Have | Complete |
| F3 | Player Collision | Must Have | Complete |
| F4 | Player Movement | Must Have | Complete |
| F5 | Movement Tutorials | Must Have | Complete |
| F6 | Word Magnet Manipulation | Must Have | Complete |
| F7 | Word Magnet Collection | Must Have | Complete |
| F8 | Word Magnet Inventory | Must Have | Complete |
| F9 | Landing Page Transition | Nice to Have | Complete |
| F10 | Landing Page LLM Choice | Must Have | Partially Complete |
| F11 | Lobby Doors/Portals Logic | Must Have | Complete |
| F12 | Attic Music Player | Nice to Have | Complete |
| F13 | Attic Common Word Shelf | Nice to Have | Complete |
| F14 | Attic Sentiment Analysis | Nice to Have | Complete |
| F15 | Blackout Room AI-Generated Text | Nice to Have | Complete |
| F16 | Market Integration | Nice to Have | Incomplete |
| F17 | End Experience Integration | Nice to Have | Complete |
| F18 | End Experience Activity Room Callbacks | Nice to Have | Complete |
| F19 | End Experience Reflection Questions | Nice to Have | Complete |
General
GameInstance Manager
Persistence of information and objects throughout the different maps (lobby, blackout, maze, etc...) is vital for the experience. A prime example of what the GameInstance Manager is responsible for is the wordInventory system, allowing the player to take their words across maps. The GameInstance provides sole origin access to core features of the application such as managing network tasks (websockets for AI integrations), state management and context providing, all persistent throughout.
Because the GameInstance is accessible everywhere, this is a consistent pattern throughout the application to access the features of the GameInstance (Get Game Instance -> Cast to BP_GameInstanceManager):
There are 5 main categories to which the game instance provides functionality: Initializing WebSockets, Communicating with Websockets, Words Management, State Management, and Initializing Audio System. These are all partitioned inside of the GameInstanceManager.h file.
- Initializing Web Sockets: There is an initializer and a getter provided for each of the web sockets that are in use. To send messages or get a web socket in, it must be initialized first. The implementation is provided in C++, but most functionality can be called in Blueprints to provide easy access. Below is an example of both the init and get of the transcription web socket.
- Communication: Functions to communicate with the web sockets are also provided by the GameInstance Manager. These are: SendForSentimentAnalysis(), TranscribeUserInput(), ResistanceTextGeneration(), and TalkToNPC().
- Word Management: The struct FWordData defines the data of a word which the inventory stores. Currently it is only storing the string of the word, however, it can be extended to any other data needed to persist across maps. To store the data there are functions AddWord() and RemoveWord() which are implemented in the BP_GameInstanceManager.
- State Management: State management is largely decoupled from the GameInstanceManager. The main state management tasks of the Game Instance is Initialization, as pictured in the last node of Figure D.
- Initializing Audio System: The audio layer works as a decoupled event system. Systems in the project need only broadcast/call events to trigger any and all sounds. For this system to work and remain persistent, the GameInstance Manager constructs BP_GameEvents and spawns in BP_AudioManager.
Context State Machine
The implementation of this state machine came from 2 requirements: 1. Providing context for the task a player is currently on to the AI NPC, and 2. Providing a modular approach to gameplay in creating a linear experience. There are three abstract classes at the core of the context state machine:
- ContextStateMachine.h
- GameplayState.h (Blueprint Implementable)
- GameplayPhase.h: (Blueprint Implementable)
The context state machine is implemented by the DefaultStateMachine.h which provides functionality for setting states, phases, and transitions. The Blueprint-friendly nodes SetStateByClass, NextPhase, and SetPhase are essential to the DefaultStateMachine.
Because of the need for States and Phases to trigger events in levels, it is essential that a world context object is provided to states and phases. This is done by passing a reference to self as the outer in SetStateByClass. This can later be retrieved in a phase through:
GameplayState and GameplayPhase are both implemented through blueprints instead of C++. This enables designers to define strict user flows for levels which the developers can implement. Please note: States correspond to maps, and phases to tasks.
All states and phases are organized under /All/Content/Blueprints/GameplayStates.
For an approachable example into states and phases, consult: /All/Content/Blueprints/GameplayStates/Attic/Phases
Level Loader
In Unreal 5.5.4 they did not provide a blueprint function for “Load Level Async” which allows for the preloading of level before opening. This significantly decreases waiting times between levels.
The LevelLoader is a game instance subsystem which can be accessed anywhere (blueprints or C++). The subsystem has been created to be robust for different use cases. Nevertheless, the main use of the LevelLoader is for “Load Level Async”, which can be called with any kind of inference that the user will be transitioning to a new level.
For an example of how this system is used, consult the level blueprint of the LobbyMap and the blueprint graph following the UpdateCat custom event.
Cat Companion
The cat companion guides the player through the experience, explaining what to do in each room via speech bubble prompts. The cat is fully animated and can run around to pre-defined patrol points.
Relevant Assets / Unreal Things:
- BP_CatUI – The cat itself, including the speech bubble component. The speech bubble is automatically resized and position to always be (vertically) on screen, and easy to read at a distance.
- WBP_CatUI – The widget component for the speech bubble.
- BP_CatTreat – Used at the start of the experience to players used to moving objects around in VR. Collision detection for the treat is handled by BP_CatUI.
- BP_PatrolPoint – A dummy blueprint used defining the positions the cat can run to.
- BP_PatrolPointHandler – Tracks all the patrol points, and which patrol point it the current one.
States/Phases
Most of the levels in the project make use of states that hold different phases of each level for narration/continuity/guidance. Other than in the Lobby, phases occur in the order that they appear. These states are in Content/Blueprints/GameplayStates.
- BP_OnboardingState: holds the 4 different phases of the starting ArbutusOutdoor experience in order.
- Introduction Phase
- Movement Phase
- Hovering and Grabbing Phase
- Entering Playhouse Phase
- BP_AtticState: holds the 4 different phases of the Attic room that act as step-by-step tutorials.
- Opening Inventory Tutorial
- Using Inventory Tutorial
- Word Shelf Tutorial
- Making Poems Tutorial
- BP_BlackoutState: holds the 5 different phases of the Blackout room in order.
- Flashlight Collection Phase
- Ranting Phase
- Poem Creation Phase
- Pushing Words Phase
- End Phase
- BP_LobbyState: holds the 6 different phases of the Lobby. This level switches phases depending on where the player is in the room. There is a phase for when the player is near each of the activity rooms’ doors/portals, as well as a Lobby introduction and idle phase for when they are first entering the level, and when they are not near any door respectively.
Rendering
For performance and stylistic reasons, most rooms in the game are designed with an unlit style. This means all materials in these scenes are set to Unlit mode. The only exception is the BlackoutRoom where lighting is used on most materials.
A few important notes:
- Using the command line command viewmode unlit does not work on production builds (many work arounds were tried to get this to work, but none of them worked). To achieve an unlit aesthetic, you must set all materials to unlit mode.
- A PostProcessVolume is present in most scenes to manually set the Exposure of the scene.
Audio Layer
The project uses an event system to handle audio such as music, ambience, and SFX throughout the project. There are two main blueprints responsible for this system:
- BP_GameEvents (.../Blueprints/GameEvents/BP_GameEvents)
- An Object blueprint that is used as a global event dispatcher for the project. In the current version, it only has audio-related events, but it is intentionally designed to be a generic hub for event dispatchers. It is extensible for other gameplay events if needed in the future. The current events are as follows:
- OnPlaySFX(Name SFXName, Vector Location)
- OnPlayUISFX(Name UISFXName)
- OnChangeMusic(Name NextMusic)
- OnChangeAmbience(Name NextAmbience)
- A reference to this blueprint is constructed and stored by BP_GameInstanceManager.
- Currently, the event InitalizeAudioManager needs to be called in the BeginPlay of every level in the project for the AudioManager to exist in those levels. Proper level-persistence implementation is needed.
- An Object blueprint that is used as a global event dispatcher for the project. In the current version, it only has audio-related events, but it is intentionally designed to be a generic hub for event dispatchers. It is extensible for other gameplay events if needed in the future. The current events are as follows:
- BP_AudioManager (.../Blueprints/AudioManager/BP_AudioManager)
- An Actor blueprint that owns the main AudioComponent, sound assets, and audio functions.
- Never directly listens to gameplay actors/systems. Instead, binds to events from BP_GameEvents and handles them with functions and custom events that play the appropriate sounds when they are fired. The current functions are as follows:
- HandlePlaySFX(Name SFXName, Vector Location): Plays a one-shot sound at the specified Location, using the sound asset whose name matches the SFXName parameter.
- HandlePlayUISFX(Name UISFXName): Plays a one-shot sound in 2D using the sound asset whose name matches the UISFXName parameter.
- HandlePlayMusic(Name MusicName): Fades in a music sound in 2D
- HandlePlayAmbience(Name AmbienceName): Fades in an ambient sound in 2D
- Sounds that need to be faded out/in (specifically the Music and Ambience sounds) are handled by custom events instead of functions so that the Delay node can be used with the fading in events and Fade Out nodes. These are the custom events that handle audio:
- HandleOnChangeMusic(Name NextMusicName)
- If the MusicAudioComponent is currently playing nothing, then this fades in the music whose name matches the NextMusicName parameter.
- If the MusicAudioComponent is currently playing something, then this fades out the current music, and fades in the music whose name matches the NextMusicName parameter.
- HandleOnChangeAmbience(Name NextAmbienceName)
- Works the same as HandleOnChangeMusic, but works with AmbienceAudioComponent instead of MusicAudioComponent and fades in the ambient sound whose name matches the NextAmbienceName parameter instead of NextMusicName.
- HandleOnChangeMusic(Name NextMusicName)
- The sound assets are organized in maps with entries of type Name → Sound Base. Each function described in 2b accesses the aptly named sound map. To add new sounds, simply place them in the appropriate map. These are the current maps:
- SFXMap: sound effects that need to be spawned in specific locations
- UISFXMap: sound effects that play in 2D
- MusicMap: base background music for levels, plays in 2D
- AmbienceMap: base ambience sounds for levels, plays in 2D
Demonstrating how a system calls an audio event:
- An example Actor called BP_AudioTester (.../Blueprints/AudioManager/BP_AudioTester) has been provided to show how to use the audio event system. This blueprint is spawned inside the ToolsPlayground level (.../Maps/ToolsPlayground) for demonstration purposes.
- The general steps are to:
- Get the GameInstance Manager
- Get GameEvents from the GameInstance Manager
- Call an event (such as OnPlaySFX) from the GameEvents
In the interest of consistency, please ensure all sound files follow the naming convention of: [TYPE]_[Name] and are stored in Content/Assets/Audio. For example:
- Sound effects: SFX_CatRemark1
- UI sound effexts: UISFX_BubblePop
- Ambient sounds: AMB_ArbutusOutdoor
- Music sounds: MUS_StarryNight
To save memory and space, most SFX were converted from Stereo to Mono track using Audacity. This can be done by:
- Opening the .wav file in Audacity
- On the top toolbar, go to Tracks → Mix → Mix Stereo Down to Mono
- Then, go to File → Export Audio and ensure you export to WAV format. The default settings when exporting can be left as is.
Sounds have been cleaned up and uses this system now, other than the Music Player in the Attic.
Player
Movement / Collision (VR)
Users can teleport around a predefined and automatically generated Nav Mesh using the left joystick. The player can rotate the camera using the right joystick.
- VRPawn: Teleportation code is handled inside the VRPawn – it comes from the VRTemplate. Snap camera rotation also comes from this template.
- IMC_Default+RadialMenu: Where the controls are defined.
- Unreal's NavMeshBoundsVolume/NavModifierVolume - used to automatically generate a nav mesh for the player to move around. The player cannot move without a nav mesh.
Movement / Collision (Desktop)
Users can move the character with the keys [W] (forward), [A] (left), [S] (backward), [D] (right) and [Spacebar] (jump). They can choose to move the camera with the mouse or the arrow keys.
- BP_FirstPersonCharacter: Movement is handled inside this blueprint under the Movement, Jump, and Look input action events.
- Both character and camera movement sensitivity have been lowered significantly to reduce nausea.
- IMC_PCMovement: Where the controls are defined.
The NavMeshModifierVolumes added for collision in VR work as collisions in the desktop build, meaning if a VR character cannot reach a certain area, the Desktop character will not be able to as well.
Users can press [C] to bring up a controls cheat sheet that will show them instructions for how to move, among other controls.
UI Interaction (VR)
Users can interact with in-world UI elements by pointing either controller at the UI element and using the trigger buttons to click.
- VRPawn: Uses a WidgetInteractionComponent to interact with UI. The triggers simulate a left click on the UI in the Trigger Pressed events.
UI Interaction (Desktop)
Users can interact with in-world UI elements by pointing the middle dot at the element, using the [Left Mouse Button] for selection and [Scroll Wheel] for scrolling up and down on the common word shelf.
- BP_FirstPersonCharacter: Has a WidgetInteraction component. Uses the LeftMouseButton and MouseWheelAxis event nodes for selection and scrolling respectively.
Word Magnets
Word Manipulation (VR)
Users can grab words using the triggers on either controller.
- VRPawn: Word grabbing is mostly handled in the EventTick. It’s split into 2 main phases – one for detecting words using a sphere cast (TraceForWords function), and the other for updating the positions of any grabbed words (StepGrabbedWord). The actual grabbing of words is handled by the Trigger Pressed events in the VRPawn.
- IMC_Default+RadialMenu: Where the controls are defined.
- BP_WordMagnet: The physical words a player can grab.
Word Manipulation (Desktop)
Users can grab words by aligning the persistent middle dot with the word and holding the Left Mouse Button (LMB).
- BP_FirstPersonCharacter: Word grabbing is handled by the GrabPressed and GrabReleased input action events.
- IMC_PCWordMagnetBehaviour: Where the controls are defined.
- GrabComponent: Holds the TryGrabPC function that the desktop version uses to grab words.
- BP_WordMagnet: The physical words a player can grab. This holds the Grab Component that makes it grabbable in the desktop version.
Word Collection (VR)
Players can collect words into a word basket, which shows up on the opposite hand from the one where they are grabbing the word. Words animate into the basket when players point a grabbed word at the basket.
- VRPawn: The basket is not its own blueprint. Instead, it is a component on the player. The Basket is detected using the same sphere cast as in TraceForWords. The animating of words moving into the basket is handled by StepPercentInBasket and StepGrabbedWord. Word Collection is handled by the TryWordRelease function.
- BP_WordInventory: A blueprint for adding words to persistent inventory handled by BP_GameInstanceManager.
- IMC_Default+RadialMenu: Where the controls are defined.
Word Collection (Desktop)
Players can collect words into a word basket, which shows up in front of and slightly below the character when they are grabbing a word. Words shrink when they are hovered over the basket to indicate collection.
- BP_FirstPersonCharacter: The basket is not its own blueprint. Instead, it is a component on the player called CollectionBox. The visibility of the basket is handled by GrabPressed and GrabReleased. The actual collection is handled by the BeginOverlap and EndOverlap for the CollectionBox.
- BP_WordInventory: A blueprint for adding words to persistent inventory handled by BP_GameInstanceManager.
Word Inventory (VR)
The player has a persistent word inventory that can be opened with the left grip button.
- BP_WordInventory: A blueprint for adding words to persistent inventory handled by BP_GameInstanceManager.
- BP_WordMenu: The blueprint for the physical UI. It is spawned in / destroyed when the player opens the inventory.
- W_WordButton: Handles the spawning of BP_WordMagnets when the player clicks on the button.
Word Inventory (Desktop)
The player has a persistent word inventory that can be opened with the [Tab] key.
- BP_WordInventory: A blueprint for adding words to persistent inventory handled by BP_GameInstanceManager.
- BP_FirstPersonCharacter: Holds a BP_WordInventory component and handles the spawning/destroying of the UI in the InventoryPressed and InventoryReleased input action events respectively.
- W_WordButton: Handles the spawning of BP_WordMagnets when the player clicks on the button.
- IMC_PCWordMagnetBehaviour: Where the controls are defined.
Highlighting (VR)
In the Blackout Room players highlight words instead of grabbing them at the start.
- VRPawn: Whether the player picks up words or not is decided by a boolean bHasFlashlight. The flashlight itself is not its own blueprint, but a component on the player.
- BP_WordMagnet: A word is highlighted based on the bIsHighlighted flag.
Highlighting (Desktop)
In the Blackout Room players highlight words using the key [H] instead of grabbing them at the start.
- BP_FirstPersonCharacter: Word highlighting is handled by the HighlightWord input action event.
- IMC_PCBlackoutRoom: Where the controls are defined.
Word Pushing/Pulling (VR)
Players can optionally adjust the rest length of their word grab using the joysticks when grabbing a word.
- VRPawn: The adjustment of the word length is handled by Move Word Away (joystick) inputs and stored in the TargetWordManipulationLength variables. The effective distance of word manipulation is defined as the shorter of the TargetWordManipulationLength and the distance to the nearest wall – this is handled by the UpdateWordManipulationLength function.
- IMC_Default+RadialMenu: Where the controls are defined.
Word Pushing/Pulling (Desktop)
Players can push a word away or pull it closer while grabbing them by using the [Scroll Wheel] or the keys [,] (Comma) and [.] (Period).
- BP_FirstPersonCharacter: The adjustment of the word location is handled by the PushPull input action event.
- IMC_PCWordMagnetBehaviour: Where the controls are defined.
Word Scaling (VR)
Players can scale words up and down by grabbing the same word with both controllers and creating a zooming motion, similar to a touch screen interface.
- VRPawn: This logic is handled in the StepGrabbedWord function.
Word Rotating (Desktop)
Players can rotate a word around while grabbing it by continuing to hold [Left Mouse Button] and holding [Right Mouse Button]. Moving the mouse while holding these two buttons will rotate the word.
- BPFirstPersonCharacter: The rotation of the word is handled by the RotateWord input action event.
Landing Page / Outdoor (ArbutusOutdoor)
There is a landing page for players to help players get used to being in a VR environment and to ground their experience from the start in general.
Landing Page Transition
The player has a SkySphere that follows them around. The landing page transition is handled by expanding this SkySphere from a small size.
- VRPawn and BP_FirstPersonCharacter: The BP_SkySphere is a child actor of the player.
- BP_SkySphere: A skysphere with an adjustable radius.
Gate
The gate walls are made of circular meshes, where the textures are based on Judith Copithorne’s concrete poems. The main rendering feature is overlaying the poems into a two-sided material. This is done so we can add multiple types of poems in different sizes.
- M_Lattice_Start_Page: The material for the blue scrolling poems. Currently, the set images are based on one concrete poem per wall (just scaled in different sizes at some parts). However, the feature of overlaying different poems is in there for the team to calibrate.
- BP_Gate: The gate itself. Note that there are four layered textures and 22 gate walls. This is structured like a flat array. For the UVScaling array is a Vector4 array, where each scalar value is based on Tex1 to Tex4 of each wall.
Opening LLM Choice
The player has three choices at the beginning of their experience that determines how the text generation in the Blackout Room will work for them:
- ChoiceA Using OpenAI’s LLM: ChatGPT will generate the text
- ChoiceB Training a UBC-hosted LLM: UBC Sandbox will generate the text
- ChoiceC Not using AI at all: A default text will be provided for them
This is intended to see how users will interact with or choose to use AI in their own experience of the project.
The blueprints responsible for this choice are:
- BP_ChoiceManager: Holds three BP_ButtonGate child actors to represent the three choices. Also holds three functions: ChooseA, ChooseB, ChooseC which set the ChoiceMade variable on GameInstanceManager to one of the choices provided by the E_ButtonChoice Enum.
- BP_GameInstanceManager: has a variable that stores ChoiceMade.
- BP_ButtonGate: On press, it calls the corresponding function on BP_ChoiceManager.
Tutorial
The tutorial is handled mostly using the Cat Companion and State/Phase system. The purpose of the tutorial is to get the player used to moving around and interacting with the BP_WordMagnet.
- BP_MoveGuidance: An instance of VR controllers which appears to show the player how to teleport and snap rotate.
Lobby (LobbyMap)
Portals/Doors
There are four doors in this level that act as portals to the different activity rooms and the exit.
- BP_BlackoutEntrance: This loads the BlackoutRoom level.
- BP_AtticEntrance: This loads the Attic level
- BP_MarketEntrance: This loads the Market level.
- BP_LobbyExit: This loads the New_End_Experience level.
- LobbyMap Level Blueprint: This handles portal logic. The Blackout Entrance, Attic Entrance, and Market Entrance all only unlock 10 seconds after the player steps in front of the word wall, to encourage interaction with the poem word wall. The Lobby Exit only unlocks after the player has visited all activity rooms.
- Important: The Lobby Exit has an override setting to toggle its locked/unlocked state, which can be accessed in the desktop version by pressing [Esc] or [Q] and in VR by pressing the [Menu] button.
Word Wall
The functionality of the word wall is split between a blueprint and a C++ class. The wall generates the Another Order poem in a random order each time the player enters the room (except for the first time).
- LobbyMap Level Blueprint: The generation of the randomized poem order is done by the level blueprint.
- BP_WordWall: The wall blueprint. Most of the functionality lies in the cpp class but this holds an event dispatcher called Interacted that helps unlock the portals mentioned above.
- WordWall.cpp: Handles the spawning of BP_WordMagnet objects, one after the other.
Attic
Tracklist / Music Player
The Attic has a music player modeled after a jukebox that plays certain tracks based on different emotions. The emotions are: Joy, Sadness, Anger, Fear, Love, Surprise, and Neutral.
- BP_Tracklist: The music player blueprint. This holds the audio component that plays tracks in the Attic and owns a BP_TracklistMenuV2 child actor component.
- BP_TracklistMenuV2: Holds the widget that represents the music player. Organizes the layout of tracks.
- W_Tracklist: Holds the 7 audio tracks corresponding to different emotions.
The music player is affected by and controlled by the adaptive audio manager. See Section D.7.3.2. for more details. This control can be enabled/disabled on the music player itself.
Common Word Menu
The common word menu gives lists of common and evocative words sorted by starting letter. Users can grab words from the wall to help in the creation of their poems.
- BP_CommonWordShelf: The 3d model of the shelf, with the physical UI as a child actor.
- BP_CommonWordMenuV2: The physical UI.
- W_CommonWordUI: The UI widget for the actual word menu. Handles the spawning of the buttons on the menu based on the available words.
- W_WordButton: Handles the spawning of BP_WordMagnets when the player clicks on the button.
- W_AlphabetFilterButton: Buttons used for choosing the starting character on the CommonWordUI.
- CommonWords: A datatable storing all the available words in the table. The common words were pre-generated using ChatGPT, with additional evocative words added by the PIs.
Sentiment Analysis
Adaptive Lighting
In order to speed up sentiment analysis and make it more interactive, we processed the sentiment assigned to each word before runtime. This is done through a python script, the code for which can be found here: https://github.com/ubcemergingmedialab/emotion_analysis
This script receives a csv file of words included in the app, and then uses DAIR and Borealis datasets to assign emotions to each word. Those then get imported to two datatables: DT_EmotionsColors for each emotions' assigned color, and DT_WordsEmotions for each word's emotion scoring.
The lights in the Attic are driven by WordMagnet Actors being placed inside of a trigger box placed in the room over the carpet. The words added get tracked as a sliding window, each light representing the sentiment of one word in the trigger box. As new words get added, all the lights in the room change to the blended emotion color of the new word. There is one main blueprint responsible for this, BP_RoomLightManager.
- BP_RoomLightManager: has two collider events for when a WordMagnet enters and leaves the trigger box. Keeps an array of string to serve as a history of words, references to all the StencilLIghts in the room, and references to the data tables defining word sentiment, number of lights and a neutral color. A few helper functions coordinate this:
- RefreshLights: The core of the logic for the lights, gets called every time the words in the trigger box change. Looks at the last word added in WordHistory and changes all the lights to the color.
- BlendEmotionColor: responsible for taking the values of colors associated with each emotion and the emotion values of a given word and blending them using the scores to weigh each emotion color.
- ChangeColor: takes the blended color and calls a function on a given StencilLight to make it animate towards the desired color.
Adaptive Audio
The music player in the room automatically fades in tracks according to the analyzed sentiment of the words on the carpet. This feature can be enabled/disabled on the music player. Disabling this feature means the player can still choose to play a track, there just won’t be any more sentiment analysis automatically changing the track.
- BP_RoomTrackManager: Determines what track to play on the music player based on the StrongestEmotion. The StrongestEmotion is determined by the emotion with the highest number of words corresponding to it. Fades in a new track every time the StrongestEmotion changes.
- DT_WordsEmotions: holds words and their corresponding emotion mappings
- DT_EmotionsTracks: holds one track for each of the 7 emotions present in the Tracklist.
Stencil Lights
The attic’s uses fake stencil lights to create a sentiment analysis feedback signal. Previously, the feedback calibrated from a post-processing tint; we transitioned it to in-level lights instead.
- M_StencilLight: is an additive unlit material without depth testing, which then uses the scene depth buffer to reconstruct the world position behind each texel and decide whether that texel should be lit or not. It is not a true stencil light by any means; it was just named that way as the style was derived by using the renderer’s stencil buffer.
- MF_WorldPosFromDepth: The core methodology here is reconstructing the world position from the scene depth. A material function called MF_WorldPosFromDepth is created to read the depth buffer for the current pixel, combines it with camera vectors, and reconstructs the world-space position of the environment behind the sphere. Because the scene depth is perspective-related, we use the camera direction vector to correct it.
- Game/Materials/Attic/Unlit/ToonLights/FauxStencil: Other assets
Blackout Room
Audio Input
This transcription functionality is well documented at EML. The connection to the AIM DxL server is a series of bytes in the form of an audio file (.wav, .webm, etc...) with the prefixed tag [TTS]. The bytes are formatted directly out of the audio capture component through a custom extension with a function getBytes() which returns collected bytes of recorded audio as a .wav file. This allows for the microphone component to directly send to the server and allows for separation of where the text is returned to. There are multiple applications for speech to text, such as input into the resistance text generator and asking questions to NPCs in game.
Separation in functionality comes from multiple delegates in the transcription WebSocket, where an enum of TranscriptionType (text generation or UI interaction) is set before making requests. This provides two different items which can be used to send in transcription requests: the microphone and the cat translator. Given that the same socket can be used for different cases, this web socket is open throughout the entire application.
- ByteAudioCapture: This is an extension to the Unreal component AudioCapture which provides the ability to call GetCapturedBytes() to access the bytes directly after they are recorded. This is essential for the transcription to send the bytes directly from memory instead of writing a file before sending. This prevents race conditions where you are trying to send from the file before it is written completely.
Transcription
This transcription functionality is well documented at EML. The connection to the AIM DxL server is a series of bytes in the form of an audio file (.wav, .webm, etc...) with the prefixed tag [TTS]. The bytes are formatted directly out of the audio capture component through a custom extension with a function getBytes() which returns collected bytes of recorded audio as a .wav file. This allows for the microphone component to directly send to the server and allows for separation of where the text is returned to. There are multiple applications for speech to text, such as input into the resistance text generator and asking questions to NPCs in game.
Separation in functionality comes from multiple delegates in the transcription WebSocket, where an enum of TranscriptionType (text generation or UI interaction) is set before making requests. This provides two different items which can be used to send in transcription requests: the microphone and the cat translator. Given that the same socket can be used for different cases, this web socket is open throughout the entire application, as it is essential for communication with the NPC and other central features.
AI Text Generation
A guiding principle for the blackout room is the concept of being able to resist a text, mirroring the practice of blackout poetry which uses creativity to resist and subvert the meaning of a text. Therefore, we needed a dynamic method of generating text that the user can resist as they go through the blackout room. Text generation happens one of two ways, dependent on the value of the ChoiceMade variable in GameInstanceManager. If the choice is ChoiceA, the app uses the OpenAI middleware provided by Digital Experience Lab to connect to an OpenAI model. If the value is ChoiceB, instead the LLM used is hosted by UBC through the LiteLLM Sandbox. The LiteLLM Sandbox does not provide transcription though, so the OpenAI API is stil used to turn the users' speech into text input for the UBC LLM.
If the value is ChoiceA, the game makes use of the WebSockets plugin to set up the role and chat completion. GameInstanceManager is responsible for sending roles, stored in TextGenerator.h.
Once the role is set up in the initial websocket instantiation, any subsequent calls are only the transcribed passage from the user expressing things which they hate.
If the value of the ChoiceMade variable is ChoiceB, the Blackout Room blueprint flow instead sends a request to the UBC Sandbox API, with the same role definition, sending a single http request for a chat completion, using the text received from the transcribed audio.
Word Pushing
Players can push unwanted words away in the Blackout Room after they are done highlighting them.
- VRPawn: Pushing gesture detection is handled by the TrackPush event (called on EventTick). The PushWordsGesture function keeps track of a history of the hands positions to get an average velocity over the last 10 ticks.
- BP_FirstPersonCharacter: Pushing is handled by the PushBlackoutRoom input action event. There are two ways to push: by moving the mouse forward with significant force, or by pressing [P].
- IMC_PCBlackoutRoom: Where the controls are defined.
- WordWall.cpp: Handles the logic for actually launching the words away.
Market
The Market room is designed to represent an overload of information, or chaos scattered around a labyrinthian mind. Users are given 6 different stalls with different words to “purchase” or keep, but they are only able to keep a set number (5) of words. This prompts users to reflect on what ideas are important to them. Each stall contains words taken from various Copithorne poems, and each stall is sorted based on abstract themes.
- Market Level Blueprint: handles the count of words collected and disabling word collection when max is reached
- BP_GameInstanceManager: stores a MarketCount int variable to keep count persistent
- BP_FirstPersonCharacter: holds the CanCollectWords flag for Desktop
- BP_VRPawn: holds the UpdateCanGrabWords setter for VR
Stall Music
Each stall has a dedicated theme with accompanying music. When the user gets close to each stall, they hear the music dedicated to that stall.
- BP_Stall: holds the audio component that plays music when you are near a stall
- StallAttenuation: sound attenuation component created specifically for Market stalls
End Experience
Activity Room Callbacks
When the user first spawns in this room, they are presented with a hallway that has portraits of their specific activity room experiences. To their left are pictures of the Lobby and the Attic. To their right are pictures of the Market and Blackout Room.
- BP_DisplayPhoto: Creates and holds a WBP_CapturedPhoto based on Level to be Displayed, which is exposed and editable in the editor.
- WBP_CapturedPhoto: Holds the image to be displayed.
- BP_PhotoCamera: The actual camera actor that is placed in the activity room levels to take photos.
- AC_PhotoCamera: Deals with the mechanics of taking a photo and creating a file name for it based on which level it is placed in or from. Saves photos in the user’s project folder under Saved/Screenshots (creates this folder if it does not exist).
Reflection Questions
In the water area room at the end of the hallway, the walls have two randomized reflection questions for the user to answer outside of the experience.
- BP_EndQuestions: Holds and displays a WBP_EndQuestions.
- WBP_EndQuestions: Has the widget and actual functionality for getting a random question to display.
- DT_EndQuestions: Holds the reflection questions. There are 20 in total.
- BP_GameInstanceManager: Has a QuestionCounter integer variable to keep count of how many questions (or how many WBP_EndQuestions) have been spawned.
Final Exit Word Magnet
Sometime after entering the water area, a final exit word magnet appears with the words “Fly Free?” Grabbing this word magnet makes the camera fade to white and ends the experience by quitting the game.
- BP_WordMagnet_Ending: Holds the word magnet that fades the camera to white and quits the game
- BP_WaterArea: Handles the timing of the button appearing. Once the user enters this boxed area, a delay of 15 seconds begins. The EndExit button appears after this delay.
Legacy Content
There are few legacy items still in the codebase, which you might find remnants of.
Items
Originally there was a bunch of different items players could grab (e.g. the flashlight was originally its own unique blueprint item). Through playtesting it was found that most of these items didn't really serve the experience very well as they were too complicated to use for new VR users.
Radial Menu
To switch between items there was an old radial menu which could appear on the player's right hand. With the removal of the items, the radial menu was also disabled.
Buttons
There were originally buttons in the Blackout Room, Attic Room, and End Experience. The Blackout Room button enabled word pushing, the Attic Room button enabled sentiment analysis, and the End Experience room exited the game. After room redesigns and reworks, these were no longer needed and removed.
Design Methods
User Flows
The team used personas and user flows to guide the design of the application in Figma. With the given design inspirations provided, the team created room concepts, style guides, and high-fidelity prototypes of the experience in Figma. Blender and Unreal Engine were used to create custom assets and design the in-game environments.
To collect user feedback, the team conducted one major playtest session in September of 2025. This playtest session consisted of an observation session and a follow-up questionnaire.
Experiment Protocol
Participants were instructed to complete the VR experience from start-to-finish (at the time, this consisted of the Arbutus Outdoors, the Lobby, and the Blackout Room). Observers were able to provide clarifications on controls and next steps as needed. Participants were allowed to pre-emptively stop the experience, whether due to crashes, nausea, or reluctance to engage with AI. Data such as completion time and notable struggles were recorded during observation.
After completing the experience, participants were then given a questionnaire to fill out. The questionnaire collected data regarding participants' perspectives on the various successes and failures of the experience.
Development
Technical Components
The following table describes the software used for design and development:
| Tool | Description |
| Unreal Engine | Game engine, version 5.5.4 |
| Blender | 3D modelling software to design custom assets |
| Maya | 3D modelling software to design custom assets |
| Meta Quest 3 | Virtual Reality headset used during development |
| Figma | Design and prototyping software used to design wireframes and UI components |
| Audacity | Audio software to edit tracks |
| ChatGPT | GPT4 and GPT4o were used for development, https://chatgpt.com/g/g-f52QYAJK1-unreal-engine-5-expert Unreal Engine specific GPT. |
Issues/Bugs
Blackout Room Poem Pushing
When prompted to push the VR controllers forward at the end of the blackout room, the 'push' movement is unreliably detected. We have seen instances of the words disappearing when users failed to push their controllers; we have seen instances of the words staying when users did push their controllers.
Future Considerations
Recommendations for future development of this project:
Usability Playtests
It is recommended that more usability tests are conducted to evaluate the effectiveness of the term’s changes.
Rendering Changes
Certain shading model changes could make the project look better.
- Shadowing of the unlit levels: If levels may stay unlit, using even a lambertian diffuse for the materials can add depth to the meshes.
- Having actual point lights in the attic by switching to Lit may be feasible in terms of performance
- Use a better water shader for the pond in Arbutus Outdoor. Currently it uses a panning texture only. Considering using a normal map and noise map for distortion.
- Grass rendering
Settings for Future Contributions
For assets called from asset registry libraries (eg. GetAssetRegistry()), add the path directory in Project Settings>Packaging>Additional Asset Directories to Cook if Cook everything in the project content directory (ignore list of maps below) is unchecked/does not work on your packaged application.
Poster

Team Members
Principal Investigators
Dr. Bronwen Tate
Associate Professor of Teaching
School of Creative Writing
University of British Columbia
Jen Moss
Lecturer
School of Creative Writing
University of British Columbia
A.E. Osworth
Lecturer
School of Creative Writing
University of British Columbia
Ray Clark
Subject Matter Expert
Team
Dana Angela Neria, Project Lead, Developer (February 2026 – August 2026)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Arts in Cognitive Systems, Minor in Applied Music Technology
University of British Columbia
Johannes Soderstrom, UI/UX Designer (July 2026 – August 2026)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Arts in Architecture, Minor in Applied Music Technology
University of British Columbia
Shivangi Singh, UI/UX Designer (September 2025 – July 2026)
Work Learn at the Emerging Media Lab at UBC
Master in Fine Arts, Design & Production
University of British Columbia
Daniel-Hyun Yoo, Developer (September 2025 – December 2025)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Science in Computer Science
University of British Columbia
Toby Song, UI/UX Designer (September 2025 – April 2026)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Arts, Combined Major in Psychology and Visual Art
University of British Columbia
Jaq Ai, Project Lead, UI/UX Designer (May 2025-April 2026)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Science in Computer Science, Minor in Arts
University of British Columbia
Amy Yiqin Li, UI/UX Designer (May 2025-April 2026)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Arts in Visual Art
University of British Columbia
Daniel Lima, Developer (May 2025-August 2025)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Commerce, Combined Major in Computer Science and Business
University of British Columbia
Mariane Olivan, Project Lead, UI/UX Designer (May 2024- August 2024)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Science in Cognitive Systems
University of British Columbia
Walker Rout, Developer (May 2024- August 2024)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Science in Cognitive Systems
University of British Columbia
Jiho Kim, UI/UX Designer (May 2024- August 2024)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Science in Cognitive Systems
University of British Columbia
Samia Sajid, UI/UX Designer (May 2024- June 2024)
Work Learn at the Emerging Media Lab at UBC
University of British Columbia
James Edralin, Developer (May 2024- August 2024, September 2025-April 2026)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Science in Computer Science
University of British Columbia
Julien Roy, Developer (May 2024- August 2024)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Science in Computer Science
University of British Columbia
Ameya Goel, UI/UX Designer (May 2024- August 2024)
Work Learn at the Emerging Media Lab at UBC
Undergraduate in Bachelor of Science in Computer Science
University of British Columbia
Staff
Phoebe Titus, 3D Generalist (July 2025 – August 2026)
Dante Cerron, Staff Developer (September 2025 – August 2026)
Ahren Spadinger-Fengler, Co-op Developer (May 2026 – August 2026)
Julien Roy, Project Lead, Co-op Developer (May 2025 – August 2025)
Frederik Svendsen, Staff Developer (May 2024 – August 2024)
Asset Credits
“Ancient Bird Carpet” (Ancient Bird Carpet | Fab) by Get Dead Entertainment is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
"Antique Desk" (https://skfb.ly/o8Ptu) by Matthew Collings is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Assetsville Town” (https://www.unrealengine.com/marketplace/en-US/product/assetsville-town) by Assetville is licensed under Epic Content License Agreement (https://www.unrealengine.com/en-US/eula/content).
"Bed low poly" (https://skfb.ly/oJnsY) by santifaster12 is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Big Crate” (https://www.fab.com/listings/6403b02c-8a96-44c9-bf9b-f80cd0cf958c) by Polytricity is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Cash Register” (https://sketchfab.com/3d-models/cash-register-f2bda073f6604ebeb22ee110f3be929a) by Andrianna is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Coffee Menu” (https://skfb.ly/oyGxA) by Zende is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Flashlight free icon” (Flashlight Icons & Symbols) by Aranagraphics is licensed under Flaticon License.
“Floor Lamp” (https://skfb.ly/otBoo) by Agha.Najam is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Gun free icon” (Search results for Gun - Flaticon) by Freepik is licensed under Flaticon License.
“House Plants: (https://sketchfab.com/3d-models/house-plants-1347ac536bea469281cc07d68e076654) by Nicolai Kilstrup is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Lantern 01 4k” (Lantern 01 4k | Fab) by Mo Hussien is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“light switch and Bulb” (light switch and Bulb | Fab) by Roy Sousa is licensed under Fab Standard License (Fab).
“Marker free icon” (Search results for Highlighter - Flaticon) by Freepik is licensed under Flaticon License.
"Messy Tack Board" (https://skfb.ly/6TLMA) by M.Reslan is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Modular Building 1st Floor Kit” (Modular Building 1st Floor Kit | Fab) by Quixel is licensed under Fab Standard License (Fab).
“Modular Stair and Handrail Kit” (Modular Stairs and Handrail Kit | Fab) by Quixel is licensed under Fab Standard License (Fab).
“Paintbrush free icon” (Search results for Paintbrush - Flaticon) by Us and Up is licensed under Flaticon License.
“Palm free icon” (Search results for Hand - Flaticon) by Icon Mart is licensed under Flaticon License.
“Paw free icon” (Search results for Paw - Flaticon) by Mihimihi is licensed under Flaticon License.
"Rendered Coffee Cup" (https://skfb.ly/o7DMZ) by cosmic.arin is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Rustic black table” (https://sketchfab.com/3d-models/rustic-black-table-662be11a0fa14fa9875c8fa7c072fe07) by Axonite is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
"Scattered Papers" (https://skfb.ly/opFKv) by aysenaz is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Simple Wood Planks” (Simple Wood Planks | Fab) by SachinGames is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Stained Wooden Ceiling” (Stained Wooden Ceiling | Fab) by Quixel is licensed under Fab Standard License (Fab).
“Wooden Spiral Staircase” (https://sketchfab.com/3d-models/wood-spiral-square-staircase-b534dd7d38b74874a6549bf32c08be49) by Sereib is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Beautiful Ambient Melody” (https://freesound.org/people/PatrickLieberkind/sounds/341541/) by Patrick Lieberkind is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
“Outdoor Food Market Ambience” (https://freesound.org/people/miaopolus/sounds/656274/) by miaopolus is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
License
MIT License
Copyright (c) 2026 University of British Columbia
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|