🏗️ Project Architecture
Mystic Valley is built using Unity 2D with C# scripting. The project follows a modular architecture where each system (magic, inventory, time, etc.) is self-contained but can communicate through events and interfaces.
Core Systems
Player Systems
- • PlayerStats.cs
- • PlayerInventoryManager.cs
- • WearableEquipmentManager.cs
- • PlayerMovement.cs
Magic Framework
- • SpellDatabase.cs
- • MagicSchool enum
- • 44 individual spell classes
- • Mana consumption system
Time Management
- • TimeManager.cs
- • DayNightCycle.cs
- • TimeDisplay.cs
- • CalendarUI.cs
- • 6-day week system
- • 30-day seasons
Scene System
- • SceneTransitionManager.cs
- • SpawnPoint.cs
- • Indoor/Outdoor detection
- • Camera persistence
💡 Key Technical Solutions
1. The Indoor/Outdoor Tinting Challenge
Problem: Player sprite kept outdoor tinting (blue at night) when entering buildings.
Solution: Implemented a transition flag system with coroutines:
// TintEverything.cs
void Update() {
if (isTransitioning)
return; // NO tinting during transitions!
// Apply appropriate tinting based on location
if (PlayerStats.Instance.IsIndoors()) {
ApplyIndoorTint(); // Always white
} else {
ApplyTimeBasedTint(); // Day/night colors
}
}
IEnumerator DelayedIndoorTransition() {
isTransitioning = true;
yield return new WaitForSeconds(0.19f); // Match scene fade
ApplyIndoorTint();
isTransitioning = false;
}
2. Anti-Exploit Inventory Design
Problem: Players could stack unlimited magical tools for game-breaking power.
Solution: Separate inventory tiers with strict routing rules:
// PlayerInventoryManager.cs
public bool AddItem(Item item, int quantity = 1) {
// Route items to appropriate inventory section
if (item.itemType == ItemType.Consumable ||
item.itemType == ItemType.Seed) {
// These go to equipment slots for quick use
return AddToEquipmentSlots(item, quantity);
} else {
// Everything else goes to main inventory
return AddToItemSlots(item, quantity);
}
}
// WearableEquipmentManager.cs limits tools:
// - Max 1 left-hand tool (wand/staff)
// - Max 1 right-hand tool (tome/orb)
// = Maximum 2 tools total (balanced!)
3. Singleton Pattern with Lazy Initialization
Usage: Ensuring single instances of managers while handling scene transitions.
public class TimeManager : MonoBehaviour {
private static TimeManager _instance;
public static TimeManager Instance {
get {
if (_instance == null) {
_instance = FindObjectOfType<TimeManager>();
if (_instance == null) {
GameObject go = new GameObject("TimeManager");
_instance = go.AddComponent<TimeManager>();
DontDestroyOnLoad(go);
}
}
return _instance;
}
}
void Awake() {
if (_instance != null && _instance != this) {
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
}
}
4. Custom Calendar Implementation
Purpose: Create a unique 6-day week system for a more mystical world feel.
// TimeManager.cs - 6-day week calculation
public enum DayOfWeek {
Sunday, Monday, Tuesday,
Wednesday, Thursday, Friday
// No Saturday in this world!
}
private void UpdateDayOfWeek() {
int totalDays = (currentYear - 1) * daysPerSeason * 4;
totalDays += ((int)currentSeason * daysPerSeason);
totalDays += currentDay - 1;
// Calculate day of week (0-5 for 6-day week)
int dayIndex = totalDays % 6;
currentDayOfWeek = (DayOfWeek)dayIndex;
}
5. Event-Driven Architecture
Purpose: Decouple systems while maintaining communication.
// TimeManager.cs
public UnityEvent OnSunrise = new UnityEvent();
public UnityEvent OnMidnight = new UnityEvent();
public UnityEvent<Season> OnSeasonChanged = new UnityEvent<Season>();
// CropTimeManager.cs subscribes to events:
void Start() {
TimeManager.Instance.OnMidnight.AddListener(ProcessCropGrowth);
TimeManager.Instance.OnSeasonChanged.AddListener(CheckSeasonalCrops);
}
void ProcessCropGrowth() {
foreach (var crop in registeredCrops) {
if (crop.IsWatered()) {
crop.AdvanceGrowthStage();
}
}
}
🔧 Development Tools & Workflow
Version Control
# GitLab workflow
git checkout -b feature/new-spell
# ... develop feature ...
git add .
git commit -m "feat: Add Bloom spell for crop growth"
git push origin feature/new-spell
# Create merge request in GitLab
Project Structure
Mystic-Valley-Windows/
├── Assets/
│ ├── Scripts/
│ │ ├── Player/
│ │ ├── Magic/
│ │ ├── Items/
│ │ ├── TimeSystem/
│ │ └── SceneTransition/
│ ├── Prefabs/
│ ├── Sprites/
│ └── Scenes/
├── ProjectSettings/
└── Packages/
Performance Optimizations
- Object Pooling: Reuse particle effects and projectiles
- Sprite Atlasing: Combine sprites to reduce draw calls
- LOD System: Reduce detail for distant objects
- Event-Based Updates: Avoid Update() where possible
📊 Technical Stats
Codebase
55+ C# scripts
~9,000 lines of code
Modular architecture
Performance
60 FPS target
<50 draw calls
2D sprite-based
Platforms
Windows (primary)
Mac/Linux (planned)
Unity 6 (2D project)
Dependencies
TextMeshPro
Unity 2D Tilemap
Cinemachine (camera)
🐛 Interesting Bugs & Fixes
The Persistent Blue Player
Bug: Player stayed blue (nighttime tint) inside buildings.
Cause: Race condition between scene transition (0.2s) and tinting update.
Fix: Added 0.19s delay to match scene fade timing exactly.
The Infinite Mana Exploit
Bug: Players could stack 9 magic wands for 900% spell power.
Fix: Redesigned inventory with body equipment slots limiting tools to 2 maximum.
The Vanishing Crop Problem
Bug: Crops disappeared when scenes changed.
Fix: Implemented proper scene persistence and crop registration system.
📚 Learning Resources & Patterns
Here are some key patterns and concepts I've learned and implemented:
- Singleton Pattern: For manager classes that need global access
- Observer Pattern: Using UnityEvents for decoupled communication
- State Machines: For NPC AI and player states
- Coroutines: For timed events and smooth transitions
- ScriptableObjects: For data containers (items, spells)
- Object Pooling: For performance with particles and projectiles