Service Pattern Migration Guide
Overview
This guide helps you migrate your existing GridBuilding projects to use the new service architecture. The new system provides better performance, cleaner code, and easier maintenance.
What’s Changing
GDScript 5.1 vs C# 6.0 Architecture
- GDScript 5.1 plugin
- Uses the original GB InjectorSystem pattern (
Injectable, InjectorSystem, GBInjectableFactory). - Favors a simple, node-hierarchy-centric mental model for Godot users.
- Treated as a locked, maintained track: we keep it working and documented but do not evolve its DI model.
- C# 6.0 plugin (GridPlacement)
- Uses a service + adapter architecture: pure C# core services plus thin Godot wrappers.
- DI is based on
ServiceCompositionRoot + ServiceRegistry and explicit constructor/property injection. - Provides helper nodes (placement, manipulation, targeting, UI managers) so games without an existing C# backend can still drop in ready-made Godot nodes that call into the core services.
This guide focuses on the 6.0 C# service pattern. The 5.1 GDScript GB InjectorSystem remains available as the simpler legacy option.
For the 5.1 GDScript line, the near-term goal is:
- Get the plugin and its tests compiling and passing.
- Avoid large-scale architecture rewrites; only fix severe boundary violations or apply refactors that clearly reduce effort to get tests green.
- If community demand for advanced GDScript integrations is high, we may later port the GDS plugin to the same service+adapter architecture, but that is out of scope for the 5.1 track.
Key API Migrations (v5 → v6)
- Modes:
BuildingMode / ManipulationMode ➜ GridMode (update UI/events/contexts to listen to GridMode). - Building APIs: Godot
BuildingBehavior / IBuildingService / BuildingService ➜ PlacementSystem + IPlacementService (replace building-flavored calls with placement service orchestration). - DI Pattern:
Injectable / InjectorSystem / GBInjectableFactory.Create* ➜ Enhanced Service Registry (ServiceCompositionRoot + ServiceRegistry, constructor/property injection).
Before (Old Architecture)
- BuildingSystem handled everything in one large class
- Mixed Godot and C# code
- Difficult to test and maintain
- Performance issues with complex GDScript logic
After (New Architecture)
- Separate services for each responsibility
- Pure C# core with thin Godot wrappers
- Easy to test and extend
- Optimized performance with C# business logic
Migration Steps
Step 1: Update Your Scene Setup
Old Way
1
2
3
4
5
6
7
| # Old: Single building system
extends Node
@onready var building_system = $BuildingSystem
func _ready():
building_system.connect("building_placed", _on_building_placed)
|
New Way
1
2
3
4
5
6
7
8
9
10
11
| # New: Separate service systems
extends Node
@onready var placement_system = $PlacementSystem
@onready var manipulation_system = $ManipulationSystem
@onready var grid_targeting_system = $GridTargetingSystem
func _ready():
placement_system.building_placed.connect(_on_building_placed)
manipulation_system.building_removed.connect(_on_building_removed)
grid_targeting_system.target_changed.connect(_on_target_changed)
|
Step 2: Update Building Placement
Old Code
1
2
3
4
5
6
7
8
| # Old: Direct building system calls
func place_building(building_type, position):
var result = building_system.place_building(building_type, position, "player")
if result.success:
print("Building placed at ", position)
else:
print("Placement failed: ", result.error)
|
New Code
1
2
3
4
5
6
7
8
9
| # New: Use placement service
func place_building(building_type, position):
var result = placement_system.place_building(building_type, position, "player")
if result.success:
print("Building placed at ", position)
# Building automatically added to scene tree
else:
print("Placement failed: ", result.error)
|
Step 3: Update Building Removal
Old Code
1
2
3
4
5
6
7
8
| # Old: Building system handled removal
func remove_building(building_node):
var result = building_system.remove_building(building_node)
if result.success:
print("Building removed")
else:
print("Removal failed: ", result.error)
|
New Code
1
2
3
4
5
6
7
8
9
| # New: Use manipulation service
func remove_building(building_node):
var result = manipulation_system.remove_building(building_node)
if result.success:
print("Building removed")
# Building automatically removed from scene tree
else:
print("Removal failed: ", result.error)
|
Step 4: Update Grid Targeting
Old Code
1
2
3
4
5
6
7
| # Old: Manual grid calculations
func get_grid_position(world_position):
var tile_map = $TileMap
return tile_map.local_to_map(world_position)
func is_valid_grid_position(grid_pos):
return grid_pos.x >= 0 and grid_pos.y >= 0
|
New Code
1
2
3
4
5
6
| # New: Use targeting system
func get_grid_position(world_position):
return grid_targeting_system.get_grid_position(world_position)
func is_valid_grid_position(grid_pos):
return grid_targeting_system.is_valid_position(grid_pos)
|
Specific Migration Examples
Building Manager Migration
Old BuildingManager
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
| # Old: Complex building manager
extends Node
class_name BuildingManager
var buildings = {}
var building_types = {}
func _ready():
load_building_types()
setup_grid()
func place_building(type, position):
# Complex validation logic
if not validate_placement(type, position):
return false
# Manual scene instantiation
var scene = load(building_types[type].scene_path)
var building = scene.instantiate()
building.position = position
add_child(building)
# Manual state management
var building_id = generate_building_id()
buildings[building_id] = {
"node": building,
"type": type,
"position": position,
"health": 100
}
return true
func validate_placement(type, position):
# 20+ lines of validation logic
if not building_types.has(type):
return false
var grid_pos = position_to_grid(position)
if grid_pos.x < 0 or grid_pos.y < 0:
return false
# More validation...
return true
|
New BuildingManager
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
| # New: Clean building manager
extends Node
class_name BuildingManager
@onready var placement_system = $PlacementSystem
@onready var manipulation_system = $ManipulationSystem
@onready var grid_targeting_system = $GridTargetingSystem
func place_building(type, position):
# Simple service call - all validation handled internally
var result = placement_system.place_building(type, position, "player")
if result.success:
print("Building placed successfully")
return true
else:
print("Placement failed: ", result.error)
return false
func remove_building(building_node):
var result = manipulation_system.remove_building(building_node)
if result.success:
print("Building removed successfully")
return true
else:
print("Removal failed: ", result.error)
return false
func can_place_building(type, position):
# Quick validation check
return placement_system.can_place_building(type, position)
|
UI Integration Migration
Old UI Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # Old: Manual UI updates
extends Control
@onready var building_manager = $"../BuildingManager"
func _on_place_button_pressed():
var selected_type = get_selected_building_type()
var grid_pos = get_mouse_grid_position()
if building_manager.place_building(selected_type, grid_pos):
update_ui()
show_success_message()
else:
show_error_message()
func update_ui():
# Manual UI state updates
var building_count = building_manager.buildings.size()
$BuildingCountLabel.text = "Buildings: " + str(building_count)
|
New UI Code
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
| # New: Event-driven UI updates
extends Control
@onready var placement_system = $"../PlacementSystem"
@onready var manipulation_system = $"../ManipulationSystem"
func _ready():
# Connect to service events
placement_system.building_placed.connect(_on_building_placed)
manipulation_system.building_removed.connect(_on_building_removed)
func _on_place_button_pressed():
var selected_type = get_selected_building_type()
var grid_pos = get_mouse_grid_position()
# Service handles all the work
placement_system.place_building(selected_type, grid_pos, "player")
func _on_building_placed(building_data):
# Automatic UI updates via events
update_ui()
show_success_message()
func _on_building_removed(building_data):
update_ui()
show_removal_message()
func update_ui():
# Get current state from services
var building_count = placement_system.get_building_count()
$BuildingCountLabel.text = "Buildings: " + str(building_count)
|
Error Handling Migration
Old Error Handling
1
2
3
4
5
6
7
8
9
10
11
| # Old: Manual error checking
func place_building(type, position):
if not building_types.has(type):
push_error("Unknown building type: " + type)
return false
if not is_valid_position(position):
push_error("Invalid position: " + str(position))
return false
# Manual error handling throughout...
|
New Error Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # New: Service-provided error information
func place_building(type, position):
var result = placement_system.place_building(type, position, "player")
if not result.success:
# Clear, actionable error messages
match result.error_type:
"UNKNOWN_BUILDING_TYPE":
show_error("Unknown building type: " + type)
"INVALID_POSITION":
show_error("Cannot build at this position")
"POSITION_OCCUPIED":
show_error("This position is already occupied")
"INSUFFICIENT_RESOURCES":
show_error("Not enough resources to build this")
_:
show_error("Placement failed: " + result.error)
return false
return true
|
What Gets Faster
- Building Validation: C# validation is 5-10x faster than GDScript
- Grid Calculations: Optimized grid algorithms
- State Management: Efficient C# data structures
- Collision Detection: Fast spatial queries
1
2
3
4
5
| # Old: Manual performance tracking
var start_time = Time.get_ticks_msec()
place_building("house", position)
var end_time = Time.get_ticks_msec()
print("Placement took: ", end_time - start_time, "ms")
|
1
2
3
4
5
6
7
8
| # New: Built-in performance metrics
func place_building(type, position):
var result = placement_system.place_building(type, position, "player")
# Services provide timing information
if result.performance_info:
print("Placement took: ", result.performance_info.execution_time_ms, "ms")
print("Validation took: ", result.performance_info.validation_time_ms, "ms")
|
Testing Your Migration
Step 1: Basic Functionality Test
1
2
3
4
5
| # Test basic placement
func test_basic_placement():
var result = placement_system.place_building("house", Vector2i(5, 5), "player")
assert(result.success, "Basic placement should work")
print("✅ Basic placement test passed")
|
Step 2: Error Handling Test
1
2
3
4
5
6
| # Test error handling
func test_error_handling():
var result = placement_system.place_building("invalid_type", Vector2i(-1, -1), "player")
assert(not result.success, "Invalid placement should fail")
assert(result.error != "", "Should provide error message")
print("✅ Error handling test passed")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Test performance improvement
func test_performance():
var start_time = Time.get_ticks_msec()
# Place multiple buildings
for i in range(100):
placement_system.place_building("house", Vector2i(i, i), "player")
var end_time = Time.get_ticks_msec()
var total_time = end_time - start_time
assert(total_time < 1000, "100 placements should take less than 1 second")
print("✅ Performance test passed: ", total_time, "ms for 100 placements")
|
Common Migration Issues
Issue 1: Missing Service References
Problem: placement_system is null
Solution: Ensure services are added to scene tree
1
2
3
4
5
6
7
| func _ready():
# Make sure services exist
if not has_node("PlacementSystem"):
var placement_system = preload("res://addons/grid_building/godot/systems/placement/placement_system.gd").new()
add_child(placement_system)
placement_system = $PlacementSystem
|
Issue 2: Event Connection Errors
Problem: Events not firing
Solution: Connect events after services are ready
1
2
3
4
5
6
| func _ready():
# Wait for services to be ready
await placement_system.ready
# Then connect events
placement_system.building_placed.connect(_on_building_placed)
|
Problem: New system slower than old
Solution: Check for configuration issues
1
2
3
4
5
6
7
| func _ready():
# Optimize service configuration
placement_system.configure({
"enable_caching": true,
"max_cache_size": 1000,
"validation_mode": "fast"
})
|
Rollback Plan
If you need to rollback during migration:
Step 1: Keep Old Systems
1
2
3
4
5
6
7
8
9
| # Keep old systems as fallback
var old_building_system = preload("res://old_systems/building_system.gd").new()
var use_new_system = true
func place_building(type, position):
if use_new_system:
return placement_system.place_building(type, position, "player")
else:
return old_building_system.place_building(type, position, "player")
|
Step 2: Gradual Migration
1
2
3
4
5
6
7
8
9
10
| # Migrate feature by feature
func place_building(type, position):
# Try new system first
if placement_system != null:
var result = placement_system.place_building(type, position, "player")
if result.success:
return result
# Fallback to old system
return old_building_system.place_building(type, position, "player")
|
Step 3: Complete Switch
1
2
3
4
| # Once confident, remove old systems
func place_building(type, position):
# Only new system
return placement_system.place_building(type, position, "player")
|
Migration Checklist
Pre-Migration
Migration
Post-Migration
Benefits You’ll See
- Cleaner Code: Your GDScript becomes much simpler
- Better Errors: Clear, actionable error messages
- Event System: Automatic updates when buildings change
- 5-10x Faster: Building validation and placement
- Less Memory: Efficient C# data structures
- Smoother UI: Non-blocking building operations
Maintenance Benefits
- Easier Testing: Services can be tested independently
- Better Debugging: Clear separation of concerns
- Future-Proof: Easy to extend and modify
Need Help?
If you run into issues during migration:
- Check the Examples: See working implementations
- Review API Reference: Complete method documentation
- Test Incrementally: Migrate one feature at a time
- Use Fallbacks: Keep old systems during transition
The new service architecture is designed to make your building systems more powerful and your code cleaner. Take it step by step, and you’ll see the benefits immediately!