EasyCastleUNITY

CGV 개발일지 10 (잡은 플레이어 위치 동기화) 본문

CGV(Castle Giant Virtual) 프로젝트 일지

CGV 개발일지 10 (잡은 플레이어 위치 동기화)

EasyCastleT 2023. 12. 14. 00:20

https://easycastleunity.tistory.com/208

 

CGV 개발일지 9(잡은 물체 위치 동기화)

https://easycastleunity.tistory.com/207 CGV 개발일지8 (VR 플레이어 손, 머리 위치 동기화) 전에 개발일지6 번에서는 Photon Fusion을 사용하여 멀티플레이를 구현해보고자 했었습니다. 하지만, Fusion을 사용하

easycastleunity.tistory.com

저번 개발일지에서는 잡은 물체의 위치를 동기화 하였습니다. 

 

이번에는 그와 비슷하지만 다른 PC 플레이어 위치 동기화를 해보겠습니다. 

 

VR 플레이어가 어떤 물체를 잡고 그에 따른 위치를 동기화하려면, 해당 물체에 대한 소유권(OwnerShip)을 VR플레이어가 가지고 있어야 합니다. 

 

그렇기에 다른 잡을 수 있는 물체들은 

위 이미지처럼 Ownership Transfer가 Fixed로 되어 있습니다.

프로젝트 흐름 상, 가상현실 플레이어가 다른 물체들을 만들기에 위 사진처럼 Fixed로 해놓으면,

가상현실 플레이어가 소유권을 가지고 있습니다. 

정확히는 Controller를 가지고 있습니다. 

 

그렇기에, 소유권을 가져올 필요없이 VR 플레이어가 물체를 잡은 것이 위치동기화가 됩니다.


PC 플레이어 위치 동기화

하지만 PC 플레이어는 PC 플레이어의 조작을 받기 때문에, 소유권을 PC 플레이어가 가지고 있습니다. 

PC 플레이어가 Owner를 가지고 있는 모습

그렇기에, VR 플레이어가 OVR의 Hand Grab으로 잡더라도, Owner를 기준으로 위치를 동기화 하기에, PC 플레이어에서는 자신이 잡히지 않은 것으로 보이게 됩니다. 

 

그러므로 잡는 순간 VR 플레이어가 소유권을 가져와야 합니다. 

 

소유권을 받아오려면, Ownership Transfer를 변경해야 합니다.

 저는 이 3가지 중에, Request를 응용하여 소유권을 받아오고, 다시 돌려주려고 합니다. 

 

정확히는 VR 플레이어가 Hand Grab을 통해 잡으면 소유권은 VR플레이어가 가지고, 

놓으면, 다시 소유권을 원래의 플레이어에게 돌려주려고 합니다. 

 

그래서 아래와 같이 PlayerNetworkGrabbable을 작성하고 부여해 주었습니다.

 

PlayerNetworkGrabbable

using Oculus.Interaction;
using Photon.Pun;
using Photon.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerNetworkGrabbable : MonoBehaviour, IPunOwnershipCallbacks
{
    public InteractableUnityEventWrapper eventWrapper;
    public PhotonView pv;
    private Rigidbody rb;
    [SerializeField] private bool isPhysics = false;
    public Player player;
    public bool isGrab = false;

    void Awake()
    {
        this.rb = GetComponent<Rigidbody>();
    }

    [PunRPC]
    public void PlayerInit(Player player)
    {
        this.player = player;
    }
    
    private void Start()
    {
        this.eventWrapper.WhenSelect.AddListener(() =>
        {
            Debug.Log("<color=lime>Grab</color>");
            this.pv.RequestOwnership();
            if (isPhysics)
            {
                this.isGrab = true;
                this.pv.RPC("WhenSelect", RpcTarget.OthersBuffered, null);
            }
        });
        
        this.eventWrapper.WhenUnselect.AddListener(() =>
        {
            Debug.Log("<color=lime>UnGrab</color>");

            if (isPhysics)
            {
                this.isGrab = false;
                this.pv.RPC("WhenUnSelect", RpcTarget.OthersBuffered, null);
                this.pv.RPC("WhenPlayerUnSelect", this.player, null);
            }
        });
    }

    void OnEnable()
    {
        PhotonNetwork.AddCallbackTarget(this);
    }

    void OnDisable()
    {
        PhotonNetwork.RemoveCallbackTarget(this);
    }

    [PunRPC]
    private void WhenSelect()
    {
        this.rb.useGravity = false;
        this.isGrab = true;
    }

    [PunRPC]
    private void WhenUnSelect()
    {
        this.rb.useGravity = true;
        this.isGrab = false;
    }

    [PunRPC]
    private void WhenPlayerUnSelect()
    {
        Debug.Log("PlayerUnSelect");
        this.pv.RequestOwnership();
    }

    #region OwnerShipCallback
    public void OnOwnershipRequest(PhotonView targetView, Player requestingPlayer)
    {
        if (targetView.IsMine && this.player != requestingPlayer && this.isGrab == false) //잡을 때, targetView.IsMine이 pc일 때만 TRUE
        {
            Debug.Log("<color=cyan>VR OnOwnershipRequest</color>");
            Debug.LogFormat("requestingPlayer: {0}", requestingPlayer.NickName);
            this.pv.TransferOwnership(requestingPlayer);
        }
        else if (targetView.IsMine && this.player == requestingPlayer && this.isGrab==false) //놓을 때, targetView.IsMine이 VR일 때만 TRUE
        {
            Debug.Log("<color=cyan>PC OnOwnershipRequest</color>");
            Debug.LogFormat("requestingPlayer: {0}", requestingPlayer.NickName);
            this.pv.TransferOwnership(this.player);
        }
    }

    public void OnOwnershipTransfered(PhotonView targetView, Player previousOwner)
    {
        if (targetView.IsMine)
        {
            Debug.LogFormat("<color=yellow>OnOwnershipTransfered: {0}, {1}</color>", targetView.name, previousOwner?.NickName);
        }
    }

    public void OnOwnershipTransferFailed(PhotonView targetView, Player senderOfFailedRequest)
    {
        if (!targetView.IsMine)
        {
            Debug.LogFormat("<color=yellow>OnOwnershipTransferFailed: {0}, {1}</color>", targetView.name, senderOfFailedRequest.NickName);
        }
    }
    #endregion
}

