KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
AWheatly.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
3
4#include "AWheatly.h"
5
6#include "ALingoPlayerState.h"
7#include "APlayerActor.h"
8#include "APlayerControl.h"
9#include "ASpeakStageActor.h"
10#include "FComponentHelper.h"
11#include "GameLogging.h"
12#include "UInteractWidget.h"
13#include "ULingoGameHelper.h"
15#include "UBroadcastManager.h"
16#include "GameFramework/PlayerState.h"
17#include "Net/UnrealNetwork.h"
18
19#include "Components/BoxComponent.h"
20#include "Components/StaticMeshComponent.h"
21#include "Components/WidgetComponent.h"
23#include "Kismet/GameplayStatics.h"
24#include "Kismet/KismetMathLibrary.h"
25
26#define WHEATLY_MESH_PATH TEXT("/Game/CustomContents/Platfrom/Assets/Wheatly_Talk/Wheatly_Talk")
27#define WHEATLY_MATERIAL_0 TEXT("/Script/Engine.Material'/Game/CustomContents/Platfrom/Assets/Wheatly_Talk/M_Wheatly_01.M_Wheatly_01'")
28#define WHEATLY_MATERIAL_1 TEXT("/Script/Engine.Material'/Game/CustomContents/Platfrom/Assets/Wheatly_Talk/M_Wheatly_02.M_Wheatly_02'")
29#define WHEATLY_MATERIAL_2 TEXT("/Script/Engine.Material'/Game/CustomContents/Platfrom/Assets/Wheatly_Talk/M_Wheatly_03.M_Wheatly_03'")
30
32{
33 PrimaryActorTick.bCanEverTick = true;
34
35 bReplicates = true;
36 SetReplicateMovement(true);
37
38 //--------------------------------------------------------------
39 // Skeletal Mesh Component 생성
40 //--------------------------------------------------------------
41 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
42
43 MeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MeshComp"));
44 MeshComponent->SetupAttachment(RootComponent);
45 MeshComponent->SetCollisionProfileName(TEXT("BlockAllDynamic"));
46
47 EyeMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EyeMesh"));
48 EyeMesh->SetupAttachment(MeshComponent, TEXT("eyelight_aimjoint"));
49
50 PlayerDetectionZone = CreateDefaultSubobject<UBoxComponent>(TEXT("PlayerDetectionZone"));
51 PlayerDetectionZone->SetupAttachment(RootComponent);
52 PlayerDetectionZone->SetBoxExtent(FVector(1000.f, 1000.f, 1000.f));
53 PlayerDetectionZone->SetCollisionProfileName(TEXT("Trigger"));
54
55 EyeSightComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EyeSightComp"));
56 EyeSightComp->SetupAttachment(RootComponent);
57
58 //--------------------------------------------------------------
59 // 스켈레탈 메시 로드
60 //--------------------------------------------------------------
62
63 static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderMesh(TEXT("/Engine/BasicShapes/Cylinder.Cylinder"));
64 if (CylinderMesh.Succeeded())
65 {
66 UStaticMesh* Mesh = CylinderMesh.Object;
67 EyeSightComp->SetStaticMesh(Mesh);
68
69 if (Mesh)
70 {
71 IndicatorBaseLength = FMath::Max(Mesh->GetBounds().BoxExtent.Z * 2.0f, 1.0f);
72 IndicatorBaseRadius = FMath::Max(Mesh->GetBounds().BoxExtent.X, Mesh->GetBounds().BoxExtent.Y);
73 }
74 }
75 EyeSightComp->SetVisibility(false);
76 EyeSightComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
77
81
82 // InteractableComponent 생성
83 InteractableComp = CreateDefaultSubobject<UInteractableComponent>(TEXT("Interactable"));
84 InteractableComp->DetectionDistance = 500.0f;
85 InteractableComp->InteractionType = EInteractionType::Button;
86 InteractableComp->InteractionPrompt = TEXT("Talk to Activate");
87
88 WidgetComp = CreateDefaultSubobject<UWidgetComponent>(TEXT("WidgetComp"));
89 ConstructorHelpers::FClassFinder<UInteractWidget> WidgetRef(INTERACT_WIDGET_PATH);
90 WidgetComp->SetWidgetClass(WidgetRef.Class);
91 WidgetComp->SetupAttachment(GetRootComponent());
92 WidgetComp->SetWidgetSpace(EWidgetSpace::Screen);
93 WidgetComp->SetDrawSize(FVector2D(2048.0f, 1024.0f));
94
95 // 초기값 설정
96 CurAnimDuration = 0.0f;
98
99}
100
101void AWheatly::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
102{
103 Super::GetLifetimeReplicatedProps(OutLifetimeProps);
104
105 DOREPLIFETIME(AWheatly, ReplicatedEyeColor);
106 DOREPLIFETIME(AWheatly, ReplicatedEyeSightEnd);
107 DOREPLIFETIME(AWheatly, bEyeSightVisible);
108}
109
111{
112 Super::BeginPlay();
113
114 if (MeshComponent)
115 {
116 UMaterialInterface* BaseMaterial = MeshComponent->GetMaterial(2);
117
118 EyeMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, this);
119 MeshComponent->SetMaterial(2, EyeMaterial);
120 }
121
122 if (EyeSightComp)
123 {
124 UMaterialInterface* BaseMaterial = EyeSightComp->GetMaterial(0);
125
126 EyeTraceMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, this);
127 EyeSightComp->SetMaterial(0, EyeTraceMaterial);
128 }
129
131 {
132 InteractableComp->InitWidget(WidgetComp);
133
134 InteractableComp->OnInteractionTriggered.RemoveDynamic(this, &AWheatly::OnInteractionTriggered);
135 InteractableComp->OnInteractionTriggered.AddDynamic(this, &AWheatly::OnInteractionTriggered);
136 InteractableComp->OnOutlineStateChanged.AddDynamic(this, &AWheatly::OnOutlineStateChanged);
137 }
138
139 // SpeakStage 자동 연결 (서버에서만)
140 if (HasAuthority() && !SpeakStageActor)
141 {
142 ASpeakStageActor* FoundStage = Cast<ASpeakStageActor>(
143 UGameplayStatics::GetActorOfClass(GetWorld(), ASpeakStageActor::StaticClass())
144 );
145
146 if (FoundStage)
147 {
148 SetSpeakStageActor(FoundStage);
149 PRINTLOG(TEXT("[AWheatly] SpeakStage auto-connected"));
150 }
151 else
152 {
153 PRINTLOG(TEXT("[AWheatly] Warning: No SpeakStageActor found in world"));
154 }
155 }
156
157 PlayAnimation(EWheatlyAnim::PowerOn);
158}
159
160void AWheatly::Tick(float DeltaSeconds)
161{
162 Super::Tick(DeltaSeconds);
163
164 if (!SpeakStageActor)
165 return;
166
167 // 퀘스트 진행 중일 때
168 if (auto CurrentSpeaker = SpeakStageActor->GetCurrentSpeaker())
169 {
170 if (HasAuthority())
171 {
172 if (auto SpeakerPawn = CurrentSpeaker->GetPawn())
173 {
174 ReplicatedEyeSightEnd = SpeakerPawn->GetActorLocation();
175 bEyeSightVisible = true;
176
178
179 // 스피커를 쳐다보도록 회전
180 FRotator LookAtRotation = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), SpeakerPawn->GetActorLocation());
181 FRotator TargetRotation( LookAtRotation.Pitch, LookAtRotation.Yaw, 0); // Yaw만 사용
182 SetActorRotation(FMath::RInterpTo(GetActorRotation(), TargetRotation, DeltaSeconds, 5.0f));
183 }
184 }
185 }
186 // 퀘스트 진행 중이 아닐 때
187 else
188 {
189 if (HasAuthority())
190 {
191 bEyeSightVisible = false;
192
194 }
195
196 TArray<AActor*> OverlappingActors;
198 PlayerDetectionZone->GetOverlappingActors(OverlappingActors, APlayerActor::StaticClass());
199
200 APawn* NearestPawn = nullptr;
201 double MinDistanceSquared = MAX_dbl;
202
203 for (AActor* OverlappingActor : OverlappingActors)
204 {
205 if (APawn* Pawn = Cast<APawn>(OverlappingActor))
206 {
207 double DistanceSquared = FVector::DistSquared(GetActorLocation(), Pawn->GetActorLocation());
208 if (DistanceSquared < MinDistanceSquared)
209 {
210 MinDistanceSquared = DistanceSquared;
211 NearestPawn = Pawn;
212 }
213 }
214 }
215
216 if(NearestPawn)
217 {
218 // LineTrace로 시야 확인
219 FHitResult HitResult;
220 FVector StartLocation = EyeMesh->GetComponentLocation();
221 FVector EndLocation = NearestPawn->GetActorLocation();
222 FCollisionQueryParams CollisionParams;
223 CollisionParams.AddIgnoredActor(this); // 자기 자신은 무시
224
225 bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECC_Visibility, CollisionParams);
226
227 // 아무것도 맞지 않았거나, 맞은 대상이 목표한 폰일 경우에만 시야가 확보된 것으로 간주
228 if (!bHit || (bHit && HitResult.GetActor() == NearestPawn))
229 {
230 // 가장 가까운 플레이어를 쳐다보도록 회전
231 FRotator LookAtRotation = UKismetMathLibrary::FindLookAtRotation(EyeMesh->GetComponentLocation(), NearestPawn->GetActorLocation());
232 FRotator TargetRotation( LookAtRotation.Pitch, LookAtRotation.Yaw, 0); // Yaw만 사용
233 SetActorRotation(FMath::RInterpTo(GetActorRotation(), TargetRotation, DeltaSeconds, 2.0f));
234 }
235 }
236 }
237}
238
239
240//----------------------------------------------------------//
241// Animation System
242//----------------------------------------------------------//
243
245{
246 SetAnimationType(InAnimType);
247
248 // 서버에서만 Multicast 호출
249 if (HasAuthority())
250 Multicast_PlayAnimation(InAnimType);
251}
252
253void AWheatly::Multicast_PlayAnimation_Implementation(EWheatlyAnim InAnimType)
254{
255 // 애니메이션 타입 설정
256 AnimType = InAnimType;
257
258 if (!MeshComponent)
259 return;
260
261 UAnimSequence* AnimSeq = AnimSequences.FindRef(AnimType);
262 if (!AnimSeq)
263 return;
264
265 // 애니메이션 직접 재생 (반복 없음)
266 MeshComponent->PlayAnimation(AnimSeq, false);
267 CurAnimDuration = AnimSeq->GetPlayLength();
268}
269
271{
272 // 기존 SpeakStage가 있다면 이벤트 바인딩 해제
273 if (SpeakStageActor)
274 SpeakStageActor->OnSpeakerChanged.RemoveDynamic(this, &AWheatly::OnSpeakStageSpeakerChanged);
275
276 SpeakStageActor = InSpeakStageActor;
277
278 // 새로운 SpeakStage에 이벤트 바인딩
279 if (SpeakStageActor)
280 {
282 // 초기 상태 동기화
283 OnSpeakStageSpeakerChanged(SpeakStageActor->GetCurrentSpeaker());
284 }
285}
286
288{
289 if (!HasAuthority() || !Player)
290 return;
291
292 // PlayerState에서 축적된 평가 결과 가져오기
293 ALingoPlayerState* PS = Player->GetPlayerState<ALingoPlayerState>();
294 if (!PS)
295 return;
296
297 if (UBroadcastManager* BroadcastMgr = UBroadcastManager::Get(GetWorld()))
298 BroadcastMgr->SendTutorMessage(FText::FromString("SPEAK COMPLETE"));
299}
300
302{
303 if (!HasAuthority())
304 return;
305
306 // Player가 유효한지 확인
307 if (!Player)
308 {
309 PRINTLOG(TEXT("[AWheatly] SyncSpeakScenarioData: Player is null"));
310 return;
311 }
312
313 // PlayerState에 데이터 저장
314 if (auto PS = Player->GetPlayerState<ALingoPlayerState>())
315 {
316 PS->SpeakScenarioData = Data;
317 PS->OnUpdateSpeakScenarioData();
318
319 PRINTLOG(TEXT("[AWheatly] SyncSpeakScenarioData: Successfully synced scenario data for %s"),
321 }
322 else
323 {
324 PRINTLOG(TEXT("[AWheatly] SyncSpeakScenarioData: Failed to get PlayerState"));
325
326 // 실패 시 SpeakStage를 종료
327 if (SpeakStageActor && SpeakStageActor->GetCurrentSpeaker())
328 SpeakStageActor->EndStage();
329 }
330}
331
332//----------------------------------------------------------//
333// Interaction System
334//----------------------------------------------------------//
335
336void AWheatly::OnInteractionTriggered(AActor* InteractingActor)
337{
338 if (!HasAuthority() || !SpeakStageActor)
339 return;
340
341 APlayerActor* InteractingPlayer = Cast<APlayerActor>(InteractingActor);
342 if (!InteractingPlayer)
343 return;
344
345 auto PC = Cast<APlayerControl>(InteractingPlayer->GetController());
346
347 // 플레이어의 SpeakQuest 완료 여부 확인
348 if (auto PS = InteractingPlayer->GetPlayerState<ALingoPlayerState>())
349 {
350 if (PS->IsSpeakQuestCompleted())
351 {
352 if ( PC )
353 PC->Client_ToastMessage(TEXT("Already Clear SpeakQuest"));
354
355 return;
356 }
357 }
358
359 // SpeakStage의 상태를 직접 확인
360 if (ALingoPlayerState* CurrentSpeaker = SpeakStageActor->GetCurrentSpeaker())
361 {
362 if ( PC )
363 PC->Client_ToastMessage(FString::Printf(TEXT("Current Turn is [%s]"), *ULingoGameHelper::GetPlayerNameFromState(CurrentSpeaker)));
364
365 return;
366 }
367
368 if ( PC )
369 PC->Client_RequestSpeakScenario(this);
370
371 // 마커 표시
372 if (ALingoGameState* GS = GetWorld()->GetGameState<ALingoGameState>())
373 {
374 GS->SetAllCompassVisibility(false);
375 GS->SetCompassVisibilityByTag("WriteQuest", true);
376 }
377}
378
379void AWheatly::OnOutlineStateChanged(bool bShouldShowOutline)
380{
381 if (MeshComponent)
382 {
383 MeshComponent->SetRenderCustomDepth(bShouldShowOutline);
384 }
385}
386
387void AWheatly::OnSpeakStageSpeakerChanged(APlayerState* NewSpeaker)
388{
389 // 눈 색상 변경은 서버에서만
390 if (HasAuthority())
391 {
392 ReplicatedEyeColor = (NewSpeaker != nullptr)
393 ? FLinearColor(1.0f, 1.0f, 0.0f, 1.0f) // Yellow: Busy
394 : FLinearColor(0.0f, 0.5f, 1.0f, 1.0f); // Blue: Available
395
397 }
398
399 // 위젯 표시 제어는 모든 클라이언트에서 실행
400 if (!InteractableComp)
401 return;
402
403 // 로컬 플레이어 확인
404 if ( auto LocalPawn = ULingoGameHelper::GetLocalPawn(GetWorld()) )
405 {
406 auto LocalPlayerState = LocalPawn->GetPlayerState<ALingoPlayerState>();
407
408 // 로컬 플레이어가 현재 발화자인지 확인
409 if (LocalPlayerState && NewSpeaker && LocalPlayerState == NewSpeaker)
410 {
411 // 발화자이면 위젯 숨김
412 InteractableComp->SetWidgetVisibility(false);
413 PRINTLOG(TEXT("[AWheatly] Local player is speaker - hiding widget"));
414 }
415 else
416 {
417 // 발화자가 아니면, DetectionRange 내에 있을 때만 위젯 표시
418 if (InteractableComp->DetectionRange)
419 {
420 if ( IsInRange(LocalPawn))
421 {
422 InteractableComp->SetWidgetVisibility(true);
423 PRINTLOG(TEXT("[AWheatly] Local player not speaker and in range - showing widget"));
424 }
425 }
426 }
427 }
428}
429
430bool AWheatly::IsInRange(const APawn* LocalPawn) const
431{
432 TArray<AActor*> OverlappingActors;
433 InteractableComp->DetectionRange->GetOverlappingActors(OverlappingActors, ACharacter::StaticClass());
434
435 for (AActor* Actor : OverlappingActors)
436 {
437 if (Actor == LocalPawn)
438 return true;
439 }
440 return false;
441}
442
447
452
454{
455 if (!bEyeSightVisible)
456 {
457 EyeSightComp->SetVisibility(false);
458 return;
459 }
460
461 const FVector StartLocation = EyeMesh->GetComponentLocation();
463}
464
466{
467 MarkerType = InMarkerType;
468}
469
470//----------------------------------------------------------//
471// Visual System
472//----------------------------------------------------------//
473
474void AWheatly::ChangeEyeColor(FLinearColor newColor)
475{
476 if (!EyeMaterial)
477 return;
478
479 EyeMaterial->SetVectorParameterValue(TEXT("EyeColor"), newColor);
480 EyeTraceMaterial->SetVectorParameterValue(TEXT("EyeColor"), newColor);
481}
482
483void AWheatly::UpdateEyeSight(const FVector& Start, const FVector& End)
484{
485 FVector Delta = End - Start;
486 float Length = Delta.Size();
487 if (Length <= KINDA_SMALL_NUMBER)
488 {
489 EyeSightComp->SetVisibility(false);
490 return;
491 }
492
493 FVector Midpoint = Start + (Delta * 0.5f);
494 EyeSightComp->SetWorldLocation(Midpoint);
495
496 FVector Direction = Delta / Length;
497 FRotator Rotation = FRotationMatrix::MakeFromZ(Direction).Rotator();
498 EyeSightComp->SetWorldRotation(Rotation);
499
500 const float LengthScale = Length / FMath::Max(IndicatorBaseLength, KINDA_SMALL_NUMBER);
501 const float TargetThickness = 10.f; // Desired thickness
502 const float MeshDiameter = FMath::Max(IndicatorBaseRadius * 2.0f, KINDA_SMALL_NUMBER);
503 const float RadiusScale = (TargetThickness / MeshDiameter)*2.5f;
504
505 EyeSightComp->SetWorldScale3D(FVector(RadiusScale, RadiusScale, LengthScale));
506 EyeSightComp->SetVisibility(true);
507}
#define INTERACT_WIDGET_PATH
Declares the player-controlled character actor.
APlayerControl 선언에 대한 Doxygen 주석을 제공합니다.
#define WHEATLY_MESH_PATH
Definition AWheatly.cpp:26
#define WHEATLY_MATERIAL_1
Definition AWheatly.cpp:28
#define WHEATLY_MATERIAL_0
Definition AWheatly.cpp:27
#define WHEATLY_MATERIAL_2
Definition AWheatly.cpp:29
EWheatlyAnim
Wheatly 애니메이션 타입
Definition AWheatly.h:13
ECompassMarkerType
FComponentHelper 구조체를 선언합니다.
YiSan 전반에서 사용하는 공용 인터페이스를 선언합니다.
#define PRINTLOG(fmt,...)
Definition GameLogging.h:30
KLingo API 요청을 담당하는 서브시스템을 선언합니다.
Main character driven directly by the player.
Speak Stage 시스템
FOnSpeakerChangedDelegate OnSpeakerChanged
현재 발화자가 변경될 때 호출되는 이벤트입니다.
Wheatly NPC 액터
Definition AWheatly.h:31
bool IsInRange(const class APawn *LocalPawn) const
Definition AWheatly.cpp:430
void OnRep_EyeColor()
Definition AWheatly.cpp:443
void ApplyEyeSight()
Definition AWheatly.cpp:453
TObjectPtr< class UWidgetComponent > WidgetComp
위젯 컴포넌트 (상호작용 UI 표시)
Definition AWheatly.h:130
TMap< EWheatlyAnim, TObjectPtr< class UAnimSequence > > AnimSequences
애니메이션 시퀀스 맵
Definition AWheatly.h:141
float CurAnimDuration
현재 재생 중인 애니메이션 길이 (초)
Definition AWheatly.h:161
TObjectPtr< class UInteractableComponent > InteractableComp
상호작용 컴포넌트
Definition AWheatly.h:126
void OnOutlineStateChanged(bool bShouldShowOutline)
Outline 상태 변경 핸들러
Definition AWheatly.cpp:379
virtual void Tick(float DeltaSeconds) override
Definition AWheatly.cpp:160
void UpdateEyeSight(const FVector &Start, const FVector &End)
Definition AWheatly.cpp:483
void Multicast_PlayAnimation(EWheatlyAnim InAnimType)
애니메이션 재생 (멀티캐스트 RPC)
float IndicatorBaseRadius
Definition AWheatly.h:166
void PlayAnimation(EWheatlyAnim InAnimType)
특정 애니메이션 재생 (서버에서 호출)
Definition AWheatly.cpp:244
void CompleteSpeakQuest(class APlayerActor *Player)
SpeakQuest 완료 처리 (서버에서만 호출)
Definition AWheatly.cpp:287
TObjectPtr< class USkeletalMeshComponent > MeshComponent
스켈레탈 메시 컴포넌트
Definition AWheatly.h:110
void ChangeEyeColor(FLinearColor newColor)
눈 색상 변경
Definition AWheatly.cpp:474
void OnInteractionTriggered(class AActor *InteractingActor)
플레이어 상호작용 핸들러
Definition AWheatly.cpp:336
void SetSpeakStageActor(class ASpeakStageActor *InSpeakStageActor)
SpeakStage 설정 (GameMode에서 호출)
Definition AWheatly.cpp:270
TObjectPtr< class UMaterialInstanceDynamic > EyeMaterial
동적 머티리얼 인스턴스 (런타임 색상 변경)
Definition AWheatly.h:134
void SyncSpeakScenarioData(class APlayerActor *Player, const struct FResponseSpeakScenario &Data)
Client에서 받은 시나리오 데이터를 동기화 (Server에서 호출됨)
Definition AWheatly.cpp:301
FLinearColor ReplicatedEyeColor
Definition AWheatly.h:148
TObjectPtr< class UMaterialInstanceDynamic > EyeTraceMaterial
Definition AWheatly.h:137
TObjectPtr< class UStaticMeshComponent > EyeSightComp
상호작용 중인 플레이어 표시기
Definition AWheatly.h:122
TObjectPtr< class UStaticMeshComponent > EyeMesh
상호작용 중인 플레이어 표시기
Definition AWheatly.h:114
virtual void SetCompassMarkerInto(ECompassMarkerType InMarkerType) override
Definition AWheatly.cpp:465
FORCEINLINE void SetAnimationType(EWheatlyAnim InAnimType)
애니메이션 타입 설정
Definition AWheatly.h:48
TObjectPtr< class UBoxComponent > PlayerDetectionZone
플레이어 감지 영역
Definition AWheatly.h:118
bool bEyeSightVisible
Definition AWheatly.h:155
void OnSpeakStageSpeakerChanged(class APlayerState *NewSpeaker)
SpeakStage의 발화자 변경 이벤트 핸들러
Definition AWheatly.cpp:387
void OnRep_EyeSightState()
Definition AWheatly.cpp:448
virtual void BeginPlay() override
Definition AWheatly.cpp:110
virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty > &OutLifetimeProps) const override
Definition AWheatly.cpp:101
float IndicatorBaseLength
Definition AWheatly.h:165
TObjectPtr< class ASpeakStageActor > SpeakStageActor
Definition AWheatly.h:145
FVector ReplicatedEyeSightEnd
Definition AWheatly.h:152
EWheatlyAnim AnimType
현재 애니메이션 타입
Definition AWheatly.h:158
게임 내 전역 이벤트를 중계하는 중앙 이벤트 버스(Event Bus) 서브시스템입니다.
static FString GetPlayerNameFromState(const class ALingoPlayerState *PlayerState)
PlayerState에서 PlayerControl을 통해 사용자 이름 가져오기
static APawn * GetLocalPawn(const UObject *WorldContextObject)
static T * LoadAsset(const TCHAR *Path)
Speak 시나리오 응답 구조체입니다.