
강의 따라 코딩하다 보니 이런 문제가 발생했다.
MatchState의 변경에 따른 알맞은 위젯을 생성하는 로직이 있는데, 디버깅해보니 이걸 '서버가 2번' 호출해서 이런 문제가 발생했다.
그러니까 저기 지금 같은 위젯이 2개 있는 거다.
포인터는 새로운 위젯을 가리켰고, 먼저 생성된 위젯은 초기값인 00:00 상태로 가만히 있는 중이다.
강의 영상에선 서버가 2번 호출하는 게 확실한데, 강사가 제공해주는 Github 링크로 가보니 서버가 1번 더 호출하는 로직이 빠져있다.
빠진 그 로직대로 작성할까 했지만..
오래 고민해보니 그러면 안 될 것 같다.
처음엔 이런 로직이었다.
따라서, 서버와 클라이언트가 게임 시작 시 위젯을 생성하는 로직을 따로 작성해줘야 했다.
이는 게임 시작 전 대기방 위젯도 마찬가지다.
하지만 왜 저런 문제가 발생했는가?
추가된 새로운 로직 때문이다.
가정된 상황은 '플레이어가 중간에 난입한 경우'다.
여기서 PlayerController는 서버도 갖고 있기 때문에, 서버도 OnRep이 호출된다.
그래서, 'GameMode가 MatchState를 직접 알려주는 경우'는 서버와 클라이언트의 위젯 생성 함수가 따로 작성돼야 하고
'PlayerController가 GameMode에 요청하는 경우'는 서버와 클라이언트가 위젯 생성 함수를 공유한다.
그래서 이런 문제가 발생했다.
그럼 어떻게 관리할 수 있을까?
근본적으로 해결하려면 서버가 알려주는 경우와 클라이언트가 요청한 경우를 나눠서 빡빡하게 작성하면 된다.
하지만 이건 위 사진과 같은 오류가 발생할 염려는 당연하게도 남아있고, 고려해야 할 상황이 많아지면 점점 더 골치아파진다.
하지만 위젯 생성 시 null체크를 한다면?
void ABlasterPlayerController::OnMatchStateSet(FName State)
{
// 변경된 MatchState를 적용하고, 상태에 따라 HUD를 새로고침
MatchState = State;
HandleWidgetState();
}
void ABlasterPlayerController::OnRep_MatchState()
{
HandleWidgetState();
}
서버에서 호출을 하든, 클라이언트에서 호출을 하든 그냥 위젯 생성 로직을 다 때려박아버리고
void ABlasterHUD::AddCharacterOverlay()
{
APlayerController* PlayerController = GetOwningPlayerController();
if (PlayerController && CharacterOverlayClass)
{
CharacterOverlay = CreateWidget<UCharacterOverlay>(PlayerController, CharacterOverlayClass);
CharacterOverlay->AddToViewport();
}
}
이랬던 기존 로직에
void ABlasterHUD::AddCharacterOverlay()
{
if (CharacterOverlay)
{
return;
}
APlayerController* PlayerController = GetOwningPlayerController();
if (PlayerController && CharacterOverlayClass)
{
CharacterOverlay = CreateWidget<UCharacterOverlay>(PlayerController, CharacterOverlayClass);
CharacterOverlay->AddToViewport();
}
}
위젯이 생성되어있는 경우 냅다 return 박아버리면 최적화 문제도 크게 발생하지 않는다.

잘 해결됐다!
작년만 해도 null체크를 자꾸 깜빡해서 크래시 내기 일쑤였는데, 이젠 코드 유지보수성과 생산성을 고려하면서 null체크를 하는 수준까지 왔다.
긴 시간은 아니더라도 거의 하루도 빠짐없이 공부하는 내가 자랑스럽다.
오늘 들은 강의는 특히 유익했던 것 같다!