결과 화면에 승자는 서서 있고 패배자는 엎드린 채로 승자는 3초뒤에 움직일 수 있고 패배자는 저 몽타주를 재생하고 움직일 수 없도록 만들려고 했다.
#include "Character/GoResultTrigger.h"
#include "Components/BoxComponent.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/PlayerController.h"
#include "EngineUtils.h"
#include "GameFramework/Pawn.h"
AGoResultTrigger::AGoResultTrigger()
{
PrimaryActorTick.bCanEverTick = false;
TriggerBox = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
RootComponent = TriggerBox;
TriggerBox->SetBoxExtent(FVector(100.f));
TriggerBox->SetCollisionProfileName(TEXT("Trigger"));
TriggerCount = 0;
RequiredPlayerCount = 4;
bReadyToCount = false;
}
void AGoResultTrigger::BeginPlay()
{
Super::BeginPlay();
TriggerBox->OnComponentBeginOverlap.AddDynamic(this, &AGoResultTrigger::OnOverlapBegin);
TriggerBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
FTimerHandle DelayHandle;
GetWorld()->GetTimerManager().SetTimer(DelayHandle, FTimerDelegate::CreateLambda([this]()
{
int32 Count = 0;
for (TActorIterator<APawn> It(GetWorld()); It; ++It)
{
if (*It && It->IsPlayerControlled())
{
Count++;
}
}
RequiredPlayerCount = Count;
bReadyToCount = true;
// ✅ 이제 트리거 작동 가능하게 다시 켜기
TriggerBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
UE_LOG(LogTemp, Warning, TEXT("플레이어 수 확정: %d"), RequiredPlayerCount);
}), 0.5f, false);
}
void AGoResultTrigger::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
if (!bReadyToCount) return;
if (OtherActor && OtherActor->IsA(APawn::StaticClass()))
{
APawn* OverlappingPawn = Cast<APawn>(OtherActor);
if (OverlappingPawn && !OverlappedPlayers.Contains(OverlappingPawn))
{
OverlappedPlayers.Add(OverlappingPawn);
TriggerCount++;
UE_LOG(LogTemp, Warning, TEXT("트리거 진입: %s (%d/%d)"), *OtherActor->GetName(), TriggerCount, RequiredPlayerCount);
if (HasAuthority() && TriggerCount >= RequiredPlayerCount)
{
UE_LOG(LogTemp, Warning, TEXT("모든 플레이어가 도착했습니다. 결과 씬으로 이동!"));
GetWorld()->ServerTravel(TEXT("/Game/Maps/ResultLevel?listen"));
return;
}
}
}
}
이게 결과창으로 향하는 트리거 코드이고
#include "Network_Structure/ResultGameMode.h"
#include "Network_Structure/BrickInGameState.h"
#include "Character/BrickCharacter.h"
#include "Network_Structure/BrickGamePlayerState.h"
#include "Kismet/GameplayStatics.h"
#include "EngineUtils.h"
#include "Camera/CameraActor.h"
#include "GameFramework/PlayerController.h"
AResultGameMode::AResultGameMode()
{
}
void AResultGameMode::BeginPlay()
{
Super::BeginPlay();
FString CurrentLevel = GetWorld()->GetMapName();
if (!CurrentLevel.Contains(TEXT("ResultLevel"))) return;
FTimerHandle DelayHandle;
GetWorld()->GetTimerManager().SetTimer(DelayHandle, FTimerDelegate::CreateLambda([this]() {
ABrickInGameState* GS = GetGameState<ABrickInGameState>();
if (!GS) return;
EGameTeam WinningTeam = GS->GetWinningTeam();
TArray<AActor*> WinSpots;
TArray<AActor*> LoseSpots;
UGameplayStatics::GetAllActorsWithTag(this, FName("WinSpot"), WinSpots);
UGameplayStatics::GetAllActorsWithTag(this, FName("LoseSpot"), LoseSpots);
TArray<ABrickCharacter*> AllWinners;
TArray<ABrickCharacter*> AllLosers;
for (TActorIterator<ABrickCharacter> It(GetWorld()); It; ++It)
{
ABrickCharacter* Char = *It;
if (!Char) continue;
ABrickGamePlayerState* PS = Cast<ABrickGamePlayerState>(Char->GetPlayerState());
if (!PS) continue;
if (PS->GetTeam() == WinningTeam)
AllWinners.Add(Char);
else
AllLosers.Add(Char);
}
auto SortByPlayerID = [](const ABrickCharacter& A, const ABrickCharacter& B) {
auto PSA = Cast<ABrickGamePlayerState>(A.GetPlayerState());
auto PSB = Cast<ABrickGamePlayerState>(B.GetPlayerState());
return PSA && PSB && PSA->GetBrickPlayerID() < PSB->GetBrickPlayerID();
};
AllWinners.Sort(SortByPlayerID);
AllLosers.Sort(SortByPlayerID);
TArray<ABrickCharacter*> WinningChars;
TArray<ABrickCharacter*> LosingChars;
// 승리팀 배치
for (int32 i = 0; i < AllWinners.Num(); ++i)
{
if (!WinSpots.IsValidIndex(i)) break;
ABrickCharacter* Char = AllWinners[i];
if (!Char) continue;
if (Char->HasAuthority())
{
Char->SetActorLocation(WinSpots[i]->GetActorLocation());
Char->SetActorRotation(WinSpots[i]->GetActorRotation());
Char->MulticastFixMeshRotation(FRotator(0.f, 0.f, 0.f));
}
Char->GetMesh()->SetRelativeRotation(FRotator(0.f, 0.f, 0.f));
Char->AttachCrown();
Char->PlayVictoryMontage();
Char->SetMovementEnabled(false);
WinningChars.Add(Char);
FTimerHandle TempHandle;
GetWorld()->GetTimerManager().SetTimer(TempHandle, FTimerDelegate::CreateWeakLambda(Char, [Char]() {
Char->SetMovementEnabled(true);
}), 3.0f, false);
}
// 패배팀 배치
for (int32 i = 0; i < AllLosers.Num(); ++i)
{
if (!LoseSpots.IsValidIndex(i)) break;
ABrickCharacter* Char = AllLosers[i];
if (!Char || !Char->HasAuthority()) continue;
Char->SetActorLocation(LoseSpots[i]->GetActorLocation());
Char->SetActorRotation(LoseSpots[i]->GetActorRotation());
Char->GetMesh()->SetRelativeRotation(FRotator(0.f, 0.f, 0.f));
Char->PlayDefeatMontage();
Char->SetMovementEnabled(false);
LosingChars.Add(Char);
}
// 로컬 컨트롤러 캐릭터만 회전 보정
for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
{
if (APlayerController* PC = Cast<APlayerController>(It->Get()))
{
ABrickCharacter* MyChar = Cast<ABrickCharacter>(PC->GetPawn());
if (!MyChar) continue;
ABrickGamePlayerState* PS = Cast<ABrickGamePlayerState>(MyChar->GetPlayerState());
if (!PS) continue;
bool bIsWinner = (PS->GetTeam() == WinningTeam);
int32 SpotIndex = bIsWinner ? AllWinners.Find(MyChar) : AllLosers.Find(MyChar);
const TArray<AActor*>& SpotArray = bIsWinner ? WinSpots : LoseSpots;
if (SpotArray.IsValidIndex(SpotIndex))
{
MyChar->MulticastApplyFinalPose(SpotArray[SpotIndex]->GetActorRotation(), FRotator(0.f, 0.f, 0.f));
}
}
}
// 밀기 로직
FTimerHandle PushCheckHandle;
GetWorld()->GetTimerManager().SetTimer(PushCheckHandle, FTimerDelegate::CreateLambda([WinningChars, LosingChars]() {
const float PushDistance = 100.f;
const float PushStrength = 100.f;
for (ABrickCharacter* Winner : WinningChars)
{
if (!Winner) continue;
FVector WinnerLocation = Winner->GetActorLocation();
FVector WinnerForward = Winner->GetActorForwardVector();
for (ABrickCharacter* Loser : LosingChars)
{
if (!Loser) continue;
FVector ToLoser = Loser->GetActorLocation() - WinnerLocation;
float Dist = ToLoser.Size();
if (Dist < PushDistance && FVector::DotProduct(WinnerForward, ToLoser.GetSafeNormal()) > 0.5f)
{
FVector PushVector = WinnerForward * PushStrength * 0.05f;
Loser->AddActorWorldOffset(PushVector, true);
}
}
}
}), 0.05f, true);
// 카메라 전환
ACameraActor* ResultCamera = nullptr;
for (TActorIterator<ACameraActor> CamIt(GetWorld()); CamIt; ++CamIt)
{
if (CamIt->ActorHasTag(FName("ResultCamera")))
{
ResultCamera = *CamIt;
break;
}
}
if (ResultCamera)
{
for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
{
if (APlayerController* PC = It->Get())
{
PC->bAutoManageActiveCameraTarget = false;
PC->SetViewTargetWithBlend(ResultCamera, 1.0f);
}
}
}
}), 0.3f, false);
}
이건 게임 종료 씬에서 사용할 씬이다.
멀티 적용을 위해 플레이어코드에서도 수정이 필요했는데
void ABrickCharacter::PlayVictoryMontage()
{
if (VictoryMontage && GetMesh() && GetMesh()->GetAnimInstance())
{
GetMesh()->GetAnimInstance()->Montage_Play(VictoryMontage);
}
}
void ABrickCharacter::PlayDefeatMontage()
{
if (DefeatMontage && GetMesh() && GetMesh()->GetAnimInstance())
{
UAnimInstance* AnimInst = GetMesh()->GetAnimInstance();
SetMovementEnabled(false);
bCanTurn = false;
if (!AnimInst->Montage_IsPlaying(DefeatMontage))
{
AnimInst->Montage_Play(DefeatMontage, 1.0f);
UE_LOG(LogTemp, Warning, TEXT("▶▶ DefeatMontage 재생 시작"));
}
}
}
void ABrickCharacter::SetMovementEnabled(bool bEnabled)
{
bCanMove = bEnabled;
if (UCharacterMovementComponent* MoveComp = GetCharacterMovement())
{
if (bEnabled)
{
MoveComp->SetMovementMode(MOVE_Walking);
}
else
{
MoveComp->DisableMovement();
}
}
}
bool ABrickCharacter::CanBeMoved() const
{
return bCanMove;
}
void ABrickCharacter::MulticastFixMeshRotation_Implementation(FRotator NewRotation)
{
if (GetMesh())
{
GetMesh()->SetRelativeRotation(NewRotation);
}
}
void ABrickCharacter::MulticastApplyFinalPose_Implementation(FRotator ActorRot, FRotator MeshRot)
{
UE_LOG(LogTemp, Warning, TEXT("MulticastApplyFinalPose CALLED on %s. Rot: %s"), *GetName(), *ActorRot.ToString());
SetActorRotation(ActorRot);
if (GetMesh())
{
GetMesh()->SetRelativeRotation(MeshRot);
}
}
이겼을 때 틀어줄 몽타주와 졌을 때 몽타주를 할당하고 졌을 때 움직이지 못하도록 하는 코드를 추가해주고
멀티를 위해서 코드를 몇개 작성해주었다.