TIL_069 : 숫자야구게임, 스마트포인터

김펭귄·2025년 11월 24일

Today What I Learned (TIL)

목록 보기
69/105

오늘 학습 키워드

  • 승패배 로직

  • 데이터 동기화 및 클라이언트에서의 UI처리 방법

  • ReplicatedUsing

  • 스마트포인터

1. 숫자야구게임(3)

1.1. 승리, 무승부 로직

  • 먼저 3 Strike를 맞춘 플레이어가 승리하게 되고, 만약 모든 플레이어가 3번의 기회 안에 다 맞추지 못 할 경우 무승부가 된다.

  • 승부판정은 GameMode에서 해주며, 승부결과는 GameState에 저장하고 각 클라이언트의 UI가 결과를 출력하는 식으로 구현하려 함

void ACXGameModeBase::JudgeGame(FString PlayerName, int InStrikeCount)
{
	if (InStrikeCount == 3)		// 승리 조건
	{
		ACXGameStateBase* CXGameState = GetGameState<ACXGameStateBase>();
		if (IsValid(CXGameState))
		{
			CXGameState->Winner = PlayerName;
		}
	}
	else
	{
		/* 모두가 다 기회 3번 썼는지 확인하는 코드..  */
        
		ACXGameStateBase* CXGameState = GetGameState<ACXGameStateBase>();
		if (IsValid(CXGameState))
		{
			CXGameState->Winner = "Draw";
		}
	}

	ResetGame();		// 게임 초기화
}

1.2. GameState

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNotification,const FString&,NewNotice);

UCLASS()
class CHATX_API ACXGameStateBase : public AGameStateBase
{
	// ... //
    
	UPROPERTY(BlueprintAssignable)
	FOnNotification OnNotification;

	UPROPERTY(Replicated, ReplicatedUsing = OnRep_Winner)
	FString Winner;

	UFUNCTION()
	void OnRep_Winner() {OnNotification.Broadcast(Winner);}
};
  • 승패 판정까지는 쉬웠지만, 막상 이 승패판정 데이터를 어디에 저장해야할지, 그리고 이 데이터를 누가 접근해서 클라이언트 화면에 띄울 것인지가 고민이 되었다.

  • 먼저 누가 이겼는지, 비긴 것인지에 대한 정보는 모든 플레이어들이 보게 될 정보라 GameState에 문자열로 저장해야겠다 생각하여 위와 같이 GameState에 저장.

  • 클라이언트에서도 동기화가 되어야 하므로, 동기화 작업을 해주었다.

  • 네트워크를 공부할 때, 게임 흐름, 판정 같이 중요한 로직은 서버에서 하고, 입력 처리 및 UI작업은 클라이언트에서 해야한다고 공부했으므로, 이 정보를 UI에 띄우는 것 역시 클라이언트에서 해주기로 하였다.

  • 따라서, Winner변수에 ReplicatedUsing = OnRep_Winner 키워드를 사용

  • 이 키워드 사용하면, 서버에서 클라이언트로 동기화 되었을 때, 클라이언트에서만 해당 함수를 호출함.
    그래서 UI작업을 서버말고 클라이언트에서만 할 수 있음

  • 따라서, OnRep_Winner 함수는 클라이언트에서만 호출되고, 해당 함수에서 FOnNotification 델리게이트의 broadcast도 클라에서만 일어나 UI작업을 처리할 수 있었다

2. 스마트포인터

  • 원시포인터 newdelete로 메모리 해제해야하지만, 스마트포인터 이용하면 알아서 해제해줌

  • 스마트 포인터는 스택에 생성되고, new같은 원시포인터를 가짐. 이 원시포인터는 힙 메모리를 가리킴. 스마트 포인터에는 소멸자가 있어, 스택이 정리되면 지역변수인 스마트포인터는 소멸자호출되면서 자동으로 힙 메모리도 해제함

  • RAII를 따르는데(Resource Acquisition Is Initialization) 메모리할당 될때 초기화도 하는거고, 소멸자 통해 해제도 같이 함

  • 원시포인터와 달리 연산자는 *, ->만 가능. 포인터 연산 안 됨

  • unique_ptr은 데이터 멤버로 원시포인터만 가지고 있음. 그래서 일반 원시포인터를 쓰는것보다 비효율적이지도 않음. 가장 빠른 스마트포인터고, move로 이전가능(복사아님)

  • shared_ptr은 참조횟수를 관리하는 스마트 포인터. 여러 소유자에게 할당하려고 할 경우 사용. 원시포인터는 모든 shared_ptr 소유자가 사라질때까지 삭제되지 않음. 크기는 2개의 포인터. 하나는 개체용이고, 다른 하나는 참조 횟수가 포함된 공유 제어 블록용

  • weak_ptrshared_ptr 인스턴스가 소유하는 개체에 대한 액세스를 제공하지만, 참조 수 계산에 포함되지 않음. 개체를 관찰하는 동시에 해당 개체를 활성 상태로 유지하지 않으려는 경우에 사용하며, 순환참조 해결 가능

profile
반갑습니다

0개의 댓글