Camera Shake
void ASeSAC_NetworkCharacter::OnRep_HP()
{
// UI에 할당할 퍼센트 계산
float percent = hp / MaxHP;
if (MainUI)
{
MainUI->HP = percent;
// 피격 효과 처리
MainUI->PlayDamageAnimation();
// 카메라 쉐이크
if (DamageCameraShake)
{
auto pc = Cast<APlayerController>(Controller);
pc->ClientStartCameraShake(DamageCameraShake);
}
}
else
{
auto hpUI = Cast<UCHealthBar>(HpUIComp->GetWidget());
hpUI->HP = percent;
}
}
사망 후 처리
Die 애니메이션에 DieEnd 노티파이 추가
UCLASS(config=Game)
class ASeSAC_NetworkCharacter : public ACharacter
{
GENERATED_BODY()
public:
/* ----------------- 멀티 플레이어 요소들 -----------------*/
// 사망 후 처리
void DieProcess();
};
void ASeSAC_NetworkCharacter::DieProcess()
{
auto pc = Cast<APlayerController>(Controller);
pc->SetShowMouseCursor(true);
GetFollowCamera()->PostProcessSettings.ColorSaturation = FVector4(0, 0, 0, 1);
}
void ASeSAC_NetworkCharacter::DamageProcess()
{
// 체력을 감소시킨다.
HP--;
//if (HP <= 0)
// IsDead = true;
}
void ASeSAC_NetworkCharacter::OnRep_HP()
{
// 사망처리
if (HP <= 0)
{
IsDead = true;
ReleasePistol(FInputActionValue());
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
GetCharacterMovement()->DisableMovement();
}
// ... 생략
}
void ASeSAC_NetworkCharacter::ReleasePistol(const FInputActionValue& Value)
{
// 총을 잡고 있지 않거나 재장전 중이거나, 로컬 사용자가 아니라면 처리하지 않는다.
if (bHasPistol == false or IsReloading or !IsLocallyControlled()) return;
ServerRPC_ReleasePistol(); // 클라이언트에서 서버로 요청 (클라이언트)
}
사망 UI
멤버변수 BindWidget
UCLASS()
class SESAC_NETWORK_API UCMainUI : public UUserWidget
{
GENERATED_BODY()
public:
// 게임 종료 버튼
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UHorizontalBox* GameOverUI;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UButton* Button_Retry;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UButton* Button_Exit;
};
#include "Components/HorizontalBox.h"
void ASeSAC_NetworkCharacter::DieProcess()
{
auto pc = Cast<APlayerController>(Controller);
pc->SetShowMouseCursor(true);
GetFollowCamera()->PostProcessSettings.ColorSaturation = FVector4(0, 0, 0, 1);
// Die UI 표시
MainUI->GameOverUI->SetVisibility(ESlateVisibility::Visible);
}
리스폰 (Respawn)
UCLASS()
class SESAC_NETWORK_API ACNetPlayerController : public APlayerController
{
GENERATED_BODY()
private:
UPROPERTY()
class ASeSAC_NetworkGameMode* GM;
public:
virtual void BeginPlay() override;
UFUNCTION(Reliable, Server)
void ServerRPC_RespawnPlayer();
};
#include "CNetPlayerController.h"
#include "SeSAC_NetworkGameMode.h"
void ACNetPlayerController::BeginPlay()
{
Super::BeginPlay();
if (HasAuthority())
GM = Cast<ASeSAC_NetworkGameMode>(GetWorld()->GetAuthGameMode());
}
void ACNetPlayerController::ServerRPC_RespawnPlayer()
{
auto player = GetPawn();
UnPossess();
player->Destroy();
GM->RestartPlayer(this);
}
UCLASS()
class SESAC_NETWORK_API UCMainUI : public UUserWidget
{
GENERATED_BODY()
public:
// 리스폰
virtual void NativeConstruct() override;
UFUNCTION()
void OnRetry();
UFUNCTION()
void OnExit();
};
#include "Components/Button.h"
#include "Components/HorizontalBox.h"
#include "CNetPlayerController.h"
void UCMainUI::NativeConstruct()
{
Super::NativeConstruct();
Button_Retry->OnClicked.AddDynamic(this, &UCMainUI::OnRetry);
Button_Exit->OnClicked.AddDynamic(this, &UCMainUI::OnExit);
}
void UCMainUI::OnRetry()
{
// 게임종료 UI 안보이도록 처리
GameOverUI->SetVisibility(ESlateVisibility::Hidden);
if (auto pc = Cast<ACNetPlayerController>(GetOwningPlayer()))
{
// 마우스 커서 안보이도록 처리
pc->SetShowMouseCursor(false);
pc->ServerRPC_RespawnPlayer();
}
}
void UCMainUI::OnExit()
{
}
// SeSAC_Network.h
DECLARE_LOG_CATEGORY_EXTERN(NetLog, Log, All);
#define NETMODE (GetNetMode()==ENetMode::NM_Client ? TEXT("Client") : GetNetMode()==NM_Standalone ? TEXT("Standalone") : TEXT("Server"))
#define CALLINFO (FString(__FUNCTION__) + TEXT("(") + FString::FromInt(__LINE__) + TEXT(")") )
#define PRINTLOG( fmt, ...) UE_LOG(NetLog, Warning, TEXT("[%s]%s : %s"), NETMODE, *CALLINFO, *FString::Printf(fmt, ##__VA_ARGS__))
// SeSAC_Network.cpp
#include "SeSAC_Network.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, SeSAC_Network, "SeSAC_Network" );
DEFINE_LOG_CATEGORY(NetLog);
UCLASS(config=Game)
class ASeSAC_NetworkCharacter : public ACharacter
{
GENERATED_BODY()
public:
virtual void PossessedBy(class AController* NewController) override;
};
void ASeSAC_NetworkCharacter::InitUIWidget()
{
PRINTLOG(TEXT("[%s] Begin"), Controller ? TEXT("Player") : TEXT("Not Player"));
// ... 생략
}
void ASeSAC_NetworkCharacter::PossessedBy(AController* NewController)
{
PRINTLOG(TEXT("Begin"));
Super::PossessedBy(NewController);
PRINTLOG(TEXT("End"));
}
좀 전에 만들어둔 로그를 InitUIWidget() 함수에서 출력
로그에서 볼 수 있듯이 처음에는 정상적으로 호출되지만, 죽은 후 Retry를 하게 되면, 크로스헤어가 정상적으로 생성되지 않음
두번째 로그부터는 UI가 Controller가 없는데 먼저 생성된 후 플레이어가 생성되어 계속 호출되다 크래시를 발생시키게 됨
싱크 수정
void ASeSAC_NetworkCharacter::BeginPlay()
{
Super::BeginPlay();
// InitUIWidget();
// 클라이언트
if (IsLocallyControlled() and !HasAuthority())
{
// UI 위젯 초기화
InitUIWidget();
}
// 총 검색
TArray<AActor*> allactors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass(), allactors);
for (const auto& actor : allactors)
{
if (actor->GetName().Contains("BP_Pistol"))
PistolActors.Add(actor);
}
}
기존의 BeginPlay()에서 호출하던 InitWidget()을 클라이언트인지를 체크하여 호출
void ASeSAC_NetworkCharacter::PossessedBy(AController* NewController)
{
PRINTLOG(TEXT("Begin"));
Super::PossessedBy(NewController);
if (IsLocallyControlled())
InitUIWidget();
PRINTLOG(TEXT("End"));
}
UCLASS()
class SESAC_NETWORK_API ACNetPlayerController : public APlayerController
{
GENERATED_BODY()
public:
// 사용할 위젯클래스
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<class UCMainUI> MainUIWidget;
// MainUIWidget으로부터 만들어진 인스턴스
UPROPERTY()
class UCMainUI* MainUI;
};
UCLASS(config=Game)
class ASeSAC_NetworkCharacter : public ACharacter
{
GENERATED_BODY()
public:
//// 사용할 위젯 클래스
//UPROPERTY(EditDefaultsOnly, Category = "UI")
//TSubclassOf<class UCMainUI> MainUIWidget;
}
이제부터는 플레이어 컨트롤러가 MainUIWidget을 소유하도록 설정하기 위해 플레이어 캐릭터의 MainUIWidget 주석 처리
void ASeSAC_NetworkCharacter::InitUIWidget()
{
PRINTLOG(TEXT("[%s] Begin"), Controller ? TEXT("Player") : TEXT("Not Player"));
// Player가 제어중이 아니라면 처리하지 않는다.
auto pc = Cast<ACNetPlayerController>(Controller);
if (!pc) return;
if (pc->MainUIWidget)
{
if (pc->MainUI == nullptr)
pc->MainUI = Cast<UCMainUI>(CreateWidget(GetWorld(), pc->MainUIWidget));
MainUI = pc->MainUI;
MainUI->AddToViewport();
MainUI->ShowCrossHair(false);
hp = MaxHP;
MainUI->HP = 1;
// 총알 모두 제거
MainUI->RemoveAllAmmo();
BulletCount = MaxBulletCount;
// 총알 추가
for (int i = 0; i < MaxBulletCount; ++i)
MainUI->AddBullet();
if (HpUIComp) // HP UI 숨김 처리
HpUIComp->SetVisibility(false);
}
}
'Development > Unreal Lab' 카테고리의 다른 글
[SeSAC, Unreal Network] 2025. 04. 14 (0) | 2025.04.14 |
---|---|
[SeSAC, Unreal Network] 2025. 04. 11 (0) | 2025.04.11 |
[SeSAC, Unreal Network] 2025. 04. 09 (0) | 2025.04.09 |
[SeSAC, Unreal Network] 2025. 04. 08 (0) | 2025.04.08 |
[SeSAC, Unreal Network] 2025. 04. 07 (0) | 2025.04.07 |