Backend: - Add /api/v1/ue/* endpoints (compute-points, cables, landing-points, satellites, status) returning flat JSON optimised for UE5 C++ parsing UE5 client (ue_client/): - PlanetDataManager: HTTP fetch + local mock JSON loader, spawns ComputePointActors - ComputePointActor / InteractiveObjectBase: hover/select state, material switching - GlobeInteractionComponent: drag-to-rotate via CesiumGeoreference origin shift, inertia, zoom - StereoRenderingManager: runtime SbS/TbB stereo toggle, IPD control (format TBD) - MotionCaptureInterface: protocol-agnostic gesture/rotate/zoom delegate interface (impl TBD) - PlanetPlayerController: unified mouse + motion-capture input routing - PlanetGameMode, Build.cs, Config, mock data Docs: - ue5_mvp_fused_plan.md updated to v3.0 for LED display context - ue_client_setup_guide.md: step-by-step editor setup guide - ue_todo.md: pending items blocked on vendor answers (stereo format + mocap protocol) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
247 lines
7.6 KiB
C++
247 lines
7.6 KiB
C++
#include "PlanetPlayerController.h"
|
||
#include "GlobeInteractionComponent.h"
|
||
#include "InteractiveObjectBase.h"
|
||
#include "Engine/World.h"
|
||
#include "GameFramework/Pawn.h"
|
||
|
||
APlanetPlayerController::APlanetPlayerController()
|
||
{
|
||
bShowMouseCursor = true;
|
||
bEnableClickEvents = true;
|
||
bEnableMouseOverEvents = true;
|
||
PrimaryActorTick.bCanEverTick = true;
|
||
|
||
GlobeInteraction = CreateDefaultSubobject<UGlobeInteractionComponent>(
|
||
TEXT("GlobeInteraction"));
|
||
}
|
||
|
||
void APlanetPlayerController::BeginPlay()
|
||
{
|
||
Super::BeginPlay();
|
||
SetInputMode(FInputModeGameAndUI());
|
||
|
||
// TODO(供应商回复后取消注释):
|
||
// BindMotionCaptureEvents();
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 输入绑定
|
||
// ---------------------------------------------------------------------------
|
||
|
||
void APlanetPlayerController::SetupInputComponent()
|
||
{
|
||
Super::SetupInputComponent();
|
||
|
||
InputComponent->BindAction("SelectPoint", IE_Pressed, this,
|
||
&APlanetPlayerController::OnLeftMouseDown);
|
||
InputComponent->BindAction("SelectPoint", IE_Released, this,
|
||
&APlanetPlayerController::OnLeftMouseUp);
|
||
InputComponent->BindAction("Escape", IE_Pressed, this,
|
||
&APlanetPlayerController::OnEscape);
|
||
|
||
InputComponent->BindAxis("RotateYaw", this, &APlanetPlayerController::OnMouseX);
|
||
InputComponent->BindAxis("RotatePitch", this, &APlanetPlayerController::OnMouseY);
|
||
InputComponent->BindAxis("ZoomCamera", this, &APlanetPlayerController::OnZoom);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Tick:悬停检测
|
||
// ---------------------------------------------------------------------------
|
||
|
||
void APlanetPlayerController::Tick(float DeltaTime)
|
||
{
|
||
Super::Tick(DeltaTime);
|
||
if (!bIsDragging) UpdateHover();
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 鼠标输入
|
||
// ---------------------------------------------------------------------------
|
||
|
||
void APlanetPlayerController::OnLeftMouseDown()
|
||
{
|
||
bLeftMouseDown = true;
|
||
bIsDragging = false;
|
||
|
||
float X, Y;
|
||
GetMousePosition(X, Y);
|
||
MouseDownPosition = FVector2D(X, Y);
|
||
|
||
// 停止惯性,准备接管控制
|
||
if (GlobeInteraction) GlobeInteraction->StopInertia();
|
||
}
|
||
|
||
void APlanetPlayerController::OnLeftMouseUp()
|
||
{
|
||
if (!bIsDragging)
|
||
{
|
||
// 没有拖拽 → 判定为点击
|
||
AInteractiveObjectBase* Clicked = GetObjectUnderCursor();
|
||
if (Clicked && Clicked != SelectedObject)
|
||
{
|
||
// 取消上一个选中
|
||
DeselectCurrent();
|
||
// 选中新物件
|
||
SelectedObject = Clicked;
|
||
SelectedObject->SetInteractiveState(EInteractiveObjectState::Selected);
|
||
OnObjectSelected.Broadcast(SelectedObject);
|
||
}
|
||
else if (!Clicked)
|
||
{
|
||
// 点了空白处 → 取消选中
|
||
DeselectCurrent();
|
||
}
|
||
else
|
||
{
|
||
// 点了已选中的物件 → 取消选中
|
||
DeselectCurrent();
|
||
}
|
||
}
|
||
|
||
bLeftMouseDown = false;
|
||
bIsDragging = false;
|
||
}
|
||
|
||
void APlanetPlayerController::OnMouseX(float Value)
|
||
{
|
||
if (!bLeftMouseDown || FMath::IsNearlyZero(Value)) return;
|
||
|
||
// 判断是否超过拖拽阈值
|
||
if (!bIsDragging)
|
||
{
|
||
float X, Y;
|
||
GetMousePosition(X, Y);
|
||
float Dist = FVector2D::Distance(FVector2D(X, Y), MouseDownPosition);
|
||
if (Dist < DragThresholdPixels) return;
|
||
bIsDragging = true;
|
||
}
|
||
|
||
if (GlobeInteraction) GlobeInteraction->RotateGlobe(Value * MouseDragSensitivity, 0.f);
|
||
}
|
||
|
||
void APlanetPlayerController::OnMouseY(float Value)
|
||
{
|
||
if (!bLeftMouseDown || FMath::IsNearlyZero(Value)) return;
|
||
if (!bIsDragging) return;
|
||
|
||
if (GlobeInteraction) GlobeInteraction->RotateGlobe(0.f, Value * MouseDragSensitivity);
|
||
}
|
||
|
||
void APlanetPlayerController::OnZoom(float Value)
|
||
{
|
||
if (FMath::IsNearlyZero(Value)) return;
|
||
if (GlobeInteraction) GlobeInteraction->ZoomGlobe(Value);
|
||
}
|
||
|
||
void APlanetPlayerController::OnEscape()
|
||
{
|
||
DeselectCurrent();
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 悬停
|
||
// ---------------------------------------------------------------------------
|
||
|
||
void APlanetPlayerController::UpdateHover()
|
||
{
|
||
AInteractiveObjectBase* UnderCursor = GetObjectUnderCursor();
|
||
|
||
if (HoveredObject && HoveredObject != UnderCursor && HoveredObject != SelectedObject)
|
||
{
|
||
HoveredObject->SetInteractiveState(EInteractiveObjectState::Normal);
|
||
}
|
||
|
||
HoveredObject = UnderCursor;
|
||
|
||
if (HoveredObject && HoveredObject != SelectedObject)
|
||
{
|
||
HoveredObject->SetInteractiveState(EInteractiveObjectState::Hovered);
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 取消选中
|
||
// ---------------------------------------------------------------------------
|
||
|
||
void APlanetPlayerController::DeselectCurrent()
|
||
{
|
||
if (!SelectedObject) return;
|
||
SelectedObject->SetInteractiveState(EInteractiveObjectState::Normal);
|
||
SelectedObject = nullptr;
|
||
OnObjectDeselected.Broadcast();
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 射线检测
|
||
// ---------------------------------------------------------------------------
|
||
|
||
AInteractiveObjectBase* APlanetPlayerController::GetObjectUnderCursor() const
|
||
{
|
||
FHitResult Hit;
|
||
bool bHit = GetHitResultUnderCursorByChannel(
|
||
UEngineTypes::ConvertToTraceType(ECC_Visibility), true, Hit);
|
||
if (!bHit) return nullptr;
|
||
return Cast<AInteractiveObjectBase>(Hit.GetActor());
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 动捕事件(TODO: 供应商回复后实现)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
void APlanetPlayerController::BindMotionCaptureEvents()
|
||
{
|
||
if (!MotionCaptureReceiver)
|
||
{
|
||
UE_LOG(LogTemp, Warning,
|
||
TEXT("PlayerController: MotionCaptureReceiver 未设置,动捕功能禁用"));
|
||
return;
|
||
}
|
||
|
||
MotionCaptureReceiver->OnGestureDetected.AddDynamic(
|
||
this, &APlanetPlayerController::OnMotionGesture);
|
||
MotionCaptureReceiver->OnRotateDelta.AddDynamic(
|
||
this, &APlanetPlayerController::OnMotionRotate);
|
||
MotionCaptureReceiver->OnZoomDelta.AddDynamic(
|
||
this, &APlanetPlayerController::OnMotionZoom);
|
||
|
||
MotionCaptureReceiver->Connect();
|
||
UE_LOG(LogTemp, Log, TEXT("PlayerController: 动捕事件已绑定"));
|
||
}
|
||
|
||
void APlanetPlayerController::OnMotionGesture(EMotionGesture Gesture)
|
||
{
|
||
if (Gesture == MotionActionMapping.SelectPoint)
|
||
{
|
||
// 手势确认 → 等同于点击当前悬停的物件
|
||
if (HoveredObject)
|
||
{
|
||
DeselectCurrent();
|
||
SelectedObject = HoveredObject;
|
||
SelectedObject->SetInteractiveState(EInteractiveObjectState::Selected);
|
||
OnObjectSelected.Broadcast(SelectedObject);
|
||
}
|
||
}
|
||
else if (Gesture == MotionActionMapping.DismissInfoCard)
|
||
{
|
||
DeselectCurrent();
|
||
}
|
||
else if (Gesture == MotionActionMapping.ZoomIn)
|
||
{
|
||
if (GlobeInteraction) GlobeInteraction->ZoomGlobe(1.f);
|
||
}
|
||
else if (Gesture == MotionActionMapping.ZoomOut)
|
||
{
|
||
if (GlobeInteraction) GlobeInteraction->ZoomGlobe(-1.f);
|
||
}
|
||
}
|
||
|
||
void APlanetPlayerController::OnMotionRotate(float DeltaYaw, float DeltaPitch)
|
||
{
|
||
if (GlobeInteraction) GlobeInteraction->RotateGlobe(DeltaYaw, DeltaPitch);
|
||
}
|
||
|
||
void APlanetPlayerController::OnMotionZoom(float DeltaScale)
|
||
{
|
||
if (GlobeInteraction) GlobeInteraction->ZoomGlobe(DeltaScale);
|
||
}
|