photon PUN是一款用于实现联网对战的插件,通过简单的设计,就可以实现加入大厅,加入房间,并且进行对战,玩家可以同步动画和位置等参数,同时可以通过RPC机制,广播参数,实现房间内的玩家消息共享。
1.打开photon官网,再打开账号,登录。
2.点击Your Applications,创建应用,选择PUN,按要求填写,然后再复制Appid,打开unity创建项目。
3.在unity的资源商店中搜索photon PUN,下载导入。
4.粘贴刚才复制的appid,点击setup project—close
5.新建一个场景,添加如下的UI组件,一个是名字输入面板,点击之后进入大厅。
6.在场景中新建一个空物体,命名NetWorkManager,同样新建一个脚本NetworkManager,填充命名空间PUN,更改继承类为MonoBehaviourPunCallbacks,首先在start函数中启动。连接服务器,具体的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using UnityEngine.UI;
using Photon.Realtime;
public class NetworkManager : MonoBehaviourPunCallbacks
{
#region 私有变量
#endregion
#region 公开变量
public static NetworkManager _instance;//单例
public InputField nameInputField;//名字输入框
public InputField RoomnameInputField;//房间名输入框
public GameObject readyBtn;//准备按钮
public GameObject startBtn;//开始游戏按钮
public GameObject NamePanel;//名字设置面板
public GameObject LobbyPanel;//大厅面板
public GameObject RoomPanel;//房间面板
public GameObject StartInitPanel;//开始初始化面板
#endregion
#region Mono函数
private void Awake()
{
if (_instance == null)
{
_instance = this;
}
}
private void Start()
{
PhotonNetwork.ConnectUsingSettings();//初始化设置,连接服务器
}
private void Update()
{
if (PhotonNetwork.LocalPlayer.IsMasterClient)//判断是否是房主,是就显示开始游戏按钮,不是就显示准备按钮
{
readyBtn.SetActive(false);
startBtn.SetActive(true);
}
else
{
readyBtn.SetActive(true);
startBtn.SetActive(false);
}
}
#endregion
#region 按钮函数
///
/// 名字设置按钮
///
public void SetNicknameButton()
{
if (nameInputField.text.Length < 2)
return;
PhotonNetwork.NickName = nameInputField.text;//将输入的名字上传至网络
if(PhotonNetwork.InLobby)//判断是否在大厅内,在就显示大厅,隐藏名字面板
{
LobbyPanel.SetActive(true);
NamePanel.SetActive(false);
Debug.Log("已经在大厅中");
}
}
///
/// 创建或加入房间按钮
///
public void joinOrCreateRoomBtn()
{
if(RoomnameInputField.text.Length<=2)
{
return;
}
LobbyPanel.SetActive(false);
RoomOptions roomOptions = new RoomOptions { MaxPlayers = 4 };//房间最大人数4人
PhotonNetwork.JoinOrCreateRoom(RoomnameInputField.text, roomOptions, default);
RoomPanel.SetActive(true);
}
#endregion
#region Photon函数
public override void OnJoinedRoom()
{
if (PhotonNetwork.LocalPlayer.IsMasterClient)//判断是否是房主,是就显示开始游戏按钮,不是就显示准备按钮
{
readyBtn.SetActive(false);
startBtn.SetActive(true);
}
else
{
readyBtn.SetActive(true);
startBtn.SetActive(false);
}
}
///
/// 当成功连接该服务器
///
public override void OnConnectedToMaster()
{
NamePanel.SetActive(true);//显示名字面板
StartInitPanel.SetActive(false);//隐藏开始初始化面板
PhotonNetwork.JoinLobby();//加入大厅
Debug.Log("加入大厅成功");
}
#endregion
}
7.新建一个RoomManagerList的空物体,同理新建一个RoomMnaager脚本,主要用来管理房间列表的显示:
using Photon.Pun;
using Photon.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RoomManager : MonoBehaviourPunCallbacks
{
public GameObject roomNamePrefab;//房间显示的预制体
public Transform gridLayout;//预制体的父物体
///
/// 房间更新的函数,每次房间消失或者增加,就会进行调用,所以要对消失的时候进行特殊处理
///
/// 返回的房间参数
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
Debug.Log("roomlist:" + roomList.Count);
for (int i = 0; i < gridLayout.childCount; i++)//遍历父物体下的子物体
{
if (gridLayout.GetChild(i).gameObject.GetComponentInChildren<Text>().text == roomList[i].Name)//有相同的物体
{
Destroy(gridLayout.GetChild(i).gameObject);//销毁
if (roomList[i].PlayerCount == 0)//如果房间玩家为0
{
roomList.Remove(roomList[i]);//移除该房间
}
}
}
foreach (var room in roomList)//遍历生成房间的显示
{
GameObject newRoom = Instantiate(roomNamePrefab, gridLayout.position, Quaternion.identity, gridLayout);//生成房间按钮
newRoom.GetComponent<RoomBtn>().roomName = room.Name;//设置房间名字
newRoom.GetComponentInChildren<Text>().text = room.Name + "(" + room.PlayerCount + "/4)";//显示参数
}
}
}
8.为了点击房间按钮,可以进入该房间,所以给按钮添加脚本事件,新建一个脚本RoomBtn,房间按钮是一个预制体,需要事先进行设置,在scroll view里面的content下新建按钮,然后记得给content添加Vertical layout group和content size fitter组件,同理在玩家面板,也需要设计玩家对应的预制体,方法类似,代码如下:
using Photon.Pun;
using Photon.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RoomBtn : MonoBehaviourPunCallbacks
{
public string roomName;
void Start()
{
}
///
/// 点击按钮,加入该房间
///
public void JoinRoomBtn()
{
PhotonNetwork.JoinRoom(roomName);//加入房间
NetworkManager._instance.RoomPanel.SetActive(true);//显示房间面板
NetworkManager._instance.LobbyPanel.SetActive(false);//隐藏大厅面板
}
}
9.在进入房间后,需要对玩家进行更新,同时要判断是不是房主,如果是,房主则可以进行开始游戏,如果不是,则显示一个准备按钮。这里其实也可以做一个聊天的联机,添加以下组件,作为聊天的输入和显示。
脚本InRoom代码:
using Photon.Pun;
using Photon.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class InRoom : MonoBehaviourPunCallbacks
{
public GameObject playerInfoPrefab;//玩家按钮预制体
public Transform conlayout;//父物体
List<GameObject> mplayers = new List<GameObject>();
Player[] allplayers;
bool ready = false;
public InputField contentInput;//聊天输入框
public GameObject textPrefab;//文本预制体
public Transform layoutContent;//父物体
void Start()
{
}
///
/// 当加入房间时
///
public override void OnJoinedRoom()
{
allplayers = PhotonNetwork.PlayerList;
foreach (var item in allplayers)
{
GameObject obj = Instantiate(playerInfoPrefab, conlayout);
m_PlayerInfo _PlayerInfo = obj.GetComponent<m_PlayerInfo>();
_PlayerInfo.playerName = item.NickName;
obj.GetComponentInChildren<Text>().text = item.NickName + "(" + "未准备" + ")";
mplayers.Add(obj);
}
}
///
/// 当有玩家进入时,更新玩家列表
///
///
public override void OnPlayerEnteredRoom(Player newPlayer)
{
GameObject obj = Instantiate(playerInfoPrefab, conlayout);
m_PlayerInfo _PlayerInfo = obj.GetComponent<m_PlayerInfo>();
_PlayerInfo.playerName = newPlayer.NickName;
obj.GetComponentInChildren<Text>().text = newPlayer.NickName+"("+"未准备"+")";
mplayers.Add(obj);
}
///
/// 当有玩家离开时,更新玩家列表
///
///
public override void OnPlayerLeftRoom(Player otherPlayer)
{
foreach (var item in mplayers)
{
if (item.GetComponentInChildren<Text>().text.Contains(otherPlayer.NickName))
{
Destroy(item);
break;
}
}
}
///
/// 当离开房间时,如果房间内还有人,则设置其他人为房主
///
public override void OnLeftRoom()
{
foreach (var item in mplayers)
{
if (item.GetComponentInChildren<Text>().text.Contains(PhotonNetwork.LocalPlayer.NickName))
{
if (PhotonNetwork.LocalPlayer.IsMasterClient)
{
foreach (var p in allplayers)
{
if (p != PhotonNetwork.LocalPlayer)
{
PhotonNetwork.CurrentRoom.SetMasterClient(p);
break;
}
}
}
Destroy(item);
break;
}
}
}
Dictionary<string, bool> IsPlayerReady = new Dictionary<string, bool>();//使用字典来进行数据广播,广播字典
///
/// 准备按钮事件
///
public void SetReadyBtn()
{
foreach (var item in mplayers)
{
m_PlayerInfo playerinfo = item.GetComponent<m_PlayerInfo>();
if (!IsPlayerReady.ContainsKey(playerinfo.playerName))
{
IsPlayerReady.Add(playerinfo.playerName, false);
}
}
ready = !ready;
foreach (var item in IsPlayerReady)
{
if(item.Key== PhotonNetwork.LocalPlayer.NickName)
{
IsPlayerReady[item.Key] = ready;
break;
}
}
photonView.RPC("ReadyBtn", RpcTarget.All,PhotonNetwork.LocalPlayer.NickName,IsPlayerReady);
}
[PunRPC]
void ReadyBtn(string xname,Dictionary<string,bool> keyValuePairs)
{
foreach (var item in keyValuePairs)
{
if(item.Value==true)
{
foreach (var p in mplayers)
{
if(p.GetComponentInChildren<Text>().text.Contains(item.Key))
{
p.GetComponentInChildren<Text>().text = item.Key + "(" + "已准备" + ")";
break;
}
}
}
else
{
foreach (var p in mplayers)
{
if (p.GetComponentInChildren<Text>().text.Contains(item.Key))
{
p.GetComponentInChildren<Text>().text = item.Key + "(" + "未准备" + ")";
break;
}
}
}
}
//foreach (var item in mplayers)
//{
// Debug.Log(item.GetComponentInChildren().text);
// if (item.GetComponentInChildren().text.Contains(xname))
// {
// if(ready)
// {
// item.GetComponentInChildren().text= xname + "(" + "已准备" + ")";
// }
// else
// {
// item.GetComponentInChildren().text = xname + "(" + "未准备" + ")";
// }
// break;
// }
//}
}
//发送消息
public void SendMessInfoBtn()
{
string info = PhotonNetwork.LocalPlayer.NickName + " :" + contentInput.text;
photonView.RPC("SendMess", RpcTarget.All,info);
}
[PunRPC]
void SendMess(string mess)
{
GameObject textobj = Instantiate(textPrefab, layoutContent);
textobj.GetComponentInChildren<Text>().text = mess;
}
//开始游戏
public void StartGameButton()
{
photonView.RPC("LoadGameScene", RpcTarget.All);
}
[PunRPC]
void LoadGameScene()
{
PhotonNetwork.LoadLevel(1);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class m_PlayerInfo : MonoBehaviour
{
public string playerName;
}
10.新建一个场景,记得添加在build setting里面。在这个场景中,搭一个简单的地面,同时新建一个胶囊体,作为玩家的预制体Player。添加以下组件,将他拖到以下的文件夹中,删除场景中的Player:
11.新建一个脚本,命名为GameManager,主要是用来生成玩家,记得在场景中添加,代码如下:
using Photon.Pun;
using Photon.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviourPunCallbacks
{
public static GameManager _insatnce;
// Start is called before the first frame update
void Awake()
{
_insatnce = this;
int index = 0;
Player[] players = PhotonNetwork.PlayerList;
foreach (var item in players)
{
if (item.NickName == PhotonNetwork.NickName)
{
index = item.ActorNumber - 1;
// PhotonNetwork.Instantiate("Player", m_spawns.mySpawns[index].spawnPos.position, Quaternion.identity);
PhotonNetwork.Instantiate("Player", Vector3.zero, Quaternion.identity);
Debug.Log("userid:" + item.ActorNumber);
}
}
}
}
12.新建mPlayer脚本,用来控制玩家移动
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class mPlayer : MonoBehaviourPunCallbacks
{
void Update()
{
if (photonView.IsMine)
{
MoveController();
}
}
public void MoveController()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
transform.Translate(new Vector3(h, 0, v) * 10 * Time.deltaTime);
}
}
13.这样,一个基础的联机机制的小demo就完成了。其实整体来说,photon这套联机机制,入门很简单,但是后期拓展和优化需要花点时间。
最后是项目源码:源码地址