Technical Documentation

Code patterns, architecture, and technical solutions in Mystic Valley

🏗️ 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

📊 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: