씬 전환과 클래스리스트 동기화


MultiManager.cs 소스입니다


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.UI;
using static Singleton;
using ExitGames.Client.Photon;



public class MultiManager : MonoBehaviourPunCallbacks
{
    public PhotonView PV;
    public List<PlayerInfo> playerInfos;
    public bool isStart, isEnd;




    void MasterInitPlayerInfo() 
    {
        // 게임시작시 초기화
for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
{
            Player player = PhotonNetwork.PlayerList[i];
            playerInfos.Add(new PlayerInfo(player.NickName, player.ActorNumber, 0, PhotonNetwork.Time + 3.0, false));
        }
        MasterSendPlayerInfo(INIT);
    }

    void MasterRemovePlayerInfo(int actorNum) 
    {
        // OnPlayerLeftRoom으로 방을 나갈경우 플레이어 제거
        PlayerInfo playerInfo = playerInfos.Find(x => x.actorNum == actorNum);
        playerInfos.Remove(playerInfo);
        MasterSendPlayerInfo(REMOVE);
    }

    [PunRPC]
    public void MasterReceiveRPC(byte code, int actorNum, int colActorNum) 
    {
        // 시간과 죽음여부
        PlayerInfo playerInfo = playerInfos.Find(x => x.actorNum == actorNum);
        double lifeTime = PhotonNetwork.Time - playerInfo.lifeTime;
        lifeTime = System.Math.Truncate(lifeTime * 100) * 0.01;
        playerInfo.lifeTime = lifeTime;
        playerInfo.isDie = true;


        // 자기가 아닌 라인이나 플레이어 충돌시 킬데스 증가
        if (code == DIE) 
        {
            playerInfo = null;
            playerInfo = playerInfos.Find(x => x.actorNum == colActorNum);
            ++playerInfo.killDeath;
        }

        MasterSendPlayerInfo(code);
    }



    void MasterSendPlayerInfo(byte code)
    {
        // 방장은 PlayerInfo 정렬 후 보내기
        playerInfos.Sort((p1, p2) => p2.lifeTime.CompareTo(p1.lifeTime));
        
        string jdata = JsonUtility.ToJson(new Serialization<PlayerInfo>(playerInfos));
        PV.RPC("OtherReceivePlayerInfoRPC", RpcTarget.Others, code, jdata);
    }

    [PunRPC]
    void OtherReceivePlayerInfoRPC(byte code, string jdata) 
    {
        // 다른 사람은 PlayerInfo 받기
        playerInfos = JsonUtility.FromJson<Serialization<PlayerInfo>>(jdata).target;
    }

    [PunRPC]
    void StartSyncRPC() 
    {
        isStart = true;
    }

 


    IEnumerator Loading()
    {
        S.SetTag("loadScene", true);
        while (!S.AllhasTag("loadScene")) yield return null;

        // 모두 씬에 있어야 생성할 수 있음, 에디터와 클라는 에디터가 마스터
        PhotonNetwork.Instantiate("Player", new Vector3(Random.Range(-5f, 5f), 0, 0), QI);

        while (!S.AllhasTag("loadPlayer")) yield return null;
    }

    IEnumerator Start()
    {
        yield return Loading();

        if (S.master()) 
        {
            MasterInitPlayerInfo();
            yield return new WaitForSeconds(3);
            PV.RPC("StartSyncRPC", RpcTarget.AllViaServer);
        }
    }


    public override void OnPlayerLeftRoom(Player otherPlayer)
    {
        // 마스터가 나가면 바뀐 마스터가 호출되서 성공
        if (S.master())
        {
            MasterRemovePlayerInfo(otherPlayer.ActorNumber);
        }
    }

}









NetworkManager.cs 소스입니다


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

public class NetworkManager : MonoBehaviourPunCallbacks
{
public override void OnConnectedToMaster() 
PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions { MaxPlayers = 4 }, null); 
}
}









PlayerScript.cs 소스입니다


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

public class PlayerScript : MonoBehaviourPun, IPunObservable
{
MultiManager MM;
PhotonView PV;
Vector3 curPos;
bool isDie;
List<GameObject> Lines = new List<GameObject>();



void Init() 
{
MM = FindObjectOfType<MultiManager>();
S.SetTag("loadPlayer", true);
PV = photonView;
}

void LineSpawn() 
{
// 두명이상 접속시 생성
Lines.Add(PhotonNetwork.Instantiate("Line", transform.position + Vector3.up * 2, QI));
}

void Start()
{
Init();

if (!PV.IsMine) return;

LineSpawn();
}



bool forbidden()
{
return !PV.IsMine || !MM.isStart || isDie;
}

void OtherMove()
{
if ((transform.position - curPos).sqrMagnitude >= 100) transform.position = curPos;
else transform.position = Vector3.Lerp(transform.position, curPos, Time.deltaTime * 10);
}

void Move()
{
transform.Translate(new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0) * Time.deltaTime * 10);
}

