Chapter 12. Networking
12.1 멀티플레이 개론
12.1-1 멀티플레이 설정
- 멀티플레이 학습의 필요성
GameMode, PlayerController, GameState, PlayerState는 상당히 중요한 게임 프레임워크 요소임.
이들에 대해 제대로 이해하려면 멀티플레이를 경험해보아야 함.
- 멀티플레이 방법
플레이 버튼 우측에 점 세개 기호 클릭 > Number of Players에 2를 설정.
Net Modes > Play as Client를 설정하면 데디케이티드 서버 환경.
- Editor Preferences > Play > Multiplayer Options
좀 더 실제와 유사한 환경을 만들기 위한 설정들이 있음.
Launch Seperate Server: 체크하지 않으면 에디터 화면이 클라이자 서버 역할을 겸함.
체크하면 각자 따로 띄워줌.
Play Net Mode: 모드에 따라 리슨 서버 혹은 데디케이티드 서버 등을 지정 가능.
Run Under One Process: 체크하지 않으면 여러 프로세스로 나눠서 각각의 역할을 진행.
체크하면 같은 프로세스 내라서 공유하는 애셋(지형, 캐릭터, ...)이 생기기에
확실한 멀티플레이 환경이 아님. 다만 성능이 안좋으면 플레이가 오래걸림.
- Character Movement Component
두 개의 클라이언트를 띄워서 보면, 다른 클라이언트가 이동하는게 보임.
Character Movement Component가 기본적으로 동기화 시켜주기 때문.
- Player to Player Server

- Listen Server

- Dedicated Server
지금까지는 자동으로 데디케이티드 서버 역할을 하는 프로세스가 켜짐.
물론 수동으로도 킬 수 있음.
server.exe를 실행하면 Open Level 명령어를 실행하면서 listen 명령어도 실행.
socket이 생성해서 다른 PC가 접속 가능하게함.
listen 명령어가 없다면 싱글 플레이가 되는 것.

Level에는 해당 Level이 사용할 GameMode 정보가 있음. 이를통해 GameMode 액터 생성.
중요한 것은 GameMode 액터가 전체 PC에서 딱 하나만(Server PC) 존재한다는 것.
그래서 GameMode는 아주 중요한 액터. 전체 게임 진행을 감독하게 됨.
배그와 같은 게임에서도 GameMode 액터가 딱 하나기 때문에, 바로 내가 승리하는 핵은 없음.

이제 클라 PC가 서버 PC에 접속을 해야함. 그러려면 서버 PC의 IP주소를 알아내야함.
대표적으로 세션 서비스라고 해서 방을 만들면(서버 PC에 Level을 띄우고 IP주소를 중앙 서버의 목록에 추가)
그리고 클라 PC에서 게임 플레이를 요청하면 만들어진 방의 IP 주소(중앙 서버의 목록 중 하나)를 반환해줌.
즉, 세션 서비스의 핵심은 접속할 IP 주소를 적절하게 주고 받게끔 중계 해주는 것. 스팀 OAuth도 마찬가지.
단 클라 PC의 접속 명령어는 Open (아이피주소) 형태. 그럼 해당 PC에 접속 시도.
그럼 서버 PC는 자신이 오픈한 Level을 알려줌. 그걸 받아서 클라 PC도 동일한 Level을 오픈함.
클라 PC에서도 동일한 Level Open이 성공되었다고 알려오면, 서버에서는 Client1 전용 PlayerController를 만듦.
이를 Client1에도 Replicate 시킴. 당연히 패킷으로 해당 정보를 Client1에 주는 것.
Client1의 PlayerController가 빙의 할 PlayerCharacter도 만들어줌. 이것도 Replicate 되는게 기본값임.

Client2도 Open (아이피주소) 명령어를 통해 접속을 시도.
그럼 서버PC에서는 Client2 전용의 PlayerController와 PlayerCharacter를 생성해서 Replicate.
이때 중요한 것은 서버 PC에 Client1의 PlayerController와 Client2의 PlayerController가 차곡차곡 생성됨.
각각 0번, 1번, ... 이런식으로 인덱싱됨. 단, 각 클라이언트에는 다른 클라이언트의 PlayerController 액터가 없음.
따라서 자신의 PC에는 자신의 PlayerController가 무조건 0번임에 주의.
정리하자면 PlayerController 액터는 Server PC와 Owning PC에만 존재함. Other PC에는 존재하지 않음.
PlayerCharacter 액터는 모두 존재함.
12.1-2 멀티플레이와 스폰
- 지뢰 클래스 생성
임시로 BP_LandMine 생성. 큐브 하나 정도.
Simulate Physics 끄기.
- 지뢰 매설 구현 준비
입력 키 생성
- 지뢰 매설 구현
캐릭터 조금 먼 앞쪽으로 BP_LandMine 스폰.
Simulate Physics가 꺼져있어서, 발쪽에 스폰되어야 함.
- 멀티 플레이 테스트
Listen 서버로 실행.
클라1으로 지뢰 매설 시 클라1에서만 보임.
클라2으로 지뢰 매설 시 클라2에서만 보임.
- Actor Replicate
BP_LandMine > Details > Replicates 체크 후 다시 테스트
Listen 서버로 실행.
클라1으로 지뢰 매설 시 클라1, 클라2에서도 보임.
클라2으로 지뢰 매설 시 클라2에서만 보임.
Pawn 클래스의 경우에는 부모에서부터 Replicates 옵션이 기본값으로 체크되어 있음.
Actor 클래스는 기본값으로 체크되지 않음. 그래서 서버에서 Replicate 안해줌.
- Dedicated Server로 테스트
클라1으로 지뢰 매설 시 클라1에서만 보임.
클라2으로 지뢰 매설 시 클라2에서만 보임.
분명 Actor Replicate를 켜줬는데 왜 안될까? Server에서 스폰 시키지 않았기 때문.
키 입력에 의해 호출된 함수는 해당 클라에서만 실행됨.
즉, Replicate는 서버에서 시작되어야만 함. 그래야 다른 클라이언트들로 복제됨.
Client / Server 구조에서는 Client들끼리 절대 연결 되어 있지 않음. 정보 공유 못함.
만약 하고 싶다면 P2P로 외부 서버를 또 만들어야 함.
C/S 구조에서는 서버의 변경 값이 클라에 전달. 클라의 변경 값은 서버나 다른 클라에 보내지지 않음.
서버를 경유해서 보내주는 방법을 취해야함. 그래서 RPC 개념이 생김.
- Remote Procedure Call(RPC)
| Server | OwningClient | NetMulticast | |
| 서버에서 호출한 경우 | 실행 | 서버가 오너 클라로 패킷보냄 오너 클라에서 실행됨. |
서버에서 실행 후 모든 클라에게 패킨 전달. 각각의 클라에서도 실행됨. |
| 액터 오너 클라가 호출한 경우 | 클라가 서버로 패킷을 보내고, 서버에서 실행됨. |
실행 | 무시 |
| 액터 오너 클라가 아닌 경우 | 무시 | 무시 | 무시 |



