KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
APlayerActor.cpp
이 파일의 문서화 페이지로 가기
1// Copyright (c) 2025 Doppleddiggong. All rights reserved. Unauthorized copying, modification, or distribution of this file, via any medium is strictly prohibited. Proprietary and confidential.
2
8#include "APlayerActor.h"
9
10#include "UMainWidget.h"
11#include "FComponentHelper.h"
12
13// Shared
14#include "AContactTrigger.h"
15#include "AEvaluationTrigger.h"
16#include "GameLogging.h"
17#include "InputCoreTypes.h"
18#include "UDialogManager.h"
20#include "UInteractionSystem.h"
21#include "UHookSystem.h"
22#include "ULingoGameHelper.h"
23#include "UPopupManager.h"
24#include "UPopup_ReadQuest.h"
25#include "ALingoGameState.h"
26#include "ALuggageHolder.h"
27#include "APlayerControl.h"
28#include "ASpeakStageActor.h"
29#include "ATeleportOut.h"
30#include "ATeleportTrigger.h"
31#include "AWheatly.h"
32#include "CompassWidget.h"
33#include "HairStrandsInterface.h"
34#include "MiniOwlBot.h"
35#include "OrderKiosk.h"
36#include "QuestionnaireKiosk.h"
37#include "UToastWidget.h"
38#include "UBroadcastManager.h"
39#include "UFadeWidget.h"
41#include "UPopup_SpeakJudes.h"
42#include "UQuestInfoWidget.h"
43#include "UVoiceRecording.h"
44
45#include "Camera/CameraComponent.h"
46#include "Components/InputComponent.h"
47#include "Components/CapsuleComponent.h"
48#include "GameFramework/CharacterMovementComponent.h"
49#include "GameFramework/PlayerController.h"
50#include "Net/UnrealNetwork.h"
51#include "Blueprint/UserWidget.h"
52#include "Components/Image.h"
53#include "Components/StaticMeshComponent.h"
54#include "Engine/StaticMesh.h"
55#include "GameFramework/PlayerState.h"
56#include "Kismet/KismetMathLibrary.h"
57#include "Onepiece/Onepiece.h"
58
59#define MAINWIDGET_PATH TEXT("/Game/CustomContents/UI/WBP_MainWidget.WBP_MainWidget_C")
60#define TOASTWIDGET_PATH TEXT("/Game/CustomContents/UI/WBP_ToastWidget.WBP_ToastWidget_C")
61
62#define HOOKMESHPATH_PATH TEXT("/Game/CustomContents/Character/Asset/MiniOwl/MiniOwlbot.MiniOwlbot")
63
65{
66 PrimaryActorTick.bCanEverTick = true;
67
68 GetCapsuleComponent()->InitCapsuleSize(45.f, 102.0f);
69 GetMesh()->SetRelativeLocationAndRotation(FVector(0, 0, -100), FRotator(0, -90, 0));
70 GetMesh()->SetRelativeScale3D(FVector(1.5));
71
72 // Movement
73 bUseControllerRotationPitch = false;
74 bUseControllerRotationYaw = true;
75 bUseControllerRotationRoll = false;
76
77 // SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
78 // SpringArmComp->SetupAttachment(GetMesh(), TEXT("Head"));
79 // SpringArmComp->TargetArmLength = 0;
80 // SpringArmComp->bUsePawnControlRotation = false;
81 // SpringArmComp->SetRelativeLocationAndRotation(FVector(0,-14.285715,21.428572), FRotator(90, 90, 0));
82 // // SpringArmComp->bInheritPitch = false;
83 // SpringArmComp->bInheritRoll = false;
84 // SpringArmComp->bInheritPitch = true;
85
86 FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
87 // FollowCamera->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
88 FollowCamera->SetupAttachment(GetMesh(), TEXT("Head"));
89 FollowCamera->SetRelativeLocationAndRotation(FVector(0.235242,-14.285715,24.304751), FRotator(90, 90, 0));
90 FollowCamera->bUsePawnControlRotation = true;
91
92 GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
93 GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
94 GetCharacterMovement()->bOrientRotationToMovement = false;
95
96 HoldPosition = CreateDefaultSubobject<USceneComponent>(TEXT("HoldPosition"));
97 HoldPosition->SetupAttachment(FollowCamera);
98 HoldPosition->SetRelativeLocation(FVector(80.952382,0,-14.285714));
99
100 LookPitch = 0.f;
101 AnotherValue = 0.f;
102
103 // System Component
104 InteractionSystem = CreateDefaultSubobject<UInteractionSystem>(TEXT("InteractionSystem"));
105 HookSystem = CreateDefaultSubobject<UHookSystem>(TEXT("HookSystem"));
106 VoiceConversationSystem = CreateDefaultSubobject<UVoiceConversationSystem>(TEXT("VoiceConversationSystem"));
107
108 // Hook
109 HookCable = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("HookCable"));
110 HookCable->SetupAttachment(GetMesh());
111 HookCable->SetVisibility(false);
112 HookCable->SetCollisionEnabled(ECollisionEnabled::NoCollision);
113 HookCable->SetMobility(EComponentMobility::Movable);
114 HookCable->SetUsingAbsoluteLocation(true);
115 HookCable->SetUsingAbsoluteRotation(true);
116
117 static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderMesh(TEXT("/Engine/BasicShapes/Cylinder.Cylinder"));
118 if (CylinderMesh.Succeeded())
119 HookCable->SetStaticMesh(CylinderMesh.Object);
120 HookCable->SetRelativeScale3D(FVector(0.05f, 0.05f, 1.0f));
121
122 HookProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("HookProjectileMesh"));
123 HookProjectileMesh->SetupAttachment(GetMesh());
124 HookProjectileMesh->SetVisibility(false);
125 HookProjectileMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
126
127 static ConstructorHelpers::FObjectFinder<UStaticMesh> HookMesh(HOOKMESHPATH_PATH);
128 if (HookMesh.Succeeded())
129 {
130 HookProjectileMesh->SetStaticMesh(HookMesh.Object);
131 HookProjectileMesh->SetRelativeScale3D(FVector(1.0f));
132 HookProjectileMesh->SetRelativeRotation(FRotator(0, 90, 0));
133 }
134
135 miniOwlBot = CreateDefaultSubobject<UChildActorComponent>(TEXT("miniOwlBot"));
136 miniOwlBot->SetupAttachment(GetRootComponent());
137 miniOwlBot->SetRelativeLocationAndRotation(basePos, baseRot);
138 ConstructorHelpers::FClassFinder<AMiniOwlBot> miniOwlBotRef(TEXT("/Game/CustomContents/Blueprints/BP_MiniOwlBot.BP_MiniOwlBot_C"));
139 if (miniOwlBotRef.Succeeded())
140 {
141 miniOwlBot->SetChildActorClass(miniOwlBotRef.Class);
142 }
143
144 // MainWidget 클래스 자동 로드
146
148
149 // Compass 마커
151 bShowOnCompass = true;
152
153 // 3인칭 메쉬는 플레이어에게 보이지 않도록
154 GetMesh()->SetOwnerNoSee(true);
155}
156
158{
159 Super::BeginPlay();
160
161 MoveComp = this->GetCharacterMovement();
162
163 VoiceConversationSystem->InitSystem(this);
165
166 // 텔레포트 이벤트 구독
167 if (auto DM = UBroadcastManager::Get(GetWorld()))
168 {
169 DM->OnTeleport.RemoveDynamic(this, &APlayerActor::OnTeleportAllPlayers);
170 DM->OnTeleport.AddDynamic(this, &APlayerActor::OnTeleportAllPlayers);
171
172 // DM->OnUpdateQuestRole.RemoveDynamic(this, &APlayerActor::OnUpdateQuestRole);
173 // DM->OnUpdateQuestRole.AddDynamic(this, &APlayerActor::OnUpdateQuestRole);
174 }
175
176 if (auto GS = ULingoGameHelper::GetLingoGameState(GetWorld()))
177 {
178 // GS->OnQuestScenarioDataUpdated.RemoveDynamic(this, &APlayerActor::OnUpdateQuestInfo);
179 // GS->OnQuestScenarioDataUpdated.AddDynamic(this, &APlayerActor::OnUpdateQuestInfo);
180 //
181 // GS->OnReadResultUpdated.RemoveDynamic(this, &APlayerActor::OnReadResultUpdated);
182 // GS->OnReadResultUpdated.AddDynamic(this, &APlayerActor::OnReadResultUpdated);
183 //
184 // GS->OnListenResultUpdated.RemoveDynamic(this, &APlayerActor::OnListenResultUpdated);
185 // GS->OnListenResultUpdated.AddDynamic(this, &APlayerActor::OnListenResultUpdated);
186
187 GS->OnRoomIdUpdated.RemoveDynamic(this, &APlayerActor::OnRoomIdUpdated);
188 GS->OnRoomIdUpdated.AddDynamic(this, &APlayerActor::OnRoomIdUpdated);
189
190 GS->OnRoomLevelUpdated.RemoveDynamic(this, &APlayerActor::OnRoomLevelUpdated);
191 GS->OnRoomLevelUpdated.AddDynamic(this, &APlayerActor::OnRoomLevelUpdated);
192
193 // 방향계 업데이트
194 FTimerHandle TimerHandle;
195 GetWorldTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateLambda([this, GS]
196 {
197 GS->SetAllCompassVisibility(false);
198 GS->SetCompassVisibilityByTag("ReadQuestStart", true);
199
200 }), 0.5f, false);
201 }
202
203 if (IsLocallyControlled())
204 {
207
208 if ( IsMainMap() )
210 }
211
212 // miniowlbot invisible 처리
213 PRINTLOG(TEXT("%s"), Cast<AMiniOwlBot>(miniOwlBot->GetChildActor()) == nullptr ? TEXT("miniOwlBot is null") : TEXT("miniOwlBot is not null"));
214 if (auto InChildActor = Cast<AMiniOwlBot>(miniOwlBot->GetChildActor()))
215 {
216 if (IsLocallyControlled())
217 {
218 Cast<AMiniOwlBot>(InChildActor)->SetActorHiddenInGame(false);
219 }
220 else
221 {
222 Cast<AMiniOwlBot>(InChildActor)->SetActorHiddenInGame(true);
223 }
224 }
225}
226
227void APlayerActor::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
228{
229 Super::GetLifetimeReplicatedProps(OutLifetimeProps);
230
231 DOREPLIFETIME(APlayerActor, LookPitch);
232 DOREPLIFETIME(APlayerActor, AnotherValue);
233}
234
236{
237 // 팝업이 열려있고 조작을 차단해야 하면 false 반환
238 if (UPopupManager* PopupMgr = UPopupManager::Get(GetWorld()))
239 {
240 if (PopupMgr->ShouldBlockPlayerControl())
241 return false;
242 }
243
244 // 팝업이 없거나 조작 허용 팝업이면 true 반환
245 return true;
246}
247
249{
250 FString MapName = GetWorld()->GetMapName();
251 MapName.RemoveFromStart(GetWorld()->StreamingLevelsPrefix);
252
253 if ( MapName.Contains(TEXT("Map1")) )
254 return true;
255 else if ( MapName.Contains(TEXT("Game")) )
256 return true;
257 else if ( MapName.Contains(TEXT("Person")) )
258 return true;
259
260 return false;
261}
262
264{
265 if (!MainWidgetClass)
266 return;
267
268 if(MainWidget)
269 return;
270
271 auto PC = Cast<APlayerControl>(GetController());
272 if ( !PC)
273 return;
274
275 MainWidget = CreateWidget<UMainWidget>(PC, MainWidgetClass);
276
277 if (MainWidget)
278 {
279 MainWidget->AddToViewport();
280
281 if ( IsMainMap())
282 {
283 if (auto GS = ULingoGameHelper::GetLingoGameState(GetWorld()))
284 MainWidget->UpdateRoomWidget( GS->GetRoomLevel(), GS->GetRoomId());
285
286 MainWidget->CompassWidget->SetVisibility( ESlateVisibility::Visible );
287 MainWidget->VoiceRecording->SetVisibility( ESlateVisibility::Visible );
288
289 // UserInfo가 로드되었으면 즉시 업데이트, 아니면 재시도
290 if (PC->HasUserInfo() )
291 {
292 MainWidget->UpdateStateWidget( PC->GetUserId(), PC->GetUserName());
293 MainWidget->UpdateChatWidget();
294 return;
295 }
296
297 // UserInfo 로드 대기 후 재시도 (0.5초 후 한 번)
298 FTimerHandle RetryTimer;
299 GetWorld()->GetTimerManager().SetTimer(RetryTimer, [this, PC]()
300 {
301 if ( PC->GetUserId())
302 {
303 MainWidget->UpdateStateWidget( PC->GetUserId(), PC->GetUserName());
304 }
305 MainWidget->UpdateChatWidget();
306 }, 0.5f, false);
307 }
308 }
309}
310
312{
313 if (!ToastWidgetClass)
314 return;
315
316 if(ToastWidget)
317 return;
318
319 APlayerController* PC = Cast<APlayerController>(GetController());
320 if (!PC)
321 return;
322
323 ToastWidget = CreateWidget<UToastWidget>(PC, ToastWidgetClass);
324 if (ToastWidget)
325 ToastWidget->AddToViewport(GameLayer::Toast);
326}
327
328void APlayerActor::PossessedBy(AController* NewController)
329{
330 Super::PossessedBy(NewController);
331
332 APlayerState* PS = GetPlayerState();
333 if (!PS)
334 return;
335
336 // 플레이어 인덱스 확인 (GameState의 PlayerArray 사용)
337 int32 PlayerIndex = -1;
338 if (AGameStateBase* GS = GetWorld()->GetGameState())
339 PlayerIndex = GS->PlayerArray.IndexOfByKey(PS);
340
341 // 2P에게 Another = 1 적용 (1P는 0)
342 AnotherValue = (PlayerIndex == 0) ? 0.0f : 1.0f;
343
345}
346
347void APlayerActor::Tick(float DeltaTime)
348{
349 Super::Tick(DeltaTime);
350
351 if (MainWidget && MainWidget->CompassWidget)
352 {
353 UCompassWidget* Compass = MainWidget->CompassWidget;
354 float CameraRotationZ = FollowCamera->GetComponentRotation().Yaw;
355
356 Compass->RotateCompass(CameraRotationZ);
357
359 }
360
361 if (auto childOwlBot = Cast<AMiniOwlBot>(miniOwlBot->GetChildActor()))
362 {
363 childOwlBot->UpdateLocation(DeltaTime);
364 }
365}
366
368{
369 Super::OnRep_Controller();
370
371 if (IsLocallyControlled())
372 {
375 }
376
377 // miniowlbot invisible 처리
378 PRINTLOG(TEXT("%s"), Cast<AMiniOwlBot>(miniOwlBot->GetChildActor()) == nullptr ? TEXT("miniOwlBot is null") : TEXT("miniOwlBot is not null"));
379 if (auto InChildActor = Cast<AMiniOwlBot>(miniOwlBot->GetChildActor()))
380 {
381 if (IsLocallyControlled())
382 {
383 Cast<AMiniOwlBot>(InChildActor)->SetActorHiddenInGame(false);
384 }
385 else
386 {
387 Cast<AMiniOwlBot>(InChildActor)->SetActorHiddenInGame(true);
388 }
389 }
390}
391
393{
394 // if (SpringArmComp)
395 // {
396 // // 다른 클라이언트에서 보이는 서버 캐릭터의 복제본의 설정 변경
397 // SpringArmComp->bUsePawnControlRotation = false;
398 //
399 // FRotator CurrentRotation = SpringArmComp->GetRelativeRotation();
400 // CurrentRotation.Pitch = LookPitch;
401 // SpringArmComp->SetRelativeRotation(CurrentRotation);
402 // }
403}
404
409
411{
412 USkeletalMeshComponent* MeshComp = GetMesh();
413 if (!MeshComp)
414 return;
415
416 // Dynamic Material Instance 생성 및 파라미터 설정
417 for (int32 i = 0; i < MeshComp->GetNumMaterials(); ++i)
418 {
419 UMaterialInterface* Material = MeshComp->GetMaterial(i);
420 if (!Material)
421 continue;
422
423 UMaterialInstanceDynamic* DynamicMaterial = Cast<UMaterialInstanceDynamic>(Material);
424 if (!DynamicMaterial)
425 DynamicMaterial = MeshComp->CreateAndSetMaterialInstanceDynamic(i);
426
427 if (DynamicMaterial)
428 DynamicMaterial->SetScalarParameterValue(FName("Another"), AnotherValue);
429 }
430
431
432 auto PS = GetPlayerState<ALingoPlayerState>();
433 if (PS)
434 {
435 PS->RefreshQuestState();
436 }
437}
438
439void APlayerActor::RecoveryMovementMode(const EMovementMode InMovementMode)
440{
441 if ( InMovementMode == MOVE_None)
442 return;
443
444 auto Movement = this->GetCharacterMovement();
445
446 Movement->SetMovementMode( InMovementMode );
447 this->bUseControllerRotationYaw = false;
448 this->bUseControllerRotationPitch = false;
449 Movement->bOrientRotationToMovement = true;
450}
451
452void APlayerActor::PlayTTSAudio(const TArray<uint8>& AudioData)
453{
454 VoiceConversationSystem->PlayVoiceAudio(AudioData);
455}
456
457void APlayerActor::Cmd_StopMove_Implementation()
458{
460}
461
462void APlayerActor::Cmd_Run_Implementation()
463{
464 if (!IsControlEnabled())
465 return;
466
468}
469
470void APlayerActor::Cmd_Move_Implementation(const FVector2D& Axis)
471{
472 if (!IsControlEnabled())
473 return;
474
475 if ( !Controller)
476 {
477 return;
478 }
479
480 // Use the controller's rotation to determine movement direction.
481 const FRotator ControlRotation = Controller->GetControlRotation();
482 const EMovementMode CurrentMovementMode = GetCharacterMovement()->MovementMode;
483
484 if (CurrentMovementMode == MOVE_Walking || CurrentMovementMode == MOVE_Falling)
485 {
486 // For ground movement, only use the Yaw rotation to prevent pitching into the ground.
487 const FRotator YawRotation(0.0f, ControlRotation.Yaw, 0.0f);
488
489 // Calculate forward and right vectors based on the Yaw rotation.
490 const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
491 const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
492
493 AddMovementInput(ForwardDirection, Axis.Y);
494 AddMovementInput(RightDirection, Axis.X);
495 }
496}
497
498void APlayerActor::Cmd_Look_Implementation(const FVector2D& Axis)
499{
500 if (!IsControlEnabled())
501 return;
502
503 AddControllerYawInput(Axis.X);
504 AddControllerPitchInput(Axis.Y);
505}
506
507void APlayerActor::Cmd_Jump_Implementation()
508{
509 if (!IsControlEnabled())
510 return;
511
513}
514
515void APlayerActor::Cmd_RecordStart_Implementation()
516{
517 if (!IsControlEnabled())
518 return;
519
520 VoiceConversationSystem->StartRecording();
521}
522
523void APlayerActor::Cmd_RecordEnd_Implementation()
524{
525 VoiceConversationSystem->StopRecording();
526}
527
528void APlayerActor::Cmd_Info_Implementation()
529{
530 if (!IsControlEnabled())
531 return;
532
533
534 auto GS = ULingoGameHelper::GetLingoGameState(GetWorld());
535 auto PS = GetPlayerState<ALingoPlayerState>();
536
537 if ( PS->bReadQuestIng &&
538 !PS->bReadQuestCompleted )
539 {
540 if (auto Popup = UPopupManager::ShowPopupAs<UPopup_ReadQuest>(GetWorld(), EPopupType::ReadQuest))
541 Popup->InitRead(GS->ReadScenarioData);
542 }
543 else if (
544 PS->bListenQuestIng &&
545 !PS->bListenQuestCompleted )
546 {
547 auto QuestRole = GetQuestRole();
548
549 if ( QuestRole == EQuestRole::Both)
550 RequestListenAudio( GS->ListenScenarioData.full_data.Kor);
551 else if ( QuestRole == EQuestRole::OnlyQuestion1)
552 RequestListenAudio( GS->ListenScenarioData.word_data1.Kor);
553 else if ( QuestRole == EQuestRole::OnlyQuestion2)
554 RequestListenAudio( GS->ListenScenarioData.word_data2.Kor);
555 }
556 else if ( PS->bSpeakQuestIng &&
557 !PS->bSpeakQuestCompleted )
558 {
559 if (auto SpeakStageActor = ULingoGameHelper::GetSpeakStageActor(GetWorld()))
560 {
561 if ( SpeakStageActor->IsMyTurn(PS) )
562 {
563 PlaySpeakInfo( SpeakStageActor->GetCurrentStepIndex() );
564 }
565 }
566 }
567}
568
570{
571 return Cast<AMiniOwlBot>(miniOwlBot->GetChildActor());
572}
573
574void APlayerActor::PlaySpeakInfo(int32 StepIndex)
575{
576 auto PS = GetPlayerState<ALingoPlayerState>();
577
578 FSpeakStageQuestion CurrentSpeakQuestion;
579 if (PS->GetCurrentSpeakQuestion(StepIndex, CurrentSpeakQuestion))
580 {
581 if (UDialogManager* DM = UDialogManager::Get(GetWorld()))
582 DM->ShowToast(*CurrentSpeakQuestion.GetQuestionMessage());
583
584 RequestSpeakAudio(CurrentSpeakQuestion.kor);
585 }
586}
587
589{
590 ALingoPlayerState* PS = GetPlayerState<ALingoPlayerState>();
591 return PS->QuestRole;
592}
593
594void APlayerActor::RequestListenAudio(const FString& AudioText)
595{
596 if (bIsRequest)
597 return;
598
599 if (auto KLingoNetwork = UKLingoNetworkSystem::Get(GetWorld()))
600 {
601 bIsRequest = true;
602
603 KLingoNetwork->RequestListenAudio(
604 AudioText,
605 FResponseListenAudioDelegate::CreateUObject(this, &APlayerActor::OnResponseListenAudio)
606 );
607 }
608}
609
610void APlayerActor::OnResponseListenAudio(FResponseListenAudio& ResponseData, bool bWasSuccessful)
611{
612 bIsRequest = false;
613
614 if (bWasSuccessful)
615 {
616 this->PlayTTSAudio(ResponseData.audio_base64);
617 UDialogManager::Get(GetWorld())->ShowToast(*ResponseData.audio_text);
618 }
619}
620
621
622void APlayerActor::RequestSpeakAudio(const FString& AudioText)
623{
624 if (bIsRequest)
625 return;
626
627 if (auto KLingoNetwork = UKLingoNetworkSystem::Get(GetWorld()))
628 {
629 bIsRequest = true;
630
631 KLingoNetwork->RequestListenAudio(
632 AudioText,
633 FResponseListenAudioDelegate::CreateUObject(this, &APlayerActor::OnResponseSpeakAudio)
634 );
635 }
636}
637
638void APlayerActor::OnResponseSpeakAudio(FResponseListenAudio& ResponseData, bool bWasSuccessful)
639{
640 bIsRequest = false;
641
642 if (bWasSuccessful)
643 {
644 this->PlayTTSAudio(ResponseData.audio_base64);
645 }
646}
647
648
649void APlayerActor::ServerRPC_StopMove_Implementation()
650{
652}
653
654void APlayerActor::MulticastRPC_StopMove_Implementation()
655{
656 bIsRunning = false;
657 GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
658}
659
660void APlayerActor::ServerRPC_DoJumpStart_Implementation()
661{
663}
664
665void APlayerActor::MulticastRPC_DoJumpStart_Implementation()
666{
667 bIsJumpStart = true;
668}
669
670void APlayerActor::ServerRPC_DoJump_Implementation()
671{
673}
674
675void APlayerActor::MulticastRPC_DoJump_Implementation()
676{
677 bIsJumpStart = false;
678 Jump();
679}
680
681void APlayerActor::ServerRPC_DoRun_Implementation()
682{
684}
685
686void APlayerActor::MulticastRPC_DoRun_Implementation()
687{
688 if (bIsJumpStart || GetMovementComponent()->IsFalling()) return;
689
690 if (bIsRunning)
691 {
692 bIsRunning = false;
693 GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
694 }
695 else
696 {
697 bIsRunning = true;
698 GetCharacterMovement()->MaxWalkSpeed = RunSpeed;
699 }
700}
701
702void APlayerActor::OnGameMessage(const FString& Message)
703{
704 PRINT_STRING(TEXT("Event Received: %s"), *Message);
705
706 // 서버에서 호출되면 클라이언트 RPC로 전달
707 if (HasAuthority())
708 {
710 }
711 else
712 {
713 ClientRPC_ShowGameMessage_Implementation(Message);
714 }
715}
716
717void APlayerActor::ClientRPC_ShowGameMessage_Implementation(const FString& Message)
718{
719 // 클라이언트에서만 실행됨 - LocalPlayerSubsystem 사용 가능
720 if (UDialogManager* DialogManager = UDialogManager::Get(GetWorld()))
721 {
722 DialogManager->ShowToast(Message);
723 }
724}
725
726void APlayerActor::OnTeleportAllPlayers(FTransform TargetTransform)
727{
728 // 서버에 텔레포트 요청
729 ServerRPC_Teleport(TargetTransform);
730
731 // 로컬 플레이어만 페이드 처리
732 if (!IsLocallyControlled())
733 return;
734
735 PRINTLOG(TEXT("APlayerActor::OnTeleportAllPlayers - Start teleport to %s"), *TargetTransform.ToString());
736
737 // 목표 위치 저장
738 PendingTeleportTransform = TargetTransform;
739
740 // FadeWidget 가져오기
741 if (!MainWidget)
742 {
743 PRINTLOG(TEXT("APlayerActor::OnTeleportAllPlayers - MainWidget is null"));
744 return;
745 }
746
747 UFadeWidget* FadeWidget = MainWidget->GetFadeWidget();
748 if (!FadeWidget)
749 {
750 PRINTLOG(TEXT("APlayerActor::OnTeleportAllPlayers - FadeWidget is null"));
751 return;
752 }
753
754 // FadeOut 완료 시 텔레포트 실행
756
757 // FadeOut 시작
758 MainWidget->FadeOut(0.5f);
759}
760
761void APlayerActor::ServerRPC_Teleport_Implementation(FTransform TargetTransform)
762{
763 SetActorLocation(TargetTransform.GetLocation());
764 if (APlayerControl* PC = Cast<APlayerControl>(Controller))
765 {
766 PC->SetControlRotation(PendingTeleportTransform.Rotator());
767 }
768}
769
770void APlayerActor::Server_NotifySpeakJudgeComplete_Implementation(const FResponseSpeakingJudes& Response)
771{
772 if (!HasAuthority())
773 return;
774
775 // PlayerState에 평가 결과 저장
776 if (auto PS = GetPlayerState<ALingoPlayerState>())
777 {
778 PS->AddSpeakJudes(Response);
779
780 // 클라이언트에게 팝업 표시 지시 (Client RPC)
782 }
783}
784
785void APlayerActor::Server_NotifyConfirmSpeakJudes_Implementation()
786{
787 if (!HasAuthority())
788 return;
789
790 // SpeakStage에 답변 완료 알림
791 if (auto PS = GetPlayerState<ALingoPlayerState>())
792 {
793 if (auto SpeakStage = ULingoGameHelper::GetSpeakStageActor(GetWorld()))
794 {
795 SpeakStage->NotifyAnswerComplete(PS);
796 }
797 }
798}
799
800void APlayerActor::Client_ShowSpeakJudesPopup_Implementation(const FResponseSpeakingJudes& Response)
801{
802 // 클라이언트에서 팝업 표시
803 if (auto Popup = UPopupManager::ShowPopupAs<UPopup_SpeakJudes>(GetWorld(), EPopupType::SpeakJudes))
804 {
805 // Confirm 버튼 클릭 시 서버에 알리는 델리게이트 설정
806 FOnSpeakJudesConfirmDelegate OnConfirm;
807 OnConfirm.BindLambda([this]()
808 {
810 });
811
812 Popup->InitPopup(Response, OnConfirm);
813 }
814}
815
817{
818 // 텔레포트 실행
819 SetActorLocation(PendingTeleportTransform.GetLocation());
820 if (APlayerControl* PC = Cast<APlayerControl>(Controller))
821 {
822 PC->SetControlRotation(PendingTeleportTransform.Rotator());
823 }
824
825 // FadeWidget 가져오기
826 if (!MainWidget)
827 return;
828
829 UFadeWidget* FadeWidget = MainWidget->GetFadeWidget();
830 if (!FadeWidget)
831 return;
832
833 // FadeOut 델리게이트 해제
834 FadeWidget->OnFadeOutComplete.RemoveDynamic(this, &APlayerActor::OnFadeOutCompleteForTeleport);
835
836 // FadeIn 시작
837 MainWidget->FadeIn(0.5f);
838}
839
841{
842 if (!MainWidget || !MainWidget->CompassWidget)
843 return;
844
845 UCompassWidget* Compass = MainWidget->CompassWidget;
846
847 // 1. 현재 월드의 모든 트래킹 대상 수집
848 TArray<AActor*> TrackedActors;
849 // GetAllActorsOfClass 시 배열 안에 있는건 clear됨. 임시배열 필요
850 TArray<AActor*> TempActors;
851
852 // AContactTrigger
853 UGameplayStatics::GetAllActorsOfClass(GetWorld(), AContactTrigger::StaticClass(), TempActors);
854 TrackedActors.Append(TempActors);
855
856 // ALuggageHolder
857 UGameplayStatics::GetAllActorsOfClass(GetWorld(), ALuggageHolder::StaticClass(), TempActors);
858 TrackedActors.Append(TempActors);
859
860 // AOrderKiosk
861 UGameplayStatics::GetAllActorsOfClass(GetWorld(), AOrderKiosk::StaticClass(), TempActors);
862 TrackedActors.Append(TempActors);
863
864 // Wheatly
865 UGameplayStatics::GetAllActorsOfClass(GetWorld(), AWheatly::StaticClass(), TempActors);
866 TrackedActors.Append(TempActors);
867
868 // Teleporter
869 UGameplayStatics::GetAllActorsOfClass(GetWorld(), ATeleportTrigger::StaticClass(), TempActors);
870 TrackedActors.Append(TempActors);
871
872 // QuestionnaireKiosk
873 UGameplayStatics::GetAllActorsOfClass(GetWorld(), AQuestionnaireKiosk::StaticClass(), TempActors);
874 TrackedActors.Append(TempActors);
875
876 // Evaluation Trigger
877 UGameplayStatics::GetAllActorsOfClass(GetWorld(), AEvaluationTrigger::StaticClass(), TempActors);
878 TrackedActors.Append(TempActors);
879
880 // Other player
881 UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerActor::StaticClass(), TempActors);
882 for (AActor* OtherActor : TempActors)
883 {
884 if (OtherActor != this)
885 TrackedActors.Add(OtherActor);
886 }
887
888 // 2. 각 TrackedActor에 대해 마커 생성 or 업데이트
889 for (AActor* TrackedActor : TrackedActors)
890 {
891 if (!TrackedActor) continue;
892
893 // Interface로 캐스팅해서 타입 정보 가져오기
894 ICompassTargetInterface* Target = Cast<ICompassTargetInterface>(TrackedActor);
895 if (!Target) continue;
896
897 if (!Target->ShouldShowOnCompass())
898 {
899 if (UImage* ExistingMarker = CompassMarkerMap.FindRef(TrackedActor))
900 {
901 Compass->SetMarkerVisibility(ExistingMarker, ESlateVisibility::Hidden);
902 }
903 continue;
904 }
905
906 // Interface 함수로 마커 타입 확인
907 ECompassMarkerType CompassMarkerType = Target->GetCompassMarkerType();
908
909 // 2-1. 마커가 없으면 생성
910 UImage* Marker = CompassMarkerMap.FindRef(TrackedActor);
911 if (!Marker)
912 {
913 Marker = Compass->AddCompassMarker(CompassMarkerType);
914 CompassMarkerMap.Add(TrackedActor, Marker);
915 }
916 else
917 {
918 // 기존 마커 텍스처 업데이트
919 UTexture2D* NewTexture = Compass->GetTextureForMarkerType(CompassMarkerType);
920 if (NewTexture)
921 {
922 Marker->SetBrushFromTexture(NewTexture);
923 }
924
925 // 다시 보이게 설정 (Hidden 상태였을 수 있음)
926 Compass->SetMarkerVisibility(Marker, ESlateVisibility::Visible);
927 }
928
929 // 2-2. 상대 회전 계산 (Yaw만 필요)
930 FRotator RelativeRotation = FindRelativeRotationAtTarget(TrackedActor);
931 float TargetYaw = RelativeRotation.Yaw;
932
933 // 2-3. 마커 위치 업데이트 (bSideLock은 false로 가정)
934 Compass->SetMarkerPosition(Marker, TargetYaw, false);
935
936 // 2-4. 거리 계산 및 표시
937 float Distance = FVector::Dist(GetActorLocation(), TrackedActor->GetActorLocation());
938 Compass->SetMarkerDistance(Marker, Distance);
939 }
940
941 // 제거된 액터의 마커 정리
942 TArray<AActor*> ActorsToRemove;
943 for (auto& Pair : CompassMarkerMap)
944 {
945 if (!Pair.Key || !TrackedActors.Contains(Pair.Key))
946 {
947 if (Pair.Value)
948 {
949 Compass->SetMarkerVisibility(Pair.Value, ESlateVisibility::Hidden);
950 }
951 ActorsToRemove.Add(Pair.Key);
952 }
953 }
954
955 for (AActor* Actor : ActorsToRemove)
956 {
957 CompassMarkerMap.Remove(Actor);
958 }
959}
960
962{
963 // Get World Location (Capsule Component)
964 FVector CapsuleLocation = GetCapsuleComponent()->GetComponentLocation();
965
966 // Get World Rotation (Capsule Component)
967 FRotator CapsuleRotation = GetCapsuleComponent()->GetComponentRotation();
968
969 // Get World Location (Root Component)
970 FVector TargetLocation = Target->GetActorLocation();
971
972 // Find Relative Look at Rotation
973 FRotator LookAtRotation = UKismetMathLibrary::FindLookAtRotation(
974 CapsuleLocation, TargetLocation);
975
976 // 상대 회전 계산
977 FRotator RelativeRotation = UKismetMathLibrary::NormalizedDeltaRotator(
978 LookAtRotation,CapsuleRotation);
979
980 // Return Value (Roll, Pitch, Yaw)
981 return RelativeRotation;
982}
983
984void APlayerActor::OnRoomIdUpdated(int64 NewRoomId)
985{
986 if (!IsLocallyControlled())
987 return;
988
989 if (MainWidget)
990 {
991 if (auto GS = ULingoGameHelper::GetLingoGameState(GetWorld()))
992 {
993 const int RoomLevel = GS->GetRoomLevel();
994 const int RoomId = NewRoomId;
995
996 MainWidget->UpdateRoomWidget(RoomLevel, RoomId);
997 }
998 }
999}
1000
1001void APlayerActor::OnRoomLevelUpdated(int32 NewRoomLevel)
1002{
1003 if (!IsLocallyControlled())
1004 return;
1005
1006 if (MainWidget)
1007 {
1008 if (auto GS = ULingoGameHelper::GetLingoGameState(GetWorld()))
1009 {
1010 const int RoomLevel = NewRoomLevel;
1011 const int RoomId = GS->GetRoomId();
1012
1013 MainWidget->UpdateRoomWidget(RoomLevel, RoomId);
1014 }
1015 }
1016}
1017
1018// void APlayerActor::OnUpdateQuestInfo()
1019// {
1020// if (!IsLocallyControlled())
1021// return;
1022// MainWidget->GetQuestInfoWidget()->InitQuestInfo();
1023// }
1024//
1025// void APlayerActor::OnUpdateQuestRole(EQuestRole QuestRole)
1026// {
1027// if (!IsLocallyControlled())
1028// return;
1029// MainWidget->GetQuestInfoWidget()->InitQuestInfo();
1030// }
1031//
1032
1033// void APlayerActor::OnReadResultUpdated(const FResponseReadResult& Result)
1034// {
1035// if (!IsLocallyControlled())
1036// return;
1037//
1038// MainWidget->GetQuestInfoWidget()->SetVisibility(ESlateVisibility::Collapsed);
1039// }
1040//
1041// void APlayerActor::OnListenResultUpdated( const FResponseListenResult& Result)
1042// {
1043// if (!IsLocallyControlled())
1044// return;
1045//
1046// MainWidget->GetQuestInfoWidget()->SetVisibility(ESlateVisibility::Collapsed);
1047// }
#define MAINWIDGET_PATH
#define HOOKMESHPATH_PATH
#define TOASTWIDGET_PATH
Declares the player-controlled character actor.
APlayerControl 선언에 대한 Doxygen 주석을 제공합니다.
ECompassMarkerType
EQuestRole
Read 퀘스트에서 플레이어의 역할을 정의합니다.
Definition EQuestRole.h:6
FComponentHelper 구조체를 선언합니다.
YiSan 전반에서 사용하는 공용 인터페이스를 선언합니다.
#define PRINTLOG(fmt,...)
Definition GameLogging.h:30
#define PRINT_STRING(fmt,...)
Definition GameLogging.h:45
UDialogManager 클래스를 선언합니다.
화면 페이드 인/아웃 효과를 제공하는 위젯
그래플링 훅 시스템
플레이어의 상호작용 감지 및 처리 시스템
KLingo API 요청을 담당하는 서브시스템을 선언합니다.
STT·GPT·TTS 파이프라인을 연결하는 음성 대화 컴포넌트를 선언합니다.
EQuestRole QuestRole
플레이어 역할 (싱글/멀티에서 문제1, 문제2 구분)
Main character driven directly by the player.
void ApplyAnotherValue()
Another 머티리얼 파라미터를 실제로 적용합니다 (서버/클라이언트 공통)
TObjectPtr< class UVoiceConversationSystem > VoiceConversationSystem
void OnGameMessage(const FString &Message)
게임 이벤트 메시지를 수신합니다.
TObjectPtr< class UHookSystem > HookSystem
bool IsControlEnabled() const
플레이어 조작이 가능한지 확인합니다
FTransform PendingTeleportTransform
텔레포트 목표 위치
void OnResponseSpeakAudio(FResponseListenAudio &ResponseData, bool bWasSuccessful)
void RequestSpeakAudio(const FString &AudioText)
FVector basePos
TObjectPtr< class UMainWidget > MainWidget
메인 UI 위젯 인스턴스
TObjectPtr< class UChildActorComponent > miniOwlBot
void CreateToastWidget()
virtual void PossessedBy(AController *NewController) override
TObjectPtr< class UCharacterMovementComponent > MoveComp
void MulticastRPC_DoRun()
void MulticastRPC_DoJumpStart()
void RecoveryMovementMode(const EMovementMode InMovementMode)
EQuestRole GetQuestRole()
FRotator baseRot
virtual void OnRep_Controller() override
float AnotherValue
2P 구분을 위한 머티리얼 파라미터 (0=1P, 1=2P)
TObjectPtr< class UStaticMeshComponent > HookProjectileMesh
TMap< AActor *, class UImage * > CompassMarkerMap
virtual void Tick(float DeltaTime) override
TSubclassOf< class UToastWidget > ToastWidgetClass
void CreateMainWidget()
메인 위젯을 생성하고 뷰포트에 추가합니다.
TObjectPtr< class UInteractionSystem > InteractionSystem
void Server_NotifyConfirmSpeakJudes()
SpeakJudge 팝업 확인 버튼 클릭을 서버에 알립니다 (Server RPC)
TObjectPtr< class UStaticMeshComponent > HookCable
TSubclassOf< class UMainWidget > MainWidgetClass
메인 위젯 블루프린트 클래스
TObjectPtr< class UCameraComponent > FollowCamera
void Client_ShowSpeakJudesPopup(const struct FResponseSpeakingJudes &Response)
SpeakJudge 결과 팝업을 클라이언트에 표시합니다 (Client RPC)
void OnResponseListenAudio(FResponseListenAudio &ResponseData, bool bWasSuccessful)
void ServerRPC_DoRun()
virtual void GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > &OutLifetimeProps) const override
void ServerRPC_Teleport(FTransform TargetTransform)
void ClientRPC_ShowGameMessage(const FString &Message)
클라이언트에서 게임 메시지를 표시합니다.
void PlaySpeakInfo(int32 QuestStep)
void RequestListenAudio(const FString &AudioText)
void ServerRPC_DoJumpStart()
void MulticastRPC_DoJump()
void OnRoomIdUpdated(int64 NewRoomId)
void OnFadeOutCompleteForTeleport()
페이드 아웃 완료 후 텔레포트 실행
void OnTeleportAllPlayers(FTransform TargetTransform)
텔레포트 이벤트 핸들러
void OnRep_LookPitch()
void UpdateCompassMarkers()
void OnRep_AnotherValue()
TObjectPtr< class USceneComponent > HoldPosition
FRotator FindRelativeRotationAtTarget(AActor *Target)
void ServerRPC_StopMove()
void PlayTTSAudio(const TArray< uint8 > &AudioData)
TTS 오디오를 재생합니다. VoiceConversationSystem으로 전달합니다.
class AMiniOwlBot * GetMiniOwlBot() const
TObjectPtr< class UToastWidget > ToastWidget
void MulticastRPC_StopMove()
virtual void BeginPlay() override
void OnRoomLevelUpdated(int32 NewRoomLevel)
virtual bool ShouldShowOnCompass() const
ECompassMarkerType GetCompassMarkerType()
void RotateCompass(float ZRotation)
UImage * AddCompassMarker(ECompassMarkerType MarkerType)
void SetMarkerDistance(UImage *InMarker, float Distance)
마커 거리 설정 (미터 단위)
void SetMarkerPosition(UImage *InMarker, float TargetRotation, bool bSideLock)
void SetMarkerVisibility(UImage *InMarker, ESlateVisibility InVisibility)
마커와 거리 텍스트의 Visibility 동시 설정
UTexture2D * GetTextureForMarkerType(ECompassMarkerType MarkerType)
토스트 메시지와 같은 간단한 다이얼로그 위젯의 표시를 관리하는 LocalPlayer 서브시스템입니다.
화면 페이드 인/아웃 효과를 제공하는 위젯
Definition UFadeWidget.h:22
FOnFadeComplete OnFadeOutComplete
페이드 아웃 완료 시 호출되는 델리게이트
Definition UFadeWidget.h:47
static void HideMouseCursor(const UObject *WorldContextObject)
마우스 커서를 숨기고 게임 전용 입력 모드로 설정합니다.
static class ASpeakStageActor * GetSpeakStageActor(const UObject *WorldContextObject)
static class ALingoGameState * GetLingoGameState(const UObject *WorldContextObject)
팝업 관리자
static const int32 Toast
Definition Onepiece.h:10
static T * LoadAsset(const TCHAR *Path)
TArray< uint8 > audio_base64
Speaking Questions 응답 구조체입니다.
SpeakQuest 오디오 질문 데이터 구조체입니다.
FString GetQuestionMessage() const
FString kor
한국어 질문