어댑터 패턴, 언리얼 네트워크

김영웅·2025년 3월 13일

어댑터(Adapter) 패턴

호환되지 않는 인터페이스를 가진 객체를 클라이언트가 원하는 인터페이스에 맞춰주는 역할을 하는 구조 디자인 패턴이다.

이는 기존에 있던 것에 맞춰 개발을 해놨는데, 어떠한 이유로 기존에 있던 걸 다른 것으로 교환했을 때 혹은 기존의 것이 바뀔 가능성이 있다면 그 교환된 것에 따라 코드를 다시 짜는건 비효율적일 수 있다.

그럴 때 기존 클래스와 같은 함수를 호출하고, 실제로는 내부에서 바뀐 로직을 처리해주는 일종의 wrapper마냥 작동하게 만들어주는 것이 어댑터 패턴이다.

class IDuck
{
public:
	virtual void Quack() = 0;
	virtual void Fly() = 0;
};

class ITurkey
{
public:
	virtual void Gobble() = 0;
	virtual void FlyShortDistance() = 0;
};

class MallardDuck : public IDuck
{
public:
	void Quack() override
	{
		cout << "꽉" << endl;
	}

	void Fly() override
	{
		cout << "날아용~" << endl;
	}
};

class WildTurkey : public ITurkey
{
public:
	void Gobble() override
	{
		cout << "gobble gobble" << endl;
	}

	void FlyShortDistance() override
	{
		cout << "짧게 날아용..." << endl;
	}
};

class TurkeyAdapter : public IDuck
{
public:
	ITurkey* turkey;

	TurkeyAdapter(ITurkey* turkey)
	{
		this->turkey = turkey;
	}

	void Quack() override
	{
		// Duck의 Quack()을 Turkey의 Gobble()로 변환
		turkey->Gobble();
	}

	void Fly() override
	{
		// 오리는 멀리 날 수 있지만, 칠면조는 짧은 거리만 날 수 있으므로
		// 여러 번 호출해 보정하는 방식으로 처리함
		for (int i = 0; i < 5; i++)
		{
			turkey->FlyShortDistance();
		}
	}
};


int main()
{
	IDuck* duck = new MallardDuck();
	duck->Quack(); 
	duck->Fly();   

	//ITurkey는 IDuck과 호환되지 않음
	ITurkey* turkey = new WildTurkey();

	//칠면조를 오리처럼 사용하고 싶다면, TurkeyAdapter를 이용
	IDuck* turkeyAdapter = new TurkeyAdapter(turkey);
	turkeyAdapter->Quack(); // 내부적으로 turkey.Gobble() 호출
	turkeyAdapter->Fly();   // 내부적으로 여러 번 FlyShortDistance() 호출
}

언리얼 네트워크

Dedicated Server

Dedicated Server는 전용 서버라는 뜻으로 서버의 로직만 추려서 빌드 된 버전을 말한다.

  • 별도 빌드 : 필요
  • 클라이언트 기능 포함 : 안 함
  • 보안 : 좋음
  • 용도 : 독립 서버를 갖춘 게임 환경
  • 개발 난이도 : 상대적으로 깔끔

Listen Server

Listen Server는 클라이언트 중 하나가 서버의 기능을 겸직하고 있는 형태다.

  • 별도 빌드 : 불필요
  • 클라이언트 기능 포함 : 네
  • 보안 : 해킹에 취약
  • 용도 : LAN 환경에서 근거리 그룹
  • 개발 난이도 : 클라이언트와 서버의 기능이 합쳐져 있으므로 권한 체크 필수

간단한 채팅 프로그램으로 네트워크를 테스트하는 도중에 생긴 문제이다.

언리얼에서 멀티 플레이를 실험할 시 새 에더터 창(PIE)에서 실행하면 엔진에서 실행되는 걸로 아는데 독립형 게임으로 실행할 시 그나마 실제 빌드한 게임과 유사하게 실행되는 것으로 안다.

허나 보던 강의대로 따라하며 네트워크를 익히는 도중 PIE에선 메세지가 전달되는데 독립형 게임에선 전달이 되지 않는 문제가 있었다.

간단한 채팅을 전달하는 것인데 문제가 생겨 찾아보니 결국 HasAuthority() 함수의 문제였다.
유니티의 Mirror(예전 UNet)에선 Authority가 해당 오브젝트의 주인인 클라이언트이기에 문제점을 잘 찾지 못했는데, 언리얼에선 Authority가 기본적으론 서버에게 있다고한다.
GameMode로 자동으로 생성된 PlayerController의 Authority가 서버에게 있어 HasAuthority()를 기준으로 메세지를 출력하니 클라이언트에선 뜨지 않고 서버에서만 뜨는 문제였다.

해당 Controller가 자신 클라이언트가 주인인지 알기 위해서는 IsLocalController()를 사용하면 된다.

언리얼에선 Authority가 대부분 서버에 있고 클라는 그 액터를 복사하는 방식이라고 한다.


질문 & 답변

튜터님께 질문 후 답변을 정리했다.


Q. 멀티 환경에서 GameMode로 인한 PlayerController 등의 자동 생성 액터의 생성 타이밍이 어떻게 되나요?

A. 클라는 클라대로 생성되고 서버는 서버대로 생성하는데, 생성 후 서버에서 Replication 활성화 된 액터는 네트워크 ID가 같다면 Link 상태가 되고 서버가 Authority를 가지게 된다.

연결되는 타이밍은 자신이 알기론 Beginplay가 불러지고 그 후 링크될거다.
BeginPlay는 멀티 환경으로 가면 여러번 호출될 수도 있는걸로 안다.

실행 순서는 정해져있지 않으니 염두해놓고 짜야한다.
클라쪽에선 서버쪽이 먼저인지 클라쪽이 먼저인지는 확정이 아니다.

Network Connection이 완벽하게 되면 어떤 함수 실행되는데 그 함수에서 초기화를 연결에 관련된 작업을 실행하는 등의 방법이 있다.
First Replication을 확보할 수 있는걸로 안다.
그 때가 어떻게보면 진짜 BeginPlay인거다.

profile
게임 프로그래머

0개의 댓글