- 스폰 문제 해결 방법 1번
다짜고자 RPC(Server에서 실행 설정)
지뢰 액터의 오너 클라(키를 눌러서 스폰한 클라)에서 서버로 패킷을 보내서, RPC되게끔 함.
단점은 클라에서 위변조할 수 있음. 패킷을 분석해서 해당 패킷을 서버에 쏘면 무조건 스폰됨을 해커가 파악.
- Validation
Invalidate는 받는 쪽에서 패킷 검사를 하지 않음. 즉, 부하가 덜함.
이동, 이펙트, ... 같은 경우엔 매 틱마다 전송하기에 한 틱 정도 패킷 검사를 하지 않는건 괜찮을 수 있음.
Validate는 받는 쪽에서 패킷 검사를 함. 부하가 비교적 있음.
스폰, 데미지, ... 같은 경우엔 무조건적으로 전송되어야 하기에 Validate로 설정해야 함.
- 스폰 함수 Server RPC의 실행 흐름
1. 특정 키를 누름
2. 오너 클라 캐릭터 클래스의 키 바인드 함수 호출
3. 여러 정보를 Serialize 후 패킷에 담아 서버로 보냄
4. 서버에서는 패킷을 Deserialize 후 오너 클라의 캐릭터 개체에서 Server RPC 함수 호출.
5. 서버에서 BP_LandMine 생성. Actor Replicate가 체크되어 있으므로 다른 클라에 정보 복제됨.
- 물리 동기화
물리까지는 완벽하게 동기화 시켜주지 않음. 계산이 너무나도 많기 때문.
- 어찌되었든 스폰은 서버에서만 이뤄져야 함.
- 네트워크 컬링 디스턴스
BP_LandMine > Details > Net Cull Distance Squared
각 클라의 뷰 타겟 위치와 해당 액터의 위치가 해당 거리 이상 멀어지면 Replicate 되지 않음.
Squared에 주의. 1000000(10m)로 지정 후 지뢰 스폰해보자.
- 컬링 하지 않고 무조건 Replicate
BP_LandMine > Details > Always Relevant 체크.
배그와 같이 100명 이상 전투하는 게임에서 해당 옵션을 체크하는 건 굉장한 부하.
12.1-3 함수가 실행되는 컴퓨터
- Begin(), Tick(), EndPlay() 이벤트 함수의 실행 위치
이런 함수들은 여러 번 호출될 수 있음에 주의 해야함.
또 여러 번 호출되지 않을수도 있음. 심지어 특정 컴퓨터에선 절대 호출되지 않을 수 있음.
이는 충돌 관련 함수(BeginOverlap, ...)들도 마찬가지.
이를 파악하기 위해서는 UKismetSystemLibrary::PrintString()을 애용해야 함. UE_LOG()로는 알 수 없음.
- BP_LandMine의 BeginPlay(), Tick(), EndPlay() 이벤트 함수의 실행 위치
이전 예제에서 RPC를 통해 서버에서 스폰되게끔 했음. 스폰될 때 BeginPlay() 함수가 호출됨.
Actor Replicate 설정되어 있으므로 이는 각 클라에 복제됨.
Tick() 함수 역시 서버, 클라1, 클라2, ... 모두에서 호출됨.
다만 서버의 부하를 줄이기 위해 그 빈도가 낮음. 클라에서 60프레임이라면, 서버에선 20프레임 느낌.
당연히 해당 지뢰가 삭제되면 EndPlay()도 호출됨.
- BeginPlay() 함수 로직이 돌 때, 어떤 PC에서 로직이 실행되는지 알려면?
HasAuthority(): 반환값이 true이면 서버에서 실행 중 false이면 서버가 아닌 PC에서 실행중.
단, Listen 서버의 경우에 true이면 방장 클라일수도 있음.
GetOwner() == GetPlayerController(0): Owning Client에서 실행중. [단, 해당 액터의 Owner 세팅 필수.]
GetOwner() != GetPlayerController(0): Other Client에서 실행중.
- BP_LandMine의 BeginPlay()로 로직 실행 PC 알아내기 실습
- 해당 액터의 Owner 세팅이 제대로 되지 않는 경우
GetOwner()는 사용하지 못함. 그렇기에 GetController() 함수를 사용함.
Pawn의 경우엔 빙의시에 무조건 Owner를 설정해주기 때문에 이를 활용.
GetController()와 GetPlayerController(0)을 비교하면 OwningClient인지 OtherClient인지 구분 가능.
- STPSCharacter 클래스에서 BeginPlay()와 Tick()함수에 로직 실행 PC 분기 지정.
12.1-4 Owning Client and Net Multicast
- 지금까지는 클라가 서버에서 함수 호출되게끔 함.
BP_LandMine 호출 함수를 서버에서 호출되게끔 진행했음.
- 서버가 클라에서 함수 호출되게끔 하려면?
OwningClient와 NetMulticast 키워드 활용.
- 만약 지뢰가 터졌을 때 폭발 이펙트가 나오게끔 하려함.
이를 OwningClient 키워드의 함수에 로직을 작성하면 서버 혹은 오너 클라에서 이 함수를 호출 할 수 있음.
만약 서버에서 호출되었다고 가정해보자.
해당 지뢰 액터의 Owner 값과 같은 플레이어 컨트롤러를 찾음.(플레이어 컨트롤러는 서버에도 모두 존재하기 때문.)
해당 플레이어 컨트롤러의 Owning Client PC에 패킷을 보냄.
그럼 해당 PC에서도 지뢰가 당연히 있을 것(지뢰를 만든 PC니까)이기에 해당 지뢰에서 폭발 이펙트 발생.
만약 오너 클라에서 호출되었다면?
똑같이 해당 지뢰 액터의 Owner 값과 같은 플레이어 컨트롤러를 찾음. 1개라서 바로 찾게됨. 그냥 그대로 실행.
만약 다른 클라에서 호출되었다면?
또 해당 지뢰 액터의 Owner 값과 같은 플레이어 컨트롤러를 찾지만 없음. 실행안됨.
이렇게 OwningClient 키워드의 RPC 함수는 캐릭터가 사망했을 때, 해당 캐릭터를 소유한 PC에 알려서
사망 UI를 띄운다던가 할 때 활용됨. UI는 해당 클라에만 존재하기 때문. 서버/다른 클라에는 없음.
- NetMulticast RPC의 경우 지뢰의 폭발 이펙트
서버에서만 이 함수를 호출 할 수 있음.
그러면 해당 액터가 존재하는 모든 서버, 클라1, 클라2, ... PC에서 폭발 이펙트를 볼 수 있음.
캐릭터의 사망 애니메이션 플레이 같은 경우에는 모든 PC에서 봐야하므로 NetMulticast 키워드를 사용.
- 내 지뢰 폭발 알림 구현
Sphere Component 추가 해서 BeginOverlap 검사.
단, SphereComponent에 Simulate Physics를 켜면 굴러다니기 때문에
기존 StaticMesh 자식으로 SphereComponent를 넣음. StaticMesh가 움직여야 자식도 움직이게끔 함.
여기서 중요한 것은 BeginOverlap() 함수의 호출 PC 파악. 역시나 서버, 클라1, 클라2, .. 모두 호출됨.
다만 Latency에 의해서 누가 먼저 호출될지는 모름. 그러니 서버를 기준으로 하는게 맞음.
클라이언트 PC에서 호출되는 BeginOverlap() 함수는 무시하고자 함. 마인 폭발로 인한 데미지 계산은
상당히 중요한 로직이기 때문에 서버를 기준으로 함.
이 상태에서 만약 내 소유의 지뢰가 터졌을 때 경고 문구가 뜨게끔 하고 싶다면?
OwningClient 키워드를 활용해서 RPC 함수를 정의해야 함.
- 지뢰 폭발 시 이펙트 구현
NetMulticast 키워드의 RPC 함수 정의.
테스트가 잘 된다면 OwningClient로 변경해서 OtherClient가 터트려보자.
12.1-5 Replicate and Rep_Notify
- 폭발한 지뢰가 다시 폭발하지 않게 구현.
bIsExploded 속성 추가. BeginOverlap() 시에 true로 변경해서 구현하면 됨.
- bIsExploded 속성을 클라에서 접근한다면?
모든 속성은 기본적으로 Replicate 되지 않음.
따라서 서버에서 true로 바꿔준다 한들, 각 클라에서는 여전히 false.
만약 클라에서 접근해야만 한다면 Replicated 키워드를 UPROPERTY()에 작성해야함.
단, 주의할 점이 두 가지가 있음.
첫번째, 그럼 무조건 모든 변수를 Replicated 해주면 문제없지않을까요?
No. 역시 부하가 심해짐. 만약 해당 변수가 초기화 후에 변하지 않을거라면 Replicate 하지말자.
두번째, 반대로 클라에서 bIsExploded를 바꿔도 모든 PC에서 변경될까요?
No. C/S구조이기 때문에 무조건 서버에서 바뀌어야 다른 클라들에게 Replicate해줌.
- 터진 지뢰는 검은색으로 변하게끔 구현시 문제
새 Material 애셋 > "M_Exploded"
M_Exploded > 그래프에서 1번키 + 좌클릭 하면 상수 노드 만들어짐.
해당 노드가 0값이므로 이를 Base Color에 연결해서 검은색 만듦. 저장.
이를 BeginOverlap()에서 지뢰의 Static Mesh에 M_Exploded를 세팅함.
테스트시에 색이 안변하는걸 알 수 있음.
- 문제 해결
NetMulticast RPC를 정의하고 해당 함수에서 SetMaterial() 호출.
BeginOverlap()에서는 NetMulticastRPC 호출.
- Net Cull Distance Squared와 지뢰 폭발 문제
Net Cull Distance Squared를 좁게 잡고, 미리 터트려놔보자.
다른 클라가 이를 못보다가 가까이 오면 아직 흰색임.
NetMulticast RPC는 Net Cull Distance Squared에 의해 동기화 가능한 클라에게만 호출 되기 때문.
그럼 다른 클라가 가까이 왔을때도 검은색이려면?
bIsExploded가 변경되고 나서 Replicate될 때 특정 함수가 호출되게끔 할 수 있음.
OnRep_ 키워드를 통해 정의 가능. 해당 함수는 서버, 클라1, 클라2, ... 모두 호출됨.
그럼 OnRep 키워드 함수에서 bIsExploded를 체크해서 SetMaterial() 호출 하면됨.
클라2가 멀리 있다가 가까이 오면서 지뢰가 스폰됨.
Net Cull Distance Squared 내이기 때문에 bIsExploded가 Replicate됨.
이때 클라2에서 Replicate되면서 OnRep 키워드 함수도 호출됨. 그러면서 SetMaterial() 호출됨.
정리하자면, 지뢰의 폭발 이펙트는 Net Cull Distance Squared 내의 플레이어들에게만 보여주면되기에
NetMulticast 키워드 함수로 수행하면 되고, 터진 지뢰의 색상 변경은 모든 플레이어에게 보여줘야하기에
OnRep 키워드 함수로 수행해야 함.
- 스폰 문제 해결 방법 2번
클라/서버 모두에서 호출되는 함수 활용.
클라에서만 호출 되는 입력 키 바인드 함수에서 bool 자료형의 멤버를 true로 toggle.
해당 멤버를 Replicate해서 서버에서 다른 클라도 (Rep_Notify인듯)
12.2 멀티 플레이를 고려한 캐릭터 클래스
12.2-1 애니메이션 동기화
- 애니메이션 동기화 문제
애니메이션 재생되기 위해 MoveForward 속성과 MoveRight 속성을 Replicate함.
그래도 애니메이션 동기화가 안됨.
왜냐면 MoveForward와 MoveRight는 OwningClient에서만 변경 중이기 때문.
서버가 아니기에 Replicate 안됨.
따라서 서버에서 실행될 RPC를 정의해서 입력 함수에서 호출해줘야함. Invalidate
- 부하 줄이기
이전 프레임 값과 현재 프레임 값이 다를 경우에만 서버 RPC를 실행.
111100001111 이렇게 12번 호출될게 아니라 2번만 실행될 수 있는것.
12.2-2 아이템 장착 동기화
- 아이템 줍기는 서버에서 실행되어야 Replicate됨.
아이템 줍기 후 필드 아이템은 삭제도 되어야 하기에 서버에서 실행되게끔 함.
삭제된 액터가 Replicate 되기 위해, BP_FieldItem > Details > Replicates 체크.
- 만약 동시에 두 캐릭터가 같은 아이템을 줍는다면?
둘 중 하나가 장착 후 삭제시킴.
이미 줍기를 서버에 요청한 또다른 클라에서는 삭제된 아이템을 장착하는 문제가 생김.
따라서 IsActorBeingDestroyed() 함수를 호출해서 예외처리가 필요함.
- 아이템 장착은 클라이언트에서 실행되어야 함.
그래야 해당 캐릭터가 아이템을 소켓에 부착하기 때문. NetMulticast로 함수 정의.
- 아이템 장착 후 장착 애니메이션 동기화
장착 후 특정 키를 눌러서 bIsWeaponEquipped 속성이 변경됨.
즉 클라에서만 속성이 변경되기 때문에 Server RPC를 정의해서 호출해야함.
bIsWeaponEquiped 속성이 변할때 장착 애니메이션이 플레이됨.
즉, OnRep_ 함수를 이용해서 해당 함수 내에서 애니메이션을 플레이하면 됨.
마찬가지로 조준 애니메이션에 사용되는 속성도 똑같은 로직을 추가.
다만, 오너 클라에서 곧바로 반응하기 위해 기존 bIsAiming 변경 코드는 그대로 두고
Server RPC 호출 코드를 추가.
- Net Culling Distance와 아이템 장착 동기화
아이템 장착 후 멀리 갔다가 다시 클라2에 가까워지면 장착된 아이템이 모두 해제됨.
멀리 가게 되면 액터를 삭제 시키기 때문. 그리고 다시 가까워졌을때 다시 스폰함.
이때 파츠별로 어떤 아이템을 장착하고 있었는지에 대한 속성과 로직이 없음.
서버에서 장비를 장착할 때 파츠별 아이템 ID를 속성에 저장.
그리고 해당 속성을 OnRep 함수를 정의. 해당 OnRep 함수에서 각 파츠에 장비를 장착하는식으로 구현.
- 애셋 비동기 로드함수의 주의점
Net Culling Distance 바깥에서 다시 들어올때 여러 파츠의 OnRep 함수가 호출됨.
이때 애셋 비동기 로드 함수도 여러 번 동시에 호출됨.
애셋 비동기 로드 함수가 Complete 전에 다시 호출되면 기존에 로드 진행중이던게 사라짐.
사람이 아이템을 주울 때는 그 클릭 속도가 0.2ms 밑으로 떨어질수가 없음 아무리 순발력이 좋아도.
이정도 시간이면 충분히 Complete 되고 나서 애셋 비동기 로드 함수가 호출됨.
그러나 지금과 같은 상황은 문제가됨. 그래서 애셋 비동기 로드 함수 호출 코드를
각 파츠별로 따로 작성해줘야함.
12.2-3 사격 동기화
- 사격 애니메이션 동기화
Fire() 함수에서 몽타주를 재생하는데, 이를 다른 클라이언트에서도 재생해야함.
내 PC에서 몽타주 재생 코드는 둬야함. 이것도 지우고 서버에서 재생 신호를 받아서 재생하면 렉이남.
그대로 둔상태로 Server RPC를 정의. 애니메이션이기에 Invalidate.
서버에서는 다시 NetMulticast RPC를 정의해서 다른 클라들에게 재생하게끔 함.
대신 오너 클라는 재생하면 안됨. 이미 재생 했기때문. Other 클라만 재생하게끔 로직 구현.
- 애니메이션 노티파이 문제
애니메이션이 재생되었기에 자동으로 노티파이도 실행됨.
그래서 다른 클라가 사격했는데 내 화면이 흔들림.
노티파이 내 로직들을 각 PC 별로 분기를 해줘야함.
- 라인트레이스 문제
라인 트레이스를 다른 클라에서도 진행하면 오차 때문에 문제가 생김.
따라서 라인 트레이스는 오너 클라에서만 진행하는게 맞음.
단, 사람이 맞으면 피 이펙트 다른게 맞으면 다른 이펙트가 나오게끔 한 부분은
다른 클라들에 멀티 캐스트 되어야함. Server RPC + NetMulticast RPC 활용.
12.2-4 멀티플레이에서 데미지 처리
- Server RPC를 정의 후 해당 함수에서 Apply Point Damage() 호출.
그럼 서버 상의 피해자 액터에 피해가 적용됨.
HP나 기타 속성을 Replicate 지정해줘야 클라들에 복제됨.
글고 피격 모션도 다른 클라들에 재생되어야 하기에 NetMulticast RPC 함수 정의.
마찬가지로 죽는 모션도 NetMulticast RPC 함수 정의후 해당 함수에서 로직 처리.
- 캡슐 컴포넌트 문제
다른 캐릭터를 킬 한 후에 죽은 캐릭터는 캡슐 컴포넌트가 No Collision으로 변하면서
진로 방해하지 않게됨. 근데 캐릭터 무브먼트 컴포넌트의 이동 처리 기준이 루트 컴포넌트.
루트 컴포넌트(==캡슐 컴포넌트)가 NoCollision으로 변하면서 어디에 닿았다는 판정이없음
따라서 캐릭터 무브먼트 컴포넌트는 공중이라고 생각하고 계속 밑으로 떨어짐.
해결 방법은 NoCollision하되, SetMovementMode() 함수를 호출해서 None 모드로 지정.
12.2-5 죽음 동기화
- Net Culling Distance와 죽음 처리
다른 캐릭터를 킬 한 뒤에 멀리 갔다가 돌아오면 다시 살아있음.
즉, 해당 캐릭터가 죽었는지에 대한 속성이 있어야하고 이를 OnRep 함수로 복제해줘야 함.
그럼 랙돌이 문제가 됨. 이 경우엔 랙돌까지 켜진 않고 그대로 누워 있게끔 하려함.
누워 있는 애니메이션을 재생 시키는 것.
- 누워있는 부분만 애니메이션 생성
Toolbar > Create Asset > 마지막 애니메이션 프레임에 두고 Current Pose 클릭 > "Death_Pose"
Death_Pose 우클릭 > Create Montage > FullBody Slot 지정. Loop Motion.
Blend In/Out에서 Blend Time을 둘 다 0으로해서 블랜드 되지 않게끔. 블랜드 되면 어색함.
- bIsDead 속성 추가
해당 속성에 OnRep 함수 정의.
해당 함수에서 Death_Pose 몽타주 재생. NoCollision과 None Movement mode 지정.
HP가 0에 가까우면 bIsDead를 true로 해줌.
- Death_Pose 재생과 랙돌의 충돌이 있을까?
없음. SetAllBodiesBelowPhysicsBlendWeight()에서 Weight를 1로 줬기 때문에.
Death_Pose는 무시되고 Physics(랙돌)만 씀.
12.2-6 조준 방향 동기화
- Controller Pitch 저장하는 속성 추가.
Server RPC + NetMulticast RPC 조합으로 해결하되, 값이 계속 변하기에 Invalidate.
패킷 좀 잃어버려도 또 전송되기 때문. 부하를 줄이자.
마찬가지로 부하를 더 줄이기 위해서 특정 값 이상 차이가 나야 Server RPC를 호출하게끔 로직 변경.
12.3 게임 모드 활용
12.3-1 게임 모드 개론
- 게임의 전체적인 진행
서버 실행 후 플레이어 접속 대기
적절한 인원 수가 채워진다면 카운트 다운 후 게임 시작
파밍 후 최후 1인이 나올 때까지 전투.
최후 1인이 나오면 최종 우승자 처리 후 일시적으로 모든 클라의 접속 끊기.
서버 리셋 후 다음 게임 준비.
- 위와 같은 전체적인 진행을 할 수 있는 액터가 게임 모드.
게임 모드는 결국 "서버"와 비슷한 느낌.
그럼 게임 모드는 누가 생성해주나?
서버 컴퓨터에서 데디 서버 실행 파일을 실행 하면 Open Level 명령어 + Listen 명령어가 처리됨.
이때 열린 레벨의 월드 세팅에 어떤 게임 모드를 쓸건지 적혀있음.
해당 게임 모드를 생성시킴. 또 그 게임 모드에서는 PlayerController, Pawn, GameState, PlayerState,...가 적혀있음.
그걸 보고 해당하는 액터들을 만들어냄.
- 그럼 각 클라에서 게임 모드 액터를 접근하면?
각 클라 PC에는 게임 모드 액터가 없어서 접근시 nullptr임.
그래서 Server RPC 함수를 정의해서 해당 함수 내에서 접근하거나
Server에서도 실행되는 함수 내에서 HasAuthority() 함수로 서버임을 확인하고 접근해야함
- 반대로 게임모드에서 해당 클라에 실행시키고자 하는 로직이 있다면?
GetPlayerController(n) 함수를 이용해서 해당 클라의 플레이어 컨트롤러를 찾음.
n은 접속한 플레이어 순서대로 인덱싱되는 번호.
그 후 해당 클라의 플레이어 컨트롤러에 OwningClient RPC 함수를 통해 로직 실행.
- 현재 생존한 플레이어 수를 모든 클라가 알아야함.
근데 플레이어 수를 OwningClient RPC 함수를 통해 알려주는 건 너무 불편함.
이때 GameState를 활용함. 속성을 만들고 해당 속성에 Replicated 키워드를 걸면 자동으로 복제해줌.
그럼 각 클라에서는 GameState의 해당 속성 값을 읽고 UI에 띄워주게 됨.
- 게임 모드 실습
BP_BattleGM, BP_BattlePC 생성.
WorldSettings > Details > GameMode Override에 BP_BattleGM
주목할 점은 GameMode Override에 None을 설정할 수 있음.
None으로 설정한 World는 ProjectSettings > Maps and Modes > Default GameMode를 사용함.
그래서 평범한 맵의 경우엔 기본값으로 지정된 게임모드를 그대로 사용하게끔 가능.
BP_BattleGM > Details > Player Controller Class에 BP_BattlePC
Defaults Pawn Class에 BP_TPSCharacter 지정.
- 데디서버 실행 시 어떤 맵을 로딩할지 어떻게 알까?
따로 지정하지 않으면, Project Settings > Maps and Modes > Server Default Map을 열게됨.
클라이언트는 따로 지정하지 않으면 Game Default Map을 열게됨.
BP_LobbyPC > JoinServer 이벤트 > OpenLevel 함수에 Loading 레벨 작성. Absolute true
Options에는 NextMap=Play 작성. 그럼 Loading에서 Play 맵으로 자동으로 이동시킴.
그런데 클라에선 Play 맵이 아니라 IP주소임. JoinServer 인자값을 "NextMap=(IPAddress)" 형태의
문자열이 되게끔 Append() 함수 활용. 해당 문자열을 Options에 전달.
BP_LoadingPC에서는 아래 그림과 같은 로직이 필요함.
테스트 방법은 일단 Play 맵을 열어서 데디 서버가 Play 맵을 열게끔 유도.
Editor Preference > Play > Automatically access server 체크 해제.
데디서버/1명 조건으로 플레이 > 틸드 키 > open title > 서버 접속.

