使用Photon PUN创建简单对战游戏

25 篇文章 10 订阅
24 篇文章 1 订阅

前言:

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 按钮函数



    /// <summary>
    /// 名字设置按钮
    /// </summary>
    public void SetNicknameButton()
    {
      
        if (nameInputField.text.Length < 2)
            return;
        PhotonNetwork.NickName = nameInputField.text;//将输入的名字上传至网络
        if(PhotonNetwork.InLobby)//判断是否在大厅内,在就显示大厅,隐藏名字面板
        {
            LobbyPanel.SetActive(true);
            NamePanel.SetActive(false);
            Debug.Log("已经在大厅中");
        }


    }

    /// <summary>
    /// 创建或加入房间按钮
    /// </summary>
    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);
        }

     
    }
    /// <summary>
    /// 当成功连接该服务器
    /// </summary>
    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;//预制体的父物体

    /// <summary>
    /// 房间更新的函数,每次房间消失或者增加,就会进行调用,所以要对消失的时候进行特殊处理
    /// </summary>
    /// <param name="roomList">返回的房间参数</param>
    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()
    {
        
    }

    /// <summary>
    /// 点击按钮,加入该房间
    /// </summary>
    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()
    {

    }
   /// <summary>
   /// 当加入房间时
   /// </summary>
    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);
        }
    }
 
    /// <summary>
    /// 当有玩家进入时,更新玩家列表
    /// </summary>
    /// <param name="newPlayer"></param>
    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);
    }
    /// <summary>
    /// 当有玩家离开时,更新玩家列表
    /// </summary>
    /// <param name="otherPlayer"></param>
    public override void OnPlayerLeftRoom(Player otherPlayer)
    {
        foreach (var item in mplayers)
        {
            if (item.GetComponentInChildren<Text>().text.Contains(otherPlayer.NickName))
            {
               
                Destroy(item);
                break;
            }
        }
    }
    /// <summary>
    /// 当离开房间时,如果房间内还有人,则设置其他人为房主
    /// </summary>
    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>();//使用字典来进行数据广播,广播字典
    /// <summary>
    /// 准备按钮事件
    /// </summary>
   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>().text);
        //    if (item.GetComponentInChildren<Text>().text.Contains(xname))
        //    {
        //        if(ready)
        //        {
        //          item.GetComponentInChildren<Text>().text= xname + "(" + "已准备" + ")";
        //        }
        //        else
        //        {
        //            item.GetComponentInChildren<Text>().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:
在这里插入图片描述
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这套联机机制,入门很简单,但是后期拓展和优化需要花点时间。
在这里插入图片描述
最后是项目源码:源码地址

  • 5
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ToDoNothing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值