KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
ALuggageHolder.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 "ALuggageHolder.h"
5#include "luggage.h"
6#include "GameLogging.h"
8#include "ALingoGameState.h"
9#include "Popup_Result.h"
10#include "UBroadcastManager.h"
11#include "UPopupManager.h"
12#include "Onepiece/Onepiece.h"
13#include "Components/BoxComponent.h"
14#include "Components/StaticMeshComponent.h"
15#include "Components/SkeletalMeshComponent.h"
16#include "Animation/AnimationAsset.h"
17#include "Components/WidgetComponent.h"
18#include "Kismet/GameplayStatics.h"
19#include "Kismet/KismetMathLibrary.h"
20#include "Net/UnrealNetwork.h"
21
23{
24 PrimaryActorTick.bCanEverTick = true;
25
26 // Replication
27 bReplicates = true;
28
29 // Root component
30 USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("DefaultSceneRoot"));
31 RootComponent = Root;
32
33 // Mesh component
34 MeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MeshComponent"));
35 MeshComponent->SetupAttachment(RootComponent);
36
37 // Box collision component
38 BoxCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxCollision"));
39 BoxCollision->SetupAttachment(MeshComponent);
40 BoxCollision->SetGenerateOverlapEvents(true);
41 BoxCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
42 BoxCollision->SetCollisionResponseToAllChannels(ECR_Overlap);
43
44 // HoldPos component
45 HoldPos = CreateDefaultSubobject<USceneComponent>(TEXT("HoldPos"));
46 HoldPos->SetupAttachment(MeshComponent);
47
48 WidgetGuideComp = CreateDefaultSubobject<UWidgetComponent>(TEXT("WidgetGuideComp"));
49 WidgetGuideComp->SetupAttachment(RootComponent);
50 WidgetGuideComp->SetRelativeLocation(FVector(0.f, 0.f, 180.f));
51 WidgetGuideComp->SetDrawAtDesiredSize(true);
52 ConstructorHelpers::FClassFinder<UUserWidget> widgetGuideClassRef(TEXT("/Game/CustomContents/UI/Widgets/WBP_LuggageSlotWidget.WBP_LuggageSlotWidget_C"));
53 if (widgetGuideClassRef.Succeeded())
54 {
55 WidgetGuideComp->SetWidgetClass(widgetGuideClassRef.Class);
56 }
57
59}
60
62{
63 Super::BeginPlay();
64
65 BoxCollision->OnComponentBeginOverlap.AddDynamic(this, &ALuggageHolder::OnBoxOverlapBegin);
66
67 // 머티리얼 파라미터 초기화 (비활성화 상태)
69}
70
71void ALuggageHolder::Tick(float DeltaTime)
72{
73 Super::Tick(DeltaTime);
74
75 // CurrentLuggage가 유효하고 활성화된 상태라면 회전
77 {
78 FRotator CurrentRotation = CurTarget->GetActorRotation();
79 CurrentRotation.Yaw += RotationSpeed * DeltaTime;
80 CurTarget->SetActorRotation(CurrentRotation);
81 }
82
84}
85
86void ALuggageHolder::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
87{
88 Super::GetLifetimeReplicatedProps(OutLifetimeProps);
89
90 DOREPLIFETIME(ALuggageHolder, bIsActivated);
91 DOREPLIFETIME(ALuggageHolder, CurTarget);
92}
93
95{
96 // bIsActivated가 복제될 때 머티리얼 업데이트
98}
99
101{
102 // CurTarget이 복제될 때 클라이언트에서도 충돌 비활성화
103 if (CurTarget)
104 {
105 if (Aluggage* Luggage = Cast<Aluggage>(CurTarget))
106 {
107 Luggage->SetAllCollision(false);
108 PRINTLOG(TEXT("AHolder::OnRep_CurTarget - Disabled collision for Luggage on client"));
109 }
110 }
111}
112
114 UPrimitiveComponent* OverlappedComponent,
115 AActor* OtherActor,
116 UPrimitiveComponent* OtherComp,
117 int32 OtherBodyIndex,
118 bool bFromSweep,
119 const FHitResult& SweepResult)
120{
121 if (!OtherActor)
122 return;
123
124 if (!HasAuthority())
125 return;
126
127 if ( bIsActivated )
128 return;
129
130 ALingoGameState* GS = Cast<ALingoGameState>(GetWorld()->GetGameState());
131 if (!GS)
132 return;
133
134 // Luggage인지 확인
135 if (auto Luggage = Cast<Aluggage>(OtherActor))
136 {
137 // Hook 중인 Luggage는 무시 (PlayerActor가 들고 있는 상태)
138 if (Luggage->bIsBeingHooked)
139 return;
140
141 const bool bSuccess = CheckLuggage(Luggage, GS->GetReadScenarioData().correct_answer_index );
142
143 // 블루프린트 이벤트 호출
144 OnActivate(bSuccess);
145
146 if (bSuccess)
147 {
148 // 정답인 경우
149 int32 CorrectIdx = Luggage->GetSpawnIdx();
150
151 // 마커 표시
152 GS->SetAllCompassVisibility(false);
153 GS->SetCompassVisibilityByTag("ReadQuestEnd", true);
154
155 FTimerHandle TimerHandle;
156 GetWorldTimerManager().SetTimer(TimerHandle, [this, CorrectIdx]
157 {
158 // 모든 클라이언트에 정답 인덱스와 함께 결과 팝업 표시
159 Multicast_ShowResultPopup(CorrectIdx);
160
161 }, 0.5f, false);
162 }
163 else
164 {
165 // 오답인 경우
166 FTimerHandle TimerHandle;
167 GetWorldTimerManager().SetTimer(TimerHandle, [this, Luggage, GS]
168 {
169 GS->AddWrongReadAnswer(Luggage->GetSpawnIdx());
170
171 // 모든 클라이언트에 오답 메시지 표시
172 Multicast_ShowWrongPopup(Luggage->GetColor(), Luggage->GetPattern());
173
174 // Dissolve 재생
175 GetWorldTimerManager().SetTimer(DestroyTimerHandle, [this, Luggage]
176 {
177 if (Luggage->UpdateDissolve())
178 {
179 GetWorldTimerManager().ClearTimer(DestroyTimerHandle);
180 // 큐브 소거 (서버에서만, 자동 복제됨)
181 Luggage->Destroy();
182 }
183 },GetWorld()->DeltaTimeSeconds, true);
184
185 }, 0.5f, false);
186 }
187 }
188}
189
190bool ALuggageHolder::CheckLuggage(Aluggage* TargetLuggage, int CorrectIndex)
191{
192 if (!TargetLuggage)
193 return false;
194
195 // Luggage의 모든 충돌 비활성화 (pickup, hook 등 모든 상호작용 차단)
196 TargetLuggage->SetAllCollision(false);
197
198 // ReadQuest 정답 인덱스 가져오기
199 if (CorrectIndex == TargetLuggage->GetSpawnIdx())
200 {
201 // Success: Luggage를 HoldPos 위치보다 살짝 위에 배치
202 FVector ActivatedLocation = HoldPos->GetComponentLocation();
203 ActivatedLocation.Z += ActivatedHeightOffset;
204 TargetLuggage->SetActorLocation(ActivatedLocation);
205 TargetLuggage->SetActorRotation(HoldPos->GetComponentRotation());
206
207 // Activate 상태로 전환
208 bIsActivated = true;
209 CurTarget = TargetLuggage;
210
211 // 서버에서도 머티리얼 업데이트 (클라이언트는 OnRep_IsActivated에서 호출됨)
213
214 return true;
215 }
216 else
217 {
218 // Fail: 서버에서 머티리얼 업데이트 (오답)
219 bIsActivated = false;
220 UpdateActivateState(false);
221
222 return false;
223 }
224}
225
227{
228 // 머티리얼 파라미터 설정 (비활성화)
229 if (MeshComponent && MeshComponent->GetNumMaterials() > 0)
230 {
231 UMaterialInstanceDynamic* DynamicMaterial = Cast<UMaterialInstanceDynamic>(MeshComponent->GetMaterial(0));
232 if (!DynamicMaterial)
233 DynamicMaterial = MeshComponent->CreateDynamicMaterialInstance(0);
234
235 if (DynamicMaterial)
236 DynamicMaterial->SetScalarParameterValue(FName("Activate"), State ? 1.0f : 0.0f);
237 }
238}
239
241{
242 // 위젯이 없으면 빌보드화 안 함
243 if (!WidgetGuideComp)
244 return;
245
246 // Visibility 체크 - 보이지 않으면 빌보드화 안 함
247 if (!WidgetGuideComp->IsVisible())
248 return;
249
250 // 카메라 가져오기
251 AActor* Camera = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0);
252 if (!Camera)
253 return;
254
255 // 카메라를 향하도록 회전 계산
256 FRotator Rotation = UKismetMathLibrary::MakeRotFromXZ( -Camera->GetActorForwardVector(), Camera->GetActorUpVector() );
257 Rotation.Pitch = 0;
258
259 // 위젯 회전 설정
260 WidgetGuideComp->SetWorldRotation(Rotation);
261}
262
264{
265 MarkerType = InMarkerType;
266}
267
273void ALuggageHolder::Multicast_ShowResultPopup_Implementation(int32 CorrectAnswerIndex)
274{
275 // 모든 클라이언트에서 로컬 GameState에 정답 인덱스 추가
276 if (ALingoGameState* GS = Cast<ALingoGameState>(GetWorld()->GetGameState()))
277 {
278 GS->AddWrongReadAnswer(CorrectAnswerIndex);
279 }
280
281 // 팝업 표시
282 if (auto Popup = UPopupManager::ShowPopupAs<UPopup_Result>(GetWorld(), EPopupType::Result))
283 {
284 Popup->InitPopup(EQuestType::Read);
285 }
286}
287
295void ALuggageHolder::Multicast_ShowWrongPopup_Implementation(const FString& LuggageColor, const FString& LuggagePattern)
296{
297 // 모든 클라이언트(호스트 포함)에서 오답 메시지 표시
298 FString StyledMessage = FString::Printf(
299 TEXT("WRONG ANSWER\n")
300 TEXT("------------------\n")
301 TEXT("The selected item does not match.\n")
302 TEXT("Color : %s\n")
303 TEXT("Pattern: %s\n")
304 TEXT("------------------\n")
305 TEXT("Please try another luggage."),
306 *LuggageColor, *LuggagePattern
307 );
308
309 if (auto DM = UBroadcastManager::Get(this))
310 DM->SendTutorMessage(FText::FromString(StyledMessage));
311}
네트워크 복제를 위한 전역 브로드캐스트 Actor
ECompassMarkerType
YiSan 전반에서 사용하는 공용 인터페이스를 선언합니다.
#define PRINTLOG(fmt,...)
Definition GameLogging.h:30
void SetCompassVisibilityByTag(FName Tag, bool bVisible)
void SetAllCompassVisibility(bool bVisible)
FORCEINLINE const FResponseReadScenario & GetReadScenarioData() const
void AddWrongReadAnswer(int32 Value)
void BillboardInteractWidget()
상호작용 위젯 빌보드화 (카메라를 향하도록)
virtual void BeginPlay() override
FTimerHandle DestroyTimerHandle
bool CheckLuggage(class Aluggage *TargetLuggage, int CorrectIndex)
float ActivatedHeightOffset
void Multicast_ShowWrongPopup(const FString &LuggageColor, const FString &LuggagePattern)
[Multicast RPC] 모든 클라이언트에 오답 메시지 표시
TObjectPtr< class USceneComponent > HoldPos
void UpdateActivateState(bool State)
virtual void SetCompassMarkerInto(ECompassMarkerType InMarkerType) override
void OnBoxOverlapBegin(UPrimitiveComponent *OverlappedComponent, AActor *OtherActor, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
void Multicast_ShowResultPopup(int32 CorrectAnswerIndex)
[Multicast RPC] 모든 클라이언트에 정답 결과 팝업 표시
virtual void Tick(float DeltaTime) override
void OnActivate(bool bSuccess)
TObjectPtr< class UWidgetComponent > WidgetGuideComp
virtual void GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > &OutLifetimeProps) const override
TObjectPtr< class AActor > CurTarget
TObjectPtr< class USkeletalMeshComponent > MeshComponent
TObjectPtr< class UBoxComponent > BoxCollision
상호작용 가능한 수하물 액터
Definition luggage.h:15
FORCEINLINE int32 GetSpawnIdx()
Definition luggage.h:96
void SetAllCollision(bool bEnable)
모든 컴포넌트의 충돌과 물리를 설정합니다.
Definition luggage.cpp:373