헤더파일

Drone Tracks - 드론 레이싱 게임 본문

포트폴리오 정리

Drone Tracks - 드론 레이싱 게임

헤더파일 2019. 8. 26. 19:21

개발환경

 

  • 언리얼 엔진 4.22
  • C++

게임 소개

 

  • 실제 같은 드론 비행
  • 키보드, 게임 패드, 드론 컨트롤러 지원
  • 스틱으로 움직일 수 있는 가상 마우스
  • 리플레이 시스템
  • 자신의 이전 플레이와 경쟁할 수 있는 고스트 레이싱
  • 자신만의 코스를 만들 수 있는 트랙 에디터
  • 스팀을 이용한 4인 멀티플레이
  • 8종류의 부품을 조합해 수백 가지 드론을 만들 수 있는 Shop, Workbench
  • 컨트롤러, 드론에 대한 세부 설정
  • 드론을 연습할 수 있는 License 모드
  • 4가지 맵, 12개의 기본 코스

01
리플레이 시스템
0123
Shop & Workbench
01
컨트롤러 세부설정 및 키 맵핑
트랙 에디터

https://store.steampowered.com/app/1124120/Drone_Tracks/

- 스팀 상점 페이지 & 게임 플레이 영상

 

Drone Tracks on Steam

Drone Tracks is a Single/Multiplayer Drone Racing Game targeted towards both Gamers with little to no experience in drone flight and drone professional racers.

store.steampowered.com

게임 구조


리플레이 시스템


ReplayGameInstance 클래스 - 언리얼 엔진의 UGameInstance 클래스를 상속받아 리플레이 기능을 사용하였습니다. 블루 플린트에서 사용하기 위해서 함수들을 블루프린트 호출 가능 함수로 만들었고 녹화 시작, 중지, 삭제, 재생, 검색 기능이 있습니다.

UCLASS()
class MRDRONE_API UReplayGameInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:
	UFUNCTION(BlueprintCallable, Category = "Replays")
		void StartRecordingReplayFromBP(FString ReplayName, FString FriendlyName);

	UFUNCTION(BlueprintCallable, Category = "Replays")
		void StopRecordingReplayFromBP();

	UFUNCTION(BlueprintCallable, Category = "Replays")
		void PlayReplayFromBP(FString ReplayName);

	UFUNCTION(BlueprintCallable, Category = "Replays")
		void FindReplays();

	UFUNCTION(BlueprintCallable, Category = "Replays")
		void RenameReplay(const FString &ReplayName, const FString &NewFriendlyReplayName);

	UFUNCTION(BlueprintCallable, Category = "Replays")
		void DeleteReplay(const FString &ReplayName);

	virtual void Init() override;
}

언리얼의 DemoNetDriver에서 현재 리플레이 상태에 대한 정보들을 얻어와서 현재 시간, 일시정지를 구현하였습니다.

bool AReplaySpectatorController::SetCurrentReplayPausedState(bool bDoPause)
{
	AWorldSettings* WorldSettings = GetWorldSettings();

	static const auto CVarAA = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DefaultFeature.AntiAliasing"));

	static const auto CVarMB = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DefaultFeature.MotionBlur"));

	if (bDoPause)
	{
		PreviousAASetting = CVarAA->GetInt();
		PreviousMBSetting = CVarMB->GetInt();

		// Set MotionBlur to OFF, Anti-Aliasing to FXAA
		CVarAA->Set(1);
		CVarMB->Set(0);

		WorldSettings->Pauser = PlayerState;
		return true;
	}
    
	CVarAA->Set(PreviousAASetting);
	CVarMB->Set(PreviousMBSetting);

	WorldSettings->Pauser = NULL;
	return false;
}


int32 AReplaySpectatorController::GetCurrentReplayTotalTimeInSeconds() const
{
	if (GetWorld())
	{
		if (GetWorld()->DemoNetDriver)
		{
			return GetWorld()->DemoNetDriver->DemoTotalTime;
		}
	}

	return 0.f;
  }

리플레이 기능은 일종의 스냅숏입니다. 리플리케이트 된 데이터를 그 순간순간 계속 기록하는 방식입니다. 데이터로는 인풋 값, 속도 값, 위치 및 회전 값 , 충돌 여부 등을 기록하였습니다. 아래 사진에서 노드 위에 흰색 구체 두 개가 있는 것이 리플리케이트 된 데이터입니다. 이 데이터를 이용하여 예전 플레이했던 모습을 재현했습니다.

 

012

속도를 기본으로 동기화하기 때문에 부드러운 움직임은 가져갈 수 있었지만 시간이 지남에 따라 충돌이나 다른 상황들로 인해 재현된 위치가 다를 수 있습니다. 따라서 충돌이나 특수한 상황에서는 위치와 회전 값으로 즉시 동기화 처리하였습니다.

 


드론의 물리적 움직임


드론이 물리적으로 정확한 움직임을 가져가게 하기 위해 PID를 이용한 계산을 하였습니다. 드론에는 4가지 움직임(Pitch, Roll, Yaw, Throttle)과 수평 및 고도 유지 움직임이 있습니다. PID를 이용해서 각 4개의 모터 별로 얼마의 힘이 들어가는지 계산하였습니다. 드론에 표시된 주황색 위치가 힘을 주는 위치입니다. PID 계산방법과 블루프린트에서 구현 모습입니다.

 

012

모터에 PID를 이용해 계산한 힘을 가합니다. 뒤쪽 모터들에 힘을 가하면 앞으로 움직이고 왼쪽 모터들에 힘을 가하면 오른쪽으로 움직입니다. 전문가용 모드가 아닌 기본 모드에서는 드론이 고도 유지를 해야 하기 때문에 현재 고도를 이용하여 고도 유지에 필요한 힘 계산을 합니다.  일반적인 움직임에서는 현재 각도를 이용하여 움직임에 필요한 힘 계산을 합니다.

 

