Building an Intelligent City Builder: Smart Placement Systems in Unity


Building an Intelligent City Builder: Smart Placement Systems in Unity
How I created an AI-driven building placement system for an isometric city builder that thinks like a real urban planner
Have you ever wondered how city building games like SimCity or Cities: Skylines make building placement feel so intuitive? After months of development on my Unity-based city builder Tutankhamun: Builders of the Eternal: Builders of the Eternal, I've built a system that automatically handles the complex logic of urban planning - and I'm excited to share how it works!
Table of Contents
- The Vision
- Smart House Placement
- Intelligent Road Building
- The Brain: GameManager Integration
- Solving the Coordinate Puzzle
- Testing: The Foundation of Reliability
- Lessons Learned
The Vision
When I started building Tutankhamun: Builders of the Eternal, I wanted to create something more than just another city builder. I envisioned a system where players could focus on the creative aspects of urban planning while the game intelligently handled the technical details.
The result? HouseManager and RoadManager - two Unity components that work together to create an intelligent building placement system for isometric city building. These aren't just simple grid-based builders; they're AI-driven systems that understand urban planning principles.
โจ What Makes This Special?
- ๐ง Smart placement: Houses automatically align with roads and follow proper urban planning rules
- ๐ฎ Isometric perfection: Designed specifically for isometric camera perspectives
- โก Real-time feedback: Visual preview system shows players exactly what they're building
- ๐บ๏ธ Intelligent pathfinding: Roads automatically route around obstacles
- ๐งช Rock-solid reliability: Over 45 unit tests ensure everything works perfectly
"The best game systems are invisible to the player - they just work." - This became my guiding principle throughout development.
Smart House Placement: Teaching the Game Urban Planning
The HouseManager was my first major breakthrough. I wanted houses to behave like they would in real urban planning - always connected to roads, properly oriented, and sensibly sized.
The Challenge: Making Buildings Think
One of the biggest challenges in city builders is making building placement feel natural. Players shouldn't have to manually figure out optimal orientations or worry about complex placement rules. Here's what I implemented:
๐ Houses That Know Where They Belong
- Road-Adjacent Requirement: No more floating houses! Every building must connect to the road network
- Smart Orientation: The system analyzes nearby roads and automatically orients buildings correctly
- Intelligent Sizing: Drag any rectangle - the system converts it to valid building dimensions
- Urban Planning Rules: Maximum 2 cells perpendicular to roads (just like real subdivisions!)
๐ฏ The Magic Behind the Scenes
Here's a glimpse of how the orientation detection works:
// Example: For a horizontal road, houses extend perpendicular (north-south)
if (roadOrientation == RoadOrientation.Horizontal)
{
targetWidth = originalWidth; // Follow road length
targetHeight = Math.Min(originalHeight, 2); // Max 2 deep - realistic urban planning!
}
๐ฑ๏ธ Player Experience First
- Drag to Build: Simple click-and-drag interface that anyone can understand
- Live Preview: Semi-transparent preview shows exactly where buildings will go
- Right-Click Cancel: Intuitive controls that feel natural
- Smart Feedback: Invalid areas simply don't show up - no confusing error messages
Under the Hood: How Smart Placement Works
The real magic happens in the analysis algorithms. When a player drags to place buildings, the system:
๐งญ Road Orientation Detection
The AI analyzes the nearby road network to understand the urban layout:
- Horizontal Roads: Buildings align along the X-axis, extending north-south
- Vertical Roads: Buildings follow the Z-axis, extending east-west
- Complex Intersections: Smart 2ร2 maximum sizing for T-junctions and roundabouts
โ Multi-Layer Validation
Every building placement goes through rigorous checking:
- โ Must be adjacent to existing roads (no isolated buildings!)
- โ Must be on buildable terrain (land/sand, not water/mountains)
- โ Cannot overlap existing structures
- โ Must follow realistic dimensional constraints
๐ Coordinate Magic
One of the trickiest parts was handling Unity's coordinate system for isometric views:
// Converts Unity's runtime coordinates to internal map format
Vector3Int mapCoordinates = GameManager.Instance.RuntimeToMapCoordinates(runtimeCoords);
// Map format: (x, 0, z_depth) where z represents depth in isometric view
Dev Tip: Coordinate conversion was one of those "seems simple but isn't" problems. I spent weeks getting this right!
Intelligent Road Building: AI-Powered Infrastructure
If smart house placement was the breakthrough, intelligent road building was the technical masterpiece. I wanted roads that could think - automatically finding the best path between points while avoiding obstacles.
The Pathfinding Brain
Creating roads isn't just about connecting two points. It's about finding the smart path that respects terrain, avoids buildings, and feels natural to players.
๐ฃ๏ธ Three-Tier Intelligence System
My road AI uses a sophisticated decision-making process:
// Three-tier pathfinding approach:
1. ๐ฏ Try straight vertical line (constant X, varying Z)
2. โก๏ธ Try straight horizontal line (constant Z, varying X)
3. ๐ง Fall back to BFS (Breadth-First Search) for complex routing
This approach means roads prefer simple, clean paths (like real urban planning) but can handle complex situations when needed.
๐ฎ Seamless Player Experience
- Real-time Preview: See your road before you build it
- Visual Feedback: Semi-transparent segments show the planned route
- Smart Terrain: Automatically avoids water and mountains
- Efficient Routing: Always finds the shortest viable path
๐ง The Algorithm in Action
The pathfinding system is where computer science meets game design:
1. ๐ฏ Straight Line Attempts (The Efficient Choice)
- Vertical paths: Keep the same X coordinate, vary Z
- Horizontal paths: Keep the same Z coordinate, vary X
- Why this matters: Real cities prefer straight roads when possible!
2. ๐ง BFS Fallback (The Problem Solver)
- Explores all possible routes when straight paths fail
- Finds the shortest valid path around obstacles
- Respects terrain and building constraints
- The magic: Complex pathfinding that feels effortless to players
๐ Terrain Intelligence
The road system understands the world around it:
private bool CanPathRoadThrough(Vector3Int mapCell)
{
// Cannot path through houses - respect existing buildings
if (GameManager.Instance.IsHouseCell(mapCell)) return false;
// Must be on land (not water/mountains) - realistic constraints
return !GameManager.Instance.IsNonLandCell(mapCell);
}
โ Construction Validation
Before placing any road, the system validates:
- ๐๏ธ Terrain suitability: Land vs water checks
- ๐ Existing structures: Houses block new roads
- ๐ฃ๏ธ Road overlap: Smart handling of road intersections
Technical Note: The pathfinding system processes thousands of potential paths in milliseconds, but players just see smooth, intelligent road placement!
The Brain: GameManager Integration
Behind every great system is great architecture. The GameManager serves as the central nervous system, coordinating between all the different building managers and maintaining the game's state.
Why Centralized State Management?
When I first started building this system, I had each manager tracking its own state. The result? Chaos. Houses didn't know about roads, roads didn't know about houses, and bugs were everywhere. The solution was a central brain:
public class GameManager : MonoBehaviour
{
// ๐ Global state tracking - the single source of truth
public HashSet<Vector3Int> roadCells; // Every road in the city
public HashSet<Vector3Int> houseCells; // Every building
public HashSet<Vector3Int> nonLandCells; // Water, mountains, etc.
// ๐ Coordinate conversion magic
public Vector3Int RuntimeToMapCoordinates(Vector3Int runtimeCoord);
// โ
Smart validation methods
public bool IsBuildableCell(Vector3Int cell);
public bool IsRoadCell(Vector3Int cell);
public bool IsHouseCell(Vector3Int cell);
}
The Power of Centralization
This architecture unlocked several powerful benefits:
- ๐ฏ Consistent State: One source of truth eliminates conflicting information
- ๐ฌ Cross-Manager Communication: Houses know about roads, roads know about houses
- ๐ Coordinate Standardization: Unified coordinate system prevents bugs
- ๐พ Save/Load Support: Central state makes serialization trivial
Architecture Lesson: Sometimes the best solution is the simplest one. A central state manager eliminated 90% of my coordination bugs!
Coordinate System
The system uses a sophisticated coordinate mapping to handle Unity's isometric grid requirements.
Coordinate Formats
Runtime Coordinates (from Unity Grid)
- Format:
(grid_x, grid_y_depth, 0)
- Source:
grid.WorldToCell()
calls - Usage: Raw Unity grid coordinates
Map Coordinates (internal logic)
- Format:
(map_x, 0, map_z_depth)
- Usage: All pathfinding, validation, and storage
- Conversion:
RuntimeToMapCoordinates()
Coordinate Conversion
public Vector3Int RuntimeToMapCoordinates(Vector3Int runtimeCoord)
{
// Convert Unity's (x, y_depth, z_slice) to logical (x, 0, z_depth)
return new Vector3Int(runtimeCoord.x, 0, runtimeCoord.y);
}
Grid Reference Management
Both managers automatically synchronize with the TerrainManager's grid:
private void EnsureGridReference()
{
if (grid == null && GameManager.Instance?.TerrainManager?.terrainTilemap?.layoutGrid != null)
{
grid = GameManager.Instance.TerrainManager.terrainTilemap.layoutGrid;
Debug.Log("Auto-assigned grid reference for coordinate alignment");
}
}
Testing: Beyond Unit Tests - Building Confidence in Complex Systems
Building complex game systems without a robust testing strategy is like navigating without a compass. That's why I developed a multi-layered testing philosophy that goes far beyond traditional unit testing to ensure rock-solid reliability in a system with over 45 verification points.
The Game Development Testing Challenge
Game systems present unique testing challenges that web developers rarely face:
- Stateful interactions: Buildings affect roads, roads affect villagers, villagers affect economy
- Frame-based execution: Logic that depends on Unity's update cycles
- Coordinate complexity: Multiple coordinate systems that must stay synchronized
- Emergent behavior: Simple rules creating complex, unpredictable outcomes
๐๏ธ Architectural Testing: Designing for Verification
The key insight was designing the system to be testable rather than retrofitting tests later:
Dependency Injection for Game Logic
public class TestableHouseManager : IHouseManager
{
private readonly ICoordinateConverter coordinateConverter;
private readonly ITerrainValidator terrainValidator;
// Constructor injection allows mock dependencies in tests
public TestableHouseManager(ICoordinateConverter converter, ITerrainValidator validator)
{
this.coordinateConverter = converter;
this.terrainValidator = validator;
}
}
State Snapshot Testing
Rather than testing individual methods, I test complete system states:
[Test]
public void CompletePlayerWorkflow_BuildsProperNeighborhood()
{
// Arrange: Set up a realistic game state
var gameState = CreateTestGameState();
gameState.AddRoad(new Vector3Int(0, 0, 0), new Vector3Int(5, 0, 0));
// Act: Simulate complete player interaction
var result = houseManager.BuildHouseBlock(area, dragDirection);
// Assert: Verify the entire resulting state
Assert.That(gameState.GetAllBuildings(), Has.Count.EqualTo(expectedCount));
Assert.That(AllBuildingsConnectedToRoads(gameState), Is.True);
Assert.That(UrbanPlanningRulesFollowed(gameState), Is.True);
}
๐ฏ Property-Based Testing for Game Logic
Traditional example-based tests miss edge cases. Property-based testing generates hundreds of random scenarios:
[Test]
public void RoadPathfinding_AlwaysFindsShortestValidPath()
{
// Generate 1000 random terrain layouts
var testCases = GenerateRandomTerrainLayouts(1000);
foreach(var terrain in testCases)
{
var path = roadManager.FindPath(start, end, terrain);
// Properties that must ALWAYS be true:
Assert.That(path.IsValid(), Is.True, "Path must be valid");
Assert.That(path.Length, Is.LessThanOrEqualTo(MaxPossibleLength()));
Assert.That(path.RespectsTerrainConstraints(), Is.True);
}
}
๐ Integration Testing with Mock Unity Environment
The real breakthrough was creating a lightweight Unity simulation for integration tests:
public class MockUnityEnvironment
{
public MockGrid Grid { get; private set; }
public MockGameManager GameManager { get; private set; }
public void SimulatePlayerAction(PlayerAction action)
{
// Simulates Unity's event system without Unity overhead
// Allows testing complete workflows in milliseconds
}
}
๐ง Behavioral Testing: Does It Feel Right?
Beyond correctness, I test for player experience quality:
[Test]
public void HousePlacement_FeelsNaturalToPlayers()
{
var placements = GenerateTypicalPlayerPlacements();
foreach(var placement in placements)
{
var result = houseManager.PlaceBuilding(placement);
// Subjective qualities made objective:
Assert.That(result.OrientationFeelsNatural(), Is.True);
Assert.That(result.SizeFeelsReasonable(), Is.True);
Assert.That(result.PositionFollowsUrbanPrinciples(), Is.True);
}
}
โก Performance Testing Under Realistic Load
Game systems must perform well under player stress:
[Test]
public void LargeCityGeneration_MaintainsPerformance()
{
var stopwatch = Stopwatch.StartNew();
// Simulate building a 100x100 city
var cityBuilder = new CityBuilder();
cityBuilder.GenerateCity(size: 100, buildings: 2000, roads: 500);
stopwatch.Stop();
// Must complete within one frame budget (16ms for 60fps)
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(16));
}
The Testing Ecosystem That Changed Everything
This comprehensive approach delivered transformative benefits:
- ๐ก๏ธ Fearless Refactoring: Massive architectural changes with confidence
- ๐ Bug Prevention: Catches issues before they become player problems
- ๐ Living Documentation: Tests explain system behavior better than comments
- โก Rapid Iteration: New features integrate safely with existing systems
- ๐ฏ Quality Assurance: Objective measurement of subjective game feel
Advanced Insight: The best game testing strategies verify not just that systems work correctly, but that they create the intended player experience. Testing "does it feel good?" is as important as testing "does it work?"
Behind the Scenes: Architecture That Scales ๐๏ธ
One thing I've learned from shipping multiple Unity projects is that good architecture is invisible - it just makes everything else work smoothly. Let me pull back the curtain on how I structured this system for maintainability and growth.
๐ Project Structure That Makes Sense
When other developers join the project, they can immediately understand what does what:
Assets/Scripts/
โโโ ๐ HouseManager.cs # Smart house placement logic
โโโ ๐ฃ๏ธ RoadManager.cs # AI-powered road pathfinding
โโโ ๐ง GameManager.cs # Central nervous system
โโโ ๐งช Tests/
โโโ HouseManagerTests.cs # Core functionality validation
โโโ HouseManagerAdvancedTests.cs # Complex scenario testing
โโโ HouseManagerRegressionTests.cs # Bug prevention armor
โโโ TestableHouseManager.cs # Testing infrastructure
โโโ HouseManagerTestRunner.cs # One-click test execution
Dev Philosophy: If someone can't understand your project structure in 30 seconds, it's too complex.
๐ฏ Design Patterns That Actually Matter
I didn't just use design patterns because they're "best practice" - each one solved a specific problem I was facing:
Singleton Pattern โ The Information Hub
- Problem: Multiple managers needed shared game state
- Solution: GameManager provides single source of truth
- Benefit: No more "which system knows what?" confusion
Strategy Pattern โ The Smart Builder
- Problem: Houses need different placement rules based on road layout
- Solution: Different building strategies for horizontal vs vertical roads
- Benefit: Easy to add new building types without touching existing code
Observer Pattern โ The City Simulator
- Problem: VillagerManager needs to know when new housing is available
- Solution: Automatic notifications when houses are built
- Benefit: Emergent city simulation without tight coupling
Template Method โ The Consistency Engine
- Problem: Coordinate conversion was scattered and error-prone
- Solution: Standardized conversion methods across all managers
- Benefit: Eliminated 90% of coordinate-related bugs
โก Performance: Making It Smooth for Players
Players don't care about your clever algorithms - they care that the game feels responsive. Here's how I optimized for the player experience:
Smart Pathfinding Performance
- The Problem: Pathfinding could freeze the game for complex routes
- The Solution: Multi-tier approach that tries fast methods first
- The Result: 95% of roads use instant straight-line paths
Preview System Optimization
- The Problem: Real-time building previews were causing frame drops
- The Solution: Only update changed preview elements, cache everything else
- The Result: Smooth 60fps even during complex building operations
Memory Management for Long Play Sessions
- The Problem: Preview objects were leaking memory over time
- The Solution: Proper cleanup and object pooling
- The Result: Players can build massive cities without performance degradation
Performance Tip: The best optimization is often not optimizing - choosing the right algorithm from the start beats micro-optimizations every time.
Seeing It In Action: The Player Experience ๐ฎ
The best way to understand this system is to see how it feels from a player's perspective. Every technical decision was made to create moments of delight and eliminate frustration.
๐ Building Your First Neighborhood
Picture this: You're a new player who just started the game...
Step 1: The Magic Drag โจ
- You click somewhere near a road and start dragging
- Instantly, a semi-transparent preview shows exactly where houses will appear
- The AI automatically calculates the perfect size and orientation - no manual rotation needed!
// Behind the scenes: This simple drag gesture triggers sophisticated analysis
// 1. Player clicks and drags โ Area defined
// 2. AI analyzes nearby roads โ Optimal orientation determined
// 3. Smart sizing โ Realistic urban planning rules applied
// 4. Instant preview โ Player sees exactly what they're getting
Step 2: The Satisfying Snap ๐ฏ
- Release the mouse and snap - perfectly aligned houses appear
- They're automatically connected to the road network
- They follow real urban planning principles (max 2 cells deep from roads)
- It just feels right without the player knowing why
Player Feedback: "I love how the houses just know where they should go. It feels like the game is reading my mind!" - Beta tester Sarah
๐ฃ๏ธ Connecting Your City with Smart Roads
The Path-Finding Magic ๐ง
You want to connect two distant areas of your city...
- Click your starting point (maybe near the town center)
- Drag toward your destination (perhaps a new residential area)
- Watch in real-time as the AI calculates the optimal route:
// The AI's decision process in real-time:
// ๐ฏ "Can I go straight vertically?" โ Try vertical line
// โก๏ธ "How about straight horizontally?" โ Try horizontal line
// ๐ง "Need to be clever?" โ Advanced pathfinding kicks in
// โ
"Found the perfect route!" โ Show preview to player
Smart Obstacle Avoidance ๐ป
The AI doesn't just find a path - it finds the smart path:
- Around Buildings: Automatically routes around existing structures
- Respecting Terrain: Knows water and mountains are no-go zones
- Efficient Routing: Prefers straight lines like real highway planning
- Visual Feedback: Shows you the route before you commit
๐ง For Fellow Developers: The Technical Magic
Coordinate Conversion Made Simple ๐
One of the biggest headaches in isometric games is coordinate conversion. Here's how I made it bulletproof:
// From messy Unity grid coordinates to clean game logic:
Vector3Int messyUnityCoords = grid.WorldToCell(mousePosition);
Vector3Int cleanGameCoords = GameManager.Instance.RuntimeToMapCoordinates(messyUnityCoords);
// Now use clean coordinates for all game logic
bool canPlayerBuildHere = GameManager.Instance.IsBuildableCell(cleanGameCoords);
This simple conversion eliminated hundreds of potential bugs and made the entire system predictable.
The Power of Immediate Feedback โก
Everything in this system is designed for immediate feedback:
- Building previews update in real-time as you drag
- Road paths are calculated and shown instantly
- Invalid areas simply don't highlight - no confusing error messages
- Success feedback is immediate and satisfying
Dev Insight: The difference between a good building system and a great one is the preview system. Players need to see their intentions reflected immediately.
Lessons Learned: What I'd Do Differently ๐ค
Building this system taught me as much about game development as it did about urban planning. Here are the biggest lessons that other developers can learn from:
๐ง Start with the Player Experience, Not the Code
My Original Approach: I started by building a generic "grid placement system" and tried to make it work for buildings.
What I Learned: This backwards approach led to months of frustration. Buildings felt mechanical and lifeless.
The Better Way: I started over by playing Cities: Skylines for hours, taking notes on what felt good, then building systems to recreate those feelings.
Lesson: Good game systems serve player emotions first, technical requirements second.
๐งช Test-Driven Architecture for Game Systems
My Resistance: "I'll add tests later when the features are done."
Reality Check: One small coordinate bug took me 3 days to track down across multiple managers because I had no systematic way to isolate the problem.
The Breakthrough: I discovered that testing game systems requires a fundamentally different approach than web development. You need to design for testability from day one.
The Game-Changer: Creating testable abstractions that decouple Unity dependencies from core logic. This meant I could test pathfinding algorithms, coordinate conversions, and placement rules without starting Unity at all.
Lesson: In game development, your testing strategy must account for state management, frame-based updates, and complex object interactions that don't exist in typical software.
๐ฏ Simplicity Scales, Complexity Doesn't
My Over-Engineering Phase: I tried to make the system handle every possible edge case from day one.
The Problem: The code became so complex that simple changes broke everything.
The Solution: I started with the simplest possible version that worked, then added complexity only when needed.
Example: The road pathfinding started as just straight lines. I added BFS only when players actually needed complex routing.
Lesson: Build the minimum viable system first, then evolve it based on real usage.
๐ Architecture Decisions Compound Over Time
Early Mistake: Each manager tracked its own state independently.
The Pain: Houses didn't know about roads, roads didn't know about houses, chaos ensued.
The Fix: Centralized state management through GameManager.
The Payoff: This single architectural decision eliminated 90% of coordination bugs and made new features trivial to add.
Lesson: Good architecture is an investment that pays dividends for years.
Want to Build Your Own? ๐ ๏ธ
If this post inspired you to build your own intelligent placement systems, here are my top recommendations:
Start Small, Think Big
- Begin with a simple grid-based placement system
- Add one intelligent feature at a time (road alignment, terrain awareness, etc.)
- Test each feature thoroughly before moving to the next
- Always prioritize player experience over technical complexity
Essential Resources
- "Game Programming Patterns" by Robert Nystrom - Architecture guidance that actually works
- Unity Test Runner Documentation - For building bulletproof systems
- "The Art of Game Design" by Jesse Schell - Understanding what makes placement feel good
Final Thoughts: Building Games That Feel Alive ๐ซ
After two weeks of intensive development, watching players effortlessly create beautiful cities with this system brings me more joy than any technical achievement. The best game systems are invisible - they amplify player creativity without getting in the way.
The HouseManager and RoadManager aren't just code - they're the foundation of countless player stories. Every perfectly placed house, every efficiently routed road, every thriving neighborhood represents a moment where technology served human creativity.
Whether you're building your first Unity project or your fiftieth, remember that great game systems solve human problems, not technical ones. Focus on the moments of delight you want to create, then build the technology to support them.
Happy building, and may your cities be forever prosperous! ๐