Clean Code Principles
Overview
GridBuilding is built with clean code principles to make your development easier. This guide shows you how to write clean, maintainable code when using the building system.
What This Means for You
Simple Godot Scripts
Your code stays focused on game logic, not building system complexity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # ✅ GOOD: Clean, focused code
extends Node
func place_building(building_type, position):
var result = $PlacementSystem.place_building(building_type, position, "player")
if result.success:
show_success_effect(position)
update_resources(-get_building_cost(building_type))
else:
show_error(result.error)
# ❌ AVOID: Complex mixed concerns
extends Node
func place_building(building_type, position):
# Don't mix building logic with game logic
if not validate_grid_position(position):
return false
if not check_player_resources(get_building_cost(building_type)):
return false
if not check_building_unlocked(building_type):
return false
# 20+ lines of building system logic...
|
Key Principles
1. Single Responsibility
Each function does one thing well:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| # ✅ GOOD: Single purpose functions
func handle_building_placement(building_type, position):
var result = attempt_placement(building_type, position)
handle_placement_result(result)
func attempt_placement(building_type, position):
return $PlacementSystem.place_building(building_type, position, "player")
func handle_placement_result(result):
if result.success:
celebrate_placement()
else:
show_placement_error(result.error)
# ❌ AVOID: Functions doing too much
func handle_building_placement(building_type, position):
# Validation, placement, UI updates, resource management...
if not validate_grid_position(position):
show_error("Invalid position")
return
if not check_resources(building_type):
show_error("Not enough resources")
return
var result = $PlacementSystem.place_building(building_type, position, "player")
if result.success:
update_ui()
play_sound()
show_particles()
save_game()
unlock_achievements()
# ... more mixed concerns
|
2. Clear Naming
Use descriptive names that explain what code does:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # ✅ GOOD: Clear, descriptive names
func can_player_afford_building(building_type):
return resource_manager.get_resources() >= get_building_cost(building_type)
func is_position_valid_for_building(building_type, grid_position):
return $PlacementSystem.can_place_building(building_type, grid_position)
func handle_successful_placement(building_data):
spawn_placement_effects(building_data.position)
deduct_building_cost(building_data.type)
update_building_statistics(building_data.type)
# ❌ AVOID: Unclear names
func check(type, pos):
return resource_manager.gold >= costs[type] and $PlacementSystem.validate(pos)
func do_stuff(result):
if result.ok:
make_effects()
take_money()
update_stats()
|
3. Small Functions
Keep functions short and focused:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| # ✅ GOOD: Small, focused functions
func place_building_at_cursor(building_type):
var grid_pos = get_cursor_grid_position()
if not can_place_building_here(building_type, grid_pos):
show_invalid_placement_feedback()
return
place_building(building_type, grid_pos)
func can_place_building_here(building_type, grid_pos):
return is_within_grid_bounds(grid_pos) and
$PlacementSystem.can_place_building(building_type, grid_pos) and
player_has_required_resources(building_type)
func show_invalid_placement_feedback():
play_error_sound()
shake_camera()
show_tooltip("Cannot build here")
# ❌ AVOID: Large functions
func place_building_at_cursor(building_type):
var grid_pos = get_cursor_grid_position()
# Too much logic in one function
if grid_pos.x < 0 or grid_pos.y < 0 or grid_pos.x >= GRID_WIDTH or grid_pos.y >= GRID_HEIGHT:
play_error_sound()
shake_camera()
show_tooltip("Out of bounds")
return
if not $PlacementSystem.can_place_building(building_type, grid_pos):
play_error_sound()
shake_camera()
show_tooltip("Position occupied")
return
if resource_manager.gold < building_costs[building_type]:
play_error_sound()
shake_camera()
show_tooltip("Not enough gold")
return
# ... more logic
|
Practical Examples
Building Manager Pattern
Clean Building Manager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| extends Node
class_name BuildingManager
# Service references
@onready var placement_service = $PlacementService
@onready var manipulation_service = $ManipulationService
@onready var resource_manager = $ResourceManager
# Public interface - simple and clear
func can_place_building(building_type, position):
return is_position_valid(position) and
player_can_afford(building_type) and
building_is_unlocked(building_type)
func place_building(building_type, position):
var result = placement_service.place_building(building_type, position, "player")
if result.success:
handle_successful_placement(building_type, result)
else:
handle_placement_failure(result)
return result.success
func remove_building(building_node):
var result = manipulation_service.remove_building(building_node)
if result.success:
handle_successful_removal(building_node, result)
else:
handle_removal_failure(result)
return result.success
# Private helpers - focused on single tasks
func is_position_valid(position):
return placement_service.can_place_building("", position) # Type-agnostic check
func player_can_afford(building_type):
return resource_manager.has_resources(get_building_cost(building_type))
func building_is_unlocked(building_type):
return unlock_manager.is_building_unlocked(building_type)
func handle_successful_placement(building_type, result):
deduct_resources(building_type)
play_success_sound()
show_placement_effect(result.position)
update_statistics(building_type)
func handle_placement_failure(result):
play_error_sound()
show_error_message(result.error)
func handle_successful_removal(building_node, result):
refund_building_cost(building_node.building_type)
play_removal_sound()
show_removal_effect(result.position)
|
UI Controller Pattern
Clean UI Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| extends Control
class_name BuildingUIController
# UI Elements
@onready var building_grid = $BuildingGrid
@onready var resource_display = $ResourceDisplay
@onready var placement_preview = $PlacementPreview
# Game Systems
@onready var building_manager = $"../BuildingManager"
@onready var grid_targeting = $"../GridTargetingSystem"
func _ready():
setup_ui_connections()
update_ui_state()
func _process(_delta):
update_placement_preview()
func setup_ui_connections():
building_grid.building_selected.connect(_on_building_selected)
building_grid.building_deselected.connect(_on_building_deselected)
func update_ui_state():
resource_display.update_resources()
update_building_grid_availability()
func update_placement_preview():
if not has_selected_building():
placement_preview.hide()
return
var grid_pos = grid_targeting.get_current_grid_position()
var can_place = building_manager.can_place_building(selected_building, grid_pos)
placement_preview.show_at(grid_pos, can_place)
func has_selected_building():
return selected_building != ""
# Event handlers - single responsibility each
func _on_building_selected(building_type):
selected_building = building_type
update_placement_preview()
func _on_building_deselected():
selected_building = ""
placement_preview.hide()
func _on_placement_requested(position):
if has_selected_building():
building_manager.place_building(selected_building, position)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| extends Node
class_name BuildingInputHandler
# Dependencies
@onready var building_manager = $"../BuildingManager"
@onready var ui_controller = $"../BuildingUIController"
# Input state
var selected_building = ""
var is_placement_mode = false
func _ready():
setup_input_actions()
func _input(event):
handle_building_input(event)
func setup_input_actions():
InputMap.add_action("place_building")
InputMap.add_action("cancel_placement")
InputMap.add_action("remove_building")
func handle_building_input(event):
if is_placement_mode:
handle_placement_input(event)
else:
handle_selection_input(event)
func handle_placement_input(event):
if event.is_action_pressed("place_building"):
attempt_placement_at_cursor()
elif event.is_action_pressed("cancel_placement"):
exit_placement_mode()
func handle_selection_input(event):
if event.is_action_pressed("remove_building"):
start_removal_mode()
func attempt_placement_at_cursor():
var grid_pos = get_cursor_grid_position()
if building_manager.place_building(selected_building, grid_pos):
exit_placement_mode() # Success - exit placement mode
else:
shake_ui() # Failure - show feedback
func exit_placement_mode():
is_placement_mode = false
ui_controller.hide_placement_preview()
func start_removal_mode():
ui_controller.show_removal_cursor()
|
Error Handling Best Practices
Clear Error Messages
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # ✅ GOOD: Specific, actionable errors
func handle_placement_result(result):
if not result.success:
match result.error_type:
"POSITION_OCCUPIED":
show_error("This position is already occupied!")
"INSUFFICIENT_RESOURCES":
show_error("Not enough resources! Need " + str(result.required_resources))
"INVALID_POSITION":
show_error("Cannot build at this location!")
"BUILDING_LOCKED":
show_error("This building is not unlocked yet!")
_:
show_error("Placement failed: " + result.error)
# ❌ AVOID: Generic, unhelpful errors
func handle_placement_result(result):
if not result.success:
show_error("Building placement failed")
|
Graceful Error Recovery
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # ✅ GOOD: Graceful error handling
func place_building_with_fallback(building_type, position):
var result = building_manager.place_building(building_type, position)
if not result.success:
# Try to help the user
if result.error_type == "POSITION_OCCUPIED":
# Find nearest valid position
var nearest_valid = find_nearest_valid_position(position)
if nearest_valid:
show_suggestion("Try placing at " + str(nearest_valid))
# Show helpful feedback
show_error_with_suggestion(result.error, get_suggestion_for_error(result.error_type))
return result.success
func get_suggestion_for_error(error_type):
match error_type:
"POSITION_OCCUPIED":
return "Try clearing the area first"
"INSUFFICIENT_RESOURCES":
return "Gather more resources or choose a cheaper building"
"INVALID_POSITION":
return "Build within the highlighted grid area"
_:
return "Check building requirements"
|
Efficient UI Updates
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # ✅ GOOD: Throttled UI updates
var _last_ui_update = 0
const UI_UPDATE_RATE = 0.1 # 10 times per second
func _process(delta):
_last_ui_update += delta
if _last_ui_update >= UI_UPDATE_RATE:
update_ui_if_needed()
_last_ui_update = 0
func update_ui_if_needed():
if has_ui_changed():
resource_display.update_resources()
update_building_buttons()
# ❌ AVOID: Update every frame
func _process(_delta):
resource_display.update_resources() # Expensive!
update_building_buttons() # Expensive!
|
Batch Operations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # ✅ GOOD: Batch multiple operations
func place_multiple_buildings(buildings_data):
# Validate all first
for building_data in buildings_data:
if not building_manager.can_place_building(building_data.type, building_data.position):
show_error("Cannot place " + building_data.type + " at " + str(building_data.position))
return false
# Then place all at once
for building_data in buildings_data:
building_manager.place_building(building_data.type, building_data.position, "player")
return true
# ❌ AVOID: Individual operations in loops
func place_multiple_buildings(buildings_data):
for building_data in buildings_data:
# Each call does validation, UI updates, etc.
building_manager.place_building(building_data.type, building_data.position, "player")
|
Testing Your Code
Unit Testing Patterns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # Test individual functions
func test_can_place_building():
# Arrange
var building_type = "house"
var valid_position = Vector2i(5, 5)
var invalid_position = Vector2i(-1, -1)
# Act & Assert
assert(building_manager.can_place_building(building_type, valid_position))
assert(not building_manager.can_place_building(building_type, invalid_position))
# Test error handling
func test_error_messages():
var result = building_manager.place_building("invalid_type", Vector2i(-1, -1))
assert(not result.success)
assert(result.error != "")
assert(result.error_type in ["UNKNOWN_BUILDING_TYPE", "INVALID_POSITION"])
|
Integration Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Test complete workflows
func test_complete_building_workflow():
# Select building
ui_controller.select_building("house")
assert(ui_controller.has_selected_building())
# Place building
var position = Vector2i(5, 5)
var success = building_manager.place_building("house", position)
assert(success)
# Verify building exists
var building = find_building_at(position)
assert(building != null)
assert(building.building_type == "house")
|
Code Review Checklist
Function Quality
Error Handling
Maintainability
Common Anti-Patterns to Avoid
1. God Complex Functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # ❌ AVOID: Functions that do everything
func handle_everything():
# Input handling
if Input.is_action_just_pressed("click"):
# Building placement
if can_place_building():
place_building()
# UI updates
update_ui()
# Resource management
deduct_resources()
# Sound effects
play_sound()
# Save game
save_game()
# Statistics
update_stats()
# Achievements
check_achievements()
|
2. Magic Numbers and Strings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # ❌ AVOID: Hard-coded values
func place_building():
if player_gold >= 100: # Magic number
building_type = "house" # Magic string
position = Vector2i(5, 5) # Magic position
# ✅ GOOD: Named constants
const HOUSE_COST = 100
const HOUSE_TYPE = "house"
func place_building():
if player_gold >= HOUSE_COST:
building_type = HOUSE_TYPE
position = get_default_building_position()
|
3. Deep Nesting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # ❌ AVOID: Deep nesting
func complex_function():
if condition1:
if condition2:
if condition3:
if condition4:
# Do something
else:
# Handle else
else:
# Handle else
else:
# Handle else
else:
# Handle else
# ✅ GOOD: Early returns
func complex_function():
if not condition1:
return handle_else1()
if not condition2:
return handle_else2()
if not condition3:
return handle_else3()
if not condition4:
return handle_else4()
# Do something
|
Benefits of Clean Code
For You
- Faster Development: Code is easier to write and modify
- Fewer Bugs: Clear code is easier to get right
- Better Performance: Optimized structure and patterns
- Easier Testing: Small, focused functions are easy to test
For Your Team
- Better Collaboration: Clean code is easy to understand
- Faster Onboarding: New developers can quickly understand the code
- Consistent Style: Everyone follows the same patterns
- Better Documentation: Self-documenting code needs less explanation
For Your Project
- Easier Maintenance: Changes are localized and predictable
- Better Performance: Optimized patterns and structures
- Future-Proof: Clean code adapts well to new requirements
- Professional Quality: Code that meets industry standards
Clean code isn’t just about making things work - it’s about making things work well, be maintainable, and be enjoyable to work with. GridBuilding’s architecture helps you write clean code by providing clear boundaries and responsibilities.
Start with these principles, and you’ll see the benefits immediately in your development workflow!