Placement Rules (5.0.3)
This guide covers runtime placement validation. For configuration and runtime-readiness checks, see Validation: Configuration.
What placement rules are
Placement rules are resources that validate whether an object can be placed at the current target. Each rule:
- receives the active targeting state via
setup(p_gts: GridTargetingState) - returns a
RuleResultfromvalidate_placement() - may run post-success side effects in
apply()
Rule sources
There are two practical rule layers in the 5.0.3 runtime:
- Base rules
- configured on
GBSettings.placement_rules - apply broadly to placement workflows
- configured on
- Placeable-specific rules
- configured on each
Placeable - apply only to that placeable’s preview/placement flow
- configured on each
Core Rule Classes
| Class | Purpose |
|---|---|
PlacementRule | Base class for all placement rules |
TileCheckRule | Base class for rules that evaluate tile/indicator state |
RuleResult | Contains validation outcome and issues |
Built-in rules
GridBuilding 5.0.3 includes these built-in placement rules:
| Rule Class | Base Class | Purpose |
|---|---|---|
WithinTilemapBoundsRule | TileCheckRule | Restricts placement to valid tilemap cells |
CollisionsCheckRule | TileCheckRule | Checks for overlapping physics |
ValidPlacementTileRule | TileCheckRule | Basic validity check |
SpendMaterialsRuleGeneric | PlacementRule | Consumes inventory/materials after successful placement |
Rule lifecycle
1. setup(p_gts: GridTargetingState) -> Array[String]
Called before the rule is used. PlacementRule.setup(...) stores the targeting state and marks the rule ready.
2. validate_placement() -> RuleResult
Called during validation. This is where the rule decides pass/fail.
3. apply() -> Array[String]
Called after successful placement if the workflow uses the apply phase for side effects.
4. tear_down() -> void
Called when the preview/rule evaluation cycle is reset or completed.
Example: custom grid bounds rule
Example: non-tile gameplay rule
Built-in rule behavior plugin users should know
WithinTilemapBoundsRule
- extends
TileCheckRule - checks each indicator against the active
TileMapLayer - fails when indicator cells resolve to no
TileData
Visual Bounds Fallback
When no collision shapes are detected on a placeable object, WithinTilemapBoundsRule automatically falls back to using visual component bounds for validation. This fallback:
- Detects visual components: Searches for
Sprite2DandPolygon2Dnodes in the object hierarchy - Calculates bounding box: Uses
VisualBoundsHelper.get_visual_bounding_box()to compute the union of all visual component rectangles - Checks corner points: Validates that all four corners of the bounding box are over valid tiles
When the fallback triggers:
- The rule has no indicators (collision shapes not detected)
- The object contains visual components (
Sprite2Dwith texture orPolygon2Dwith polygon data) - No collision region is defined for the object
Why collision regions are recommended: While the visual bounds fallback provides basic validation, implementing explicit collision regions is strongly recommended for the following reasons:
- Precise control: Collision shapes allow you to define the exact footprint of your object, independent of visual representation
- Accurate validation: Visual bounds may include transparent areas or visual effects that don’t represent the actual placement footprint
- Performance: Collision-based detection is more efficient than traversing node hierarchies for visual components
- Consistency: Using collision shapes ensures consistent behavior across all placement rules (
WithinTilemapBoundsRule,CollisionsCheckRule,ValidPlacementTileRule) - Flexibility: You can create complex collision shapes (concave polygons, multiple shapes) that accurately represent your object’s placement requirements
Example of recommended setup:
Visual bounds fallback limitations:
- May include non-solid visual areas in validation
- Cannot represent complex footprints (L-shapes, concave areas)
- Less performant for objects with many visual components
- Visual effects (particles, animations) may cause unexpected validation behavior
ValidPlacementTileRule
- extends
TileCheckRule - validates that tiles have required custom data fields and matching values
- does NOT have a visual bounds fallback - requires collision shapes for indicator generation
Important: No Visual Bounds Fallback
Unlike WithinTilemapBoundsRule, ValidPlacementTileRule does not fall back to visual bounds when collision shapes are missing. This rule requires collision-based indicators to function properly.
Why collision regions are critical for ValidPlacementTileRule:
- No fallback mechanism: Without collision shapes, the rule cannot generate indicators and will fail to validate
- Tile data validation: The rule checks specific tile custom data fields (e.g., “buildable”, “walkable”) which require precise tile position detection
- Multiple tile coverage: Collision shapes define exactly which tiles need to have matching custom data
- Complex footprint support: Buildings often span multiple tiles, and collision shapes accurately represent which tiles must be validated
If you don’t implement collision regions for ValidPlacementTileRule:
- The rule will have no indicators to check
- Validation will pass vacuously (no indicators = no violations) - this is logically correct but provides no validation
- You lose the ability to validate that all covered tiles have the required custom data
- Placement may succeed on tiles that shouldn’t be valid for building
Why vacuous truth is the correct behavior:
- The rule checks tile custom data on a per-tile basis
- If there are no collision shapes, there are no tiles to check
- No tiles to check means no violations can exist
- This is semantically correct: “all covered tiles have valid data” is true when there are no covered tiles
- A visual bounds fallback would be inappropriate because visual bounds don’t tell you which tiles to check for custom data
Example: Proper collision setup for ValidPlacementTileRule:
CollisionsCheckRule
- extends
TileCheckRule - checks each indicator with a shapecast collision mask
- excludes preview bodies and configured collision exclusions
- supports both clear-space and required-overlap flows through
pass_on_collision
Practical guidance
- always call
super.setup(...)if you override setup - use
TileCheckRulewhen your rule depends on indicator positions or tilemap cells - use
PlacementRuledirectly when the rule depends on owner/inventory/game-state logic - keep side effects in
apply()if they should only happen after successful placement - put rules that should affect every placement into
GBSettings.placement_rules
Common mistakes
- forgetting to call
setup(...)before validation - treating rules as editor-only resources instead of runtime logic
- duplicating grid-position logic in UI instead of reading targeting state
- putting placeable-specific rules into global settings when they should live on the
Placeable