언리얼 멀티플레이 FPS C++
언리얼 멀티플레이 FPS C++
멀티플레이 게임에 대해 공부하기 위해 만든 프로젝트입니다.
멀티플레이 FPS를 개발하면서 Steam이나 Lan매칭을 위한 멀티플레이 플러그인을 만들어서 사용했습니다.
처음에는 그냥 멀티플레이 FPS를 C++로 만들면서 공부나 해볼까 하면서 진행한 프로젝트였는데 이 프로젝트를 진행하면서 생각보다 멀티플레이 게임을 만들때 생각해야되는 것이 많구나 하는 것을 느낀 프로젝트였습니다.
프로젝트.
개발 언어 : c++ & 블루프린트(약간)
개발 엔진 : Unreal5.4
개발 환경(실행 환경) : 윈도우 11.
멀티플레이 FPS 코드(멀티플레이 플러그인 포함).
https://github.com/ykarr/MultiFPS
기능.
1. 멀티플레이 플러그인.
1.1 멀티플레이 메인화면.
1.2 멀티플레이 호스트 메뉴.
멀티플레이 가능 인원 설정, Lan연결인지 Steam연결인지 설정해서 Session생성했습니다.
1.3 멀티플레이 세션 찾기 메뉴.
LAN이나 Online(Steam) 선택해서 세션을 찾고, 10초동안 해당 세션에 들어갈 수 있도록 만들었습니다.
2. 캐릭터.
2.1 팀 선택.
세션에 입장하면 팀을 선택할 수 있습니다.
Select Team Widget(Client)을 통해 팀을 선택하면 GameMode(Server)에서 각 팀의 정해진 스폰 위치중 랜덤한 위치에 정해진 팀 캐릭터를 스폰시키게 됩니다.
2.2 1인칭 캐릭터.
처음 캐릭터를 만들 때 고민을 좀 했습니다.
상대방에게 몸은 모두 보이고, 그림자도 모두 보여야 하는데 1인칭 게임이다보니 머리가 Player에게 보이면 안된다고 생각했습니다. 그래서 이것을 해결하기 위해 고민을 좀 했습니다.
결국 좋은 방법이 생각나지 않아서 Mesh를 두개 사용해서 해결했습니다.
1개는 SkeletonMesh->bOwnerNoSee = true; 를 설정해서 Player에게 보이지 않고 상대방에게만 보이지 않도록 설정했습니다. 나머지 한개는 GetMesh()->bOnlyOwnerSee = true;로 설정해서 플레이어에게만 보이도록 설정하고 Mesh의 Head부분만 숨겼습니다. 이렇게 해서 그림자도 bOwnerNoSee 설정을 해놓은 Mesh만 보이도록 설정해서 겉으로 보기에는 다 정상적으로 나오게 만들었습니다.
2.3 상태 관리.
Enum을 이용해 상태관리.
걷는중인지, 뛰는중인지, 움크리고 있는지 이부분을 Enum과 Tarray를 이용해 스택형으로 관리했습니다.
Ex) 뛰다가 움크리면 움크리기가 실행되고, 움크리기를 그만하면 다시 키를 누르지 않아도 뛰고있게됩니다.
UnArm, Pistol, Rifle 이렇게 3가지 경우가 있는데 이 경우를 Enum을 이용해 관리했습니다.
이 Enum을 무기 장착 Soket설정 등등에 사용했습니다.
2.4 Attribute
DataTable을 활용해 캐릭터의 Status를 설정했습니다.
2.5 Aim offset
아래의 함수를 사용해 AimDirection을 구하고 AimDirection값으로 Aim Offset을 설정했습니다.
void ABattleCharacterBase::CalculateAimDirection()
{
FRotator Rot = UKismetMathLibrary::NormalizedDeltaRotator(GetControlRotation(), GetActorRotation());
AimDirection = Rot.Pitch;
}
캐릭터의 컨트롤러 기준 회전값과 캐릭터의 월드 기준 회전값 간의 차이를 계산하고, 이 값을 -180 ~ 180범위로 정규화해서 Rot 변수에 저장합니다.
만약 Rot.pitch의 값이 음수라면 해당 플레이어의 Aim이 아래를 향하고 있는 것이고, 양수라면 하늘을 향하는 것입니다.
2.6 아이템 장착.
무기 아이템(Collision)에 Player가 접근했을 때 무장상태가 아닌경우 손에 무기 아이템을 장착하고 손이나 등에 무기가 있는경우 무장되지 않은 무기를 소켓에 장착합니다.
아이템을 장착하면서 UI 업데이트가 진행됩니다.
2.7 총 장전.
장전 애니메이션은 현재 장착중인 무기 아이템에서 가져와서 실행했습니다.
장전 애니메이션은 일시적인 행위라서 나중에 들어올 플레이어들에게 영향을 끼치지 않아도 되므로, 굳이 RepNotify하지 않아도 됩니다. 이 부분은 따로 아래에 설명을 작성했습니다.
2.8 총 쏘기.
클라이언트에서 총을 쏘는 키를 누르면 서버에서 Shoot코드를 실행하고, 총 사운드와 이펙트를 Multicast로 했습니다.
Linetrace를 활용해 Linetrace의 위치가 Head bone인경우 Head Shoot 그 외의 나머지인경우 BodyShoot으로 설정해 받는 데미지가 다르도록 설정했습니다.
탄약 수나 사격 조건은 서버에서만 확인할 수 있도록 만들었고, 총을 쏠 때 사운드나 이펙트를 Multicast로 호출해서 모든 클라이언트가 동일한 사운드와 이펙트를 실행하도록 만들었습니다.
즉, 클라이언트에서는 서버 RPC 호출만 담당했습니다.
3. 아이템.
3.1 데이터 테이블.
아이템에 대한 정보는 데이터 테이블을 사용해 관리했습니다.
아이템 이름, Type, Thumbnail아이콘, 해당 무기의 사용 애니메이션, 사운드, 이펙트, 데미지 등을 저장해 사용했습니다.
3.2 아이템 장착(상호작용)
아이템을 장착하는 상호작용은 Collision으로 처리했습니다.
Begin overlap함수등을 만들어 특정 Collision에 Player Tag를 가지고 있는 Actor가 접근하면 장착하거나 하지않거나를 수행하는 방식으로 코드를 작성했습니다.
아이템을 버리는 경우 특정 Actor가 Collision 영역에 들어왔을때만 호출되므로, 버리는게 모두 완료된 이후 begin overlap이 동작하도록 bool함수를 설정해서 들고있다가 떨어뜨린 무기라면 Sphere Collision 범위 밖으로 나갔다 와야 다시 상호작용 할 수 있도록 설정했습니다.
알게된점.
멀티플레이 게임에서의 서버의 역할.
RepNotify와 Multicast의 차이와 사용이유.
Multicast.
기능: 서버에서 실행된 함수를 모든 클라이언트에게 브로드캐스트.
제한: Multicast는 실행 시점에 연결된 클라이언트만 영향을 받음.
ex) 플레이어가 무기를 장착하는 애니메이션을 실행하기 위해 Multicast를 사용하면 이후 서버에 접속한 클라이언트는 이 애니메이션을 볼 수 없음.
적합: 일시적 효과
ex) 폭발, 파티클 등의 일시적 효과.
RepNotify
기능: 변수가 변경될 때 해당 변경을 클라이언트에 알리고, 추가적으로 서버와 클라이언트에서 이를 처리할 기회 제공.
특징: 서버에서 변수가 변경되면 클라이언트로 자동 복제.
변수가 변경될 때에만 클라이언트의 OnRep_함수가 호출됨.
지속적으로 유지되어야하는 정보에 적합.( 게임에 전송해야 할 시각적 변경 사항이 있는 경우. )
모든 클라이언트가 항상 최신 상태를 유지하도록 보장.
새로운 클라이언트가 서버에 연결되더라도 현재 상태를 동기화 받을 수 있음.
--------------------------------------------------------
* NONE : 기본값이며 변화를 안 알린다
* Replicated : 다른 머신 모두가 그 변화를 알 수 있도록한다
-> 설정이 되어 있으면, 변화가 다른 머신에 반드시 전달 되다는 보장이 있다
* 엄청난 양의 리플리케이션을 하는 경우에는 네트워크 지연(랙)이 발생할수 있다. 빠르긴 하지만 즉시 작업은 아니다
* RepNotify : Replicated키워드의 역활을 그대로 한다. 여기에 추가로 변수가 변할 떄, 누군가가 변경을 하면, 이에 대한 반응을 할 수 있는 기회를 준다. 함수를 호출하여 작업을 할 수 있는 기회를 얻는다
*주의점
- Replication관련 옵션에서 Replicated와 RepNotify는 차이가 있는데.
Replicated: 계속 감지하고
RepNotify: 변화가 있을때에만 감지한다.
- C++에서는 RepNotify는 수동으로 호출해줘야 하지만 블루 프린트에서는 편의상 자동으로 클라이언트와 서버에서 호출한다. 만약 한번만 실행이 되어진다면 그것은 함수수호출 리플리케이션이 적절하다.(폭발 같은 일시적인것)
하지만 계속 유지되어 지는것은 변수 리플리케이션을 사용하는것이 좋다.
아쉬운 점.
혼자서 처음 만들어보는 멀티플레이 게임이다보니 이게 왜 버그가 났는지에 대해 단시간에 찾기가 쉽지 않았고, Replicated 문제가 발생하면 Delay로 해결해서 이 부분에서 문제가 생기는 경우도 많았습니다.
처음에 가벼운 마음으로 시작한 프로젝트라서 평소와 다르게 한개의 클래스에 과도하게 많은 기능을 넣어 수정하기가 힘든 부분이 있었습니다.
평소 Standalon프로젝트를 많이 개발하면서 만든 기능들은 다른 방식으로 구현해보고 싶어서 구현한 부분이 몇가지 있는데 만들고 보니 이 부분들이 좀 아쉬웠습니다.
느낀점.
블루프린트로 만들어진 멀티플레이 FPS를 참고하며 C++로 개발을 시작했습니다.
진행 화정에서 C++과 블루프린트에서 멀티플레이가 다르게 동작하는 부분이 있다는 것을 알게 되었습니다.
이러한 차이점을 이해하고 해결하기 위해 관련 자료를 찾아보며 멀티플레이에 대한 전반적인 이해를 높일 수 있었습니다.
특히 블루프린트에서 자동으로 처리 되는 부분을 C++에서는 수동으로 구현해야 하는 경우도 있었고, 예상치 못한 버그가 자주 발생해 이를 수정하며 많은 공부가 되었습니다.
또한 멀티플레이 게임 특성상 솔로 게임과는 달리 동기화 문제를 신경 써야 했고, 동일한 기능을 개발하더라도 코드가 더 길어지고 다양한 문제가 발생했습니다.
처음에는 간단한 멀티플레이 FPS를 만들어보자 하는 가벼운 마음으로 시작했지만, 프로젝트를 진행하면서 멀티플레이 환경에서 고려해야 할 요소들이 훨씬 많다는 것을 깨닫게 되는 프로젝트였습니다.
플레이 영상.
https://youtu.be/4WwZydjSaW0?si=drN332raHMK-MLAc