Files
Northbound/BUILDING_SYSTEM_SETUP.md

11 KiB

Building System Setup Guide

Overview

The building system allows players to place buildings with:

  • Grid-based snapping for aligned placement
  • Overlap detection to prevent buildings from colliding
  • Ground validation to ensure buildings are placed on terrain
  • Ghost preview with green (valid) / red (invalid) visual feedback
  • Network synchronization across all players

Components

1. BuildingData (ScriptableObject)

Defines properties for each building type:

  • Building name and prefab reference
  • Grid size (width, length, height)
  • Placement offset and rotation settings

2. Building (MonoBehaviour)

Component attached to placed building instances. Tracks:

  • Grid position
  • Rotation (0-3, representing 0°, 90°, 180°, 270°)
  • Reference to BuildingData

3. BuildingManager (NetworkBehaviour - Singleton)

Central manager that:

  • Maintains list of all placed buildings
  • Validates placement (ground check + overlap detection)
  • Handles grid snapping
  • Spawns buildings on network via ServerRpc

4. BuildingPlacement (NetworkBehaviour)

Player-side building interface:

  • Uses new Input System (PlayerInputActions, Keyboard.current, Mouse.current)
  • Handles input (B key to toggle, Q/E to rotate, click to place)
  • Shows ghost preview at mouse position
  • Updates preview material (green/red) based on validity
  • Proper input lifecycle management (enable/disable/dispose)

Setup Instructions

Step 0: Setup Input Actions (Required First!)

Before using the building system, you MUST add the required input actions.

See INPUT_ACTIONS_SETUP.md for detailed instructions.

Quick summary - Add these to your Player action map in InputSystem_Actions.inputactions:

  1. ToggleBuildMode (Button) → Keyboard B
  2. Rotate (Value/Axis) → Keyboard R
  3. Build (Button) → Mouse Left Button

After adding, save the asset to regenerate the code.

Step 1: Create BuildingData Assets

  1. Right-click in Project window → Create → Northbound → Building Data
  2. Configure your building:
    • Assign the building prefab
    • Set grid size (width, length, height)
    • Adjust placement offset if needed
    • Enable/disable rotation

Step 2: Setup Building Prefabs

For each building prefab:

  1. Add NetworkObject component
  2. Add Building component
  3. Ensure it has a Renderer for preview
  4. Add colliders for gameplay (player collision, physics)
  5. Important: Colliders are NOT used for placement validation - only BuildingData grid size matters
  6. Set the prefab's layer to anything EXCEPT the Ground layer (buildings will be raycast-ignored during placement)

Step 3: Setup BuildingManager

  1. Create empty GameObject in your scene named "BuildingManager"
  2. Add BuildingManager component
  3. Add NetworkObject component
  4. Configure settings:
    • Grid Size: Size of one grid unit (default: 1)
    • Ground Layer: Layer mask for valid ground (e.g., "Ground")
    • Available Buildings: Add your BuildingData assets

Step 4: Add BuildingPlacement to Player

  1. Open your Player prefab
  2. Add BuildingPlacement component
  3. Configure:
    • Ground Layer: Same as BuildingManager
    • Max Placement Distance: How far player can place (default: 100)
    • Valid Material: Green transparent material (auto-created if null)
    • Invalid Material: Red transparent material (auto-created if null)

Step 5: Setup Layers

  1. Create a layer named "Ground" (or your preferred name)
  2. Assign this layer to terrain/ground objects
  3. Important: Make sure building prefabs are on a different layer (not Ground)
    • This allows raycasting through buildings to hit the ground
    • You can place buildings even when cursor is over existing buildings

Controls

Default bindings (customizable in Input Actions):

  • B: Toggle build mode on/off
  • R: Rotate building (90° increments)
  • Left Mouse Button: Place building (if valid position)
  • Mouse Movement: Move preview to placement location

Input System

The building system uses Unity's new Input System with action callbacks:

  • Actions: ToggleBuildMode, Rotate, Build
  • Subscribe to .performed events in OnNetworkSpawn
  • Unsubscribe in OnNetworkDespawn
  • No hardcoded keys - all controls defined in Input Actions asset
  • Easy to rebind without code changes

Pattern:

_inputActions.Player.ToggleBuildMode.performed += OnToggleBuildMode;
_inputActions.Player.Rotate.performed += OnRotate;
_inputActions.Player.Build.performed += OnBuild;

This uses callback-based input, similar to the pattern shown in InputSystem_Actions.cs examples.

Technical Implementation

Modern Input System (Callback Pattern)

// Initialization in OnNetworkSpawn
_inputActions = new PlayerInputActions();
_inputActions.Player.ToggleBuildMode.performed += OnToggleBuildMode;
_inputActions.Player.Rotate.performed += OnRotate;
_inputActions.Player.Build.performed += OnBuild;
_inputActions.Enable();

// Callback handlers
private void OnToggleBuildMode(InputAction.CallbackContext context)
{
    ToggleBuildMode();
}

private void OnRotate(InputAction.CallbackContext context)
{
    float rotateValue = context.ReadValue<float>();
    // Positive = rotate right, Negative = rotate left
}

private void OnBuild(InputAction.CallbackContext context)
{
    TryPlaceBuilding();
}

// Cleanup in OnNetworkDespawn
_inputActions.Player.ToggleBuildMode.performed -= OnToggleBuildMode;
_inputActions.Player.Rotate.performed -= OnRotate;
_inputActions.Player.Build.performed -= OnBuild;
_inputActions.Disable();
_inputActions.Dispose();

