-
19장 좀비 서바이버 멀티플레이어 : 네트워크 게임 월드 구현Unity/레트로의 유니티 게임 프로그래밍 에센스 2021. 7. 20. 15:41
19.1 네트워크 플레이어 캐릭터 준비
19.1.1 Photon View 컴포넌트
- 네트워크를 통해 동기화될 모든 게임 오브젝트는 Photon View 컴포넌트를 가져야 함.
- Photon View 컴포넌트는 게임 오브젝트에 네트워크상에서 구별 가능한 식별자인 View ID를 부여
- 또한 Observed Components 리스트에 등록된 컴포넌트들의 변화한 수치를 관측하고, 네트워크를 넘어서 다른 클라이언트에 전달.
- View ID 1~999까지? -> 정보를 담을 스크립트를 위한 게임오브젝트로 개수 제한에서 벗어날 수 있지 않을까?
- max 값 변경 : https://answers.unity.com/questions/596457/how-can-i-add-more-photonview-ids.html
- 로컬에 있는 게임 오브젝트와 다른 클라이언트에 있는 리모트 게임 오브젝트는 같은 네트워크 ID를 부여받음.
- IPunObservable 인터페이스르 상속한 컴포넌트만 관측할 수 있음.
*** 동기화 방법 ***
> Photon View 컴포넌트의 Observe option 필드에서는 값을 관측하고 동기화하는 방식을 변경할 수 있음.
> Observe option에서 가능한 설정.
> Off : 동기화 하지 않음.
> Reliable Delta Compressed : 상대방이 최근에 수신한 값과 동일한 값은 송신하지 않음.
> Unreliable : 패킷의 수신 여부를 검사하지 않고 지속적으로 송신함.
> Unreliable On Changed : Unreliable과 동일하나 값의 변화가 감지될 때만 송신.
> Photon View를 거치지 않고 RPC등의 별개의 방법으로 값을 동기화할 경우에도 Off를 사용하여 대역폭을 아낄 수 있음.
19.1.2 Photon Transform View 컴포넌트
- Photon Transform View 컴포넌트는 자신의 게임 오브젝트에 추가된 트랜스폼 컴포넌트 값의 변화를 측정하고, Photon View 컴포넌트를 사용해 동기화.
- Photon Transfomr View는 자신이 로컬이라면 송신, 리모트라면 수신함
- Photon Transform View 컴포넌트는 Photon View 컴포넌트 없이는 동작할 수 없음.
19.1.3 Photon Animator View 컴포넌트
- Photon Animator View 컴포넌트는 로컬일 때는 자신의 애니매이터 컴포넌트의 파라미터들을 관측하고, Photon View 컴포넌트를 사용해 다른 클라이언트에 있는 자신의 리모프에 전달.
- 리모트일 때는 네트워크를 통해 로컬이 건넨 수치들을 받아 자신의 애니메이터 컴포넌트의 파라미터에 덮어씀.
- 동기화를 원하는 파라미터는 드롭다운 메뉴를 클릭하고 Discrete 또는 Continuous로 동기화.
- DIscrete는 값의 연속적인 변화를 Continuous에 비해 잘 반영하지 못하지만 대역폭을 아낄 수 있음.
19.1.4 CameraSetup 스크립트
- 로컬인 캐릭터를 추적 대상으로 함.
- phtonView.IsMin : 속한 게임 오브젝트가 주도권(로컬인지)을 가지고 있는지를 알려줌.
- cinemachineVirtualCamera.Follow 추적 대상
- cinemachineVirtualCamera.LookAt 주시 대상
- Photon View에 Photon Transform View나 Photon Animator View를 추가함.
19.2 네트워크용 플레이어 캐릭터 컴포넌트
19.2.1 PlayerInput 스크립트
19.2.2 PlayerMovement 스크립트
19.2.3 PlayerShooter 스크립트
19.2.4 LivingEntity 스크립트
- 호스트에서만 체력 관리 및 대미지 처리 실행(서버측에서)
- [PunRPC] 로 선언된 메서드는 다른 클라이언트에서 원격 실행할 수 있음.
- RPC를 통해 어떤 메서드를 다른 클라이언트에서 원격 실행할 때는 Photon View 컴포넌트의 RPC() 메서드를 사용함.
- photonVeiw.RPC() 파라미터 : 원격 실행할 메서드 이름(string), 원격 실행할 대상 클라이언트(RpcTarget 타입), 원격 실행할 메서드에 전달할 값(필요한 경우).
- photonVeiw.RPC("DoSomething", RpcTarget.All);
- PhotonNetwork.IsMasterClient : 현재 코드를 실행하는 클라이언트가 마스터 클라이언트인(호스트)인지 반환하는 프로퍼티.
19.2.5 PlayerHealth 스크립트
-
인터페이스의상속한 메서드에 [PunRPC]가 있었다면 override할 때에도 [PunRPC]를 적어야함.19.3 네트워크 Gun
- 부모 게임 오브젝트에 Photon View 컴포넌트가 이미 추가되어 있다고 해도 자식 게임 오브젝트에 독자적으로 실행할 네트워크 처리가 있다면 자식 게임 오브젝트에서도 Photon View 컴포넌트를 추가하여 View ID를 부여해야함.
19.3.1 변경된 Gun 스크립트
- Photon View 컴포넌트를 사용해 동기화를 구현할 모든 컴포넌트(스크립트)는 IPunObservable 인터페이스를 상속하고 OnPhotonSerializeView() 메서드를 구현해야 함.
- OnPhontonSerializeView() 메서드는 Photon View 컴포넌트를 사용해 로컬과 리모트 사이에서 어떤 값을 어떻게 주고받을지 결정.
- IPunObservable 인터페이스를 상속한 컴포넌트는 Photon View 컴포넌트의 Observed Components에 등록되어 로컬과 리모트에서 동기화될 수 있음.
- stream.IsWriting : 현재 스트림이 쓰기 모드인지 반환. 로컬이면 true, 리모트이면 false.
- SendNext() : 메서드로 스트림에 값을 삽입.
- ReceiveNext() : 메서드로 가져옴.
- ReceiveNext() 메서드를 통해 값이 들어올 때는 범용적인 object 타입으로 들어오기 때문에 읽는 측에서는 원본 타입으로 형변환함. 또한 스트림에 삽입한 순서대로 값이 도착. -> 주고 받는 변수들의 나열 순서가 일치해야함.
####
클라이언트는 입력만 감지 및 호스트로 입력 송신. 호스트측이 클라이언트의 입력을 수신하여 동기화하고 그 입력에 따라 로컬 오브젝트 위치 이동 및 클라이언트에 위치 송신. 클라이언트는 수신 받은 위치로 리모트 오브젝트 위치에 동기화.
####
19.4 네트워크 좀비
19.4.1 변경된 Enemy 스크립트
-
"""
LivingEntiry 스크립트에서 OnDamage() 메서드에 이미 [PunRPC] 속성을 선언했지만,
Enemy 스크립트에서 OnDamage()를 오버라이드하면서 [PunRPC]속성이 해지되었기 때문에
Enemy의 OnDamage() 메서드에서 [PunRPC]속성을 다시 선언.
"""
19.5. 네트워크 아이템
19.5.1 AmmoPack 스크립트
- Destroy() 메서드를 사용하면 해당 월드에서만 사라짐. 만약 호스트측에서 Destroy()메서드를 호출했다면, 다른 클라이언트에서는 해당 게임 오브젝트가 파괴되지 않음. 따라서 Photon.Network.Destroy() 메서드 사용.
19.5.2 HealthPack 스크립트
19.5.3 Coin 스크립트
19.5.4 PhotonNetwork.Instantiate()
- PhotonNetwork.Instantiate() 메서드는 입력으로 Photon View 컴포넌트가 추가된 프리팹을 받아 해당 프리팹의 복제본을 모든 클라이언트에서 생성. 생성된 게임 오브젝트의 소유권은 PhotonNetwork.Instatiate()를 직접 실행한 측에 있음.
- PUN 구현상의 문제로 PhotonNetwork.Instantiate()를 사용해 생성한 프리팹들은 Resources라는 이름의 폴더에 있어야 함.
- PhotonNetwork..Instatiate()는 생성할 프리팹 자체를 입력받지 않고, 생성할 프리팹의 이름을 string타입으로 받음.
############
Resources 폴더
- Resources 폴더는 에셋의 사용 여부와 상관없이 항상 메모리에 해당 에셋을 로드하는 특수한 폴더.
- 최적화를 위해 유니티는 씬에서 한 번도 사용하지 않는 에셋들은 자동을 빌드에서 제거되지만, Resources폴더에 있는 에셋들은 사용되지 않아도 빌드에 포함됨. 게임 실행부터 종료까지 메모리에 로드된 상태로 유지 됨.
- 게임 도중에 Resources.Load() 메서드를 사용해서 해당 에셋을 실시간으로 사용.
- 최대한 사용하지 않아서. 빌드 크기와 메모리 사용량을 줄이도록 하자...
############
19.5.5 ItemSpawner 스크립트
- Instantiate() -> PhotonNetwork.Instantiate()
- Destroy() -> PhotonNetwork.Instantiate()
- PhotonNetwork.Instantiate() 는 지연 시간을 줄 수 없어서 PhotonNetwork.Instantiate() 를 호출하는
DestroyAfter()에 지연 시간을 줌.
19.6 네트워크 매니저
19.6.1 GameManager 스크립트
- MonoBehaviourPunCallbacks를 상속한 스크립트는 여러 Python 이벤트를 감지할 수 있음.
- PhotonNetwork.LeaveRoom()은 현재 네트워크 룸을 나가는 메서드. 단, 씬을 전환한다는 의미는 아님.
- PhotonNetwork.Instantiate()를 사용해 게임 도중에 생성된 것이 아닌 처음부터 씬에 있었던 게임오브젝트라면, 그 소유권은 호스트에 있음. 게임 도중의 기준이란?? 게임 도중에 생성된 것이라면. 상황에 따라 다르다???
< 19.7 GameManager AddScore() >
- 호스트 입장에서 GameManager 게임 오브젝트는 로컬.
OnPhoteonSerializeView : 주기적으로 자동 실행되는 동기화 메서드.
< Start()에서 로컬 플레이어 캐릭터 생성>
- 각 캐릭터들은 각각의 클라이언트 입장에서 로컬 플레이어 캐릭터.
- PhotonNetWork.Instantiate() 메서드는 각각의 클라이언트에서 따로 실행됨. -> ??
- 1. 클라이언트 B가 룸에 접속 -> B에서 GameManager의 Start() 실행.
- 2. Start() 내부코드, PhotonNetwork.Instantiate()에 의해 플레이어 캐릭터 b를 클라이언트 A와 클라이언트 B에서 생성.
- b는 B, a는 A가 소유권을 가짐.
- 1> 클라이언트 A가 룸을 생성하고 접속.
- 2> A에서 GameManager Start() 실행.
- 3> A가 PhotonNetwork.Instantiate()에 의해 플레이어 캐릭터 a를 A에 생성.
- 4> 클라이언트 B가 룸에 접속 -> 클라이언트 B에 자동으로 a가 생성됨.
- 5> B에서 GameManager Start() 실행.
- 6> B가 PhotonNetwork.Instantiate()에 의해 플레이어 캐릭터 b를 A와 B에 생성.
19.7 적 생성기 포팅
19.7.1 EnemySpawner 스크립트
19.7.2 웨이브 정보 동기화
- 적 생성은 호스트의 로컬에서만 실행됨. 다른 클라이언트는 호스트가 생성한 적 게임 오브젝트의 복제본을 네트워크를 통해서 전달 받음.
- enemies 리스트에 생성된 적을 등록하는 절차는 호스트의 EnemySpawner에서만 실행됨.
19.7.3 Setup() 원격 실행
19.7.4 적 사망 이벤트
- onDeath 이벤트에 이벤트 리스너에 등록하는 코드는 호스트에서만 실행됨.
- 적 리스트에서 해당 리스트 제거, 사망한 적을 10초 뒤 파괴, 게임 매니저 100점 추가하는 처리는 호스트에서만 실행.
- 파괴되는 처리는 다른 클라이언트에 자동 반영되지 않음. PhotonNetwork.Destroy() 사용. 이 함수는 지연시간을 받지 않음.
19.7.5 직렬화와 역직렬화
- PUN은 RPC로 원격 실행할 메서드에 함께 첨부할 수 있는 입력 타입에 제약이 있음.
- RPC를 통해 다른 클라이언트로 전송 가능한 대표적인 타입으로는 byte, bool, int, float, string, Vector3, Quaternion이 있음.
- 기존에 RPC에서 지원하지 않던 타입을 직업 지원하도록 정의하는 것이 가능.
- PhotonPeer.RegisterType() 메서드를 실행하고, 원하는 타입을 명시하고, 어떻게 직렬화/역직렬화할지 명시.
- PhotonPeer.RegisterType(타입, 번호, 직렬화 메서드, 역직렬화 메서드);
- 커스텀 타입은 255개까지 등록가능하며, 각각의 타입은 고유 번호를 할당받아야 함.
void Awake() {
PhotonPeer.RegisterType(typeof(Color), 128, ColorSerialization.SerializeColor, ColorSerialization.DeserializeColor);
}
19.8 완성본 테스트
- 먼저 참가하는 측이 룸을 생성하므로 호스트 역할을 맡게 됨.
19.9 마치며.
- 동기화할 게임 오브젝트는 Photon View 컴포넌트를 가지고 있어야 함.
- Photon View 컴포넌트의 Observed Components 리스트에는 관측 및 동기화할 컴포넌트가 등록됨.
- Photon Transform View 컴포넌트와 Photon Animator View 컴포넌트는 위치와 애니메이션을 Photon View 컴포넌트를 사용해 동기화함.
- MonoBehaviourPun을 상속한 스크립트는 photonView 프로퍼티를 사용해 자신의 게임 오브젝트에 추가된 Photon View 컴포넌트에 접근할 수 있음.
- RPC를 통해 원격 실행할 메서드에는 [PunRPC] 소겅이 선언되어야 함.
- photonView.RPC() 메서드로 [PunRPC] 선언된 메서드를 다른 클라이언트에서 원격 실행함.
- IPunObservable 인터페이스를 상속한 컴포넌트는 Photon View 컴포넌트를 사용해 동기화될 수 있음.
- IPunObservable 인터페이스를 상속하면 OnPhotonSerializeView() 메서드를 구현해야 함.
- OnSerializeView() 메서드에서 stream.IsWriting을 이용해 현재 스트림이 송신(쓰기) 모드인지 수신(읽기) 모드인지 검사할 수 있음.
- 로컬 오브젝트에서는 stream.IsWriting 값이 true. 이떄는 SendNext() 메서드로 값을 보냄.
- 리모트 오브젝트에서는 stream.IsWriting 값이 false. 이때는 ReceiveNext() 메서드로 값을 받음.
- 모든 클라이언트에서 동일하게 게임 오브젝트가 생성되게 하려면 PhotonNetwork.Instantiate() 메서드를 사용.
- PhotonNetwork.Instantiate() 메서드로 생성할 프리팹은 Resources 폴더에 있어야함.
- 모든 클라이언트에서 동일하게 게임 오브젝트가 파괴되려면 PhotonNetwork.Destroy() 메서드를 사용.
- 오브젝트를 네트워크 통신을 통해 주고받으려면 오브젝트를 바이트 형태로 변환해야 함.
- 직렬화는 오브젝트를 바이트 데이터로 변환.
- 역직렬화는 바이트 데이터를 원본 오브젝트로 변환.
'Unity > 레트로의 유니티 게임 프로그래밍 에센스' 카테고리의 다른 글
18장. 좀비 서바이버 멀티플레이어 : 네트워크 이론과 로비 구현 (0) 2021.05.21 17장 좀비 서바이버 : 최종 완성과 포스트 프로세싱 (0) 2021.05.20 16장 좀비 서바이버 : 생명과 좀비 AI (0) 2021.02.17 15장 좀비 서바이버 : 총과 슈터 (0) 2021.02.13 14장. 좀비 서바이버 : 레벨 아트와 플레이어 준비 (0) 2021.02.12