Skip to content

Grid Placement v5.0.12

Extending the Plugin

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

Status
Legacy
Version
v5.0.12
Source updated
2026-09-07
Generated on
2026-10-30

6.3. These pages document the 6.3 release, where placeable is now entry (what changed) and PlacementOwner is deprecated (migration). Still on 6.2? Your version's guides ship inside the plugin itself, under addons/grid_placement/guides/; the 6.2 URLs on this site now redirect to this series.

Historical 5.x guide. This page describes a legacy API and is not current integration guidance. For new projects, start with Getting Started.

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

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

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 ManipulationSystem for building selection
  • Leverage BuildingSystem for 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 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.

Using this in a game?

Grid Placement v6.3.0 — full source, playable demos, commercial license

The guide above is the same setup the shipped demos use. Try the build in your browser before you buy it.