This guide covers runtime placement validation — rules that run when players attempt to place objects. For configuration validation at startup, see Validation Tools.
Placement Rules = Runtime validation when placing objects (this guide) GBInjectorSystem validation = Configuration validation at initialization time
What Are Placement Rules?
Placement rules are components that validate whether an object can be placed at a specific location. Each rule:
- Receives the current targeting state via
setup(p_gts: GridTargetingState) - Returns a
RuleResultwith success/failure messages - Optionally performs actions in
apply()after successful placement
Core Rule Classes
| Class | Purpose |
|---|---|
PlacementRule |
Base class for all placement rules (v5.0) |
TileCheckRule |
Base class for rules that evaluate against TileMapGrid |
RuleResult |
Contains validation outcome and messages |
This guide documents the v5.0 API. Both v5.0 and v6.0 use PlacementRule as the base class for custom placement rules.
Built-in Rules (v5.0)
GridBuilding 5.0.8 includes these built-in placement rules:
| Rule Class | Base Class | Purpose | Key Properties |
|---|---|---|---|
WithinTilemapBoundsRule |
TileCheckRule |
Restricts placement to tilemap bounds | Uses tilemap's used_rect |
CollisionsCheckRule |
TileCheckRule |
Checks for overlapping physics | apply_to_objects_mask, collision_mask |
ValidPlacementTileRule |
TileCheckRule |
Basic validity check | Uses ValidPlacementTileRuleSettings |
SpendMaterialsRuleGeneric |
PlacementRule |
Consumes inventory items | cost_map, inventory_component_path |
Example: Custom Grid Bounds Rule (TileCheckRule)
For rules that evaluate against the TileMap, extend TileCheckRule. In 5.0.8, you calculate the current cell using the target_map and the positioner's global position.
class_name MyGridBoundsRule
extends TileCheckRule
## Custom rule that limits placement to specific grid coordinates.
## This example restricts placement to the center 10x10 area of the grid.
@export var min_x: int = -5
@export var max_x: int = 5
@export var min_y: int = -5
@export var max_y: int = 5
func validate_placement() -> RuleResult:
var target_map: TileMapLayer = _grid_targeting_state.target_map
var positioner: Node2D = _grid_targeting_state.positioner
if target_map == null or positioner == null:
return RuleResult.build(self, ["Targeting state incomplete"])
# Calculate current map cell from global position
var local_pos := target_map.to_local(positioner.global_position)
var target_cell := target_map.local_to_map(local_pos)
if target_cell.x < min_x or target_cell.x > max_x or target_cell.y < min_y or target_cell.y > max_y:
var message := "Can only place within bounds (x: %d to %d, y: %d to %d)" % [min_x, max_x, min_y, max_y]
return RuleResult.build(self, [message])
return RuleResult.build(self, [])
func apply() -> Array[String]:
# Optional post-placement logic
return []
Using the Custom Rule
# Add to a placeable's placement_rules
var bounds_rule := MyGridBoundsRule.new()
bounds_rule.min_x = -10
bounds_rule.max_x = 10
var placeable: Placeable = load("**res://items/my_item.tres**")
placeable.placement_rules.append(bounds_rule)
Example: Rule with Dependencies (PlacementRule)
For rules that don't need direct tile access, extend PlacementRule directly. Dependencies are typically accessed through the GBCompositionContainer which you can capture during resolve_gb_dependencies or via the _grid_targeting_state's owner context.
class_name MyEconomyRule
extends PlacementRule
## Rule that checks player currency before allowing placement.
@export var required_credits: int = 100
func validate_placement() -> RuleResult:
# Example: Accessing a global economy service
var economy = _grid_targeting_state.get_owner().get_node("Economy")
if economy == null:
return RuleResult.build(self, ["Economy service not available"])
if economy.credits < required_credits:
return RuleResult.build(self, ["Insufficient credits"])
return RuleResult.build(self, [])
func apply() -> Array[String]:
var economy = _grid_targeting_state.get_owner().get_node("Economy")
if economy:
economy.credits -= required_credits
return []
Rule Lifecycle
1. setup(p_gts: GridTargetingState) -> Array[String]
Called to initialize the rule with targeting state. In 5.0.8, this is handled by the IndicatorManager.
func setup(p_gts: GridTargetingState) -> Array[String]:
# Always call super.setup to initialize _grid_targeting_state and _ready
return super.setup(p_gts)
2. validate_placement() -> RuleResult
Called during validation to check if placement is allowed. Returns a RuleResult.
func validate_placement() -> RuleResult:
# Use _grid_targeting_state to perform checks
if _is_valid():
return RuleResult.build(self, [])
return RuleResult.build(self, ["Invalid placement"])
3. apply() -> Array[String]
Called after successful placement to perform actions (spend resources, trigger events, etc.). Returns an array of error messages (empty if successful).
func apply() -> Array[String]:
# Logic to run when placement is confirmed
return []
4. tear_down() -> void
Called when the rule evaluation cycle is finished or changed.
func tear_down() -> void:
# super.tear_down cleans up _ready state
super.tear_down()
Validated By
- Rule Logic & Inheritance: res://addons/grid_placement/placement/placement_rules/placement_rule.gd — Defines the base contract and lifecycle for 5.0.8 rules.
- Tile-Based Rules: res://addons/grid_placement/placement/placement_rules/tile_check_rule.gd and res://addons/grid_placement/test/rules/validation/tilemap_bounds_rule_unit_test.gd — Verify coordinate-based validation.
- Rule Orchestration: res://addons/grid_placement/test/rules/validation/rule_system_integration_tests.gd — Validates the full sequence of
setup->validate_placement->apply. - Built-in Implementation: res://addons/grid_placement/placement/placement_rules/template_rules/spend_materials_rule_generic.gd — Example of a production-stable rule using the
apply()hook.
Source
docs/v5-0/guides/placement-rules.md
Plugin docs root:gdscript/plugins/grid_placement_dev/docs