GridBuilding 5.0.2 introduces a strict separation of concerns for object manipulation (moving, rotating, flipping placed objects). This guide explains why this split exists and how it affects your code.
The Core Split
In 5.0.2, manipulation is divided into two distinct responsibilities:
- ManipulationSystem: The “Brain”. Owns business logic, state transitions, and validation.
- ManipulationParent: The “Body”. Owns visual transforms, scene hierarchy, and input handling for rotation/flip.
Why this split exists
The 5.0.2 architecture intentionally separates orchestration from presentation.
- Testability: The
ManipulationSystemcan be tested (mostly) without needing a complex scene tree, as it operates on state and signals. - Clarity: The node hierarchy remains understandable. You know exactly where to look for rotation logic (Parent) vs validation logic (System).
- Stability: By isolating transform logic, we prevent “drift” where an object slowly moves off-grid due to floating point errors in repeated calculations.
Responsibilities
| Component | Role | Owns | Does NOT own |
|---|---|---|---|
ManipulationSystem | Business Logic | Lifecycle (start/commit), validation (can I move this?), state transitions (is_moving), API (try_move). | Direct visual transforms, local rotation math, scene tree parentage. |
ManipulationParent | Visual Layer | Rotation/Flip/Scale containers, transform input handling, holding the ghost/preview object. | Validation rules, resource consumption, grid occupancy checks. |
Scene Hierarchy (Mental Model)
When you pick up an object, the hierarchy temporarily looks like this:
| |
The GridPositioner2D moves the entire assembly to the target grid cell. The ManipulationParent rotates the assembly around that center point.
Critical 5.0.2 Behaviors
1. Movable Validation
In 5.0.2, ManipulationSystem.try_move() strictly enforces the is_movable() check on the source object.
- Old Behavior: You could sometimes force-move static objects via script.
- New Behavior: The system will silently reject the move if
Manipulatable.is_movableis false.
2. Transform Preservation
When a move completes, the accumulated transform (rotations applied during the move) must be preserved.
- Flow: Move Start -> Copy original transform -> User rotates/flips -> Place -> Apply final transform to new instance.
- Bug Watch: If your objects reset rotation after placement, check that you aren’t overwriting the transform in
_ready().
Validated By
The separation of concerns and the specific behaviors of the manipulation system are verified by the following test suites:
- System Logic: res://addons/grid_building/systems/manipulation/manipulation_system.gd — Core implementation of the manipulation “brain”.
- Rotation Handling: res://addons/grid_building/test/integration/grid_positioner_rotation_integration_test.gd — Validates the interaction between
GridPositioner2DandManipulationParent. - Workflow Integration: res://addons/grid_building/test/e2e/all_systems_integration_tests.gd — Validates the complete manipulation lifecycle from start to commit.
- Rotation Reset: res://addons/grid_building/test/unit/rotation_reset_on_placeable_switch_test.gd — Tests that rotation state is correctly managed when switching placeables.
Related Guides
3. Visual Desyncs
Because the parent handles the visual transform, if you manually set the node.rotation of the object inside the parent, you might get double rotations or unexpected offsets.
- Fix: Always rotate the
ManipulationParent(or use therotate_90()API), never the child object directly.
Glossary
- ManipulationSystem: The singleton that orchestrates move/rotate logic.
- ManipulationParent: The
Node2Dthat holds the preview object. - Source Object: The original object in the world being moved.
- Preview Object: The temporary visual copy attached to the mouse cursor.
- GridPositioner2D: The component that snaps the preview to the grid cell center.
Common Pitfalls
- Script Access: Do not try to
get_node("ManipulationSystem")from inside a random scene script. Use dependency injection or a global singleton reference if your architecture supports it. - Input Consumption: The
ManipulationParentoften handles input for rotation. If your camera controller consumes all input, the rotation keys might not trigger. Ensure input propagation is handled correctly (e.g.,_unhandled_input). - Orphaned Previews: If the system is interrupted (e.g., the player dies while building), ensure
cancel_interaction()is called to clean up theManipulationParentand its preview child.
| |