This document is about: PUN 2
SWITCH TO

PUN Classic (v1), PUN 2, Bolt는 휴업 모드입니다. Unity2022에 대해서는 PUN 2에서 서포트하지만, 신기능의 추가는 없습니다. 현재 이용중인 고객님의 PUN 및 Bolt 프로젝트는 중단되지 않고, 퍼포먼스나 성능이 떨어지는 일도 없습니다. 앞으로의 새로운 프로젝트에는 Photon Fusion 또는 Quantum을 사용해 주십시오.

7 - 플레이어 네트워킹

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

PhotonView 컴포넌트

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

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

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

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

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회) 마지막 호출이 같이 전송 되기 때문에 값들이 저장 됩니다.
수신 클라이언트는 그후 값들을 부드러운 전환을 유지 하기 위하여 순서대로 적용 시킵니다. 더 부드러운 모드 효과를 달성하기 위해서 더 많은 데이터를 전송 합니다.

사용자 입력 관리

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

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

  1. PlayerAnimatorManager 를 오픈 합니다.

  2. PlayerAnimatorManager 클래스를 MonoBehaviour 에서 MonoBehaviourPun 으로 변경 하여 photonView 컴포넌트를 노출 시킬 수 있도록 합니다.

  3. Update() 호출에서 맨 처음에 다음을 추가합니다

    C#

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

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

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

카메라 제어

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

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

  1. PlayerManager 스크립트를 오픈 합니다.

  2. Awake()Update() 메소드 사이에 다음의 코드를 추가합니다.

    C#

            /// <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() 를 호출하여 플레이어를 따라다니는 로직을 효과적으로 건네줄 것 입니다.

광선 발사 제어

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

  1. PlayerManager 스크립트를 오픈합니다.

  2. if 문장으로 입력 처리를 둘러 쌉니다.

    C#

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

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

  1. PlayerManager 스크립트를 오픈 합니다.

  2. IPunObservable 을 구현 합니다.

    C#

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

    C#

    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()를 사용 합니다.

체력 동기화

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

  1. PlayerManager 스크립트를 오픈합니다.

  2. IPunObservable.OnPhotonSerializeView 내에서 IsFiring 변수에 대해 SendNext 그리고 ReceiveNext 과 같이 Health에 대해서도 적용해줍니다.

    C#

    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 변수를 동기화를 위한 시나리오 전부입니다.

다음 파트.
이전 파트.

Back to top