Complete Phase 2b: block placement/removal, ship-relative snapping, mouse-look, visual polish

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>
This commit is contained in:
Joshua Deville
2026-07-09 22:32:38 -04:00
parent 7dc47acdb0
commit 5c6b038188
10 changed files with 42 additions and 7 deletions

View File

@@ -50,6 +50,7 @@
#include "K2Node_MacroInstance.h"
#include "K2Node_DynamicCast.h"
#include "K2Node_InputKey.h"
#include "K2Node_InputAxisKeyEvent.h"
#include "K2Node_SpawnActorFromClass.h"
#include "K2Node_CallArrayFunction.h"
#include "EdGraphSchema_K2.h"
@@ -660,6 +661,22 @@ static UEdGraphNode* CreateBPNodeFromJson(UEdGraph* Graph, UBlueprint* Blueprint
Creator.Finalize();
NewNode = KeyNode;
}
else if (NodeType == TEXT("InputAxisKey"))
{
FString KeyName;
if (!NodeJson->TryGetStringField(TEXT("key_name"), KeyName))
{
OutError = TEXT("InputAxisKey node missing 'key_name'.");
return nullptr;
}
FGraphNodeCreator<UK2Node_InputAxisKeyEvent> Creator(*Graph);
UK2Node_InputAxisKeyEvent* AxisKeyNode = Creator.CreateNode(false);
AxisKeyNode->Initialize(FKey(*KeyName));
AxisKeyNode->NodePosX = PosX;
AxisKeyNode->NodePosY = PosY;
Creator.Finalize();
NewNode = AxisKeyNode;
}
else if (NodeType == TEXT("SpawnActor"))
{
FGraphNodeCreator<UK2Node_SpawnActorFromClass> Creator(*Graph);
@@ -678,7 +695,7 @@ static UEdGraphNode* CreateBPNodeFromJson(UEdGraph* Graph, UBlueprint* Blueprint
}
else
{
OutError = FString::Printf(TEXT("Unknown node type '%s'. Supported: CallFunction, Event, CustomEvent, CastTo, Branch, Sequence, VariableGet, VariableSet, MacroInstance, InputKey, SpawnActor."), *NodeType);
OutError = FString::Printf(TEXT("Unknown node type '%s'. Supported: CallFunction, Event, CustomEvent, CastTo, Branch, Sequence, VariableGet, VariableSet, MacroInstance, InputKey, InputAxisKey, SpawnActor."), *NodeType);
return nullptr;
}