7 - 플레이어 네트워킹

이 섹션에서는 "Player" 프리팹을 변경 할 것 입니다. 우리는 지금까지 플레이어를 생성했지만 PUN 환경에서 사용할 때 정상적으로 동작할 수 있도록 변경 할 것 입니다. 변경 사항은 매우 적지만 개념은 매우 중요 합니다. 따라서 이 섹션은 정말 정말 중요 합니다.

Contents

PhotonView 컴포넌트

첫 번째, 가장 중요한 것으로 PhotonView 컴포넌트의 프리팹이 있어야 합니다. PhotonView는 각 컴퓨터들의 다양한 인스턴스들을 하나로 연결해 주며 어떤 컴포넌트들을 관찰하고 어떻게 관찰 할지를 정의 해 줍니다.

  1. My Robot Kyle 에 PhotonView 컴포넌트를 추가 합니다.
  2. Observe OptionUnreliable On Change 로 설정 합니다.
  3. 효과를 내기 위해서 PhotonView 가 무엇인가를 관찰 할 필요가 있다고 경고를 주는 것에 주목 하세요.

이제 어떤 것을 관찰 할지를 설정 후 이 PhotonView 컴포넌트로 되돌아와 설정을 마칠 것 입니다.

Back To Top

Transform 동기화

우리가 동기화할 명백한 특징은 캐릭터의 Position 과 Rotation 이며 플레이어들이 돌아다니는것이 다른 컴퓨터에서 다른 플레이어의 이동, 회전이 유사한 방식으로 표현 되어야 합니다.

자신의 스크립트에서 Transform 컴포넌트를 직접 관찰할 수도 있지만, 네트워크 지연과 데이터 동기화가 되는 효과 등 많은 어려움을 겪게 될 것 입니다. 다행스럽게도 이러한 일반적인 작업을 더 쉽게 만들기 위해 [Photon Transform View] 컴포넌트를 사용할 예정 입니다. 기본적으로 어려운 작업은 이 컴포넌트가 알아서 해 줍니다.

  1. 'My Robot Kyle' 프리팹에 PhotonTransformView 를 추가 합니다.
  2. 헤더 타이틀에서 PhotonTransformViewPhotonView 컴포넌트의 Observable component 항목으로 드래그앤 드롭 합니다.
  3. 이제 PhotonTransformView 에서 Synchronize Position 을 체크 합니다.
  4. Synchronize Position에서 "Lerp Value for Interpolation Option" 을 선택 합니다.
  5. Lerp Speed 를 10 으로 설정 합니다(값이 더 많을 수록 더 빨리 따라 잡게 됩니다)
  6. SynchronizeRotation를 체크 합니다.

PhotonTransformView Prefab
PhotonTransformView Prefab

팁: 하위 섹션내의 파란 책은 헬프 링크입니다. 클릭하여 정보를 보고, 다양한 설정과 효과에 대해서 학습 하세요.
PhotonTransformView Help
PhotonTransformView Help

Back To Top

Animator 동기화

PhotonAnimatorView는 성가신 네트워크 설정을 만들어 주어 많은 시간과 고통을 줄여 줄 것입니다. PhotonAnimatorView는 어떤 레이어 가중치와 어떤 파라미터들을 동기화 할지를 정의 할 수 있도록 해 줍니다. 레이어 가중치는 게임을 진행하는 동안 변경되었다면 동기화 되기 위해서만 필요 합니다. 그리고 전혀 동기화 되지 않는다면 없어질 가능성이 있습니다. 파라미터들도 동일 합니다. 때로는 다른 팩터들로 부터 애니메이터 값을 유도 할 수도 있습니다. 속도 값은 이에 대한 좋은 예제 입니다. 이 값을 정확하게 동기화 할 필요는 없으나 값을 평가하기 위해 위치의 변경에 대하여 동기화 되도록 사용할 수 있습니다. 가능하면 가장 잘 해낼 수 있는 작은 파라미터로 동기화 하도록 해 주세요.

  1. My Robot Kyle 프리팹에 PhotonAnimatorView를 추가 합니다.
  2. PhotonAnimatorView 헤더 타이틀을 드래그 하여 PhotonView 컴포넌트 안의 새로운 Observable 컴포넌트 항목에 드롭 합니다.
  3. 이제 Synchronized Parameters 에서 SpeedDiscrete 로 설정 합니다.
  4. DirectionDiscrete 로 설정 합니다.
  5. JumpDiscrete 로 설정 합니다.
  6. HiDisabled 로 설정 합니다.

Observe PhotonAnimatorView
Observe PhotonAnimatorView

각 값은 disable 될 수 있으며 각각 또는 연속적으로 동기화 될 수 있습니다. 우리의 경우에 있어서 Hi 파라미터를 사용하지 않으므로 이것을 disable 할 것이며 대역폭을 절약 합니다.

