KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
AWeightSwitch.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 "AWeightSwitch.h"
5
6#include "ALingoGameState.h"
7#include "APlayerActor.h"
8#include "GameLogging.h"
9#include "luggage.h"
10#include "Popup_Result.h"
11#include "UBroadcastManager.h"
13#include "UPopupManager.h"
14#include "UPopup_MsgBox.h"
15#include "UTweenAnimInstance.h"
16#include "Components/BoxComponent.h"
17
19{
20 PrimaryActorTick.bCanEverTick = true;
21
28 bReplicates = true;
29 bAlwaysRelevant = true;
30
31 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
32
33 SwitchBody = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SwitchBody"));
34 SwitchBody->SetupAttachment(RootComponent);
35 SwitchBody->SetRelativeRotation(FRotator(0.f, 0.f, 90.0f));
36 SwitchBody->SetRelativeLocation(FVector::ZeroVector);
37 SwitchBody->SetRelativeScale3D(FVector(2.f));
38 SwitchBody->SetMobility(EComponentMobility::Movable);
39
40 SwitchCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("SwitchCollision"));
41 SwitchCollision->SetupAttachment(SwitchBody);
42 SwitchCollision->SetBoxExtent(FVector(40.f, 16.f, 50.f));
43 SwitchCollision->SetRelativeLocation(FVector::ZeroVector);
44 SwitchCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
45 SwitchCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
46 SwitchCollision->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
47 SwitchCollision->SetCollisionResponseToChannel(ECC_PhysicsBody, ECR_Overlap);
48}
49
51{
52 Super::BeginPlay();
53
54 this->TriggerDelay = Duration;
55 this->DetectTarget = false;
56 this->ElapsedTime = 0;
57 this->bActivateState = false;
58
59 SwitchCollision->OnComponentBeginOverlap.AddDynamic(this, &AWeightSwitch::OnBeginOverlap);
60 SwitchCollision->OnComponentEndOverlap.AddDynamic(this, &AWeightSwitch::OnEndOverlap);
61
62 UBroadcastManager::Get(GetWorld())->OnWeightSwitch.AddDynamic(this, &AWeightSwitch::OnWeightSwitch);
63
64 InitSwitch();
65}
66
68{
69 if (!SwitchBody)
70 {
71 PRINTLOG( TEXT("SwitchBody is null"));
72 return;
73 }
74
75 // 1. 머티리얼 얻기
76 UMaterialInterface* Material = SwitchBody->GetMaterial(0);
77 if (!Material)
78 {
79 PRINTLOG( TEXT("Material is null"));
80 return;
81 }
82
83 // 2. 다이나믹 머티리얼 인스턴스 생성
84 UMaterialInstanceDynamic* DynMaterial = UMaterialInstanceDynamic::Create(Material, this);
85 if (!DynMaterial)
86 {
87 PRINTLOG( TEXT("Failed to create dynamic material"));
88 return;
89 }
90
91 // 3. 변수에 저장
92 EmissiveMaterial = DynMaterial;
93
94 // 4. 메시에 적용 (이 단계가 누락되어 있었음!)
95 SwitchBody->SetMaterial(0, EmissiveMaterial);
96
97 // 5. 파라미터 설정
98 EmissiveMaterial->SetVectorParameterValue(EmissiveParam, DeactivateColor);
99
100 UAnimInstance* AnimInstance = SwitchBody->GetAnimInstance();
101 if (!AnimInstance)
102 {
103 PRINTLOG( TEXT("AnimInstance is null"));
104 return;
105 }
106
107 // 7. 캐스팅 및 변수 설정
108 UTweenAnimInstance* CubeButtonAnim = Cast<UTweenAnimInstance>(AnimInstance);
109 if (CubeButtonAnim)
110 {
111 AnimBlueprint = CubeButtonAnim;
112 }
113 else
114 {
115 PRINTLOG( TEXT("Failed to cast to UABP_CubeButton"));
116 }
117}
118
120{
121 if (!EmissiveMaterial)
122 {
123 PRINTLOG( TEXT("EmissiveMaterial is null"));
124 return;
125 }
126
127 // 1. 색상 선택 (State가 true면 ActivateColor, false면 DeactivateColor)
128 FLinearColor SelectedColor = State ? ActivateColor : DeactivateColor;
129
130 // 2. 머티리얼 파라미터 설정
131 EmissiveMaterial->SetVectorParameterValue(EmissiveParam, SelectedColor);
132
133 // 3. 애님 블루프린트의 ChangeState 호출
134 if (AnimBlueprint)
135 {
136 AnimBlueprint->ChangeState(State);
137 }
138 else
139 {
140 PRINTLOG( TEXT("AnimBlueprint is null"));
141 }
142
143 OnActivate(State);
144}
145
147{
148 if ( State )
149 {
150 PRINT_STRING(TEXT("CLICK ON"));
151 }
152 else
153 {
154 PRINT_STRING(TEXT("CLICK OFF"));
155 }
156}
157
158void AWeightSwitch::Tick(float DeltaTime)
159{
160 Super::Tick(DeltaTime);
161
162 if ( DetectTarget == false)
163 return;
164
165 ElapsedTime += GetWorld()->GetDeltaSeconds();
166
167 if( ActivateTrigger() )
168 {
169 // 물건이 TriggerDelay 타임 이상 올라가면 발동
170 UBroadcastManager::Get(GetWorld())->SendWeightSwitch(ButtonIndex, true);
171 }
172}
173
175{
176 TriggerDelay -= GetWorld()->GetDeltaSeconds();
177
178 if ( TriggerDelay < 0 )
179 {
181 return true;
182 }
183
184 return false;
185}
186
196 UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
197 UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
198{
199 if (!OtherActor)
200 return;
201
202 // [개선] 서버에서만 정답 판정 수행
203 if (!HasAuthority())
204 {
205 PRINTLOG(TEXT("OnBeginOverlap: Client detected overlap, skipping (server will handle)"));
206 return;
207 }
208
209 // 조건 1 : 플레이어일 경우 오픈
210 if ( IsPlayerDetect )
211 {
212 if (Cast<APlayerActor>(OtherActor))
213 {
214 // 리스트에 추가 (중복 방지)
215 OverlappingActors.AddUnique(OtherActor);
216
217 // 첫 번째 물체가 올라갔을 때만 타이머 시작
218 if (OverlappingActors.Num() == 1)
219 {
220 this->DetectTarget = true;
221 this->ElapsedTime = 0.0;
222 }
223 }
224 }
225
226 // 조건 2 : 정답 캐리어일 경우 오픈
227 if (Aluggage* Luggage = Cast<Aluggage>(OtherActor))
228 {
229 // [개선] GameState nullptr 체크
230 ALingoGameState* GS = Cast<ALingoGameState>(GetWorld()->GetGameState());
231 if (!GS)
232 {
233 PRINTLOG(TEXT("OnBeginOverlap: GameState is null! Cannot validate answer."));
234 return;
235 }
236
237 // [개선] ScenarioData 유효성 체크
238 if (GS)
239 {
240 const int32 CorrectIdx = GS->GetReadScenarioData().correct_answer_index;
241
242 PRINTLOG(TEXT("[WeightSwitch] Server validating: LuggageIdx=%d, CorrectIdx=%d"),
243 Luggage->GetSpawnIdx(), CorrectIdx);
244
245 if (CorrectIdx == Luggage->GetSpawnIdx())
246 {
247 // 리스트에 추가 (중복 방지)
248 OverlappingActors.AddUnique(OtherActor);
249
250 // 첫 번째 물체가 올라갔을 때만 타이머 시작
251 if (OverlappingActors.Num() == 1)
252 {
253 this->DetectTarget = true;
254 this->ElapsedTime = 0.0;
255 }
256
257 if (AnswerFound) return;
258
259 AnswerFound = true;
260
261
262 // [개선] Multicast RPC로 모든 클라이언트에 정답 팝업 표시 (정답 인덱스 전달)
263 FTimerHandle TimerHandle;
264 GetWorldTimerManager().SetTimer(TimerHandle, [this, GS, Luggage]
265 {
266 int32 LuggageIdx = Luggage->GetSpawnIdx();
267 // 모든 클라이언트에 정답 인덱스와 함께 결과 팝업 표시
268 Multicast_ShowResultPopup(LuggageIdx);
269
270 // 오답 캐리어 로그 (서버에서만)
271 TArray<int32> WrongList = GS->WrongReadAnswerList;
272 if (WrongList.Num() == 0)
273 return;
274
275 PRINTLOG( TEXT("[AWeightSwitch] Wrong luggage :"));
276 for (auto Wrong : WrongList)
277 {
278 PRINTLOG( TEXT("%d, "), Wrong);
279 }
280 }, 0.5f, false);
281 }
282 else
283 {
284 // [개선] Multicast RPC로 모든 클라이언트에 오답 팝업 표시
285 FTimerHandle TimerHandle;
286 GetWorldTimerManager().SetTimer(TimerHandle, [this, GS, Luggage]
287 {
288 FString LuggageColor = Luggage->GetColor();
289 FString LuggagePattern = Luggage->GetPattern();
290 int32 LuggageIdx = Luggage->GetSpawnIdx();
291
292 // 모든 클라이언트에 오답 메시지 표시
293 Multicast_ShowWrongPopup(LuggageColor, LuggagePattern);
294
295 // 오답 목록에 인덱스 추가 (서버에서만)
296 GS->AddWrongReadAnswer(LuggageIdx);
297
298 // 큐브 소거 (서버에서만, 자동 복제됨)
299 Luggage->Destroy();
300 }, 0.5f, false);
301 }
302
303 }
304 }
305}
306
312 UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
313 UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
314{
315 if (!OtherActor)
316 return;
317
318 // [개선] 서버에서만 실행
319 if (!HasAuthority())
320 return;
321
322 // 리스트에서 제거
323 OverlappingActors.Remove(OtherActor);
324
325 // 모든 물체가 내려갔을 때만 비활성화
326 if (OverlappingActors.Num() == 0)
327 {
328 this->DetectTarget = false;
329 this->ElapsedTime = 0.0;
330
331 // 물건이 모두 떨어지면 해제
332 UBroadcastManager::Get(GetWorld())->SendWeightSwitch(ButtonIndex, false);
333 }
334}
335
336
337void AWeightSwitch::OnWeightSwitch(int InButtonIndex, bool InActive)
338{
339 if ( InButtonIndex != ButtonIndex)
340 return;
341
342 if (this->bActivateState == InActive)
343 return;
344
345 this->bActivateState = InActive;
346
348}
349
355void AWeightSwitch::Multicast_ShowResultPopup_Implementation(int32 CorrectAnswerIndex)
356{
357 // 모든 클라이언트에서 로컬 GameState에 정답 인덱스 추가
358 if (ALingoGameState* GS = Cast<ALingoGameState>(GetWorld()->GetGameState()))
359 {
360 GS->AddWrongReadAnswer(CorrectAnswerIndex);
361
362 PRINTLOG(TEXT("[Multicast_ShowResultPopup] Added correct answer index %d to local GameState"), CorrectAnswerIndex);
363 }
364
365 // 팝업 표시
366 if (auto Popup = UPopupManager::ShowPopupAs<UPopup_Result>(GetWorld(), EPopupType::Result))
367 {
368 Popup->InitPopup(EQuestType::Read);
369 }
370
371 // // 모든 클라이언트(호스트 포함)에서 정답 팝업 표시
372 // if (UPopupManager* PopupMgr = UPopupManager::Get(GetWorld()))
373 // {
374 // PopupMgr->ShowResult();
375 // PRINTLOG(TEXT("[WeightSwitch] Showing result popup on %s"),
376 // HasAuthority() ? TEXT("Server") : TEXT("Client"));
377 // }
378}
379
387void AWeightSwitch::Multicast_ShowWrongPopup_Implementation(const FString& LuggageColor, const FString& LuggagePattern)
388{
389 // 모든 클라이언트(호스트 포함)에서 오답 메시지 표시
390 if (UPopupManager* PopupMgr = UPopupManager::Get(GetWorld()))
391 {
392 // FString Title = TEXT("Wrong Answer!");
393 FString Message = FString::Printf(TEXT("Wrong Answer\nThis is not the correct Answer.\n\nColor: %s\nPattern: %s"),
394 *LuggageColor, *LuggagePattern);
395
396 if (auto DM = UBroadcastManager::Get(this))
397 DM->SendTutorMessage(FText::FromString(Message));
398
399 // PopupMgr->ShowMsgBox(Title, Message, EMsgBoxType::OK,
400 // FOnMsgBoxOkDelegate::CreateLambda([]() {
401 // // 확인 버튼 클릭 시 아무 작업도 하지 않음 (팝업만 닫힘)
402 // }));
403 //
404 // PRINTLOG(TEXT("[WeightSwitch] Showing wrong popup on %s (Color: %s, Pattern: %s)"),
405 // HasAuthority() ? TEXT("Server") : TEXT("Client"), *LuggageColor, *LuggagePattern);
406 }
407}
Declares the player-controlled character actor.
YiSan 전반에서 사용하는 공용 인터페이스를 선언합니다.
#define PRINTLOG(fmt,...)
Definition GameLogging.h:30
#define PRINT_STRING(fmt,...)
Definition GameLogging.h:45
KLingo API 요청을 담당하는 서브시스템을 선언합니다.
UTweenAnimInstance 클래스를 선언합니다.
TArray< int32 > WrongReadAnswerList
FORCEINLINE const FResponseReadScenario & GetReadScenarioData() const
void AddWrongReadAnswer(int32 Value)
TObjectPtr< class UMaterialInstanceDynamic > EmissiveMaterial
virtual void Tick(float DeltaTime) override
void OnWeightSwitch(int InButtonIndex, bool InActive)
TArray< class AActor * > OverlappingActors
void SetActivate(bool State)
void OnEndOverlap(UPrimitiveComponent *OverlappedComponent, AActor *OtherActor, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex)
WeightSwitch Overlap 종료 처리
virtual void OnActivate_Implementation(const bool State)
void OnActivate(const bool State)
void Multicast_ShowResultPopup(int32 CorrectAnswerIndex)
[Multicast RPC] 모든 클라이언트에 정답 결과 팝업 표시
TObjectPtr< class USkeletalMeshComponent > SwitchBody
void Multicast_ShowWrongPopup(const FString &LuggageColor, const FString &LuggagePattern)
[Multicast RPC] 모든 클라이언트에 오답 메시지 표시
TObjectPtr< class UBoxComponent > SwitchCollision
TObjectPtr< class UTweenAnimInstance > AnimBlueprint
FLinearColor DeactivateColor
FLinearColor ActivateColor
void OnBeginOverlap(UPrimitiveComponent *OverlappedComponent, AActor *OtherActor, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
WeightSwitch Overlap 시작 처리
virtual void BeginPlay() override
상호작용 가능한 수하물 액터
Definition luggage.h:15
팝업 관리자
블루프린트에서 사용할 수 있는 간단한 트윈 애님 인스턴스.