0123

 

드론 레이서들을 위한 PID제어를 끈 Acro모드도 지원하였습니다.

 


드론 네트워크 동기화


character movement 컴포넌트를 이용하면 언리얼에서 동기화와 보간 작업을 해주기 때문에 별다른 코드 없이도 부드러운 움직임을 얻을 수 있습니다. 하지만 드론은 물리로 움직이기 때문에 별도의 동기화 작업이 필요했습니다. 먼저 클라이언트는 자신의 드론을 시뮬레이션합니다. 그 후 현제 선속도와 각속도를 다른 클라이언트들에게 보내서 다른 클라이언트에 보이는 자신의 드론을 동기화했습니다.

 

속도로만 동기화하게 되면 부드러운 움직임을 얻을 수 있지만 네트워크 딜레이로 인해 시간이 지남에 따라 정확성이 떨어집니다. 따라서 특수 상황(충돌이나 죽을 때)이 일어나거나 3초에 한 번씩 위치와 회전 값을 이용한 동기화를 해줬습니다. 약간의 부자연스러움이 있지만 다른 드론 게임들과 비교했을 때 충분히 부드러운 움직임을 얻을 수 있었습니다.

 


데이터 테이블을 이용한 드론 부품 관리


부품에는 8종류가 있고 각 종류별로 수십 개의 부품이 있으므로 따로 관리가 필요했습니다. 각 부품들의 메쉬, 텍스쳐, 무게, 가격 등의 정보를 데이터 테이블로 구성하여 관리하였습니다. 각 부품별 특수한 정보, 예를 들면 추가 메쉬나 출력 같은 정보는 따로 데이터테이블 둬서 관리하였습니다. 데이터 테이블의 인덱스만 알면 되기 때문에 상점에서 산 부품을 쉽게 기억하고 워크벤치를 이용해 부품 조합도 쉽게 할 수 있습니다.

 

0123


키 매핑, 드론 컨트롤러 지원


드론 컨트롤러는 다양한 제조사가 있기 때문에 윈도우에서 입력받을 때 각기 다른 키 번호로 입력이 들어올 수 있습니다. 따라서 키 입력을 언리얼 내장 방식을 사용하지 않고 명령 패턴과 이벤트 디스 패쳐를 이용한 관찰자 패턴을 이용한 방식으로 만들었습니다. 게임 플레이에 필요한 행동들과 UI에 필요한 행동들을 열거형으로 정의하고 사용자가 자신의 컨트롤러에 맞게 키 번호를 행동에 연결시킬 수 있습니다. 키 입력이 있을 때 이벤트를 호출하면 키입력 이벤트와 바인딩된 객체는 어떤 행동이 발생했는지 값은 어떤지 전달받을 수 있습니다.

 

012


스팀 온라인 서브시스템 활용


언리얼의 스팀 온라인 서브시스템을 활용했습니다. 아이피 주소가 없어도 스팀에 접속한 상태라면 멀티플레이가 가능합니다. 세션 만들기, 세션 찾기, 세션 참가를 할 수 있고 방장이 맵과 드론 모드를 정하면 모든 플레이어에게 동기화되어 해당 설정으로 플레이할 수 있습니다. 게임 시작 후에는 Update Session함수를 통해 connection 숫자를 변경하여 더 이상 다른 클라이언트가 접속하지 못하게 하였습니다. 맵 정보가 바뀔 때마다 Update Session함수에 Extra Setting을 통해 정보를 수정하여 Session을 검색했을 때 해당 정보가 나오도록 했습니다.

 

세션 검색 및 상태 표시
방장 맵 선택 및 시작, 플레이어 준비, 드론 선택 및 멀티 동기화
01
세션 정보 업데이트


멀티플레이 동기화 


모든 플레이어가 접속할 때까지 기다리게 하였고 첫 번째 장애물을 처음 통과한 시점부터 시간이 세어지게 하였습니다. 실시간으로 순위를 동기화하였고 닉네임도 스팀으로 플레이한다면 스팀 닉네임이 나오도록 하였습니다. 게임 종료 후에는 10초 카운트 뒤에 모든 플레이어가 게임이 끝나도록 하였고 게임 결과창에 기록, 포인트, 순위를 동기화하였습니다. 호스트가 뒤로 가기를 누르면 다 같이 로비로 가져서 다시 게임을 플레이할 수 있고 클라이언트가 나간다면 세션을 나와 다른 방을 찾을 수 있습니다.

멀티플레이 인게임 모습
종료 대기 및 결과창

steam online subsystem을 사용하려면 Seamless Travel을 사용해야 하기 때문에 약간의 문제점들이 있었습니다. 위젯들이 레벨 이동 시에도 지워지지 않는 문제를 해결하기 위해 현재 사용되고 있는 위젯들의 리스트를 만들어서 레벨 이동 시에 한 번에 지워줬습니다. 컨트롤러가 유지돼서 오기 때문에 일부 beginplay에서는 컨트롤러가 유효하지 않은 경우가 있었습니다. 컨트롤러가 유효할 때까지 대기 후에 컨트롤러에 이벤트들을 바인드 했습니다. 

 

012


C++ 도우미 함수들


기본적인 개발은 블루프린트 위주로 하였지만 파일 입출력, 소리, 정렬 , 컨트롤러 민감도 설정 같은 부분은 c++로 개발하였습니다. FunctionLibrary 클래스를 상속받아 블루프린트 어디에서나 쉽게 사용할 수 있도록 만들었습니다.

 

 

Comments