Overview
The Grid Building plugin provides foundational building mechanics - placement, validation, collision detection, and visual feedback. This guide explains how to extend the plugin with game-specific features while maintaining clear boundaries between core plugin functionality and custom game logic.
Plugin Scope vs Game-Specific Features
✅ What the Plugin Provides (Core Features)
Core Building Mechanics:
- Grid-based placement system
- Collision detection and validation
- Visual indicators (valid/invalid placement)
- Rule-based placement validation
- Mouse and keyboard input handling
- Scene tree management for building objects
Architecture Components:
BuildingSystem- Core placement logicGridPositioner2D- Input handling and positioningPlacementValidator- Rule validation systemCollisionMapper- Collision detectionIndicatorManager- Visual feedbackManipulationSystem- Object selection and manipulation
❌ What the Plugin Does NOT Provide (Game-Specific)
Game Design Features:
- Building variants and customization systems
- Quantity limits and resource management
- Upgrade and progression systems
- Unlockable content and tech trees
- Economy and resource costs
- Combat stats and game balance
- Save/load for game progress
- Custom UI layouts and themes
Why These Are Excluded:
- Scope Management: Keeps plugin focused and maintainable
- Game Flexibility: Each game needs different implementations
- Performance: Avoids unnecessary complexity for all users
- Opinionless Design: Plugin doesn't impose specific game design patterns
Implementation Guidelines
🎯 Core Principle: Composition Over Extension
✅ Recommended Approach:
# Build game-specific systems AROUND the plugin
class_name GameBuildingManager
extends Node
var grid_building_system:BuildingSystem
var quantity_limits:Dictionary = {}
var unlocked_buildings:Array[String]= []
func can_place_building(placeable:Placeable)-> bool:
# Check plugin validation first
if not grid_building_system.can_place(placeable):
return false
# Add game-specific checks
if not is_building_unlocked(placeable.building_type):
return false
if exceeds_quantity_limit(placeable.building_type):
return false
return true❌ Avoid Direct Plugin Modification:
# Don't modify plugin classes directly
class_name CustomBuildingSystem extends BuildingSystem:
# This creates maintenance burden and update conflictsGame-Specific Feature Implementation
1. Building Variants
Problem: Single building type with multiple visual/functional variants
Solution: Custom variant selection system using existing plugin foundation
# Custom variant manager
class_name BuildingVariantManager
extends Node
var current_variants:Dictionary = {}
var variant_definitions:Dictionary = {}
func setup_variant_definitions():
variant_definitions["cannon_tower"]= [
{"name":"Basic","scene":"res://buildings/cannon_basic.tscn","stats": {"damage":10}},
{"name":"Heavy","scene":"res://buildings/cannon_heavy.tscn","stats": {"damage":25}},
{"name":"Rapid","scene":"res://buildings/cannon_rapid.tscn","stats": {"damage":8,"fire_rate":2.0}}
]
func get_current_variant(building_type:String)-> Dictionary:
return current_variants.get(building_type, variant_definitions[building_type][0])
func cycle_variant(building_type:String, direction:int = 1):
var variants= variant_definitions[building_type]
var current_index= variants.find(get_current_variant(building_type))
var new_index= (current_index+ direction)% variants.size()
current_variants[building_type]= variants[new_index]
# Update the placeable resource with new variant
update_placeable_for_variant(building_type, variants[new_index])
# UI Integration Example
func setup_variant_ui():
var left_arrow= $VariantUI/LeftArrow
var right_arrow= $VariantUI/RightArrow
var variant_label= $VariantUI/VariantLabel
left_arrow.pressed.connect(func():cycle_variant(selected_building_type,-1))
right_arrow.pressed.connect(func():cycle_variant(selected_building_type,1))Integration Points:
- Modify
Placeableresources dynamically based on selected variant - Use plugin's existing placement system with variant-specific scenes
- Handle variant persistence in save/load system
2. Building Quantity Limits
Problem: Restrict maximum number of specific building types
Solution: Custom validation rule + tracking system
# Custom placement rule for quantity limits
class_name QuantityLimitRule
extends PlacementRule
@export var max_quantity:int = 1
@export var building_type:String = ""
var placed_count:int = 0
func get_name()-> String:
return "QuantityLimit_%s" % building_type
func validate_condition(params:RuleValidationParameters)-> RuleResult:
var result= RuleResult.new()
result.is_valid= placed_count< max_quantity
if not result.is_valid:
result.failure_reason= "Maximum%d %s buildings allowed" % [max_quantity, building_type]
return result
# Building quantity tracker
class_name BuildingQuantityTracker
extends Node
var building_counts:Dictionary = {}
var quantity_limits:Dictionary = {}
func setup_limits():
quantity_limits["command_center"]= 1
quantity_limits["tesla_tower"]= 3
quantity_limits["super_weapon"]= 1
func on_building_placed(building_type:String):
building_counts[building_type]= building_counts.get(building_type,0)+ 1
update_ui_availability()
func on_building_removed(building_type:String):
building_counts[building_type]= max(0, building_counts.get(building_type,0)- 1)
update_ui_availability()
func can_place_more(building_type:String)-> bool:
var current= building_counts.get(building_type,0)
var limit= quantity_limits.get(building_type,999)
return current< limit
func update_ui_availability():
# Disable UI buttons for buildings at limit
for building_typein quantity_limits:
var button= building_ui.get_button(building_type)
button.disabled= not can_place_more(building_type)Integration Points:
- Add custom placement rules to existing validation system
- Connect to plugin's building placement/removal signals
- Update UI state based on current counts
3. Building Upgrades
Problem: Upgrade existing buildings to more powerful variants
Solution: Scene replacement system using plugin's manipulation tools
# Building upgrade system
class_name BuildingUpgradeManager
extends Node
var upgrade_paths:Dictionary = {}
var upgrade_costs:Dictionary = {}
func setup_upgrade_definitions():
upgrade_paths["basic_tower"]= "advanced_tower"
upgrade_paths["advanced_tower"]= "elite_tower"
upgrade_costs["basic_tower"]= {"gold":100,"tech_points":1}
upgrade_costs["advanced_tower"]= {"gold":250,"tech_points":3}
func can_upgrade_building(building_node:Node2D)-> bool:
var building_type= get_building_type(building_node)
# Check if upgrade path exists
if not upgrade_paths.has(building_type):
return false
# Check resources
var cost= upgrade_costs.get(building_type, {})
return player_resources.can_afford(cost)
func upgrade_building(building_node:Node2D):
var building_type= get_building_type(building_node)
var upgraded_type= upgrade_paths[building_type]
# Get current position and state
var position= building_node.global_position
var tile_position= grid_system.world_to_tile(position)
# Remove old building (using plugin's manipulation system)
manipulation_system.remove_building(building_node)
# Create upgraded building
var upgraded_placeable= load("res://buildings/%s.tres" % upgraded_type)
var placement_result= building_system.try_place_at_tile(upgraded_placeable, tile_position)
if placement_result.is_successful():
# Deduct resources
player_resources.spend(upgrade_costs[building_type])
# Transfer any persistent data (health, experience, etc.)
transfer_building_data(building_node, placement_result.placed_building)
# UI Integration for upgrade prompts
func show_upgrade_ui(building_node:Node2D):
var upgrade_panel= $UpgradePanel
var building_type= get_building_type(building_node)
if can_upgrade_building(building_node):
upgrade_panel.setup_upgrade_option(building_type, upgrade_paths[building_type])
upgrade_panel.show_at_position(building_node.global_position)Integration Points:
- Use plugin's
ManipulationSystemfor building selection - Leverage
BuildingSystemfor placement validation - Integrate with existing placement rules and collision detection
4. Unlockable Recipes
Problem: Dynamic building availability based on game progression
Solution: Dynamic resource management + UI filtering
# Building unlock system
class_name BuildingUnlockManager
extends Node
var unlocked_buildings:Array[String]= ["basic_tower","wall"]
var unlock_conditions:Dictionary = {}
func setup_unlock_conditions():
unlock_conditions["advanced_tower"]= {"wave":5,"research":"advanced_weapons"}
unlock_conditions["missile_silo"]= {"wave":10,"buildings_placed":20}
unlock_conditions["shield_generator"]= {"research":"energy_shields","gold_spent":1000}
func check_unlock_conditions():
for building_typein unlock_conditions:
if building_typein unlocked_buildings:
continue
var conditions= unlock_conditions[building_type]
if meets_all_conditions(conditions):
unlock_building(building_type)
func unlock_building(building_type:String):
if building_typenot in unlocked_buildings:
unlocked_buildings.append(building_type)
# Load the placeable resource dynamically
var placeable_path= "res://buildings/%s.tres" % building_type
var placeable= load(placeable_path)
# Add to available buildings in UI
building_ui.add_building_option(placeable)
# Show unlock notification
show_unlock_notification(building_type)
func filter_available_buildings(all_buildings:Array[Placeable])-> Array[Placeable]:
var available= []
for placeablein all_buildings:
if placeable.building_typein unlocked_buildings:
available.append(placeable)
return available
# Dynamic UI updates
class_name DynamicBuildingUI
extends Control
var building_buttons:Dictionary = {}
func setup_initial_buildings():
var available= unlock_manager.filter_available_buildings(all_building_types)
for placeablein available:
add_building_button(placeable)
func add_building_option(placeable:Placeable):
var button= create_building_button(placeable)
building_container.add_child(button)
building_buttons[placeable.building_type]= button
# Connect to existing building system
button.pressed.connect(func(): building_system.select_building_type(placeable))Integration Points:
- Filter available buildings before passing to plugin systems
- Use existing
BuildingSystemplacement logic - Connect unlock events to game progression systems
Custom Integration Patterns
Resource Management Integration
# Wrap plugin calls with resource checks
class_name ResourceAwareBuildingSystem
extends Node
func attempt_building_placement(placeable:Placeable, position:Vector2i)-> bool:
# Check resources first
var cost= get_building_cost(placeable)
if not player_resources.can_afford(cost):
show_insufficient_resources_message()
return false
# Use plugin for actual placement
var result= building_system.try_place_at_tile(placeable, position)
if result.is_successful():
player_resources.spend(cost)
return true
return falseSave/Load Integration
# Save/load game state including plugin data
class_name GameSaveManager
extends Node
func save_game()-> Dictionary:
var save_data= {}
# Save plugin state
save_data["buildings"]= building_system.get_placed_buildings_data()
save_data["grid_state"]= grid_system.get_state_data()
# Save game-specific state
save_data["unlocked_buildings"]= unlock_manager.unlocked_buildings
save_data["building_counts"]= quantity_tracker.building_counts
save_data["player_resources"]= player_resources.to_dict()
return save_data
func load_game(save_data:Dictionary):
# Restore plugin state
building_system.restore_buildings_from_data(save_data["buildings"])
grid_system.restore_state_from_data(save_data["grid_state"])
# Restore game-specific state
unlock_manager.unlocked_buildings= save_data["unlocked_buildings"]
quantity_tracker.building_counts= save_data["building_counts"]
player_resources.from_dict(save_data["player_resources"])
# Update UI to reflect loaded state
building_ui.refresh_available_buildings()Best Practices
1. Maintain Plugin Boundaries
- Never modify plugin source files directly
- Use composition and delegation patterns
- Extend functionality through events and signals
2. Use Plugin Extension Points
# Plugin provides signals for extension
building_system.building_placed.connect(_on_building_placed)
building_system.building_removed.connect(_on_building_removed)
manipulation_system.building_selected.connect(_on_building_selected)3. Custom Validation Rules
# Create game-specific placement rules
class_name CustomResourceRule extends PlacementRule:
func validate_condition(params:RuleValidationParameters)-> RuleResult:
# Add to existing validation pipeline
pass4. UI Integration Patterns
# Wrap plugin UI components with game-specific logic
class_name GameBuildingUI extends Control:
var core_building_ui:BuildingUI # Plugin component
func _ready():
# Enhance plugin UI with game features
add_variant_selection()
add_resource_display()
add_upgrade_buttons()Conclusion
The Grid Building plugin provides a solid foundation for grid-based building mechanics. By following these patterns, you can implement sophisticated game-specific features while maintaining the plugin's simplicity and your game's flexibility.
Key Takeaways:
- Compose, don't modify: Build around the plugin, not into it
- Use provided extension points: Signals, custom rules, and UI enhancement
- Separate concerns: Keep game logic separate from core building mechanics
- Maintain upgrade path: Avoid changes that prevent plugin updates
This approach ensures your game can leverage the plugin's robust foundation while implementing unique gameplay features that match your vision.