Modern Netcode RPC Pattern

[ServerRpc(RequireOwnership = false)]
public void PlaceBuildingServerRpc(int buildingIndex, Vector3 position, int rotation, ServerRpcParams serverRpcParams = default)
{
    // Validation
    if (!IsValidPlacement(...)) return;

    // Server-side spawn
    GameObject obj = Instantiate(...);
    NetworkObject netObj = obj.GetComponent<NetworkObject>();
    netObj.Spawn(); // Automatically syncs to all clients
}

How It Works

Build Mode Flow

  1. Player presses B → enters build mode
  2. Preview appears at mouse cursor position
  3. System raycasts to find ground (two-stage):
    • Method 1: Direct raycast to ground, ignoring building colliders
    • Method 2: If ground not visible (buildings blocking), raycast to anything, then cast downward from that point to find ground below
    • This allows placement even in completely surrounded cells
  4. System checks placement validity:
    • Is there ground below?
    • Does grid bounds overlap with existing buildings?
  5. Preview shows green if valid, red if invalid
  6. Player rotates with R if desired
  7. Player clicks to confirm placement
  8. ServerRpc sent to server to spawn building
  9. Building synchronized to all clients

Validation System (Grid-Based)

IsValidPlacement() checks:
├── Ground Check: Raycast down to find ground
└── Overlap Check: Compare GRID BOUNDS (not colliders!)
    ├── Uses BuildingData width/length/height
    ├── NOT the actual collider size
    └── Rotates grid size automatically (90°/180°/270°)

Important: Collision detection uses the grid size from BuildingData, not the physical colliders on the building prefab. This means:

  • A building with width=2, length=3, height=2 occupies a 2x3 grid area
  • Even if the visual model is smaller/larger, the grid size determines overlap
  • This allows consistent, predictable building placement

Grid Snapping

  • World position → rounded to nearest grid size
  • Example: gridSize=2, position (3.7, 0, 5.2) → snaps to (4, 0, 6)

Rotation System

  • Rotation values: 0, 1, 2, 3 → 0°, 90°, 180°, 270°
  • Building size auto-swaps width/length when rotated 90° or 270°

Example Building Setup

Small House (2x2)

Name: Small House
Prefab: SmallHousePrefab
Width: 2
Length: 2
Height: 3
Allow Rotation: true

Wall Segment (1x3)

Name: Wall
Prefab: WallPrefab
Width: 1
Length: 3
Height: 2
Allow Rotation: true

Debug Visualization

The system includes visual debugging to help you understand grid placement:

In Scene View (when build mode is active):

  • Green/Red Wire Cube: Shows the grid bounds being checked
    • Green = valid placement
    • Red = invalid (overlapping or no ground)
  • Yellow Grid Cells: Shows individual grid cells occupied
  • Yellow Sphere: Grid origin point (snapped position)

On Placed Buildings:

  • Cyan Wire Cube: Shows the grid bounds of placed buildings
  • Yellow Wire Cube (when selected): Highlights the selected building's grid
  • Magenta Sphere: Grid position origin

Enable/disable with:

  • BuildingPlacement → Show Grid Bounds
  • Building → Show Grid Bounds

Troubleshooting

Preview doesn't appear:

  • Check if BuildingManager.Instance exists
  • Verify availableBuildings list has BuildingData
  • Ensure Ground layer is set correctly

Can't place buildings:

  • Check ground layer mask matches terrain
  • Verify building prefabs have NetworkObject
  • Ensure BuildingManager has NetworkObject and is spawned

Buildings overlap despite visual gap:

  • Remember: Collision uses grid size from BuildingData, not visual model size
  • Check the grid bounds in Scene view (cyan wire cubes)
  • Adjust width/length/height in BuildingData to match desired grid footprint

Can't place buildings when cursor is near existing buildings:

  • FIXED: System now uses RaycastAll and ignores building colliders
  • Raycasts pass through buildings to hit the ground
  • You can place buildings even when cursor is hovering over old ones
  • Just make sure buildings are NOT on the Ground layer

Can't place in center cell when surrounded by buildings:

  • FIXED: Two-stage raycast system
  • If direct ground raycast fails, system raycasts to any object then down to find ground
  • Works even when all adjacent cells are built and blocking the ground view
  • You can fill in gaps surrounded by buildings

Network issues:

  • BuildingManager must be spawned on network
  • Ensure player has BuildingPlacement component with IsOwner

Extending the System

Add Building Categories

Edit BuildingData.cs and add:

public enum BuildingCategory { Housing, Production, Defense }
public BuildingCategory category;

Add Resource Costs

[Header("Cost")]
public int woodCost;
public int stoneCost;

Then check resources in BuildingManager.PlaceBuildingServerRpc()

Add Build Range Limit

In BuildingPlacement.UpdatePreviewPosition():

float distanceToPlayer = Vector3.Distance(transform.position, snappedPosition);
if (distanceToPlayer > maxBuildRange)
    isValid = false;

Multiple Building Selection

Add UI buttons that change selectedBuildingIndex in BuildingPlacement

Notes

  • Buildings are spawned on the server via ServerRpc
  • Preview is local-only (each player sees their own preview)
  • Ground layer should include all walkable/buildable surfaces
  • Grid size should match your game's scale (larger buildings = larger grid)