👏

Photonでマルチプレイ同期するタイマーの実装

2022/09/16に公開

環境

  • Unity
  • PUN2

やりたいこと

  • マルチプレイで同期するタイマーの実装
  • 誰でもスタート/ストップでき、その状況はルーム全員に共有される
  • 後からルームに入った人も、その時点でのタイマーの状態に同期する

設計

  • PhotonのRoomのCustomPropertiesを基軸とする(残り時間と、タイマーが動いているか、を渡す)
  • あるLocalPlayerがタイマーをStart/Stopするときに、そのLocalPlayerがRoomのCustomPropertiesを更新する。
  • PunCallbackのOnRoomPropertiesUpdateを用いて、Roomの全員が直近に更新されたCustomPropertiesを受け取る。(ここまでで、その時点でRoomにいるプレイヤー全員がタイマーを同期することが可能になる)
  • 他のPlayerがRoomにJoinするタイミングで、RoomのMasterClientがCustomPropertiesをアップデートする。これにより、後からRoomのJoinしたプレイヤーにもタイマーが同期される。

コード

(自分のサービスのコードの重要部分を抜粋したのでこのままでは動かない)

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

public class TimerManager : MonoBehaviourPunCallbacks
{

    // トータル制限時間
    private float remainingTimeSecond;

    [SerializeField]
    private float maxTimeMinute;

    private bool isCounting = false;

    // PhotonのCustomProperties用キー定数
    private const string REMAINING_TIME_SECOND_KEY = "remainingTime";
    private const string IS_COUNTING_KEY = "isCounting";

    void Update()
    {
        if (!isCounting)
        {
            return;
        }

        // 制限時間が0秒以下なら何もしない
        if (remainingTimeSecond <= 0f)
        {
            return;
        }

        CountDown();
    }

    private void CountDown()
    {
        remainingTimeSecond -= Time.deltaTime;
	
	// Scene内のUIに残り時間を表示する
        DisplayRemainingTime();
    }

    // 一時停止
    public void Pause()
    {
        isCounting = false;
        SetRoomCustomPropertiesForTimer();
    }

    // 再開
    public void Resume()
    {
        isCounting = true;
        SetRoomCustomPropertiesForTimer();
    }

    private void DisplayRemainingTime()
    {
        // Scene内のUIに残り時間を表示する
    }

    private void SetRoomCustomPropertiesForTimer()
    {
        var props = new ExitGames.Client.Photon.Hashtable();
        props[REMAINING_TIME_SECOND_KEY] = remainingTimeSecond;
        props[IS_COUNTING_KEY] = isCounting;
        PhotonNetwork.CurrentRoom.SetCustomProperties(props);
    }

    private void SetTimerUI()
    {
        // UIのOnOffなどを制御
    }

    #region PunCallbacks
    public override void OnPlayerEnteredRoom(Player newPlayer)
    {
        if (PhotonNetwork.IsMasterClient)
        {
            SetRoomCustomPropertiesForTimer();
        }
    }

    public override void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
    {
        if (propertiesThatChanged[REMAINING_TIME_SECOND_KEY] != null)
        {
            remainingTimeSecond = (float)propertiesThatChanged[REMAINING_TIME_SECOND_KEY];
        }

        if (propertiesThatChanged[IS_COUNTING_KEY] != null)
        {
            isCounting = (bool)propertiesThatChanged[IS_COUNTING_KEY];
            SetTimerUI();
        }
    }
    #endregion
}

Discussion