- 로비 레벨
PlaySound2D() 함수를 이용해서 BGM을 출력할 수도 있음.
BP_LobbyPC > "JoinServer" 이벤트 생성. 인자로 FString IPAddress 받음.
받은 인자를 Open Level 함수의 인자로 다시 전달해서 호출.
WBP_Lobby > 서버 접속 버튼의 On Clicked 이벤트에서 BP_LobbyPC의 JoinServer 이벤트 호출
호출하면서 EditText에 적힌 아이피 주소를 전달. 원칙적으로는 아이피 주소가 맞는지 체크해야함.
이상태로 데디서버/2명 조건으로 플레이하면 데디서버로 Lobby 레벨을 열어버림.
이를 수정하려면 플레이 버튼 옆 점 세개 > Settings > Automatically access server 체크 해제 후
Play 레벨을 열자. 이상태에서 데디서버/2명 플레이를 누르면 플레이됨. 데디서버는 입장을 기다리게 됨.
근데 클라도 Play 레벨로 들어감. 틸드 키 > open Title 작성 후 엔터하면 타이틀 레벨로 감.
이상태에서 서버 접속 버튼을 클릭. 그럼 Play 레벨로 정상 이동되며 데디서버에 접속된 것.
근데 마우스와 캐릭터 조종이 안됨. Title 레벨에서 setuimode와 mouse 커서 숨김이 유지되기 때문.
BP_BattlePC > BeginPlay()에서 SetInputModeGameOnly() 호출. mouse 커서 숨김도 진행.
12.3-2 로그인/로그아웃 이벤트
- BP_BattleGM 더블클릭
OnPostLogin() 함수를 통해 접속을 감지 OnLogout() 함수를 통해 퇴장을 감지.
OnPostLogin() 함수의 인자가 접속한 컨트롤러.
이 함수에서 접속한 플레이어 캐릭터들을 TArray에 추가.
앞으로 이 TArray에다가 로직을 하면 접속한 플레이어 캐릭터들에게 명령을 줄 수 있음.
OnLogout() 이벤트에서는 TArray에서 인자로 받은 플레이어 컨트롤러를 제거
- 클라이언트 접속시 접속한 플레이어 수 띄우기.
BP_BattlePC > FString형 속성 NotificationFromServer 추가.
WBP_ClientMain 생성 > PlayerController형 PlayerController 속성 추가
Text 위젯 추가해서 해당 Text와 BP_BattlePC의 NotificationFromServer를 바인딩.
BP_BattlePC > BeginPlay() 함수에서 false == HasAuthority()인 경우에 WBP_ClientMain ui 생성
- 서버 접속 시 안내 문구를 클라 화면에 UI로 띄우는 방법
방법 1. NotificationFromServer 속성을 Replicate 지정.
방법 2. OwningClient RPC 함수 정의해서 NotificationFromServer 문구 업데이트.
이번에는 방법 1 적용. Replicated 속성 지정.
그리고 BP_BattleGM에서 OnPostLogin() 함수 안에서 NotificationFromServer 수정시 자동 복제.
12.3-3 메인 타이머
- 메인 타이머 구현
BP_BattleGM의 BeginPlay()에서 1초마다 반복적으로 도는 Timer를 만듦.
그때마다 실행되는 이벤트에서 시계를 1초씩 더해줌.
동시에 그때마다 최후의 1인 남았는지 확인. 이 확인은 1초정도 늦어도전혀 문제없기 때문.
Tick()에서 무언가를 하는게 되게 부담스러운 일이기 때문에 BeginPlay()에서 진행.
- GameState 제작
EPlayingState 정의 > Wait, Play, End
위에서 만든 MainTimer 이벤트에서 위 이넘 값에 따라 분기.
- 현재 EPlayingState에 따라 접속 차단.
OnPostLogin()에서 EPlayingState가 Waiting이면 TArray에 넣고,
그게 아닐때는 PlayerController를 SetLifeSpan(0.1) 함수를 통해 삭제되게끔 함.
12.3-4 다른 플레이어 기다리기
- EPlayingState가 Waiting일 때 현재 인원수 체크 후 게임 시작
위에서 만든 TArray의 개수가 2이하이면 TArray를 돌면서 NotificationFromServer를 수정해서
대기 중이라고 수정함.
2이상이면 카운트 다운 시작.
카운트 다운을 위한 속성 RemainTimeToStart int32형 생성.
그리고 해당 값을 가지고 NotificationFromServer를 수정.
만약 RemainTimerToStart == 0이면 EPlayingState를 Play로 변경. 전투 시작 문구 안내.
단, 문구를 Delay를 통해 5초 후 빈칸 문구로 변경.
- 카운트 중에 인원이 모종의 이유로 접속 해제되면?
RemainTimeToStart를 다시 15로 리셋. 다시 카운트.
12.3-5 살아남은 인원 체크하기
- 최후 1인 검사
EPlayingState가 Play일때 AlivePCs가 1인지 검사
- 죽은경우 AlivePCs에서 자신을 빼는 로직
BP_BattleGM에 DeadPCs TArray 생성.
OnPlayerControllerDead 이벤트 생성 후 인자로 해당 PC를 전달받음.
전달 받은 PC를 AlivePCs에서 제거 후 DeadPCs에 추가.
BP_BattlePC에서 OnPlayerCharacterDead 이벤트 추가.
해당 이벤트에서는 GetGameMode()를 BP_BattleGM으로 캐스팅.
캐스팅 성공 시 BP_BattleGM->OnPlayerControllerDead(this) 호출.
BP_TPSCharacter에서 bIsDead를 설정해주는 Server RPC에서
BP_BattlePC의 OnPlayerCharacterDead 이벤트 호출.
12.3-6 GameState를 활용한 정보공유
- GameState를 이용한 생존 인원수 UI 구현
BP_BattleGS 생성 > int32 AliveCount 생성 > Replicated 체크. Always Relavant 체크.
BP_BattleGM > BP_BattleGS를 default Game State에 지정.
MainTimer에서 AlivePCs의 Length로 BP_BattleGS의 AliveCount 업데이트.
WBP_ClientMain > 우상단에 TEXT 위젯 추가 후 AliveCount 업데이트
BP_BattlePC > BeginPlay 이벤트에서 WBP_ClientMain 생성시에 GameState 꽂아줄 수 있음.
- 전투 전에는 무적 상태 구현
BP_BattleGS > bool bIsInvincible 속성 추가 > EPlayingState가 Waiting이라면 true.
Battle이 되면 false로 바꾸기 위해서 BP_BattleGM > bp_battlegs를 가져와서 battle 상태로 바뀔때 false.
BP_TPSCharacter > 피격 당했을때 GetGameState가져와서 bIsInvincible에 따라 예외처리.
12.3-7 게임 결과 창
- 게임 결과창 구현
WBP_Result 생성
TextMessage FString, Rank FString 속성 추가. 각각 TEXT 위젯과 바인드.
BP_BattlePC > ShowDeathResult 이벤트와 ShowWinnerResult 이벤트를 OwningClient RPC로 추가.
ShowDeathResult에는 인자 추가. Rank를 int32로 전달받음.
WBP_Result를 생성하며 Rank를 전달.
ShowWinnerResult에도 마찬가지로 WBP_Result 생성. 단, 당연히 1등이라 Rank는 둠. add to viewport 꼭 해줘야함.
또한 두 이벤트 마지막에 SetInputMode()를 호출. this, WBP_Result, UIOnly를 인자로 전달.
ShwMouseCursur를 true로 함.
BP_BattleGM > MainTimer에서 남은 플레이어가 1명인지 확인 할 때, 1명이면 AlivePCs에서 0번을 가져와서
ShowWinnerResult()를 호출해줌.
OnPlayerControllerDead() 이벤트에서는 DeadPCs를 추가할 때 추가해주면서 WBP_Result를 생성.
등수는 빼주기 직전 AlivePCs의 Length가 될것.
12.3-8 하단 HP바
- 하단 HP바 구현
생략.
12.3-9 전투 종료
- 게임 결과창에서 카운트 다운 후 로비 레벨로 이동 구현
Lobby Level 대충 만듦.
WBP_Result > Go to lobby 버튼 생성. OnClicked() 시에 Open Lobby 로직.
- 카운트 다운이 끝나면 모두 Lobby로 보내기 구현
BP_BattlePC > GotoLobby 이벤트 생성 > Open Lobby
GotoLobby는 반드시 OwningClient여야함. Server가 Lobby로 가면안됨. 또 Validate 지정.
BP_BattleGM > int32 RemainTimeToLobby 속성 추가
RemainTimeToLobby가 0이면 MainTimerHandle 제거.
AlivePCs와 DeadPCs를 돌면서 GotoLobby() 이벤트 호출.
RemainTimToLobby를 안내 문구로 안내.
- 데디케이티트 서버 리셋
BP_BattleGM > GotoLobby()이벤트를 모두에게 호출 후 Open Level에 options로 Listen 지정
레벨이름에는 GetCurrentLevelName()을 줘서 아에 새롭게 레벨을 다시 열어버림.
만약 세션 서버가 있다면, 데디 서버가 새롭게 레벨을 열었다면
세션 서버에게 "나 준비되었으니 유저를 들여보내줘"라고 알림.
그럼 세션 서버는 해당 데디 서버의 IP 주소를 신규 유저들에게 알리기 위해 등록함.
12.4 클라우드 서비스를 활용한 서버 접속
12.4-1 언리얼 깃 클론
- 추후에 본인의 게임을 AWS에 올릴 때 언리얼 엔진 소스코드 빌드가 필요함
언리얼 엔진 소스코드는 깃에 Private 리포지토리로 저장되어 있어서
무턱대고 다운 받을 수는 없음. 초대를 받아야함.
깃을 설치하고 아래 내용 진행.
- 깃헙 가입. 이때 아이디(닉네임)를 복사 해둠.
언리얼 공홈 가입 및 로그인
아이디(오른쪽 상단) 클릭 > 개인 > 연결된 계정 > 깃헙 아이디 기입. 저장해도 안되면 10분뒤에 재시도.
깃헙 아이디로 지정된 이메일을 해당 포털에서 로그인하면 언리얼에서 초대 메일 와있음.
초대 받고 깃헙 로그인 시 organization에 에픽 게임즈가 생김. 에픽 게임즈 로고 클릭.
UnrealEngine 리포지토리 클론. 원하는 폴더에서 cmd창 켜서 git clone (언리얼소스코드리포지토리주소)
클론이 완료되면 cmd 창 켜서 git branch checkout 5.1 작성 후 엔터.
Setup.bat 실행. 실행하면 required 폴더들을 설치해줌. 이 폴더에는 필수 애셋들이 들어있음.
GenerateObjectFiles.bat 실행. 이때 .NET Framework가 설치되어 있어야함.
.sln 파일이 생성되면 더블클릭 후 VS에서 실행. 솔루션 탐색기에서 UE5 프로젝트가 볼드체인지 확인.
볼드체가 아니라면 UE5 우클릭 > 시작 프로젝트 설정.
Development Editor, Win64, UE5로 지정 후 Ctrl + Shift + B. 솔루션 빌드가 진행되는데 상당히 오래걸림.
빌드 완료되면 Ctrl + F5. 언리얼 에디터가 시작됨. 정상적으로 열리면 닫음.
프로젝트 폴더에서 .uproject 파일 우클릭 > Switch Unreal Engine Version
5.1은 언리얼 에디터를 통한 버전, 그 아래에 Source build at ...이 있음. 선택 후 OK
이러면 엔진의 소스코드를 변경하면서 우리 프로젝트도 진행할 수 있게되는 것. 그대로 진행.
- 엔진이 바뀌면서 다시 세팅해줘야하는 ProjectSettings > Maps and Modes
Default GameMode는 BP_BattleGM
Editor Startup Map은
Game Default Map은 title
Server Default Map은
Game Instance Class도
12.4-2 클라이언트와 서버 빌드
8.4-3 Game Build
- {프로젝트명}.Target.cs 파일에 ExtraModuleNames
ExtraModuleNames에 추가된 모듈들은 정적 라이브러리(.lib) 형태로 실행 파일에 포함됨.
즉, 게임이 실행되기 위해서 반드시 해당 모듈이 있어야함.
- 게임 빌드 과정
빌드: 컴파일을 통한 실행 파일을 만드는 과정.
쿠킹: 지정한 플랫폼에 맞춰 컨텐츠 애셋들을 변환하는 과정.
패키징: 실행 파일과 변환된 컨텐츠 애셋을 모두 모아서 하나의 프로그램으로 만드는 과정.
즉, 게임 패키지 == 실행 파일 + 변환된 컨텐츠 애셋
- StudyProjectServer.Target.cs, StudyProjectClient.Target.cs 파일 생성 및 빌드
언리얼 에디터 종료.
Source > StudyProjectEditor.Target.cs를 복사 붙혀넣기 > 해당 파일을 StudyProjectServer.Target.cs로 파일명 수정.
해당 파일을 VS로 열어서 "Editor"라는 글자를 모두 Server로 변경 후 저장.
똑같이 StudyProjectServer.Target.cs 복붙 > 이름 변경 StudyProjectClient.Target.cs
해당 파일을 VS로 열어서 "Server"라는 글자를 모두 Client로 변경 후 저장.
.uproject 파일 실행 > Windows > Project Launcher > Modify에 WindowsClient 지정.
좌상단에 Setting(고급) 클릭하면 Modify 밑에 Environment와 Data Build가 나옴
Environment에는 Shipping, DataBuild에는 By the book 지정. 그다음 Execute 버튼 클릭.
빌드 중에 게임 실행되는건 그냥 종료.
빌드 결과물은 Saved > StagedBuilds > WindowsClient가 있음.
해당 폴더 속 StudyProjectClient.exe를 실행시키면 게임이 실행됨.
근데 전체화면이라 조금 불편함. 해당 exe 파일 우클릭 > 바로가기 만들기
바로가기 우클릭 > 속성 > 대상 맨 뒤에 " -windowed resx=800 resy=600" 적용 확인.
이번에는 똑같이 데디 서버 빌드.
Windows > Project Launcher > Modify에 WindowsServer 지정.
좌상단에 Setting(고급) 클릭하면 Modify 밑에 Environment와 Data Build가 나옴
Environment에는 Development, DataBuild에는 By the book 지정. 그다음 Execute 버튼 클릭.
Development로 해야 로그가 출력됨. 중간에 데디 서버가 실행되면 그냥 종료.
빌드 결과물은 Saved > StagedBuilds > WindowsServer가 있음.
해당 폴더 속 StudyProjectServer.exe를 실행시키면 데디서버임.
바로 실행해도 아무것도 안뜸. 해당 exe 파일 우클릭 > 바로가기 만들기
바로가기 우클릭 > 속성 > 대상 맨 뒤에 " (LevelName)?listen -log" 타이핑 후 적용 확인.
바로가기 더블클릭하면 데디서버가 접속을 대기함.
이상태에서 위에서 만든 StudyProjectClient.exe의 바로가기 실행. 2개 실행 후 IP를 17777에서 7777로 변경.
클라가 게임 시작하면 데디 서버 프롬프트 창에 로그가 뜨며 접속을 알림.
종료시에는 데디 서버 프롬프트 클릭 > Ctrl + C 누르면 종료됨.
12.4-3 AWS에 데디케이티드 서버 올리기
- AWS 가입.
Free Tier로 가입하지만, 신용카드 정보가 저장되어 있음. 막쓰면 안됨.
그리고 사용 후에는 반드시 컴퓨터를 Terminated 시켜야함. 계속 가동되면서 결제될지도.
- AWS 로그인 후 지역 선택
해당 지역마다 Internet Data Center가 따로 있음. 물리적으로 다른 기기가 배치되어 있음.