void Update()
{
if(!PV.IsMine) OtherMove();
if (forbidden()) return;

Move();
if (Input.GetKeyDown(KeyCode.Space)) LineSpawn();
}



void OtherSendMaster(PhotonView colPV) 
{
// 자기가 아닌 라인이나 플레이어 충돌
if (colPV != null && S.actorNum() != colPV.Owner.ActorNumber) 
MM.PV.RPC("MasterReceiveRPC", RpcTarget.MasterClient, DIE, S.actorNum(), colPV.Owner.ActorNumber);

// 벽 충돌
else MM.PV.RPC("MasterReceiveRPC", RpcTarget.MasterClient, DIEWALL, S.actorNum(), 0);
}

public void OnTriggerStay(Collider col)
{
// 충돌시 방장한테 전달
if (forbidden()) return;
isDie = true;

OtherSendMaster(col.GetComponent<PhotonView>());
S.destroy(Lines);
S.SetPos(transform, new Vector3(0, 100, 0));
}



public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
stream.SendNext(transform.position);
else 
curPos = (Vector3)stream.ReceiveNext(); 
}
}









Singleton.cs 소스입니다


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.SceneManagement;
using Hashtable = ExitGames.Client.Photon.Hashtable;


public class Singleton : MonoBehaviourPun
{
public const byte INIT = 0, REMOVE = 1, DIEWALL = 2, DIE = 3;
public static readonly Quaternion QI = Quaternion.identity;



#region 싱글톤
public static Singleton S;
void Awake()
{
if (null == S)
{
S = this;
DontDestroyOnLoad(this);
}
else Destroy(this);
}
#endregion



#region Set Get
public bool master() => PhotonNetwork.LocalPlayer.IsMasterClient;

public int actorNum(Player player = null)
{
if (player == null) player = PhotonNetwork.LocalPlayer;
return player.ActorNumber;
}

public void destroy(List<GameObject> GO)
{
for (int i = 0; i < GO.Count; i++) PhotonNetwork.Destroy(GO[i]);
}

public void SetPos(Transform Tr, Vector3 target) 
{
Tr.position = target;
}

public void SetTag(string key, object value, Player player = null)
{
if (player == null) player = PhotonNetwork.LocalPlayer;
player.SetCustomProperties(new Hashtable { { key, value } });
}

public object GetTag(Player player, string key)
{
if (player.CustomProperties[key] == null) return null;
return player.CustomProperties[key].ToString();
}

public bool AllhasTag(string key)
{
for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
if (PhotonNetwork.PlayerList[i].CustomProperties[key] == null) return false;
return true;
}
#endregion



void Setting() 
{
Screen.SetResolution(960, 540, false);
PhotonNetwork.NickName = "플레이어" + Random.Range(0, 100);
PhotonNetwork.AutomaticallySyncScene = true;
PhotonNetwork.SendRate = 40;
PhotonNetwork.SerializationRate = 20;
PhotonNetwork.AutomaticallySyncScene = true;
}

void Start()
{
Setting();
}



void OnGUI()
{
GUI.skin.label.fontSize = 30;
GUI.skin.button.fontSize = 30;
GUILayout.BeginVertical("Box", GUILayout.Width(400), GUILayout.MinHeight(400));


GUILayout.Label("서버시간 : " + PhotonNetwork.Time);
GUILayout.Label("상태 : " + PhotonNetwork.NetworkClientState);
GUILayout.Label("씬 : " + SceneManager.GetActiveScene().name);


if (PhotonNetwork.IsConnected)
{
if (GUILayout.Button("연결끊기")) PhotonNetwork.Disconnect();
}
else
{
if (GUILayout.Button("접속")) PhotonNetwork.ConnectUsingSettings();
}


if (PhotonNetwork.InRoom && master()) 
{
if (GUILayout.Button("로비 씬")) PhotonNetwork.LoadLevel("LobbyScene");
if (GUILayout.Button("게임 씬")) PhotonNetwork.LoadLevel("GameScene");
}


GUILayout.EndVertical();
}
}










StaticScript.cs 소스입니다


using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[System.Serializable]
public class Serialization<T>
{
    public Serialization(List<T> _target) => target = _target;
    public List<T> target;
}



[System.Serializable]
public class PlayerInfo
{
    public PlayerInfo(string _nickName, int _actorNum, int _killDeath, double _lifeTime, bool _isDie)
    {
        nickName = _nickName;
        actorNum = _actorNum;
        killDeath = _killDeath;
        lifeTime = _lifeTime;
        isDie = _isDie;
    }

    public string nickName;
    public int actorNum;
    public int killDeath;
    public double lifeTime;
    public bool isDie;
}