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;
}