This document is the source of truth for the VALE codebase. Anything in here that is contradicted by code is a bug. Anything in code that is not in here is a design smell.
UnityEngine.InputSystem)UnityEngine.UI) — easier tutorials than UI ToolkitYou wake at the edge of an ancient fairy realm whose light is fading. The Fairy Elder asks you to recover five lost Lumenshards scattered across the Vale and to bring them to the Sacred Glade at its heart. Each shard is guarded by a fairy keeper who will share a memory when spoken to. When all five shards are placed on the Glade’s altar, the Vale’s light returns.
apps/vale/
├── Packages/manifest.json
├── ProjectSettings/
│ ├── ProjectVersion.txt (committed)
│ └── PROJECTSETTINGS-NOTE.md (regen-on-first-open)
├── Assets/Vale/
│ ├── Scripts/
│ │ ├── Vale.Runtime.asmdef (runtime scripts asmdef; Editor/ has its own)
│ │ ├── Core/ (GameManager, SaveSystem, SceneController, InputManager, AudioManager)
│ │ ├── Player/ (PlayerController, PlayerFlight, PlayerInteraction, PlayerCamera)
│ │ ├── Quests/ (QuestManager, Quest, Objective, QuestData)
│ │ ├── Dialogue/ (DialogueRunner, DialogueData, DialogueLine, DialogueChoice)
│ │ ├── Collectibles/ (Collectible, Lumenshard, CollectibleManager, CollectibleData)
│ │ ├── NPC/ (NPCBase, FairyNPC, FairyElder, NPCWander, NPCData)
│ │ ├── UI/ (HUDController, QuestLogUI, DialogueUI, InteractionPromptUI, PauseMenuUI)
│ │ ├── Audio/ (AmbientAudioManager, FootstepPlayer, InteractionAudio)
│ │ └── Editor/ (Vale.Editor.asmdef + EditorWindow scripts)
│ ├── Scenes/ (FairyVale.unity + boot scenes)
│ ├── Prefabs/ (Player, FairyNPC, Lumenshard, Tree, Mushroom, SacredGlade, Altar)
│ ├── Materials/ (Lit, ShaderGraph, Particle materials)
│ ├── Audio/ (BGM, SFX, ambient loops)
│ └── Settings/ (HDRP volume profiles, render settings)
└── README.md, SETUP.md, ARCHITECTURE.md, ASSETS.md
Every C# file declares its namespace explicitly. Use Vale.<Subsystem>:
Vale.Core — game manager, save system, scene controller, input manager, audio managerVale.Player — player controller, flight, interaction, cameraVale.Quests — quest systemVale.Dialogue — dialogue systemVale.Collectibles — collectibles (Lumenshards)Vale.NPC — fairy NPCs, Fairy Elder, wander AIVale.UI — HUD, quest log, dialogue UI, prompts, pause menuVale.Audio — ambient audio, footsteps, interaction soundsVale.Editor — editor-only procedural placement and toolspublic static GameManager Instance { get; private set; } with assignment in Awake.IInteractable, IDialogueSpeaker).public event Action<Quest> OnQuestCompleted;[SerializeField] private for serialized fields; expose via public getter.[Header("Group Name")] to group inspector fields.[Tooltip("Description")] on every serialized field.[Range(min, max)] for numeric sliders.Awake() for one-time setup (references, singleton assignment).OnEnable() for event subscription.Start() for cross-component setup.OnDisable() for event unsubscription.OnDestroy() for singleton cleanup.Update() only when frame-by-frame work is needed; prefer events.public event Action<Quest> OnQuestCompleted;
private void RaiseQuestCompleted(Quest q) => OnQuestCompleted?.Invoke(q);
No UnityEvents on ScriptableObjects. Use C# event Action<>.
JSON to Application.persistentDataPath/save.json. Schema:
{
"version": 1,
"sceneName": "FairyVale",
"player": { "position": [x,y,z], "rotation": [x,y,z,w] },
"quests": { "<questId>": { "state": "Active|Completed|Failed", "objectiveProgress": [0,1,2] } },
"inventory": { "<collectibleId>": count },
"shardsPlaced": [true, false, true, false, false],
"timestamp": "ISO-8601"
}
SaveSystem.Save() is idempotent; called on quest complete and on pause.
IInteractablenamespace Vale.Core
{
public interface IInteractable
{
string InteractionPrompt { get; } // "Press E to talk", "Press E to pick up"
bool CanInteract(PlayerController player);
void Interact(PlayerController player);
}
}
IDialogueSpeakernamespace Vale.Dialogue
{
public interface IDialogueSpeaker
{
string SpeakerName { get; }
Sprite Portrait { get; }
DialogueData GetDialogue(PlayerController player, string dialogueId = "default");
}
}
IQuestTargetnamespace Vale.Quests
{
public interface IQuestTarget
{
string QuestId { get; } // which quest this contributes to
string ObjectiveId { get; } // which objective within the quest
void OnObjectiveTrigger(); // called by objective tracker
}
}
ICollectiblenamespace Vale.Collectibles
{
public interface ICollectible
{
CollectibleData Data { get; }
bool IsCollected { get; }
void Collect(PlayerController player);
}
}
| Manager | Namespace | Responsibility |
|---|---|---|
GameManager |
Vale.Core |
Boot, save/load trigger, scene transitions, quit |
SaveSystem |
Vale.Core |
JSON save/load to persistent data path |
InputManager |
Vale.Core |
New Input System wrapper (input actions, device management) |
SceneController |
Vale.Core |
Async scene loading, spawn point resolution |
AudioManager |
Vale.Core |
BGM, SFX, ambient layer routing |
QuestManager |
Vale.Quests |
Active quests, completion, reward grants |
DialogueRunner |
Vale.Dialogue |
Active dialogue, advance, choice handling |
CollectibleManager |
Vale.Collectibles |
Tracked collectibles, placement state |
All singletons use DontDestroyOnLoad for the GameManager only; subsystem
managers live in the gameplay scene and are re-initialized on scene load.
QuestData : ScriptableObject[CreateAssetMenu(fileName = "Quest_", menuName = "Vale/Quest Data")]
public class QuestData : ScriptableObject
{
public string questId;
public string title;
[TextArea] public string description;
public List<ObjectiveData> objectives;
public List<RewardData> rewards;
public string nextQuestId; // optional follow-up
}
ObjectiveData[Serializable]
public class ObjectiveData
{
public string objectiveId;
[TextArea] public string description;
public ObjectiveType type; // Collect, Talk, Reach, Custom
public string targetId; // collectible id, NPC id, location id
public int requiredCount;
}
DialogueData : ScriptableObject[CreateAssetMenu(fileName = "Dialogue_", menuName = "Vale/Dialogue Data")]
public class DialogueData : ScriptableObject
{
public string dialogueId;
public string npcId;
public List<DialogueNode> nodes;
public string startNodeId;
}
DialogueNode[Serializable]
public class DialogueNode
{
public string nodeId;
public string speakerName;
[TextArea(3, 6)] public string text;
public string nextNodeId; // null = end dialogue
public List<DialogueChoice> choices;
}
CollectibleData : ScriptableObject[CreateAssetMenu(fileName = "Collectible_", menuName = "Vale/Collectible Data")]
public class CollectibleData : ScriptableObject
{
public string collectibleId;
public string displayName;
[TextArea] public string description;
public Sprite icon;
public int value;
}
NPCData : ScriptableObject[CreateAssetMenu(fileName = "NPC_", menuName = "Vale/NPC Data")]
public class NPCData : ScriptableObject
{
public string npcId;
public string displayName;
public Sprite portrait;
public List<DialogueData> dialogues;
public bool isQuestGiver;
public List<string> questsOffered;
public float wanderRadius;
public float wanderSpeed;
}
Hierarchy:
├── Lighting (HDRP Volume)
├── Terrain (Unity Terrain + HDRP Lit terrain shader)
├── Foliage (procedurally placed trees, mushrooms)
├── NPCs
│ ├── FairyElder (quest giver)
│ ├── Keeper_Lumenshard1..5 (fairy NPCs near each shard)
├── Collectibles
│ ├── Lumenshard_1..5
├── SacredGlade (altar at the Vale's heart)
│ └── Altar (5 slot positions, lights when shards placed)
├── Player (PlayerController + Camera + Interaction)
├── UI (Canvas with HUD, prompts, dialogue panel)
├── Audio (AmbientAudioManager, MusicSource, SFXSource)
└── Main Camera (Cinemachine virtual camera)
The scene is built procedurally via Editor menu items:
Vale → Build Vale Scene regenerates NPCs, shards, and the glade from data
assets; Vale → Populate Forest scatters trees/mushrooms.
| File | Purpose |
|---|---|
ValeSceneBuilder.cs |
MenuItem("Vale/Build Vale Scene") — creates the scene hierarchy from data assets |
ForestPopulator.cs |
MenuItem("Vale/Populate Forest") — procedurally scatters trees/mushrooms on terrain |
ShardPlacer.cs |
MenuItem("Vale/Place Lumenshards") — places 5 shards around the Vale in cleared glades |
NPCPlacer.cs |
MenuItem("Vale/Place NPCs") — places Elder and 5 Keepers at fixed story positions |
All Editor scripts require using UnityEditor; and live under Assets/Vale/Scripts/Editor/.