Place/remove (task #21): all four block types (Hull/Thruster/Power/Storage)
now have working place chains on BP_PlayerCharacter (keys 1-4), plus a
remove key (X) that raycasts to a targeted block, verifies it via
Array_Contains against PlacedBlocks before destroying it (guards against
deleting terrain/ship/other actors), and calls RemoveBlockFromArray.
Stat aggregation (task #22): rewrote RecalculateStats on BP_ShipPawn to
iterate PlacedBlocks with a ForEachLoop, casting each element to its block
class and summing bonuses onto base values, instead of reading four fixed
Slot_* variables. Multiple blocks of the same type now correctly stack
(e.g. two Hull blocks = +100, not capped at one). Also fixed a real bug
found during manual playtesting: AddBlockToArray/RemoveBlockFromArray never
actually called RecalculateStats (dead-end exec pins), so stats never
updated after the initial BeginPlay call regardless of what was placed.
Ship-relative placement: raycast hit location is now converted into the
ship's local space, clamped to a 600-unit radius, snapped to the 200-unit
grid, then converted back to world space. Recomputed every tick from the
ship's current transform, so placement always stays near/aligned with the
ship instead of landing at arbitrary world coordinates. Also fixed a
related bug: the raycast never checked bBlockingHit, so a miss (e.g. aiming
at open sky) silently fell back to world origin (0,0,0) -- coincidentally
the ship's spawn point -- making blocks appear to "teleport" there and then
trail behind once the ship moved. Added bHasValidBuildTarget, gating all
four placement branches on an actual hit.
Mouse-look: extended the UnrealMCPython plugin itself (hot-patched via
Live Coding) with a new "InputAxisKey" node type wrapping
UK2Node_InputAxisKeyEvent::Initialize(FKey), since the existing "InputKey"
node type only supports press/release button semantics, not continuous
axis values. Wired Mouse X/Y to AddControllerYawInput/AddControllerPitchInput
(Y inverted), switched bUseControllerRotationYaw/Pitch back on, and
repurposed the old A/D actor-rotation logic into A/D strafe via
AddMovementInput(RightVector). Also added mouse-wheel zoom on the ship's
CameraBoom (TargetArmLength, clamped 300-2000).
Visual polish: ship mesh was a single scaled Cone; added a cylinder
fuselage and two wing panels for a recognizable ship silhouette. Added
distinct colored materials per block type (Hull/Thruster/Power/Storage)
plus a translucent ghost-preview material, replacing the default gray
checker look. Added a SkyAtmosphere actor to fix the "will render black"
sky light warning that made the level unreadable.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BuildGhost: a pre-placed cube actor (not runtime-spawned, to avoid
the SpawnActor Class-pin problem for this piece) referenced via
GhostBlockRef. Shown and moved to SnappedLocation each tick while
bBuildMode is on; hidden otherwise. Collision left enabled on the
ghost (couldn't find the right property path to disable it through
this tool -- BodyInstance.CollisionEnabled isn't a flat property);
minor known edge case where the raycast could self-hit the ghost,
not addressed yet.
One Hull/Thruster/Power/Storage block placed near the ship spawn and
wired into the corresponding Slot_* references. Verified via Python
(direct RecalculateStats call + property reads): MaxHull 150,
MaxPower 150, StorageCapacity 30, ShipMovement.MaxSpeed 4500 -- all
exactly base + block bonus. Confirms the block-slot aggregation
system works end-to-end. Smoke-tested in PIE: no runtime errors.
ResourceNode_01 placed in the landing zone. E key on the character:
checks distance to the resource node (via a typed ResourceNodeRef
variable, same object-reference technique as the ship/character
cross-refs), and if within 300 units calls AddStorage on the ship
(through ShipPawnRef) and destroys the node. AddStorage increments
StorageAmount up to StorageCapacity, printing feedback either way.
Smoke-tested in PIE: no runtime errors.
ACharacter defaults bUseControllerRotationYaw to true, which makes
the Controller's rotation overwrite the actor's rotation every tick
-- stomping our manual AddActorLocalRotation calls almost immediately
(visible as a barely-perceptible tick instead of a turn). Plain Pawn
(the ship) doesn't have this default, which is why the same rotation
logic worked fine there. Disabled it on both the class default and
the placed instance.
F key on the ship: teleports the player character to just above the
ship's location and possesses it. F key on the character: possesses
the ship back. Same controller, so possessing one pawn automatically
unpossesses the other.
Notable implementation detail: GameplayStatics::GetActorOfClass
looked like the obvious way to find "the other pawn" at runtime, but
this MCP plugin's pin_defaults mechanism can't correctly set a Class-
type pin (it accepts the string silently but the compiler later
rejects it as an invalid default) -- there's no supported node type
for a class-literal either. Worked around it by adding real object-
reference variables (PlayerCharacterRef / ShipPawnRef) via
unreal.BlueprintEditorLibrary.get_object_reference_type() directly
through the Python escape hatch, marking them instance-editable, and
wiring the actual actor references between the two placed level
instances. Avoids needing any runtime class lookup at all.
BP_PlayerCharacter (Character-based): first-person camera, WASD
movement + A/D turning (same input scheme as the ship), SuitOxygen
stat that depletes each tick, clamped at 0. Not auto-possessed yet --
that's the ship exit/enter transition (next task). Placed one
instance in L_TestFlight near the ship spawn.
Also reverted level gravity to default (-980): FloatingPawnMovement
(ship) never reads world gravity regardless of its value, but
CharacterMovementComponent (this character) needs real gravity to
walk normally. Zero gravity was an unnecessary leftover from Phase 0
that would have broken on-foot movement.
Added a landing zone as a region of the existing test level (at
X=8000) rather than a separate level/L_Landing01 -- avoids needing
level-streaming and cross-level pawn-transfer machinery that Phase 1
doesn't need yet. Ground platform (scaled plane), 3 scattered rock
obstacles, and a flat landing pad marker disc.
CameraBoom was parented to ShipMesh, which is rotated 90deg to point
the placeholder cone forward -- the camera inherited that rotation,
producing a badly skewed near-edge-on view. Reparented CameraBoom to
the actor root instead, so its pitch is relative to actor-forward,
not the mesh's cosmetic rotation.
Also added 5 marker cubes around the spawn point: the level was
completely empty, so with a rigidly-attached chase camera, forward/
backward translation had zero visual reference and could look like
nothing was happening even if movement was working correctly.
The level had zero actors besides the ship, so it rendered fully
black (no directional light, no sky light). Added a DirectionalLight
and a real-time-capture SkyLight so the placeholder ship is actually
visible during PIE testing.
BP_ShipPawn: cone mesh, FloatingPawnMovement for gravity-free flight,
spring-arm chase camera. Placed in L_TestFlight and set to
auto-possess Player 0 for PIE testing. No input wiring yet.