324 lines
11 KiB
Markdown
324 lines
11 KiB
Markdown
# 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:**
|
|
```csharp
|
|
_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)
|
|
```csharp
|
|
// 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
|
|
```csharp
|
|
[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:
|
|
```csharp
|
|
public enum BuildingCategory { Housing, Production, Defense }
|
|
public BuildingCategory category;
|
|
```
|
|
|
|
### Add Resource Costs
|
|
```csharp
|
|
[Header("Cost")]
|
|
public int woodCost;
|
|
public int stoneCost;
|
|
```
|
|
|
|
Then check resources in `BuildingManager.PlaceBuildingServerRpc()`
|
|
|
|
### Add Build Range Limit
|
|
In `BuildingPlacement.UpdatePreviewPosition()`:
|
|
```csharp
|
|
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)
|