- 가상 머신 시작
가상 컴퓨터를 EC2라고 함. 클릭 후 아래 절차 진행
1. 이름 및 태그 작성
2. 애플리케이션 및 OS 이미지에 Windows 선택
3. 인스턴스 유형(가상 머신의 성능)은 프리티어는 딱 하나 가능(t2.micro).
근데 t2.micro는 윈도우 서버가 사용하기엔 무리가 있음. 작은 프로젝트의 리눅스 서버라면 가능할지도.
그래서 우리는 t2.medium으로 지정.
4. 키페어. 그대로 둠.
5. 네트워크 설정. 클라우드 서비스 이용시에는 두 가지 방화벽이 있음.
클라우드 자체 방화벽이 있고, 가상 머신의 방화벽이 있음. 두 방화벽의 포트를 뚫어줘야함.
그래야 내 어플리케이션(데디 서버)까지 패킷이 전달됨.
좌측에 편집 클릭
하단에 보안 그룹 규칙 추가. 해당 규칙의 유형 클릭 > 사용자 지정 UDP 지정.
포트 범위에는 언리얼이기 때문에 기본 7777.
소스 유형에는 사용자 지정. 원본에는 모든 사용자가 들어올 수 있어야하니 0.0.0.0/0 작성.

6. 스토리지 구성. 그대로 둠.
7. 고급 세부 정보. 그대로 둠.
8. 우측 플로팅 바에 요약. 하단에 인스턴스 시작 클릭
그럼 키 페어 생성 다이얼로그가 뜸. 키 페어는 암호가 담긴 파일 정도라고 생각하면 됨.
새 키 페어 생성 체크 후 키 페어 이름에 "studyproject-dedicated-server" 작성.
키 페어 생성 클릭하면 자동으로 .pem 파일이 다운로드 됨. 유출되면 안되는 아주 중요한 파일.
원격으로 가상 머신에 접속할 때 .pem 파일이 있어야 암호를 얻어올 수 있음.
즉 이 .pem 파일만 있으면 내 가상 머신에 누구든지 접근가능함.
9. 다시 요약에 인스턴스 시작 클릭.
아래와 같은 문구가 나오는데 빨간 부분을 클릭하면 인스턴스들을 볼 수 있음.
아래 그림들의 참조 글귀를 보며 진행.




