This project was originally developed as a senior year team project in cerenkdy/SeniorProject. This repository is a mirror of that work, republished here with full commit history preserved.
A first-person Unity game where players learn vocabulary by exploring an open world, photographing objects, and completing quiz challenges. Every mechanic — photo capture, the album, the quiz, language switching — is connected through a clean, decoupled architecture.
- Project Overview
- How to Play
- Architecture — The Big Picture
- Core System: ServiceLocator
- Language System
- Player & Camera System
- Photo Mode & Object Detection
- Photo Capture System
- Photo Album System
- Quiz System
- UI System Overview
- Language Selection Screen
- Data Flow — Full Walkthrough
- How to Add New Content
- Scene Setup & Inspector Assignments
- Folder Structure
- Controls Reference
Photobulary is a vocabulary learning game built in Unity. The core idea is simple:
Instead of flashcards, you walk around a real-looking environment and photograph objects to learn their names in a foreign language.
The game supports multiple language pairs (currently English ↔ Turkish) and can be extended to any language by adding a single JSON file — no code changes required.
Key features:
- First-person exploration with photo capture
- Photo album with filtering (All / Liked / Quiz Correct / Quiz Wrong)
- Timed vocabulary quiz: find and photograph specific objects
- Full language selection before play begins
- Persistent progress (saved to PlayerPrefs between sessions)
| Step | What happens |
|---|---|
| Walk around the world | WASD + mouse look |
| Right-click | Enter photo mode (camera zoom + crosshair) |
| Aim at any object | Object glows white when it can be photographed |
| Left-click | Take the photo |
| Photo card appears | Shows the word in source + target language |
| Click Save | Photo saved to your album |
| Click Delete | Photo discarded |
| Press Tab | Open/close your photo album |
| Step | What happens |
|---|---|
| Press T (or click the T button) | Quiz setup panel opens |
| Pick how many words (3 / 5 / 10 / All) | Select word count |
| Click Start | Quiz begins — HUD shows in top-right, target word at bottom |
| Find the named object and photograph it | Same photo mode as normal |
| Photo card appears showing ✓ Correct or ✗ Wrong | Green or red banner |
| Click ✓ Save / ! Save / Skip | Advance to next word |
| After all words: results screen | Accuracy, duration, weak words |
The project uses a Service Locator pattern instead of Singletons. Every manager registers itself once, and any other system can find it by type — without hard references between scripts.
ServiceLocator (static registry)
│
├── LanguageManager — all word lookups, language switching
├── PhotoAlbumManager — stores / retrieves saved photos
├── QuizManager — quiz state machine
├── QuizProgressTracker — persists weak words across sessions
├── ObjectDatabase — object categories, difficulty, points
└── PlayerProgress — player XP and learned words
Data flows like this:
World Object (PhotoSubject)
→ ObjectHighlighter (detects it via raycast)
→ PhotoModeController (user presses left-click)
→ PhotoCaptureManager (takes screenshot)
→ PhotoEntry (data container: image + words)
→ PhotoCardPresenter (shows save/delete UI)
→ PhotoAlbumManager (saves to PlayerPrefs)
→ PhotoAlbumPresenter (displays in album)
During a quiz the path forks at PhotoModeController:
PhotoCaptureManager → PhotoEntry
→ QuizManager.EvaluateCapture() (is this the right object?)
→ PhotoCardPresenter (shows ✓/✗ + Save/Skip)
→ QuizManager.CompleteCapture() (updates score, advances word)
File: Assets/_Project/Scripts/Core/ServiceLocator.cs
A static dictionary that maps a C# type to a single instance of that type.
// Any manager registers itself in Awake():
ServiceLocator.Register(this);
// Any other script retrieves it safely:
if (ServiceLocator.TryGet<LanguageManager>(out var lm))
string word = lm.GetSourceWord("cat");Why not Singletons?
- Singletons create tight coupling:
LanguageManager.Instance.GetWord(...)breaks if the scene doesn't have one. - With ServiceLocator,
TryGetreturns false gracefully — no null reference crashes. - Easy to swap implementations (e.g., a mock language manager for unit tests).
Important rule: Every manager must call ServiceLocator.Register(this) in its Awake() method. Systems that depend on other managers must access them in Start() or later (after all Awake() calls have run).
File: Assets/_Project/Scripts/Data/LanguageManager.cs
The central hub for all word lookups and language switching. It automatically loads every JSON file placed in Assets/_Project/Resources/Languages/.
Key concepts:
| Term | Meaning |
|---|---|
| Source language | The language the player is actively practising (e.g., English) |
| Target language | The native / translation language (e.g., Turkish) |
The player selects these at game start via the Language Selection screen. The choice is saved to PlayerPrefs and restored next session.
Public API:
lm.GetWordPair("cat") // Returns WordPair { sourceWord="Cat", targetWord="Kedi", ... }
lm.GetSourceWord("cat") // "Cat" (language being learned)
lm.GetTargetWord("cat") // "Kedi" (translation)
lm.SetLanguages("en", "tr") // Switch language pair at runtime
lm.AvailableLanguages // Dictionary of all loaded language codes → LanguageData
lm.CurrentSource // "en"
lm.CurrentTarget // "tr"Events:
lm.OnLanguageChanged += (source, target) => { /* refresh UI */ };File: Assets/_Project/Scripts/Data/LanguageData.cs
Each JSON language file is deserialized into a LanguageData object. It stores a list of WordEntry objects and converts them to a Dictionary at runtime for fast O(1) lookup.
{
"languageCode": "en",
"languageName": "English",
"nativeName": "English",
"words": [
{
"objectId": "cat",
"word": "Cat",
"pronunciation": "kæt",
"audioClipName": "en_cat",
"exampleSentence": "The cat is sleeping."
}
]
}Every objectId in the JSON must exactly match the objectId set on the PhotoSubject component in the scene.
- Copy
Assets/_Project/Resources/Languages/_template.json - Fill in
languageCode,languageName,nativeName, and all word entries - Place the file in
Assets/_Project/Resources/Languages/ - Done. The game loads it automatically — no code changes needed.
File: Assets/_Project/Scripts/Gameplay/PlayerMovement.cs
Standard first-person WASD movement using Unity's Character Controller and the new Input System.
Counter-based lock system: Multiple systems can lock movement independently. The player only moves when the lock count is zero.
playerMovement.Lock(); // +1 to lock count (e.g., quiz setup opens)
playerMovement.Unlock(); // -1 from lock count (e.g., quiz setup closes)
playerMovement.IsLocked; // true if lock count > 0This prevents conflicts: if both the album AND a dialogue lock movement, unlocking just the album keeps the player frozen until the dialogue also unlocks.
File: Assets/_Project/Scripts/Interactions/CameraLook.cs
Handles mouse-look rotation. Also has Lock() / Unlock() to freeze the camera independently of movement.
File: Assets/_Project/Scripts/Systems/CameraZoom.cs
Changes the camera field-of-view to simulate a camera lens zoom when entering photo mode.
File: Assets/_Project/Scripts/Gameplay/PhotoModeController.cs
The central coordinator for the photo capture flow. It owns all input actions and decides what happens after a photo is taken.
State machine:
[Exploring] ──right-click──► [PhotoMode]
│ left-click (on valid target)
▼
[Capturing] ──► PhotoCaptureManager
│
┌───────────┴───────────┐
Quiz active? Normal mode?
│ │
EvaluateCapture() ShowCard(entry)
ShowCardForQuiz()
Key responsibilities:
- Enters/exits photo mode (locks movement, changes sensitivity, zooms camera)
- Blocks photo mode when album or quiz setup is open
- After capture: routes the result either to quiz flow or normal card flow
- Listens to
PhotoCardPresenter.OnCardClosed()to restore player control
File: Assets/_Project/Scripts/Interactions/ObjectHighlighter.cs
Raycasts from the center of the camera every frame while in photo mode. When it hits a PhotoSubject, it applies a white pulse glow effect to signal the object is capturable.
Camera center → Raycast → PhotoSubject?
YES → SetupRenderers → StartPulse (fade in/out glow animation)
NO → ClearHighlight (remove glow, clear current target)
File: Assets/_Project/Scripts/Gameplay/PhotoSubject.cs
Attach this script to any world object the player should be able to photograph.
Inspector fields:
| Field | Purpose |
|---|---|
objectId |
The key that links this object to all data (MUST match JSON entries) |
previewSprite |
Optional UI preview image |
highlightRenderer |
Renderer to apply glow effect to |
The objectId is the single link that connects a world object to its language data, quiz questions, album filters, and point values. Getting this right is critical.
PhotoSubject.objectId = "cat"
→ LanguageManager finds "cat" → "Cat" / "Kedi"
→ ObjectDatabase finds "cat" → category, difficulty, points
→ QuizManager matches captures against "cat"
→ PhotoAlbumManager.GetUniqueObjectIds() includes "cat"
File: Assets/_Project/Scripts/Systems/PhotoCaptureManager.cs
Handles the actual screenshot and creates a PhotoEntry data object.
Flow:
TakePhoto(PhotoSubject subject)is called byPhotoModeController- Waits one frame (
WaitForEndOfFrame) so Unity finishes rendering - Reads pixels from the screen, crops a square from the center
- Looks up the word pair from
LanguageManagerusingsubject.ObjectId - Creates a
PhotoEntryand firesOnPhotoCaptured
File: Assets/_Project/Scripts/Data/PhotoEntry.cs
A serializable data container for one photograph.
public class PhotoEntry
{
public string objectId; // "cat" — links to all data systems
public string nameEN; // "Cat" (source word at capture time)
public string nameTR; // "Kedi" (target word at capture time)
public byte[] imageBytes; // PNG bytes of the cropped screenshot
public string capturedAt; // "2025-05-07 14:32"
public bool isLiked; // toggled by the heart button in the album
public bool quizCorrect; // true if saved as a correct quiz answer
}PhotoEntry objects are JSON-serialized and stored in PlayerPrefs by PhotoAlbumManager.
File: Assets/_Project/Scripts/Gameplay/PhotoAlbumManager.cs
Stores all saved photos in memory and persists them to PlayerPrefs as JSON. Registered with ServiceLocator so any script can access it.
Key methods:
album.SavePhoto(entry) // Add photo + save to PlayerPrefs
album.RemovePhoto(index) // Delete by index + save
album.ToggleLike(index) // Toggle isLiked flag + save
album.GetPhoto(index) // Retrieve a single entry
album.GetFilteredPhotos(filter) // Returns a filtered list (see below)
album.GetUniqueObjectIds() // List of distinct objectIds (used by quiz)
album.Count // Total number of saved photosFilter system (AlbumFilter enum):
| Filter | Shows |
|---|---|
All |
Every saved photo |
Liked |
Photos where isLiked == true |
QuizCorrect |
Photos where quizCorrect == true |
QuizWrong |
Photos whose objectId is in QuizProgressTracker.WeakWordIds |
File: Assets/_Project/Scripts/UI/UIToolkit/PhotoAlbumPresenter.cs
Renders the album as a two-page book spread using UI Toolkit. Built from PhotoAlbumScreen.uxml and PhotoAlbumScreen.uss.
Layout:
[Filter Tabs (left)] [← Prev] [Page Left | Spine | Page Right] [Next →]
- Each page holds a 2×2 grid = 4 photos per page, 8 per spread
- Filter tabs: green ● (All), yellow ★ (Quiz Correct), pink ♥ (Liked), red ! (Quiz Wrong)
- Clicking a card opens the full-card overlay with photo, words, and a flip-to-details animation
- Delete (X) button on each card removes it from the album immediately
- Like (♥) button in the full card overlay toggles the
isLikedflag - Saves cursor/movement state correctly on open and close
The quiz system is split into three scripts for clear separation of concerns.
File: Assets/_Project/Scripts/Gameplay/QuizManager.cs
The core state machine. Manages the word queue, score, and timing.
Lifecycle:
StartQuiz(wordIds)
→ shuffles words, resets counters
→ fires OnQuizWordChanged (UI shows first word)
→ fires OnScoreUpdated (0 correct, 0 wrong, N remaining)
[player takes a photo]
EvaluateCapture(entry) ← called by PhotoModeController
→ checks entry.objectId vs current target word
→ stores result as _pendingIsCorrect
→ returns bool (correct or not)
[photo card shown to player, player clicks Save or Skip]
CompleteCapture(saveToAlbum) ← called by PhotoCardPresenter
→ if correct: _correctCount++, optionally saves with quizCorrect=true
→ if wrong: _wrongCount++, adds objectId to QuizProgressTracker
→ fires OnAnswerProcessed, OnScoreUpdated
→ if more words: BroadcastCurrentWord (UI shows next word)
→ if no more words: EndQuiz()
EndQuiz()
→ creates QuizSessionRecord
→ saves to QuizProgressTracker
→ fires OnQuizCompleted
Events (QuizUIPresenter subscribes to all of these):
OnQuizWordChanged (string id, string displayWord) // new target word
OnScoreUpdated (int correct, int wrong, int remaining)
OnAnswerProcessed (bool wasCorrect) // flash feedback
OnQuizCompleted (QuizSessionRecord record) // show resultsWhy EvaluateCapture + CompleteCapture instead of one method?
The old ProcessCapture did everything in one step (evaluate → save → advance). This skipped the photo card UI. The new two-step design:
EvaluateCapture— judges the answer, stores it as pending- Player sees the card (correct/wrong banner, save/skip choice)
CompleteCapture— commits the result, advances the quiz
This lets the player decide whether to keep the photo even if the quiz moves on.
File: Assets/_Project/Scripts/UI/UIToolkit/QuizUIPresenter.cs
Builds and manages all quiz UI entirely in C# code (no UXML). Uses a UIDocument with a high Sort Order so it renders on top of everything.
Panels:
| Panel | When visible |
|---|---|
| T button (bottom-left) | Always, during normal gameplay |
| Setup backdrop | After pressing T, before quiz starts |
| HUD (top-right) | During active quiz |
| Word prompt (bottom-center) | During active quiz |
| Results backdrop | After quiz ends |
Setup panel flow:
- Calls
PhotoAlbumManager.GetUniqueObjectIds()to count available words - Shows word-count selector: 3 / 5 / 10 / All (N)
- On Start: shuffles all unique IDs, picks the selected count, calls
QuizManager.StartQuiz(list)
IsUIBlocking property: Returns true while setup or results panels are open. PhotoModeController checks this to prevent right-click photo mode from activating during those states.
File: Assets/_Project/Scripts/Data/QuizProgressTracker.cs
Persists quiz history across game sessions using PlayerPrefs.
What it tracks:
WeakWordIds— objectIds the player has answered incorrectly at least once and never corrected- Full session history (date, word count, accuracy, duration)
How weak words work:
- When a wrong answer is committed and the player saves it with
! Save,AddWeakWord(objectId)is called immediately - At the end of each quiz,
RecordSession()removes correctly answered words from the weak list - The ! (Quiz Wrong) tab in the album uses
WeakWordIdsas its filter
File: Assets/_Project/Scripts/Data/QuizData.cs
Plain serializable data classes used by the quiz system:
QuizSessionRecord // one completed quiz session (date, counts, duration, word lists)
QuizSaveData // the root object saved to PlayerPrefs (weak words + session history)All UI is built with Unity UI Toolkit (UIElements). Each screen has its own UIDocument component on its own GameObject, with a Sort Order that controls which renders on top.
Recommended Sort Order values:
| UIDocument | Sort Order |
|---|---|
| Game HUD / Photo Mode Overlay | 0 |
| Photo Card | 5 |
| Photo Album | 5 |
| Quiz UI | 10 |
| Language Selection | 20 |
Why separate UIDocuments? Each system can show/hide independently without affecting others. The Sort Order guarantees the quiz panel is always above the album, and the language selection is above everything.
File: Assets/_Project/Scripts/UI/UIToolkit/PhotoCardPresenter.cs
Displays the photo result card after a capture. Uses PhotoResultScreen.uxml (overlay + action buttons) and PhotoCard.uxml (the card component with front/back flip).
Normal mode:
- Save (green) — saves photo to album + awards progress points
- Delete (red) — discards photo
Quiz mode:
- ✓ Save (green) — calls
QuizManager.CompleteCapture(saveToAlbum: true)with correct answer - ! Save (orange) — calls
QuizManager.CompleteCapture(saveToAlbum: true)with wrong answer, marks word as weak - Skip (grey) — calls
QuizManager.CompleteCapture(saveToAlbum: false), no photo saved - X / ESC — same as Skip
The card front shows source and target words looked up live from LanguageManager. Clicking the photo flips to the back side showing pronunciation and example sentences.
File: Assets/_Project/Scripts/UI/UIToolkit/LanguageSelectionPresenter.cs
Shown automatically at game start. Built entirely in C# code — no UXML needed.
Flow:
- Locks player movement and camera on
Start() - Reads all available languages from
LanguageManager.AvailableLanguages - Builds a button row for "I speak:" and "I am learning:"
- Validates that the two selected languages are different
- On Start Playing: calls
LanguageManager.SetLanguages(native, learning) - Unlocks player, hides itself, sets cursor to locked
The selected language pair is saved to PlayerPrefs automatically inside LanguageManager.SetLanguages() and restored on the next game session.
Inspector setup required:
- Assign PlayerMovement reference
- Assign CameraLook reference
- UIDocument Sort Order = 20
Here is the complete path from "player sees an object" to "photo appears in the album":
1. World
PhotoSubject("cat") exists in the scene with objectId = "cat"
2. ObjectHighlighter (runs every frame in photo mode)
Raycast hits the cat's collider
→ Glow effect applied to the cat's renderer
→ CurrentSubject = the cat's PhotoSubject
3. PhotoModeController (player presses left-click)
Checks: _isPhotoMode && HasTarget && !_isCardOpen
→ Calls PhotoCaptureManager.TakePhoto(subject)
4. PhotoCaptureManager (runs coroutine)
WaitForEndOfFrame → ReadPixels → CropCenter
→ LanguageManager.GetWordPair("cat") → { sourceWord="Cat", targetWord="Kedi" }
→ Creates PhotoEntry { objectId="cat", nameEN="Cat", nameTR="Kedi", imageBytes=... }
→ Fires OnPhotoCaptured(entry)
5. PhotoModeController.OnPhotoCaptured(entry)
Quiz active? YES → QuizManager.EvaluateCapture(entry) → returns true/false
NO → PhotoCardPresenter.ShowCard(entry)
6a. Quiz path
EvaluateCapture: "cat" == currentTarget? → stores pending result
PhotoCardPresenter.ShowCardForQuiz(entry, isCorrect)
→ Shows card with green ✓ or red ✗ banner
→ Player clicks Save or Skip
→ CommitQuizCapture(saveToAlbum) → QuizManager.CompleteCapture(save)
→ if correct: PhotoAlbumManager.SavePhoto(entry with quizCorrect=true)
→ if wrong+save: PhotoAlbumManager.SavePhoto(entry) + QuizProgressTracker.AddWeakWord("cat")
→ Score updated → Next word broadcast
→ PhotoModeController.OnCardClosed() → player regains control
6b. Normal path
PhotoCardPresenter.ShowCard(entry) → player sees photo card
Save → PhotoAlbumManager.SavePhoto(entry) + PlayerProgress.MarkWordLearned("cat")
Delete → discard
→ PhotoModeController.OnCardClosed() → player regains control
7. PhotoAlbumManager
photo appended to _savedPhotos
Serialized to JSON → PlayerPrefs.SetString("photo_album", json)
8. PhotoAlbumPresenter (when Tab is pressed)
Loads from PhotoAlbumManager
Renders 2×2 grid per page in book-spread layout
Filter tabs let player browse by All / Liked / Correct / Wrong
- Place your 3D model in the scene
- Add the PhotoSubject component
- Set
objectIdto a unique lowercase string (e.g.,"bicycle") - Make sure the same
objectIdexists in every language JSON file:
{ "objectId": "bicycle", "word": "Bicycle", "pronunciation": "ˈbaɪsɪkəl", "exampleSentence": "I ride a bicycle." }- Add the object to the correct physics layer so
ObjectHighlightercan raycast it - That's it — the photo, album, and quiz systems will all recognize it automatically
- Duplicate
Assets/_Project/Resources/Languages/en.json - Change
languageCode,languageName,nativeName - Translate every word entry
- Save as
Assets/_Project/Resources/Languages/de.json(for German, for example) - Press Play — the language appears automatically in the Language Selection screen
| GameObject | Components |
|---|---|
GameSystems |
PersistentSystems, LanguageManager, PhotoAlbumManager, PlayerProgress, ObjectDatabase |
QuizManager |
QuizManager, QuizProgressTracker |
Player |
PlayerMovement, CameraLook |
Player/Camera |
CameraZoom, ObjectHighlighter, PhotoCaptureManager |
PhotoModeController |
PhotoModeController |
| UI GameObjects (one per screen) | UIDocument + matching Presenter script |
| GameObject | Script | Sort Order |
|---|---|---|
PhotoCardUI |
PhotoCardPresenter |
5 |
PhotoAlbumUI |
PhotoAlbumPresenter |
5 |
QuizUI |
QuizUIPresenter |
10 |
LanguageSelectionUI |
LanguageSelectionPresenter |
20 |
| Field | Assign |
|---|---|
| Player Movement | PlayerMovement on the Player |
| Camera Look | CameraLook on the Player |
| Camera Zoom | CameraZoom on the Camera |
| Highlighter | ObjectHighlighter on the Camera |
| Capture Manager | PhotoCaptureManager on the Camera |
| Camera Screen | CameraScreenController on a UI GameObject |
| Photo Card UI | PhotoCardPresenter |
| Album Presenter | PhotoAlbumPresenter |
| Photo Mode Action | Right Mouse Button action |
| Capture Action | Left Mouse Button action |
| Exit Action | Escape key action |
| Album Action | Tab key action |
| Quiz Action | T key action |
| Field | Assign |
|---|---|
| Quiz Toggle Action | T key InputActionReference |
| Player Movement | PlayerMovement |
| Camera Look | CameraLook |
| Field | Assign |
|---|---|
| Player Movement | PlayerMovement |
| Camera Look | CameraLook |
Assets/
└── _Project/
├── Scripts/
│ ├── Core/
│ │ ├── ServiceLocator.cs ← global manager registry
│ │ └── PersistentSystems.cs ← DontDestroyOnLoad guard
│ │
│ ├── Data/
│ │ ├── PhotoEntry.cs ← one saved photo (data container)
│ │ ├── LanguageData.cs ← JSON structure + WordEntry + WordPair
│ │ ├── LanguageManager.cs ← loads JSONs, handles language switching
│ │ ├── ObjectDefinition.cs ← object category, difficulty, points
│ │ ├── ObjectDatabase.cs ← ScriptableObject lookup for objects
│ │ ├── ObjectCategory.cs ← category enum
│ │ ├── PlayerProgress.cs ← XP, learned words, save/load
│ │ ├── MovementConfig.cs ← ScriptableObject for walk/run speeds
│ │ ├── QuizData.cs ← QuizSessionRecord + QuizSaveData
│ │ └── QuizProgressTracker.cs ← persists weak words across sessions
│ │
│ ├── Gameplay/
│ │ ├── PlayerMovement.cs ← WASD + counter-lock system
│ │ ├── PhotoSubject.cs ← marks an object as photographable
│ │ ├── PhotoAlbumManager.cs ← stores/retrieves all saved photos
│ │ ├── PhotoCapture.cs ← low-level screen capture utility
│ │ ├── PhotoModeController.cs ← photo mode state machine (main coordinator)
│ │ └── QuizManager.cs ← quiz state machine
│ │
│ ├── Interactions/
│ │ ├── CameraLook.cs ← mouse-look rotation
│ │ └── ObjectHighlighter.cs ← raycast + glow effect
│ │
│ ├── Systems/
│ │ ├── PhotoCaptureManager.cs ← screenshot + PhotoEntry creation
│ │ └── CameraZoom.cs ← FOV zoom for photo mode
│ │
│ └── UI/UIToolkit/
│ ├── PhotoCardPresenter.cs ← save/delete card after capture
│ ├── PhotoAlbumPresenter.cs ← two-page book album UI
│ ├── QuizUIPresenter.cs ← setup / HUD / results quiz panels
│ ├── LanguageSelectionPresenter.cs ← language picker at game start
│ └── CameraScreenController.cs ← camera viewfinder overlay
│
├── UI/
│ └── Layouts/
│ ├── UXML/
│ │ ├── Components/PhotoCard.uxml ← card front/back component
│ │ └── Screens/
│ │ ├── PhotoResultScreen.uxml ← save/delete overlay
│ │ ├── PhotoAlbumScreen.uxml ← book-spread album layout
│ │ └── PhotoModeOverlay.uxml ← camera crosshair overlay
│ └── USS/
│ ├── Common.uss
│ ├── PhotoCard.uss
│ ├── PhotoAlbumScreen.uss
│ └── PhotoResultScreen.uss
│
└── Resources/
└── Languages/
├── en.json ← English word list
└── tr.json ← Turkish word list
| Input | Action |
|---|---|
| WASD | Move |
| Mouse | Look around |
| Left Shift | Sprint |
| Right Mouse Button | Toggle photo mode (zoom in/out) |
| Left Mouse Button | Capture photo (only while in photo mode and aiming at an object) |
| Tab | Open / close photo album |
| T | Open quiz setup |
| Escape | Exit photo mode / close photo card |
- Unity (6000.x) — game engine
- C# — scripting language
- Unity UI Toolkit — all in-game UI (UIElements, UXML, USS)
- Unity Input System — all input handling via InputActionReference
- Unity PlayerPrefs + JsonUtility — save/load persistence
- Universal Render Pipeline (URP) — rendering
This project was built as part of a senior design course. All systems are designed to be modular and easy to extend — new languages, objects, and UI screens can be added without touching existing code.