Chris' Tutorials
Docs/Grid Placement

Grid Placement v5.0.8

Extending the Plugin

Add game-specific features and extend the Grid Building plugin.

StatusCurrent
Versionv5.0.8
UpdatedActive v5.0.8 guide line from Grid Placement repo
Source note:This page rendered plugin-owned docs or generated metadata inside unified Astro docs shell.

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 logic
  • GridPositioner2D - Input handling and positioning
  • PlacementValidator - Rule validation system
  • CollisionMapper - Collision detection
  • IndicatorManager - Visual feedback
  • ManipulationSystem - 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 conflicts

Game-Specific Feature Implementation

1. Building Variants (Issue #18)

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 Placeable resources 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 (Issue #19)

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_type in 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 (Issue #20)

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 ManipulationSystem for building selection
  • Leverage BuildingSystem for placement validation
  • Integrate with existing placement rules and collision detection

4. Unlockable Recipes (Issue #21)

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_type in unlock_conditions:
        if building_type in 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_type not 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 placeable in all_buildings:
        if placeable.building_type in 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 placeable in 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 BuildingSystem placement 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 false

Save/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
        pass

4. 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.

Source

docs/v5-0/guides/extending-grid-building-plugin.md

Plugin docs root:gdscript/plugins/grid_placement_dev/docs