Discrete synchronization 의 의미는 초당 10번 값이 전송된다는 것 입니다(OnPhotonSerializeView 에서). 수신 클라이언트들은 이 값을 로컬 애니메이터로 전달 합니다.

Continuous 동기화의 의미는 매 프레임마다 PhotonAnimatorView 를 수행 한다는 것 입니다. OnPhotonSerializeView 이 호출 되었을 때(초당 10회) 마지막 호출이 같이 전송 되기 때문에 값들이 저장 됩니다. 수신 클라이언트는 그후 값들을 부드러운 전환을 유지 하기 위하여 순서대로 적용 시킵니다. 더 부드러운 모드 효과를 달성하기 위해서 더 많은 데이터를 전송 합니다.

Back To Top

사용자 입력 관리

네트워크상의 사용자 컨트롤에서 중요한 관점은 모든 플레이어에 대해 동일한 프리팹으로 인스턴스가 생성 되었지만 실제로 컴퓨터 앞에서 플레이 하고 있는 사용자는 그 중 하나라는 점 입니다. 모든 다른 인스턴스들은 다른 컴퓨터에서 플레이 하고 있는 다른 사용자를 나타내고 있는 것 입니다. 따라서 첫 번째 어려운 점은 "입력 관리" 입니다. 어떻게 모든 사용자가 아닌 하나의 인스턴스에 입력을 사용할 수 있도록 하며 어떤 플레이어가 내 것인지 어떻게 알 수 있을까요? isMine 개념을 상기 시켜 보세요.

전에 생성 했던 PlayerAnimatorManager 스크립트를 편집 합니다. 현재 형태에서는 차이를 알 수가 없으니 구현 해 보도록 합시다.

  1. PlayerAnimatorManager 를 오픈 합니다.
  2. PlayerAnimatorManager 클래스를 MonoBehaviour 에서 MonoBehaviourPun 으로 변경 하여 photonView 컴포넌트를 노출 시킬 수 있도록 합니다.
  3. Update() 호출에서 맨 처음에 다음을 추가합니다

        if (photonView.IsMine == false && PhotonNetwork.IsConnected == true)
    {
        return;
    }
  4. PlayerAnimatorManager 스크립트를 저장합니다.

    좋습니다. 인스턴스가 'client' 어플리케이션에서 제어되고 있다면 photonView.IsMine은 true일 것 입니다. 이 의미는 이 인스턴스가 어플리케이션 내에서 물리적으로 이 컴퓨터 앞에서 플레이 하고 있는 사람을 나타낸다는 것 입니다. 만약 false 이면 아무 것도 하지 않고 PhotonView 컴포넌트에 의해서만 transform 과 전에 설정한 애니메이터 컴포넌트들을 동기화 할 것 입니다.

    하지만 if 문장에서 왜 PhotonNetwork.IsConnected == true 가 있어야 할까요? 예. :) 개발 단계에서 이 프리팹을 연결 없이 테스트 하고 싶을 수도 있기 때문입니다. 예를 들어 더미 신에서 네트워킹 자체 기능에 관련되지 않은 것을 생성하고 코드를 검증 하기 위해서 입니다. 이 추가적인 표현식을 통해서 연결되지 않았어도 입력을 허용하도록 할 것 입니다. 매우 간단한 트릭이고 개발 기간동안에 작업흐름을 좋게 할 수 있습니다.

Back To Top

카메라 제어

입력과 동일한 것으로 플레이어는 게임 뷰 하나만 있습니다. 따라서 카메라가 다른 플레이어들이 아닌 로컬 플레이어를 따라 다니도록 하는 CameraWork 스크립트가 필요 합니다. 따라서 CameraWork 가 언제부터 따라 다닐 지를 정의하는 기능이 있는 이유 입니다.

CameraWork 컴포넌트를 제어 할 수 있도록 PlayerManager 스크립트를 변경 해 보겠습니다.

  1. PlayerManager 스크립트를 오픈 합니다.
  2. Awake()Update() 메소드 사이에 다음의 코드를 추가합니다.

                /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during initialization phase.
            /// </summary>
            void Start()
            {
                CameraWork _cameraWork = this.gameObject.GetComponent<CameraWork>();                
                if (_cameraWork != null)
                {
                    if (photonView.IsMine)
                    {
                        _cameraWork.OnStartFollowing();
                    }
                }
                else
                {
                    Debug.LogError("<Color=Red><a>Missing</a></Color> CameraWork Component on playerPrefab.", this);
                }
            }
  3. PlayerManager 스크립트를 저장합니다.

먼저 우리가 예상하고 있는 CameraWork 컴포넌트를 얻습니다. 만약 찾을 수 없으면 오류를 로그에 기록 합니다. 그리고 나서 photonView.IsMinetrue 이면 이 인스턴스를 따라가야 할 필요가 있다는 의미 입니다. 그래서 _cameraWork.OnStartFollowing() 를 호출 하여 신의 카메라가 바로 이 인스턴스를 따라 가도록 만듭니다.