10. rdp 파일 다운로드 완료
StudyProject.rdp 우클릭 > 편집 > 로컬 리소스 > 프린터와 클립보드 체크 해제
자세히 클릭 > 스마트 카드, WebAuthn, 로컬 디스크(C:)만 체크.
그럼 원격 컴퓨터에서 내 컴퓨터로 바로 접근 가능해짐.
이 덕분에 필요한 파일들을 카피해갈수 있음.
연결 클릭 > 사용자 자격 증명 입력 > 암호에 메모장에 저장한 암호 복붙. 기억 체크 후 접속
다음에 나오는 다이얼로그는 아무것도 안누르고 예.
그럼 서버 컴퓨터에 원격 접속됨. 서버 컴퓨터에 Networks 다이얼로그가 뜸. Yes 클릭.
오른쪽 상단에 Public IP Address가 있음. 이를 통해 접속 예정.
서버 컴퓨터 > 시작 표시줄 > 탐색기 아이콘 클릭 > This PC > C on ...이 있음. 이게 아까 설정덕분임.
들어가서 이전에 Saved 폴더 > WindowsServer 폴더 만든걸 서버 컴퓨터 바탕화면에 복붙.
카피 되는 동안에 시작 > WindowsAdministrative... 클릭 > Windows Defender Firewall with Advanced ... 더블클릭
InBound Rules가 접속 규칙임. 더블클릭 > 오른쪽 New Rules 더블클릭 > Port 체크 후 Next
UDP 체크 > Specific local ports에 7777 작성. 아까 보안 그룹 규칙 추가한 그 포트 주소. Next 클릭
Next > Next > Name에 studyproject-dedicated-server 작성 > Finish 클릭하면 방화벽 설정 끝.
바탕화면 > WindowsServer > StudyProjectServer 바로가기 실행
근데 Microsoft Visual C++ ...와 DirectX Runtime이 없다고 나옴. 일단 OK 클릭.
This PC > C On ...에서 이전에 클론 받은 언리얼 깃 소스코드 폴더 > UnrealEngine > Engine > Extras
Redist > en-us > UE5PreregSetup_x64를 바탕화면에 복붙.
이걸 더블클릭해서 설치 진행. .NET Framework가 없다고 나와도 그냥 Close 클릭.
다시 StudyProjectServer 바로가기 실행하고 우상단 Public IP Address를 메모장에 적어둠.
내 컴퓨터로 돌아와서 StudyProjectClient 바로가기 실행 > IP UI에 Public IP Address 작성. 뒤에 :7777도 작성해야 함.
11. 대여한 가상 머신 종료
실행한 데디 서버 프롬프트 클릭 > Ctrl + C
아래 그림과 같이 작업한 후 좌측 EC2 대시보드 > 인스턴스(실행 중) 0이면 완벽.
그 아래에 볼륨도 있음. 클릭 후 남겨진 볼륨이 있을 경우 우측 볼륨 생성 버튼 옆 작업 클릭 > 볼륨 삭제 클릭.
만약 지역을 서울 외에 다른 곳으로 왔다갔다한다면 그 또한 꼭 체크해야함.

studyproject-dedicated-server