오늘은 동기화 위주로 작업을 했다.
네트워크 동기화란 것이 어떤 것이 있는지 다시 한 번 공부해 보았다.
간단하게 설명하자면 이와 같이 세 가지 방법이 있다.
여기서 변동이 많은 경우에는 1번을 쓰고, 변동이 적은 경우에는 2번을 쓴다. 3번의 경우는 아무래도 게임매니저 등 전체적인 변수 선언이 필요할 때 사용한다.
그러면 인게임 맵에서 일어나는 일들은 변동이 많은 경우인가 생각해보면, UI를 띄우는 거나 맵이 이동하거나 하는 등의 내용은 결국 빈번하게 일어나는 일이 아니기 때문에 RPC를 이용해 동기화하는 것이 좋다고 판단했다.
처음에는 감이 잡히지 않았지만 RPC를 이용해 동기화하는 것이 그리 어려운 건 아니었다.
대신, 엄청난 노가다라는 것을 알게 되었다.
간단하게 사용한 예시를 하나 가져오자면 이와 같은 방식으로 진행된다.
동기화를 해야 하는 오브젝트 및 UI 등의 요소에는 PhotonView가 붙어 있어야 하며, MonoBehaviourPun으로 스크립트를 붙인 다음 할 행동을 정의해야 한다. 아래와 같이 승리 수를 표시하기 위해 게이지바를 채우는 식으로 작동하는 UI 이미지의 경우, [PunRPC]를 붙인 함수를 정의한다.
using Photon.Pun;
using UnityEngine;
using UnityEngine.UI;
public class WinnerImageFillingUI : MonoBehaviourPun
{
private Image fillImage;
private void Awake()
{
fillImage = GetComponent<Image>();
fillImage.fillAmount = 0;
}
[PunRPC]
public void FillAmountInit(float value)
{
fillImage.fillAmount = value;
}
}
이제 이 함수를 가져와서 사용해야 하는데, 해당 오브젝트의 포톤뷰를 가져와서 RPC를 사용한다.
...
PhotonView leftImageFillView;
PhotonView rightImageFillView;
private void Awake()
{
...
leftImageFillView = leftFillImage.GetComponent<PhotonView>();
rightImageFillView = rightFillImage.GetComponent<PhotonView>();
...
}
private void OnEnable()
{
Init();
}
private void Init()
{
if (PhotonNetwork.IsMasterClient)
{
ImageInit();
string winner = TestIngameManager.Instance.ReadScore(out int left, out int right);
Debug.Log("엥");
TextInit(winner, left, right);
ImageInit(left, right);
}
}
...
/// <summary>
/// 각 상황에 따른 이미지 효과를 줌
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
private void ImageInit(int left, int right)
{
if (left == 0)
{
leftImageFillView.RPC(nameof(WinnerImageFillingUI.FillAmountInit), RpcTarget.All, 0f);
}
else
{
leftImageFillView.RPC(nameof(WinnerImageFillingUI.FillAmountInit), RpcTarget.All, (float)left / 2);
}
if (right == 0)
{
rightImageFillView.RPC(nameof(WinnerImageFillingUI.FillAmountInit), RpcTarget.All, 0f);
}
else
{
rightImageFillView.RPC(nameof(WinnerImageFillingUI.FillAmountInit), RpcTarget.All, (float)right / 2);
}
if (left == 2 || right == 2)
{
AddScoreAnimation();
RoundChange();
return;
}
leftBackgroundImageView.RPC(nameof(WinnerImageUI.WinnerImageScaleChange), RpcTarget.All, 0f, roundImageShrinkDuration, gameUIManager.RoundOverPanelDuration - roundImageShrinkDuration);
rightBackgroundImageView.RPC(nameof(WinnerImageUI.WinnerImageScaleChange), RpcTarget.All, 0f, roundImageShrinkDuration, gameUIManager.RoundOverPanelDuration - roundImageShrinkDuration);
}
이와 같이 RPC로 연결하는 과정을 오늘 진행했으며, 사실 조금 의문이 들기도 했다.
나만 작업하는 것도 아니고 이렇게 하다간 RPC가 무분별하게 많이 생길 것 같은데, 이게 보통이라고 한다. 좀 더 획기적인 방법으로 연결할 방법이 없지야 않겠지만, 잘못된 방법으로 설계하고 있는 건 아니라는 것을 알았다.
이런 식으로 RPC로 연결하던 와중에 문제를 하나 발견했다.
FillAmount로 처리되어야 하는 승리 UI가 이상하게 작동하는 것이다.
단독으로 작동할 때에는 정상 작동을 하는데, 플레이어가 두 명이 되는 순간 마스터 클라이언트 쪽에서 이미지가 생겼다가 바로 사라지는 버그가 발생한 것이다.
이 문제의 원인이 무엇인가 찾아보기 위히 디버그를 찍어봤는데, 이상한 점을 발견했다.
분명 이미지 초기화는 한 번 호출했는데 두 번 호출이 되고 있었고 마스터는 처음엔 알맞게 호출했다가 초기화된 호출값을 내뱉고, 반대로 그냥 클라이언트는 처음에 초기화된 값을 내뱉었다가 나중에 알맞은 호출값을 내뱉는 것이다.
좀 더 자세하게 추적을 들어가보니, 이게 두 번 호출되고 있는 이유를 알 수 있었다.
이유는 마스터와 클라이언트 둘 다 해당 함수를 호출하고, 서로에게 신호를 보내고 있었다는 것이다. 이것 때문에 마스터는 처음엔 알맞은 값으로 내놨는데 결국 클라이언트가 보낸 신호로 덮어져서 이미지가 지워지는 현상이 발생한 것이다.
그래서 해당 함수를 마스터만 호출할 수 있도록 해야 하는 조치가 추가로 필요했다.
private void Init()
{
if (PhotonNetwork.IsMasterClient)
{
ImageInit();
string winner = TestIngameManager.Instance.ReadScore(out int left, out int right);
Debug.Log("엥");
TextInit(winner, left, right);
ImageInit(left, right);
}
}
이와 같이 적용하여 문제를 해결했다.
로프 깨짐 현상을 방지하기 위해 동기화를 시도하거나 다른 여러가지 방법을 시도해봤지만, 프레임 드랍이 발생하는 등의 문제가 발생했다.
로프 동기화는 아무래도 게임매니저가 구성되고 커스텀 프로퍼티 등의 요소를 이용하여 동기화하는 작업을 시도해야 할 것으로 예상된다.