다른 모든 플레이어 인스턴스들의 photonView.IsMinefalse 로 설정되어 있을 것이므로 각각의 _cameraWork 는 아무것도 하지 않을 것 입니다.

이 작업의 마지막 사항입니다:

  1. My Robot Kyle 프리팹의 CameraWork 컴포넌트의 Follow on Start를 Diable 합니다.

CameraWork FollowOnStart Off
CameraWork FollowOnStart Off

이것은 위에서 설명한것과 같이 PlayerManager 스크립트가 _cameraWork.OnStartFollowing() 를 호출하여 플레이어를 따라다니는 로직을 효과적으로 건네줄 것 입니다.

Back To Top

광선 발사 제어

발사는 위에서 언급된 입력의 기본을 따릅니다. photonView.IsMinetrue 인 경우에만 동작 해야 합니다.

  1. PlayerManager 스크립트를 오픈합니다.
  2. if 문장으로 입력 처리를 둘러 쌉니다.

        if (photonView.IsMine) 
    {
        ProcessInputs ();
    }
  3. PlayerManager 스크립트를 저장 합니다.

이것을 테스트 할 때 localplayer 발사만 볼 수 있습니다. 다른 인스턴스들도 발사가 필요 합니다! 네트워크를 경유하여 발사를 동기화 해주는 메카니즘이 필요 합니다. 이것을 하기 위해서 수동으로 IsFiring boolean 값을 동기화 할 것 입니다. 지금까지는 우리의 변수들의 내부 동기화를 PhotonTransformViewPhotonAnimatorView 로 했습니다. Unity 인스펙터를 통해 편리하게 노출 했지만 여기에서는 게임에 매우 특수한 것이 필요 하므로 이것을 수동으로 할 것 입니다.

  1. PlayerManager 스크립트를 오픈 합니다.
  2. IPunObservable 을 구현 합니다.

        public class PlayerManager : MonoBehaviourPunCallbacks, IPunObservable
    {
        #region IPunObservable implementation
    
    
        public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
        {
        }
    
    
        #endregion
  3. IPunObservable.OnPhotonSerializeView 안에서 다음 코드를 추가 합니다.

        if (stream.IsWriting)
    {
        // We own this player: send the others our data
        stream.SendNext(IsFiring);
    }
    else
    {
        // Network player, receive data
        this.IsFiring = (bool)stream.ReceiveNext();
    }
  4. PlayerManager 스크립트를 저장 합니다.

  5. 유니티 에디터로 돌아가서 에셋에서 My Robot Kyle 프리팹을 선택하고 PhotonView 내의 observe entry를 추가하고 그 위에 PlayerManager 를 드래그합니다.

Observe PhotonAnimatorView
Observe PlayerManager

마지막 단계가 없으면, PhotonView에서 관찰되지 않기 때문에 IPunObservable.OnPhotonSerializeView 는 절대로 호출 되지 않습니다.

IPunObservable.OnPhotonSerializeView 메소드 내에서 stream 변수를 전달 했습니다. 이것은 네트워크를 통하여 전송될 것 이고 이 호출로 데이터를 읽고 쓸 수 있게 됩니다. localPlayer (photonView.IsMine == true) 일 때 write 할 수 있으며 그렇지 않으면 read 합니다.

스트림 클래스는 무엇을 하는지 알 수 있는 헬퍼를 가지고 있으므로 stream.isWriting 을 통하여 현재 인스턴스 케이스에서 무엇이 올지 예측 합니다. 데이터를 write 하는 것이 예상되면, 데이터 직렬화의 고된 작업이 필요 없는 편리한 메소드인 stream.SendNext() 를 이용하여 IsFiring value 를 data 의 스트림에 추가합니다. 데이터를 read 하는 것이 예상 되면 stream.ReceiveNext()를 사용 합니다.

Back To Top

체력 동기화

네트워킹에서 플레이어 기능을 업데이트하는 것을 마치려면 체력 값을 동기화하여 각 플레이어의 인스턴스가 올바른 체력값을 갖도록 하는 것 입니다. 이것은 위에서 다루었던 IsFiring 값과 동일한 원리입니다.

  1. PlayerManager 스크립트를 오픈합니다.
  2. IPunObservable.OnPhotonSerializeView 내에서 IsFiring 변수에 대해 SendNext 그리고 ReceiveNext 과 같이 Health에 대해서도 적용해줍니다.

        if (stream.IsWriting)
    {
        // We own this player: send the others our data
        stream.SendNext(IsFiring);
        stream.SendNext(Health);
    }
    else
    {
        // Network player, receive data
        this.IsFiring = (bool)stream.ReceiveNext();
        this.Health = (float)stream.ReceiveNext();
    }
  3. PlayerManager 스크립트를 저장합니다.

이것이 Health 변수를 동기화를 위한 시나리오 전부입니다.

다음 파트.
이전 파트.

기술문서 TOP으로 돌아가기