Logic Coordinates vs Physics Engine

Design Decision: No Physics for Map Interaction

Unity’s Physics Engine (with Collider, Trigger, OnCollisionEnter) is powerful but costly when applied to a system interacting with hundreds of tiles on a map.

Solution: Raycast + Logical Coordinates

Determining which tile a player taps:

Camera.ScreenPointToRay(touchPosition)
    → Raycast against the map's single MeshCollider
    → Get the 3D hit point
    → Convert to Hex coordinates (Q, R) via a mathematical formula
    → Look up HexTile from Grid[Q][R]

Only 1 MeshCollider is needed for the entire map instead of 200 individual Colliders.

Strategy: Hybrid Range Detection (Physics Serving Logic)

Rather than calculating distances every frame (pure math polling) or using OverlapSphere (physics polling), the project uses Unity’s Event-based Trigger mechanism for optimal performance.

1. Why SphereCollider + Trigger?

Using OnTriggerEnter and OnTriggerExit frees the Main Thread from continuously scanning attack ranges for dozens of towers simultaneously. Unity’s physics engine handles this at a deeper level and only fires notifications when something actually changes.

// TowerView.cs - Leveraging the Physics Engine to serve Domain Logic
private void OnTriggerEnter(Collider other)
{
    if (other.CompareTag("Enemy"))
    {
        var enemyView = other.GetComponent<EnemyView>();
        if (enemyView != null) _enemiesInRange.Add(enemyView.Enemy);
    }
}

private void OnTriggerExit(Collider other)
{
    if (other.CompareTag("Enemy"))
    {
        var enemyView = other.GetComponent<EnemyView>();
        if (enemyView != null) _enemiesInRange.Remove(enemyView.Enemy);
    }
}

2. Connecting to Domain Logic

The View is responsible for “collecting” nearby enemies into a list, then passing that list to the Domain every frame for Target Picking and damage calculation.

private void Update()
{
    // Clean up dead enemies before pushing data into the Domain
    _enemiesInRange.RemoveAll(e => e == null || e.IsDead);
    
    // Pass the enemy list to the Domain for processing
    Tower.Tick(Time.deltaTime, _enemiesInRange);
}

Mobile Performance Benefits

  • Power efficient: The CPU never has to compute square root distance calculations for every enemy on the map each frame.
  • Stability: Prevents frame spikes when tower count increases in later waves.
  • Clean separation: The Domain remains completely unaware of Unity Physics — it only receives a pure List<Enemy>, preserving the testability of combat logic.