Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SpectaRacer

SpectaRacer is a car racing themed endless runner lens for Snap Spectacles. Three lanes of enemies and collectibles scroll toward the player at an ever-increasing speed. Survive as long as possible, rack up coins, and track your distance: all overlaid on your real environment.


SpectaRacer.mp4

How to Play

Point and/or pinch and hold:

  • Top yellow part of the steering wheel and steer right/left to move the car.
  • Bottom part of the steering wheel to adjust the level position.

Gameplay

Action Result
Drive into a coin Score increases, coin SFX plays
Collide with an enemy Damage +33%, crash SFX plays
Three enemy hits Game over panel appears
Press pause button Game freezes, VFX pause, pause panel opens
Resume / Restart / Main Menu All wired through the pause overlay

Three enemy collisions end the run. The game over screen appears automatically and offers a one-tap restart or return to the main menu.


Features

  • 3-lane endless runner with procedurally selected enemy/coin patterns
  • Adaptive difficulty: speed and pattern complexity ramp smoothly over 120 seconds
  • Full game loop: main menu β†’ gameplay β†’ pause β†’ game over β†’ restart, with no reloads
  • Live HUD: real-time damage percentage, distance in metres, and coin score
  • Weather system: toggle an environmental effect and its rain VFX mid-game; rain audio tracks the switch state
  • Spatial audio: distinct coin collect and crash sound effects; looping ambient rain that respects both menu and weather toggle state
  • Complete menu system: four panels (main menu, pause, tutorial, game over) wired entirely in TypeScript with no Inspector callbacks
  • Singleton architecture: PlayPauseManager and SnapSaberGlobalManager accessible from any script via getInstance()

Project Structure

SpectaRacer/
β”œβ”€β”€ Assets/
β”‚   β”œβ”€β”€ Examples/
β”‚   β”‚   β”œβ”€β”€ CustomUI/               # UIKit visual customisation scripts
β”‚   β”‚   β”‚   β”œβ”€β”€ UIKitCustomVisualsRectangleButton.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ UIKitCustomVisualsRoundButton.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ UIKitCustomVisualsFrame.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ UIKitCustomVisualsSlider.ts
β”‚   β”‚   β”‚   └── UIKitCustomVisualsSwitch.ts
β”‚   β”‚   └── MiniDemos/SnapSaber/TS/ # Core game scripts
β”‚   β”‚       β”œβ”€β”€ MainMenu.ts
β”‚   β”‚       β”œβ”€β”€ PlayPauseManager.ts
β”‚   β”‚       β”œβ”€β”€ SnapSaberGlobalManager.ts
β”‚   β”‚       β”œβ”€β”€ SnapSaberCollisionHandler.ts
β”‚   β”‚       β”œβ”€β”€ LanePatternController.ts
β”‚   β”‚       β”œβ”€β”€ CoinInstantiator.ts
β”‚   β”‚       β”œβ”€β”€ EnemyInstantiator.ts
β”‚   β”‚       └── RoadInstantiator.ts
β”‚   β”œβ”€β”€ Materials/                  # PBR materials
β”‚   β”œβ”€β”€ Prefabs/                    # Coin, enemy, and road tile prefabs
β”‚   β”œβ”€β”€ Shader/                     # Custom .ss_graph shaders
β”‚   └── Scene.scene
β”œβ”€β”€ Packages/
β”‚   β”œβ”€β”€ SpectaclesInteractionKit.lspkg
β”‚   └── SpectaclesUIKit.lspkg
β”œβ”€β”€ Support/
β”‚   └── StudioLib.d.ts              # Lens Studio API type declarations
└── tsconfig.json

Architecture

Core Scripts

Script Responsibility
MainMenu.ts Panel navigation (4 panels), level enable/disable, rain audio, weather toggle, game lifecycle
PlayPauseManager.ts Pause/resume/restart, HUD output (damage %, distance, score), game-over detection, VFX freeze
SnapSaberGlobalManager.ts Singleton: score tracking, damage strikes, coin/crash SFX
SnapSaberCollisionHandler.ts Car collision detection, routes hits to GlobalManager
LanePatternController.ts Wave-based pattern selection across 3 lanes, speed ramp, pause/resume/reset
CoinInstantiator.ts Spawns and moves coin prefabs; end-zone cleanup via overlap collider
EnemyInstantiator.ts Spawns and moves enemy prefabs; end-zone cleanup via overlap collider
RoadInstantiator.ts Continuously spawns road tile prefabs at 0.5 s intervals

Design Patterns

Singleton via global scope: SnapSaberGlobalManager and PlayPauseManager register themselves in Lens Studio's global object on onAwake, providing type-safe getInstance() accessors to any script without creating inspector dependencies.

Callback for cross-boundary events: When damage reaches 100%, PlayPauseManager invokes an onGameOver callback registered by MainMenu at game start. This avoids a circular import between the two scripts while keeping the event flow explicit and traceable.