기본로직

 

PlayerInit 함수를 통해, 처음 자신을 소유하는 Player 정보를 저장합니다. 

 

그리고, 잡을 때 this.pv.RequestOwnership(); 을 통해 소유권을 요청합니다. 

소유권을 요청하면, OnOwnershipRequest 가 호출이 됩니다. 

이 OnOwnershipRequest는 호출의 대상이 된, 플레이어의 것이 호출이 됩니다. 

(만약, VR 플레이어가 Request를 하였다면, PC 플레이어의 OnOwnershipRequest가 호출이 되는것)

그 다음, this.pv.TransferOwnership(requestingPlayer); 를 통해 소유권을 VR 플레이어로 가져옵니다. 

 

놓을 때를 보면, this.pv.RPC("WhenPlayerUnSelect", this.player, null); 을 통해

미리 저장한 플레이어에서 WhenPlayerUnSelect 함수가 호출이 됩니다. 

이 함수를 통해 PC Player가 Request를 합니다. 

Request를 하면 VR 플레이어의 OnOwnershipRequest 가 호출이 됩니다. 

그리고 this.pv.TransferOwnership(this.player); 가 실행되며, 저장하고 있는 플레이어에게 소유권을 넘깁니다. 


PC 플레이어가 2명 이상인 경우

그런데 위 로직은, VR 플레이어 한명, pc 플레이어 한명, 일 때는 유효한 로직입니다. 

 

하지만 pc 플레이어가 2명 이상이면 문제가 발생합니다. 

정확히는 잡을 때까지는 문제가 없습니다. 

 

양손으로 2명의 pc 플레이어를 잡고, 놓을 때 문제가 발생합니다. 

 

바로,  OnOwnershipRequest가 전역적으로 불리다는 것입니다. 

정확히는 PC플레이어가 Request를 하면 VR 플레이어의 프로젝트에 있는 모든 OnOwnershipRequest가 호출이 됩니다. 

그래서 다른 플레이어의 있는 OnOwnershipRequest도 호출이 되면서, 

계속 잡고 있던 플레이어의 소유권이 멀쩡하던 플레이어는 갑자기 놓은 플레이어에게로 넘어가는 상황이 나오게 됩니다. 

 

그래서 해결방법을 통해, 잡혀 있는 상태와 안 잡혀 있는 상태를 구분하였습니다. 

잡으면 bool 변수 isGrab을 true로 놓으면 isGrab은 false로 만듭니다. 

 

호출순서상, 놓으면 isGrab이 false가 먼저 되고 OnOwnershipRequest가 호출이 됩니다. 

이것은 유의하여, 잡고 있는 상태 isGrab=true면, TransferOwnership이 호출이 안되도록 하였습니다. 

 

그래서 잡혀 있는 상태라면, 소유권이 변경되지 않고 잡혀 있는 플레이어의 소유권은 그대로 VR 플레이어가 가지고 있을 수 있습니다. 


시연영상

 

위 과정을 거쳐 PcPlayer를 잡은 상태로 위치 동기화 합니다. 

 

VR 시점
PC 시점