Controller-owned pause: LanePatternController.pauseAll() and resumeAll() freeze all nine lane instantiators (coins, enemies, roads) in a single call. PlayPauseManager needs only one inspector reference to produce a complete, reliable freeze: including VFX components.

Wave state machine: LanePatternController cycles gap β†’ wave β†’ gap. Pattern pools are split into Easy, Medium, and Rest tiers. Difficulty weight shifts from 0 to 1 over difficultyRampTime seconds. A lane-switching enforcement rule tracks consecutive waves on the same safe lane and forces a rotation when the threshold is exceeded: keeping players active rather than camping.

Weather-aware audio: Rain audio is controlled by MainMenu and guards against both menu panel state and the weather switch toggle. startRain() checks weatherToggleSwitch.isOn before playing, so every resume path respects the current weather preference without additional coordination.


Setup

Requirements

  • Lens Studio v5.15.4
  • Snap Spectacles (physical device) or the Spectacles simulator built into Lens Studio

Opening the Project

  1. Clone or download this repository.
  2. Open Lens Studio.
  3. Choose File β†’ Open Project and select the SpectaRacer/ folder.
  4. Lens Studio resolves the packed .lspkg dependencies from the Packages/ folder automatically: no install step needed.
  5. Press Play in Lens Studio to run in the simulator, or push to a paired Spectacles device via the device panel.

No npm, no build commands, no external services. Everything runs inside Lens Studio.

Optional: TypeScript type-checking in your editor

The tsconfig.json is noEmit and exists solely for editor intelligence. To run a type check from the terminal:

cd SpectaRacer
npx tsc --noEmit

Wiring Guide

The scene is pre-wired in Scene.scene. If you rebuild from scratch, every inspector slot carries a @hint tooltip. The critical connections are summarised below.

MainMenu

Must live on a SceneObject outside LevelHandleMover so it stays active while the level is disabled.

Slot Assign
panel1 – panel4 Root SceneObject of each panel
levelHandleMover The parent SceneObject for the entire level
rainAudio AudioComponent on the MainMenu SceneObject
weatherObject SceneObject to show/hide for the weather effect
weatherToggleSwitch The Switch component on the weather toggle
Button slots Drag each RectangleButton component from its SceneObject

PlayPauseManager

Lives inside LevelHandleMover. Controls game logic only: no button wiring needed here.

Slot Assign
laneController The LanePatternController SceneObject
coinLeft/Middle/Right One CoinInstantiator per lane
enemyLeft/Middle/Right One EnemyInstantiator per lane
roadLeft/Middle/Right One RoadInstantiator per lane
damageText Text component for the damage display
distanceText Text component for the distance display
scoreText Text component for the score display
rainVFX VFXComponent for rain particles (paused with game)
cloudVFX VFXComponent for cloud particles: optional

Sound Effects

Coin + crash SFX: Add two AudioComponents to the SnapSaberGlobalManager SceneObject. Assign one .mp3 per component. Drag each into the coinAudio and enemyAudio slots on SnapSaberGlobalManager.

Ambient rain: Add one AudioComponent to the MainMenu SceneObject. Assign the rain .mp3. Set Play On Awake β†’ OFF (the script controls playback). Drag it into the rainAudio slot on MainMenu. Rain plays during gameplay, pauses when the pause menu opens, and respects the weather switch: if the switch is off, rain stays silent on resume.


Customisation

Difficulty Tuning

All parameters are exposed as @input fields on LanePatternController and adjustable in the Inspector without touching code.

Field Default Effect
initialMoveSpeed 100 Starting speed (cm/s)
maxMoveSpeed 300 Peak speed after ramp
speedRampTime 120 s Time to reach max speed
difficultyRampTime 120 s Time until hard patterns dominate
gapFrequency 5 Insert a rest wave every N waves
minCoinsPerWave 2 Fewest coins per wave
maxCoinsPerWave 6 Most coins per wave
gapDuration 1.5 s Silence between waves

Adding Lane Patterns

Patterns are number[3] arrays inside LanePatternController.ts:

0 = EMPTY   1 = COIN   2 = ENEMY

Add entries to PATTERNS_EASY, PATTERNS_MEDIUM, or PATTERNS_REST. The safety invariant (at least one non-enemy lane per pattern) is enforced by convention: every pattern in the library already satisfies it.

Swapping Prefabs

Drop a replacement prefab into the prefab slot on any instantiator. The end-zone collision system identifies objects by name prefix (CoinCube_, EnemyCube_, RoadTile_): spawned instances keep these prefixes automatically since the instantiator sets the name on spawn.


Package Versions

Package Version
SpectaclesInteractionKit 0.17.2
SpectaclesUIKit 0.1.5
SnapDecorators 1.0.0
Utilities 1.0.0
Lens Studio API target 5.15.0 / Snapchat 13.60

License

MIT. Giving credit would be appreciated.

About

πŸŽοΈπŸ’¨ Car Racing Themed Endless Runner for Snap Spectacles

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages