• PUN,即Photon Unity Networking,它是一个Unity多人游戏插件包。它提供了身份验证选项、匹配,以及快速、可靠的通过我们的Photon后端实现的游戏内通信。 在实现聊天功能之前,你需要把PUN导入到你的项目中来,并...

    PUN,即Photon Unity Networking,它是一个Unity多人游戏插件包。它提供了身份验证选项、匹配,以及快速、可靠的通过我们的Photon后端实现的游戏内通信。

    在实现聊天功能之前,你需要把PUN导入到你的项目中来,并完成玩家加入房间等功能。

     

    现在,我已经写好了一个房间,并让两个玩家加入了房间内

    房间内有两个玩家

     

    并且写好了聊天框,聊天框要挂载photonview组件

    聊天框

    photonview

     

    下面是关键代码

    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using System.Collections;
    
    [RequireComponent(typeof(PhotonView))]
    public class InRoomChat : Photon.MonoBehaviour
    {
        //声明变量
        public Rect GuiRect;
        public bool IsVisible = true;
        public bool AlignBottom = false;
        public List<string> messages = new List<string>();
        private string inputLine = "";
        public Vector2 scrollPos;
        public InputField inputMessage;
        public GameObject btnSend;
        public GameObject lblRoomMessage;
        public static readonly string ChatRPC = "Chat";
    
        public void Start()
        {
            FindObject();
        }
        //加载需要的对象
        public void FindObject()
        {
            inputMessage = GameObject.Find("inputRoomMessage").GetComponent<InputField>();
            btnSend = GameObject.Find("btnRoomMessage");
            lblRoomMessage = GameObject.Find("lblRoomMessage");
            if (btnSend != null)
            {
                btnSend.GetComponent<Button>().onClick.AddListener(
                    delegate { SendContent(); }
                );
            }
        }
    
        //发送聊天内容
        public void SendContent()
        {
            if (!this.IsVisible || !PhotonNetwork.inRoom)
            {
                return;
            }
            //获取聊天框内容
            inputLine = inputMessage.text;
    
            if (!string.IsNullOrEmpty(this.inputLine))
            {
                lblRoomMessage.GetComponent<Text>().text = "";
                //关键代码  使用photonView.RPC发送消息
                this.photonView.RPC("Chat", PhotonTargets.All, this.inputLine);
                this.inputLine = "";
                inputMessage.text = "";
                inputMessage.ActivateInputField();
            }
            else
            {
                return;
            }
    
        }
    
        //接收方收取消息
        [PunRPC]
        public void Chat(string newLine, PhotonMessageInfo mi)
        {
            string senderName = "anonymous";
            lblRoomMessage.GetComponent<Text>().text = "";
            if (mi.sender != null)
            {
                if (!string.IsNullOrEmpty(mi.sender.NickName))
                {
                    senderName = mi.sender.NickName;
                }
                else
                {
                    senderName = "player " + mi.sender.ID;
                }
            }
    
            this.messages.Add("<color=#EEAD0E>" + senderName +":</color> " + newLine);
    
    
            //只显示最新的24条消息
            List<string> newmessages = new List<string>();
            if (messages.Count > 24)
            {
                for (int i = (messages.Count - 24); i < messages.Count; i++)
                {
                    newmessages.Add(messages[i]);
                }
    
                for (int i = 0; i < newmessages.Count; i++)
                {
                    lblRoomMessage.GetComponent<Text>().text += newmessages[i] + "\n";
                }
            }
            else
            {
                for (int i = 0; i < messages.Count; i++)
                {
                    lblRoomMessage.GetComponent<Text>().text += messages[i] + "\n";
                }
            }
    
    
    
        }
    
        public void AddLine(string newLine)
        {
            this.messages.Add(newLine);
        }
    }

    关键的代码就是这一句, photonView组件自带的功能,第一个参数是接收方法,第三个参数是聊天内容

    this.photonView.RPC("Chat", PhotonTargets.All, this.inputLine);

     接收方的方法,前面要带上[PunRPC]

        [PunRPC]
        public void Chat(string newLine, PhotonMessageInfo mi)

    {

    //接收后的处理

    }

     

    就是这些了,省略了一些其他部分的内容。希望这篇文档能有帮助。

    展开全文
  • 由于这段时间公司的项目有点忙,好久没有更新啦,...今天的介绍内容是PUN SDK的运用,主要通过官方Demo来一起学习。1、配置Photon Server服务器 (1)、配置Photon Server的服务器IP地址 在IP地址的列表中找到本机的

    由于这段时间公司的项目有点忙,好久没有更新啦,在这里希望得到大家的谅解,好了,废话不多说啦,直奔主题!
    今天的介绍内容是PUN SDK的运用,主要通过官方Demo来一起学习。

    1、配置Photon Server服务器

    (1)、配置Photon Server的服务器IP地址
    在IP地址的列表中找到本机的IP地址,确定即可,这里一定要设置成本机的IP地址,否则客户端会连接不上服务器,查找本机IP地址的方法这里就不介绍啦,自行度娘一下。

    这里写图片描述

    图 2-1 配置Photon Server服务器

    (2)、启动Photon Server服务器

    启动服务器,开启应用,等到Photon Server的图标颜色由灰变亮就说明服务器启动成功。

    这里写图片描述

    图 2-2 配置Photon Server服务器

    2、配置客户端
    将PUN SDK导入Unity中后,找到Photon Unity Networking–>Resources下的PhotonServerSettings配置文件。如图2-3所示:

    这里写图片描述

    图 2-3 配置客户端

    之后修改该配置文件
    (1)、Hosting
    设置服务器的类型,其中有Not Set(不设置)、Photon Cloud(Photon云服务器)、Self Hosted(自己作为主机)、Offline Mode(离线模式)和Best Region(最佳区域)。
    在这里我们选择Self Hosted,就是将自己的机器作为服务器来使用。

    (2)、Server Address
    设置服务器的IP地址。因为联网的游戏需要服务器做同步和更新,所以客户端必须连接服务器才可以,而通过IP地址就可以连接服务器。
    在Server Address项中, 将当做服务器机器的IP地址填入该项内,这里我们输入本机的IP地址。

    (3)、Server Port
    设置服务器的端口号,虽然说上面我们通过IP地址连接了服务器,但是服务器上有好多应用,还必须让服务器知道是需要对接哪个应用程序,这里对接不同的应用程序是通过端口号来实现的。

    一般设置该端口号从范围从0到65535,而端口号又分为知名端口号和动态端口号。

    知名端口顾名思义就是大众所熟知的端口号,范围从0到1023,这些端口号一般固定分配给一些服务。比如21端口分配给FTP服务,25端口分配给SMTP(简单邮件传输协议)服务,80端口分配给HTTP服务,135端口分配给RPC(远程过程调用)服务等等。

    动态端口的范围从1024到65535,这些端口号一般不固定分配给某个服务,也就是说许多服务都可以使用这些端口。只要运行的程序向系统提出访问网络的申请,那么系统就可以从这些端口号中分配一个供该程序使用。比如1024端口就是分配给第一个向系统发出申请的程序。在关闭程序进程后,就会释放所占用的端口号。

    所以这里我们设置端口号为5055。

    (4)、Protocol
    设置网络协议,主要分为两类UDP和TCP,其中TCP是可靠的网络传输,其需要的带宽大。而UDP是不可靠的网络传输,其需要的带宽较小。关于其他的差异,大家可以在网上查找其他资料。
    我们在这里选择TCP协议。

    这里写图片描述

    图 2-4 配置客户端

    到这里客户端的配置就完成了,那就让我们打开一个Demo运行一下吧。
    下面是运行的截图

    这里写图片描述

    图 2-5 运行结果

    展开全文
  • 一、前言 Photon Unity Networking (PUN)是一种用于多人游戏的Unity软件包。 灵活的匹配可以让玩家进入房间,可以通过网络同步对象。 快速和可靠的通信是通过专用的...2、【Unity3D】 Photon多人游戏开发教程 3...

    一、前言

    Photon Unity Networking (PUN)是一种用于多人游戏的Unity软件包。
    灵活的匹配可以让玩家进入房间,可以通过网络同步对象。
    快速和可靠的通信是通过专用的Photon 服务器完成的,因此客户端连接不需要1对1。

    二、参考文章

    1、【PUN】Photon Unity Networking(PUN)的简单使用
    2、【Unity3D】 Photon多人游戏开发教程
    3、PUN介绍(干货)
    4、Photon Unity Networking 案例(一)
    5、Unity3D利用Photon实现实时联网对战(二)PUN SDK介绍
    6、Photon Unity Networking基础教程 7 修改Player的联网版本
    7、使用Photon Unity Networking开发多人网络游戏的基本思路(一):大厅与等待房间

    三、正文

    快速搭建

    1.下载PUN插件,下载地址:https://doc.photonengine.com/en-us/pun/current/getting-started/pun-intro
    在这里插入图片描述
    会跳转到AssetStore商店:
    在这里插入图片描述
    需要注意的是版本要Unity2017.4.7以上,如果是以前的版本,可以安装PUN1.0版本

    或者直接在Unity里面Alt+9访问商店,然后搜索PUN插件

    2.然后需要打开Photon的官网注册一个账号,https://dashboard.photonengine.com/Account/SignIn?ReturnUrl=%2fzh-CN%2fpubliccloud
    在这里插入图片描述
    登录以后,点击新建一个APP:
    在这里插入图片描述
    类型的话,如果是聊天室可以选择Photon Chat,普通的选择Photon PUN就可以了
    在这里插入图片描述
    复制App ID,到Unity项目中的Photon/PhotonUnityNetworking/Resources/PhotonServerSettings的
    App Id Realtim
    在这里插入图片描述
    3.新建场景,新建一个Plane,和Cube,将Cube设成预制体,放到Resouces文件夹:
    在这里插入图片描述
    4.给Cube加上Photon View组件,如果要同步的话,这个组件是必须的
    在这里插入图片描述
    将Cube的Transform拖入Observed Components
    5.新建脚本ClickFloor,将脚本付给Plane

    using Photon.Pun;
    using UnityEngine;
    
    public class ClickFloor : MonoBehaviour
    {
        public GameObject m_Prefab;
    
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit))
                {
                    PhotonNetwork.Instantiate(m_Prefab.name, hit.point + new Vector3(0, 3, 0), Quaternion.identity, 0);
                }
            }
        }
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    6.新建脚本PhotonConnect.cs

    using UnityEngine;
    using Photon.Pun;//导入Photon命名空间
    using Photon.Realtime;
    
    public class PhotonConnect : MonoBehaviour
    {
        void Start()
        {
            //初始化版本号
            PhotonNetwork.ConnectUsingSettings();
            PhotonNetwork.GameVersion = "1";
        }
    
        //按钮事件 创建房间
        public void Btn_CreateRoom(string _roomName)
        {
            
            //设置房间属性
            RoomOptions m_Room = new RoomOptions { IsOpen = true, IsVisible = true, MaxPlayers = 4 };
            PhotonNetwork.CreateRoom(_roomName, m_Room);
        }
    
        //根据房间名加入房间
        public void Btn_JoinRoom(string _roomName)
        {
            PhotonNetwork.JoinRoom(_roomName);
        }
    
        //随机加入已经创建的房间
        public void Btn_JoinRandomRoom()
        {
            PhotonNetwork.JoinRandomRoom();
        }
    
        void OnGUI()
        {
            //显示连接信息
            GUILayout.Label(PhotonNetwork.NetworkClientState.ToString(),GUILayout.Width(300),GUILayout.Height(100));
        }
    }
    
    

    7.将脚本付给Main Camera(任意一个场景中的对象就行),然后新建3个按钮,绑定事件:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    8.Cube预制体Apply一下,然后从场景中删除,运行:
    在这里插入图片描述

    API解析

    连接和回调

    ConnectUsingSettings 建立连接

    PhotonNetwork.ConnectUsingSettings();
    

    PUN 使用回调让你知道客户什么时候建立了连接,加入了一个房间等等。

    例如:IConnectionCallbacks.OnConnectedToMaster.

    为了方便起见,可以继承MonoBehaviourPunCallbacks接口,它实现了重要的回调接口并自动注册自己,只需覆盖特定的回调方法

    public class YourClass : MonoBehaviourPunCallbacks
    {
        public override void OnConnectedToMaster()
        {
            Debug.Log("Launcher: 连接到主客户端");
        }
    }
    

    加入和创建房间

    加入房间

    PhotonNetwork.JoinRoom("someRoom");
    

    加入存在的随机房间

    PhotonNetwork.JoinRandomRoom();
    

    创建房间

    PhotonNetwork.CreateRoom("MyMatch");
    

    如果想跟朋友一起玩,可以编一个房间名称,并使用JoinOrCreateRoom创建房间,将IsVisible 设为false,那么就只能使用房间名来加入(而不是随机加入创建的房间了)

    RoomOptions roomOptions = new RoomOptions();
    roomOptions.IsVisible = false;
    roomOptions.MaxPlayers = 4;
    PhotonNetwork.JoinOrCreateRoom(nameEveryFriendKnows, roomOptions, TypedLobby.Default);
    

    游戏逻辑

    可以使用PhotonView组件将游戏对象实例化为“联网游戏对象”,它标识对象和所有者(或控制器)更新状态给其他人

    需要添加一个PhotonView组件选择Observed组件并使用PhotonNetwork.Instantiate若要创建实例,请执行以下操作。

    PhotonStream 负责写入(和读取)网络对象的状态,每秒钟几次,脚本需要继承接口IPunObservable,它定义了OnPhotonSerializeView。看起来是这样的:

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            Vector3 pos = transform.localPosition;
            stream.Serialize(ref pos);
        }
        else
        {
            Vector3 pos = Vector3.zero;
            stream.Serialize(ref pos); 
        }
    }
    

    远程过程调用

    Remote Procedure Calls (RPC)使你可以调用”networked GameObjects”上的方法,对由用户输入等触发的不常用动作很有用。

    一个RPC会被在同房间里的每个玩家在相同的游戏对象上被执行,所以你可以容易地触发整个场景效果就像你可以修改某些GameObject。

    作为RPC被调用的方法必须在一个带PhotonView组件的游戏对象上。该方法自身必须要被[PunRPC]属性标记。

    [PunRPC] 
    void ChatMessage(string a, string b) 
    { 
          Debug.Log("ChatMessage " + a + " " + b); 
    }
    

    要调用该方法,先访问到目标对象的PhotonView组件。而不是直接调用目标方法,调用PhotonView.RPC()并提供想要调用的方法名称:

    PhotonView photonView = PhotonView.Get(this); 
    photonView.RPC("ChatMessage", PhotonTargets.All, "jup", "and jup!");
    

    回调函数

    接口 解释
    IConnectionCallbacks 与连接相关的回调。
    IInRoomCallbacks 房间内发生的回调
    ILobbyCallbacks 与游戏大厅有关的回调。
    IMatchmakingCallbacks 与配对有关的回调
    IOnEventCallback 对接收到的事件进行一次回拨。这相当于C#事件OnEventReceived.
    IWebRpcCallback 一个用于接收WebRPC操作响应的回调。
    IPunInstantiateMagicCallback 实例化双关预制板的单个回调。
    IPunObservable PhotonView序列化回调。
    IPunOwnershipCallbacks 双关所有权转让回调。

    更多API参考:
    https://doc-api.photonengine.com/en/pun/v2/namespace_photon_1_1_pun.html

    四、案例

    1.简单的多人游戏

    1.新建Launcher.cs脚本

    using UnityEngine;
    using Photon.Pun;
    
    
    namespace Com.MyCompany.MyGame
    {
        public class Launcher : MonoBehaviour
        {
            #region Private Serializable Fields
    
    
            #endregion
    
    
            #region Private Fields
    
    
            /// <summary>
            /// This client's version number. Users are separated from each other by gameVersion (which allows you to make breaking changes).
            /// </summary>
            string gameVersion = "1";
    
    
            #endregion
    
    
            #region MonoBehaviour CallBacks
    
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during early initialization phase.
            /// </summary>
            void Awake()
            {
                // #Critical
                // this makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
                PhotonNetwork.AutomaticallySyncScene = true;
            }
    
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during initialization phase.
            /// </summary>
            void Start()
            {
                Connect();
            }
    
    
            #endregion
    
    
            #region Public Methods
    
    
            /// <summary>
            /// Start the connection process.
            /// - If already connected, we attempt joining a random room
            /// - if not yet connected, Connect this application instance to Photon Cloud Network
            /// </summary>
            public void Connect()
            {
                // we check if we are connected or not, we join if we are , else we initiate the connection to the server.
                if (PhotonNetwork.IsConnected)
                {
                    // #Critical we need at this point to attempt joining a Random Room. If it fails, we'll get notified in OnJoinRandomFailed() and we'll create one.
                    PhotonNetwork.JoinRandomRoom();
                }
                else
                {
                    // #Critical, we must first and foremost connect to Photon Online Server.
                    PhotonNetwork.ConnectUsingSettings();
                    PhotonNetwork.GameVersion = gameVersion;
                }
            }
    
    
        #endregion
    
    
        }
    }
    

    打开PhotonServerSettings:
    在这里插入图片描述
    2.扩展MonoBehaviourPunCallback
    修改MonoBehaviour为MonoBehaviourPunCallbacks
    加using Photon.Realtime;命名空间
    添加以下两个方法:

    public class Launcher : MonoBehaviourPunCallbacks
    {
    
    #region MonoBehaviourPunCallbacks Callbacks
    
    
    public override void OnConnectedToMaster()
    {
        Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN");
        PhotonNetwork.JoinRandomRoom();
    }
    
    
    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause);
    }
    
    
    #endregion
    

    当我们有效地加入一个房间时,它将通知您的脚本:

    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");
    
        // #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
        PhotonNetwork.CreateRoom(null, new RoomOptions());
    }
    
    public override void OnJoinedRoom()
    {
        Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");
    }
    

    新建字段:

    /// <summary>
    /// The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created.
    /// </summary>
    [Tooltip("The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created")]
    [SerializeField]
    private byte maxPlayersPerRoom = 4;
    

    然后修改PhototonNetwork.CreateRoom()调用并使用这个新字段

    // #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
    PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = maxPlayersPerRoom });
    

    在这里插入图片描述
    3.UI界面搭建
    开始按钮
    新建一个Button,命名为Play Button,绑定事件Launcher.Connect()
    打卡脚本Launcher.cs,移除Start()函数

    在这里插入图片描述
    4.玩家名字
    创建PlayerNameInputField.cs脚本:

    using UnityEngine;
    using UnityEngine.UI;
    
    
    using Photon.Pun;
    using Photon.Realtime;
    
    
    using System.Collections;
    
    
    namespace Com.MyCompany.MyGame
    {
        /// <summary>
        /// Player name input field. Let the user input his name, will appear above the player in the game.
        /// </summary>
        [RequireComponent(typeof(InputField))]
        public class PlayerNameInputField : MonoBehaviour
        {
            #region Private Constants
    
    
            // Store the PlayerPref Key to avoid typos
            const string playerNamePrefKey = "PlayerName";
    
    
            #endregion
    
    
            #region MonoBehaviour CallBacks
    
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during initialization phase.
            /// </summary>
            void Start () {
    
    
                string defaultName = string.Empty;
                InputField _inputField = this.GetComponent<InputField>();
                if (_inputField!=null)
                {
                    if (PlayerPrefs.HasKey(playerNamePrefKey))
                    {
                        defaultName = PlayerPrefs.GetString(playerNamePrefKey);
                        _inputField.text = defaultName;
                    }
                }
    
    
                PhotonNetwork.NickName =  defaultName;
            }
    
    
            #endregion
    
    
            #region Public Methods
    
    
            /// <summary>
            /// Sets the name of the player, and save it in the PlayerPrefs for future sessions.
            /// </summary>
            /// <param name="value">The name of the Player</param>
            public void SetPlayerName(string value)
            {
                // #Important
                if (string.IsNullOrEmpty(value))
                {
                    Debug.LogError("Player Name is null or empty");
                    return;
                }
                PhotonNetwork.NickName = value;
    
    
                PlayerPrefs.SetString(playerNamePrefKey,value);
            }
    
    
            #endregion
        }
    }
    

    5.为玩家的名字创建UI
    在场景中新建UI—InputField,添加事件On Value Change (String),拖动PlayerNameInputField附加到对象上,选择SetPlayerName方法
    在这里插入图片描述
    6.连接信息显示
    使用“GameObject/UI/Panel”菜单创建UI面板,命名为Control Panel,
    拖放Play Button和Name InputField在Control Panel
    新建一个text用作信息显示,命名为Progress Label
    在这里插入图片描述
    7.打开Launcher.cs脚本
    添加以下两个属性

    [Tooltip("The Ui Panel to let the user enter name, connect and play")]
    [SerializeField]
    private GameObject controlPanel;
    [Tooltip("The UI Label to inform the user that the connection is in progress")]
    [SerializeField]
    private GameObject progressLabel;
    

    添加到Start()方法:

    progressLabel.SetActive(false);
    controlPanel.SetActive(true);
    

    添加到Connect()方法:

    progressLabel.SetActive(true);
    controlPanel.SetActive(false);
    

    添加到OnDisconnected()方法:

    progressLabel.SetActive(false);
    controlPanel.SetActive(true);
    

    7.创建不同的场景
    创建一个新场景,保存它并命名Room for 1
    新建一个Plane,缩放到20,1,20
    新建4个Cube:
    Cube1
    在这里插入图片描述
    Cube2
    在这里插入图片描述
    Cube3
    在这里插入图片描述
    Cube4
    在这里插入图片描述
    8.新建c#脚本GameManager.cs

    using System;
    using System.Collections;
    
    
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    
    using Photon.Pun;
    using Photon.Realtime;
    
    
    namespace Com.MyCompany.MyGame
    {
        public class GameManager : MonoBehaviourPunCallbacks
        {
    
    
            #region Photon Callbacks
    
    
            /// <summary>
            /// Called when the local player left the room. We need to load the launcher scene.
            /// </summary>
            public override void OnLeftRoom()
            {
                SceneManager.LoadScene(0);
            }
    
    
            #endregion
    
    
            #region Public Methods
    
    
            public void LeaveRoom()
            {
                PhotonNetwork.LeaveRoom();
            }
    
    
            #endregion
        }
    }
    

    9.退出房间按钮
    新建一个面板Top Panel,设置锚点
    在这里插入图片描述
    添加一个退出按钮,命名为Leave Button,绑定事件Game Manager的LeaveRoom()
    在这里插入图片描述
    10.创造其他场景
    2人场景:
    Cube1:
    在这里插入图片描述
    Cube2:
    在这里插入图片描述
    Cube3:
    在这里插入图片描述
    Cube4:
    在这里插入图片描述
    3人场景:
    Cube1:
    在这里插入图片描述
    Cube2:
    在这里插入图片描述
    Cube3:
    在这里插入图片描述
    Cube4:
    在这里插入图片描述
    4人场景:
    Floor 比例尺:60,1,60
    Cube1:
    在这里插入图片描述
    Cube2:
    在这里插入图片描述
    Cube3:
    在这里插入图片描述
    Cube4:
    在这里插入图片描述
    11.生成设置场景列表
    File/Build Settings拖放所有场景
    在这里插入图片描述
    12.加载场景
    打开GameManager.cs
    添加新方法:

    #region Private Methods
    
    
    void LoadArena()
    {
        if (!PhotonNetwork.IsMasterClient)
        {
            Debug.LogError("PhotonNetwork : Trying to Load a level but we are not the master Client");
        }
        Debug.LogFormat("PhotonNetwork : Loading Level : {0}", PhotonNetwork.CurrentRoom.PlayerCount);
        PhotonNetwork.LoadLevel("Room for " + PhotonNetwork.CurrentRoom.PlayerCount);
    }
    
    
    #endregion
    

    13.检测其他玩家的加入:

    #region Photon Callbacks
    
    
    public override void OnPlayerEnteredRoom(Player other)
    {
        Debug.LogFormat("OnPlayerEnteredRoom() {0}", other.NickName); // not seen if you're the player connecting
    
    
        if (PhotonNetwork.IsMasterClient)
        {
            Debug.LogFormat("OnPlayerEnteredRoom IsMasterClient {0}", PhotonNetwork.IsMasterClient); // called before OnPlayerLeftRoom
    
    
            LoadArena();
        }
    }
    
    
    public override void OnPlayerLeftRoom(Player other)
    {
        Debug.LogFormat("OnPlayerLeftRoom() {0}", other.NickName); // seen when other disconnects
    
    
        if (PhotonNetwork.IsMasterClient)
        {
            Debug.LogFormat("OnPlayerLeftRoom IsMasterClient {0}", PhotonNetwork.IsMasterClient); // called before OnPlayerLeftRoom
    
    
            LoadArena();
        }
    }
    
    
    #endregion
    

    14.加入游戏大厅
    将下列内容附加到OnJoinedRoom()方法

    // #Critical: We only load if we are the first player, else we rely on `PhotonNetwork.AutomaticallySyncScene` to sync our instance scene.
    if (PhotonNetwork.CurrentRoom.PlayerCount == 1)
    {
        Debug.Log("We load the 'Room for 1' ");
    
    
        // #Critical
        // Load the Room Level.
        PhotonNetwork.LoadLevel("Room for 1");
    }
    

    打开场景Launcher运行它。点击“Play”但如果你离开房间,你会注意到当你回到大厅时,它会自动重新加入要解决这个问题,我们可以修改Launcher.cs脚本

    添加新属性:

    /// <summary>
    /// Keep track of the current process. Since connection is asynchronous and is based on several callbacks from Photon,
    /// we need to keep track of this to properly adjust the behavior when we receive call back by Photon.
    /// Typically this is used for the OnConnectedToMaster() callback.
    /// </summary>
    bool isConnecting;
    

    Connect()方法添加:

    // keep track of the will to join a room, because when we come back from the game we will get a callback that we are connected, so we need to know what to do then
    isConnecting = PhotonNetwork.ConnectUsingSettings();
    

    结果:

    public void Connect()
    {
        progressLabel.SetActive(true);
        controlPanel.SetActive(false);
        if (PhotonNetwork.IsConnected)
        {
            PhotonNetwork.JoinRandomRoom();
        }
        else
        {
            isConnecting = PhotonNetwork.ConnectUsingSettings();
            PhotonNetwork.GameVersion = gameVersion;
        }
    }
    

    OnConnectedToMaster()方法加入:

    // we don't want to do anything if we are not attempting to join a room.
    // this case where isConnecting is false is typically when you lost or quit the game, when this level is loaded, OnConnectedToMaster will be called, in that case
    // we don't want to do anything.
    if (isConnecting)
    {
        // #Critical: The first we try to do is to join a potential existing room. If there is, good, else, we'll be called back with OnJoinRandomFailed()
        PhotonNetwork.JoinRandomRoom();
        isConnecting = false;
    }
    

    15.玩家设置
    模型在Assets\Photon\PhotonUnityNetworking\Demos\Shared Assets\Models
    Kyle Robot.fbx
    新建一个空场景,拖入Kyle Robot.fbx进入场景,将模型拖入Resources文件夹,做成一个预制体:

    双击My Kyle Robot修改碰撞器:
    在这里插入图片描述
    动画设置
    在这里插入图片描述
    配这个Kyle Robot我们的控制器预制件,只需设置属性Controller要指向的动画组件的Kyle Robot
    在这里插入图片描述
    使用控制器参数
    新建PlayerAnimatorManager.cs脚本:

    using UnityEngine;
    using System.Collections;
    
    
    namespace Com.MyCompany.MyGame
    {
        public class PlayerAnimatorManager : MonoBehaviour
        {
            #region MonoBehaviour Callbacks
    
    
            // Use this for initialization
            void Start()
            {
            }
    
    
            // Update is called once per frame
            void Update()
            {
            }
    
    
            #endregion
        }
    }
    

    创建变量:

    private Animator animator;
    // Use this for initialization
    void Start()
    {
        animator = GetComponent<Animator>();
        if (!animator)
        {
            Debug.LogError("PlayerAnimatorManager is Missing Animator Component", this);
        }
    }
    // Update is called once per frame
    void Update()
    {
        if (!animator)
        {
            return;
        }
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        if (v < 0)
        {
            v = 0;
        }
        animator.SetFloat("Speed", h * h + v * v);
    }
    

    动画管理员脚本:方向控制
    编辑脚本PlayerAnimatorManager

    #region Private Fields
    
    
    [SerializeField]
    private float directionDampTime = 0.25f;
    
    
    #endregion
    

    Update里面添加:

    animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime);
    

    动画管理员脚本:跳跃
    编辑脚本PlayerAnimatorManager

    // deal with Jumping
    AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
    // only allow jumping if we are running.
    if (stateInfo.IsName("Base Layer.Run"))
    {
        // When using trigger parameter
        if (Input.GetButtonDown("Fire2"))
        {
            animator.SetTrigger("Jump");
        }
    }
    

    结果:

    using UnityEngine;
    using System.Collections;
    
    namespace Com.MyCompany.MyGame
    {
        public class PlayerAnimatorManager : MonoBehaviour
        {
            #region Private Fields
    
            [SerializeField]
            private float directionDampTime = .25f;
            private Animator animator;
    
            #endregion
    
            #region MonoBehaviour CallBacks
    
            // Use this for initialization
            void Start()
            {
                animator = GetComponent<Animator>();
                if (!animator)
                {
                    Debug.LogError("PlayerAnimatorManager is Missing Animator Component", this);
                }
    
            }
    
            // Update is called once per frame
            void Update()
            {
                if (!animator)
                {
                    return;
                }
                // deal with Jumping
                AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
                // only allow jumping if we are running.
                if (stateInfo.IsName("Base Layer.Run"))
                {
                    // When using trigger parameter
                    if (Input.GetButtonDown("Fire2"))
                    {
                        animator.SetTrigger("Jump");
                    }
                }
                float h = Input.GetAxis("Horizontal");
                float v = Input.GetAxis("Vertical");
                if (v < 0)
                {
                    v = 0;
                }
                animator.SetFloat("Speed", h * h + v * v);
                animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime);
            }
    
            #endregion
        }
    }
    

    摄像机设置
    添加组件CameraWork到My Kyle Robot预制件

    PhotonView组件
    给模型添加一个PhotonView组件:
    设置Observe Option到Unreliable On Change

    增加武器射线
    点击模型,打开层级列表,找到头部:
    在这里插入图片描述
    设置两个Cube为射线,然后父对象为Head:
    在这里插入图片描述
    在这里插入图片描述
    控制射线:
    创建一个新的脚本:PlayerManager.cs

    using UnityEngine;
    using UnityEngine.EventSystems;
    
    using Photon.Pun;
    
    using System.Collections;
    
    namespace Com.MyCompany.MyGame
    {
        /// <summary>
        /// Player manager.
        /// Handles fire Input and Beams.
        /// </summary>
        public class PlayerManager : MonoBehaviourPunCallbacks
        {
            #region Private Fields
    
            [Tooltip("The Beams GameObject to control")]
            [SerializeField]
            private GameObject beams;
            //True, when the user is firing
            bool IsFiring;
            #endregion
    
            #region MonoBehaviour CallBacks
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during early initialization phase.
            /// </summary>
            void Awake()
            {
                if (beams == null)
                {
                    Debug.LogError("<Color=Red><a>Missing</a></Color> Beams Reference.", this);
                }
                else
                {
                    beams.SetActive(false);
                }
            }
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity on every frame.
            /// </summary>
            void Update()
            {
    
                ProcessInputs();
    
                // trigger Beams active state
                if (beams != null && IsFiring != beams.activeInHierarchy)
                {
                    beams.SetActive(IsFiring);
                }
            }
    
            #endregion
    
            #region Custom
    
            /// <summary>
            /// Processes the inputs. Maintain a flag representing when the user is pressing Fire.
            /// </summary>
            void ProcessInputs()
            {
                if (Input.GetButtonDown("Fire1"))
                {
                    if (!IsFiring)
                    {
                        IsFiring = true;
                    }
                }
                if (Input.GetButtonUp("Fire1"))
                {
                    if (IsFiring)
                    {
                        IsFiring = false;
                    }
                }
            }
    
            #endregion
        }
    }
    

    生命值
    打开PlayerManager剧本
    增加一个公众Health属性

    [Tooltip("The current Health of our player")]
    public float Health = 1f;
    

    以下两个方法添加到MonoBehaviour Callbacks区域。

    /// <summary>
    /// MonoBehaviour method called when the Collider 'other' enters the trigger.
    /// Affect Health of the Player if the collider is a beam
    /// Note: when jumping and firing at the same, you'll find that the player's own beam intersects with itself
    /// One could move the collider further away to prevent this or check if the beam belongs to the player.
    /// </summary>
    void OnTriggerEnter(Collider other)
    {
        if (!photonView.IsMine)
        {
            return;
        }
        // We are only interested in Beamers
        // we should be using tags but for the sake of distribution, let's simply check by name.
        if (!other.name.Contains("Beam"))
        {
            return;
        }
        Health -= 0.1f;
    }
    /// <summary>
    /// MonoBehaviour method called once per frame for every Collider 'other' that is touching the trigger.
    /// We're going to affect health while the beams are touching the player
    /// </summary>
    /// <param name="other">Other.</param>
    void OnTriggerStay(Collider other)
    {
        // we dont' do anything if we are not the local player.
        if (! photonView.IsMine)
        {
            return;
        }
        // We are only interested in Beamers
        // we should be using tags but for the sake of distribution, let's simply check by name.
        if (!other.name.Contains("Beam"))
        {
            return;
        }
        // we slowly affect health when beam is constantly hitting us, so player has to move to prevent death.
        Health -= 0.1f*Time.deltaTime;
    }
    

    在公共字段区域中添加此变量

    public static GameManager Instance;
    

    Start()方法添加:

    void Start()
    {
        Instance = this;
    }
    

    Update函数添加:

    if (Health <= 0f)
    {
        GameManager.Instance.LeaveRoom();
    }
    

    16.联网
    Transform 同步

    添加组件PhotonTransformView

    动画同步
    添加组件PhotonAnimatorView

    在这里插入图片描述
    16.用户输入管理
    打开PlayerAnimatorManager.cs
    Update添加

    if (photonView.IsMine == false && PhotonNetwork.IsConnected == true)
    {
        return;
    }
    

    17.摄像机控制
    打开PlayerManager剧本

    /// <summary>
    /// MonoBehaviour method called on GameObject by Unity during initialization phase.
    /// </summary>
    void Start()
    {
        CameraWork _cameraWork = this.gameObject.GetComponent<CameraWork>();
    
    
        if (_cameraWork != null)
        {
            if (photonView.IsMine)
            {
                _cameraWork.OnStartFollowing();
            }
        }
        else
        {
            Debug.LogError("<Color=Red><a>Missing</a></Color> CameraWork Component on playerPrefab.", this);
        }
    }
    

    禁用Follow on Start
    在这里插入图片描述
    18.开火控制

    打开脚本PlayerManager

    if (photonView.IsMine)
    {
        ProcessInputs ();
    }
    

    添加接口IPunObservable

    public class PlayerManager : MonoBehaviourPunCallbacks, IPunObservable
    {
        #region IPunObservable implementation
    
    
        public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
        {
        }
    
    
        #endregion
    

    IPunObservable.OnPhotonSerializeView添加以下代码

    if (stream.IsWriting)
    {
        // We own this player: send the others our data
        stream.SendNext(IsFiring);
    }
    else
    {
        // Network player, receive data
        this.IsFiring = (bool)stream.ReceiveNext();
    }
    

    将PlayerManager组件拖入PhotonView组件

    在这里插入图片描述
    19.生命值同步
    打开脚本PlayerManager

    if (stream.IsWriting)
    {
        // We own this player: send the others our data
        stream.SendNext(IsFiring);
        stream.SendNext(Health);
    }
    else
    {
        // Network player, receive data
        this.IsFiring = (bool)stream.ReceiveNext();
        this.Health = (float)stream.ReceiveNext();
    }
    

    20.实例化玩家

    打开GameManager脚本
    在公共字段区域中添加以下变量

    [Tooltip("The prefab to use for representing the player")]
    public GameObject playerPrefab;
    

    在Start()方法,添加以下内容

    if (playerPrefab == null)
    {
        Debug.LogError("<Color=Red><a>Missing</a></Color> playerPrefab Reference. Please set it up in GameObject 'Game Manager'",this);
    }
    else
    {
        Debug.LogFormat("We are Instantiating LocalPlayer from {0}", Application.loadedLevelName);
        // we're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
        PhotonNetwork.Instantiate(this.playerPrefab.name, new Vector3(0f,5f,0f), Quaternion.identity, 0);
    }
    

    在这里插入图片描述
    21.跟随玩家
    打开PlayerManager脚本
    在“公共字段”区域中,添加以下内容

    [Tooltip("The local player instance. Use this to know if the local player is represented in the Scene")]
    public static GameObject LocalPlayerInstance;
    

    在Awake()方法,添加以下内容

    // #Important
    // used in GameManager.cs: we keep track of the localPlayer instance to prevent instantiation when levels are synchronized
    if (photonView.IsMine)
    {
        PlayerManager.LocalPlayerInstance = this.gameObject;
    }
    // #Critical
    // we flag as don't destroy on load so that instance survives level synchronization, thus giving a seamless experience when levels load.
    DontDestroyOnLoad(this.gameObject);
    

    将实例化调用包围在if条件

    if (PlayerManager.LocalPlayerInstance == null)
    {
        Debug.LogFormat("We are Instantiating LocalPlayer from {0}", SceneManagerHelper.ActiveSceneName);
        // we're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
        PhotonNetwork.Instantiate(this.playerPrefab.name, new Vector3(0f, 5f, 0f), Quaternion.identity, 0);
    }
    else
    {
        Debug.LogFormat("Ignoring scene load for {0}", SceneManagerHelper.ActiveSceneName);
    }
    

    22.管理场景外的玩家
    打开PlayerManager脚本

    #if UNITY_5_4_OR_NEWER
    void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode loadingMode)
    {
        this.CalledOnLevelWasLoaded(scene.buildIndex);
    }
    #endif
    

    在Start()方法,添加以下代码

    #if UNITY_5_4_OR_NEWER
    // Unity 5.4 has a new scene management. register a method to call CalledOnLevelWasLoaded.
    UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
    #endif
    

    在“MonoBehaviour回调”区域中添加以下两个方法

    #if !UNITY_5_4_OR_NEWER
    /// <summary>See CalledOnLevelWasLoaded. Outdated in Unity 5.4.</summary>
    void OnLevelWasLoaded(int level)
    {
        this.CalledOnLevelWasLoaded(level);
    }
    #endif
    
    
    void CalledOnLevelWasLoaded(int level)
    {
        // check if we are outside the Arena and if it's the case, spawn around the center of the arena in a safe zone
        if (!Physics.Raycast(transform.position, -Vector3.up, 5f))
        {
            transform.position = new Vector3(0f, 5f, 0f);
        }
    }
    

    覆盖OnDisable方法如下

    #if UNITY_5_4_OR_NEWER
    public override void OnDisable()
    {
        // Always call the base to remove callbacks
        base.OnDisable ();
        UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
    }
    #endif
    

    23.玩家UI 血条和名字预设体
    在场景中新建UI,Slider,锚点,中间位置,rect宽度80高度15,背景设置成红色,加一个CanvasGroup组件,设置Interactable和Blocks Raycast为false,拖入到Prefab文件夹,删除场景中的实例,我们不再需要它了

    创建一个新的C#脚本PlayerUI.cs

    using UnityEngine;
    using UnityEngine.UI;
    
    
    using System.Collections;
    
    
    namespace Com.MyCompany.MyGame
    {
        public class PlayerUI : MonoBehaviour
        {
            #region Private Fields
    
    
            [Tooltip("UI Text to display Player's Name")]
            [SerializeField]
            private Text playerNameText;
    
    
            [Tooltip("UI Slider to display Player's Health")]
            [SerializeField]
            private Slider playerHealthSlider;
    
    
            #endregion
    
    
            #region MonoBehaviour Callbacks
    
    
            #endregion
    
    
            #region Public Methods
    
    
            #endregion
    
    
        }
    }
    

    添加属性:

    private PlayerManager target;
    

    添加此公共方法

    public void SetTarget(PlayerManager _target)
    {
        if (_target == null)
        {
            Debug.LogError("<Color=Red><a>Missing</a></Color> PlayMakerManager target for PlayerUI.SetTarget.", this);
            return;
        }
        // Cache references for efficiency
        target = _target;
        if (playerNameText != null)
        {
            playerNameText.text = target.photonView.Owner.NickName;
        }
    }
    

    添加此方法

    void Update()
    {
        // Reflect the Player Health
        if (playerHealthSlider != null)
        {
            playerHealthSlider.value = target.Health;
        }
    }
    

    24.实例化
    打开脚本PlayerManager
    添加一个公共字段以保存对Player UI预置的引用,如下所示:

    [Tooltip("The Player's UI GameObject Prefab")]
    [SerializeField]
    public GameObject PlayerUiPrefab;
    

    将此代码添加到Start()方法

    if (PlayerUiPrefab != null)
    {
        GameObject _uiGo =  Instantiate(PlayerUiPrefab);
        _uiGo.SendMessage ("SetTarget", this, SendMessageOptions.RequireReceiver);
    }
    else
    {
        Debug.LogWarning("<Color=Red><a>Missing</a></Color> PlayerUiPrefab reference on player Prefab.", this);
    }
    

    将此添加到Update()功能

    // Destroy itself if the target is null, It's a fail safe when Photon is destroying Instances of a Player over the network
    if (target == null)
    {
        Destroy(this.gameObject);
        return;
    }
    

    将此代码添加到CalledOnLevelWasLoaded()方法

    GameObject _uiGo = Instantiate(this.PlayerUiPrefab);
    _uiGo.SendMessage("SetTarget", this, SendMessageOptions.RequireReceiver);
    

    在“MonoBehaviour回调”区域中添加此方法

    void Awake()
    {
        this.transform.SetParent(GameObject.Find("Canvas").GetComponent<Transform>(), false);
    }
    

    在“公共字段”区域中添加此公共属性

    [Tooltip("Pixel offset from the player target")]
    [SerializeField]
    private Vector3 screenOffset = new Vector3(0f,30f,0f);
    

    将这四个字段添加到“私有字段”区域

    float characterControllerHeight = 0f;
    Transform targetTransform;
    Renderer targetRenderer;
    CanvasGroup _canvasGroup;
    Vector3 targetPosition;
    

    将此添加到Awake方法域

    _canvasGroup = this.GetComponent<CanvasGroup>();
    

    将下列代码追加到SetTarget()后法_target已经设定好了。

    targetTransform = this.target.GetComponent<Transform>();
    targetRenderer = this.target.GetComponent<Renderer>();
    CharacterController characterController = _target.GetComponent<CharacterController> ();
    // Get data from the Player that won't change during the lifetime of this Component
    if (characterController != null)
    {
    characterControllerHeight = characterController.height;
    }
    

    在“MonoBehaviour回调”区域中添加此公共方法

    void LateUpdate()
    {
    // Do not show the UI if we are not visible to the camera, thus avoid potential bugs with seeing the UI, but not the player itself.
        if (targetRenderer!=null)
        {
            this._canvasGroup.alpha = targetRenderer.isVisible ? 1f : 0f;
        }
    
    
    // #Critical
    // Follow the Target GameObject on screen.
    if (targetTransform != null)
    {
        targetPosition = targetTransform.position;
        targetPosition.y += characterControllerHeight;
        this.transform.position = Camera.main.WorldToScreenPoint (targetPosition) + screenOffset;
    }
    }
    

    2.游戏大厅与等待房间

    1.游戏大厅

    // 用于连接Cloud并进入游戏大厅
    
    PhotonNetwork.ConnectUsingSettings(string version)
    
    //进入游戏大厅的默认回调函数
    
    void OnJoinedLobby()
    
    //显示连接日志
    
    GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString())
    

    2.创建等待房间

    //设置房间属性并创建房间
    
    RoomOptions ro = new RoomOptions();
    
    ro.IsOpen = true;ro.IsVisible = true;
    
    //设置最大玩家数 为了简单就从2个人开始做起吧 可以随意设置
    
    ro.MaxPlayers = 2;
    
    PhotonNetwork.CreateRoom(srting roomName, ro, TypedLobby.Default);
    
    //创建房间失败的回调函数
    
    void OnPhotonCreateRoomFailed()
    

    3.加入等待房间

    //随机加入房间
    
    PhotonNetwork.JoinRandomRoom();
    
    //随机进入房间失败(可能是因为没有空房间)的回调函数
    
    //默认的回调函数一定不能眼花写错!!!
    
    void OnPhotonRandomJoinFailed()
    
    {可以调用PhotonNetwork.CreateRoom创建一个}
    
    //进入房间的回调函数
    
    void OnJoinedRoom()
    {
    	StartCoroutine(this.ChangeToWaitScene());
    	//写一个协程 当成功进入房间后就加载等待房间的场景
    }
    IEnumerator ChangeToWaitScene()
     {
    	//切换场景期间中断与photon服务器的网络信息传输 
    	//(加载场景尚未完成的情况下 服务器传递的网络信息可能会引发不必要的错误)
    	PhotonNetwork.isMessageQueueRunning = false;
    	//加载场景
    	AsyncOperation ao = SceneManager.LoadSceneAsync("RoomForWait");
    	 yield return ao;
    }
    

    加入房间的同时最好将玩家姓名PhotonNetwork.player.NickName读取或者设置,可以与PlayerPrefs连用实现数据的持久化。

    4.房间列表的显示
    UGUI里面的Grid Layout Group和 Horizontal Layout Group就是针对于这种情况设计的。我们可以将一个房间列表存储成一个预设,每次有新房间生成就生成一个预设。上面这俩组件可以帮助你把这些房间列表预设排列得整齐划一。

    需要用到的prefab都要存在根目录下的Resources文件夹。硬性规定。

    //只有在大厅里的房间有玩家进入的时候才会执行 接收房间列表

    void OnReceivedRoomListUpdate()
        {
    		//给单个房间列表的预设增加标签
            GameObject[] a = GameObject.FindGameObjectsWithTag("OneRoom");
            for (int i = 0; i < a.Length; i++)
                Destroy(a[i].gameObject);
            //每次接收房间列表前把旧的预设销毁 这样就能更新在线人数和房间总人数
            //利用接收房间目录信息的函数生成单个列表预设 
            //PhotonNetwork.GetRoomList()可以获取房间列表里的房间数组
            foreach (RoomInfo _room in PhotonNetwork.GetRoomList(){
               //接收房间列表
                GameObject room = (GameObject)Instantiate(OneRoom);
                room.transform.SetParent(RoomList.transform, false);
                roomData rd = room.GetComponent<roomData>();
                rd.roomName = _room.Name;
                rd.connectPlayer = _room.PlayerCount;
                rd.maxPlayers = _room.MaxPlayers;
                rd.DisplayRoomData();//把数据都获取并设置好了就显示在面板上
            }
        }
    

    至于roomData脚本里面存储的就是房间名、房间人数、最大容纳人数等基本信息,同时最好根据房间人数是否满来设置加入房间的按钮interactable= ture还是false。

    这时候如果点击房间列表上的Join 就能进入房间了。

    大致效果如下(那个NO.是我给房间用随机数字命名的房间名。场景中其实还有个输入玩家姓名的输入框,如果玩家没有输入姓名就自动随机给个数字当名称。)
    在这里插入图片描述

    展开全文
  • 一、目录 【Unity3D从入门到进阶】文章目录及设置这个专栏的初衷

    目录

    【Unity3D从入门到进阶】文章目录及设置这个专栏的初衷

    一、前言

    Photon Unity Networking (PUN)是一种用于多人游戏的Unity软件包。
    灵活的匹配可以让玩家进入房间,可以通过网络同步对象。
    快速和可靠的通信是通过专用的Photon 服务器完成的,因此客户端连接不需要1对1。

    二、参考文章

    1、【PUN】Photon Unity Networking(PUN)的简单使用
    2、【Unity3D】 Photon多人游戏开发教程
    3、PUN介绍(干货)
    4、Photon Unity Networking 案例(一)
    5、Unity3D利用Photon实现实时联网对战(二)PUN SDK介绍
    6、Photon Unity Networking基础教程 7 修改Player的联网版本
    7、使用Photon Unity Networking开发多人网络游戏的基本思路(一):大厅与等待房间

    三、正文

    快速搭建

    1.下载PUN插件,下载地址:https://doc.photonengine.com/en-us/pun/current/getting-started/pun-intro
    在这里插入图片描述
    会跳转到AssetStore商店:
    在这里插入图片描述
    需要注意的是版本要Unity2017.4.7以上,如果是以前的版本,可以安装PUN1.0版本

    或者直接在Unity里面Alt+9访问商店,然后搜索PUN插件

    2.然后需要打开Photon的官网注册一个账号,https://dashboard.photonengine.com/Account/SignIn?ReturnUrl=%2fzh-CN%2fpubliccloud
    在这里插入图片描述
    登录以后,点击新建一个APP:
    在这里插入图片描述
    类型的话,如果是聊天室可以选择Photon Chat,普通的选择Photon PUN就可以了
    在这里插入图片描述
    复制App ID,到Unity项目中的Photon/PhotonUnityNetworking/Resources/PhotonServerSettings的
    App Id Realtim
    在这里插入图片描述
    3.新建场景,新建一个Plane,和Cube,将Cube设成预制体,放到Resouces文件夹:
    在这里插入图片描述
    4.给Cube加上Photon View组件,如果要同步的话,这个组件是必须的
    在这里插入图片描述
    将Cube的Transform拖入Observed Components
    5.新建脚本ClickFloor,将脚本付给Plane

    using Photon.Pun;
    using UnityEngine;
    
    public class ClickFloor : MonoBehaviour
    {
        public GameObject m_Prefab;
    
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit))
                {
                    PhotonNetwork.Instantiate(m_Prefab.name, hit.point + new Vector3(0, 3, 0), Quaternion.identity, 0);
                }
            }
        }
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    6.新建脚本PhotonConnect.cs

    using UnityEngine;
    using Photon.Pun;//导入Photon命名空间
    using Photon.Realtime;
    
    public class PhotonConnect : MonoBehaviour
    {
        void Start()
        {
            //初始化版本号
            PhotonNetwork.ConnectUsingSettings();
            PhotonNetwork.GameVersion = "1";
        }
    
        //按钮事件 创建房间
        public void Btn_CreateRoom(string _roomName)
        {
            
            //设置房间属性
            RoomOptions m_Room = new RoomOptions { IsOpen = true, IsVisible = true, MaxPlayers = 4 };
            PhotonNetwork.CreateRoom(_roomName, m_Room);
        }
    
        //根据房间名加入房间
        public void Btn_JoinRoom(string _roomName)
        {
            PhotonNetwork.JoinRoom(_roomName);
        }
    
        //随机加入已经创建的房间
        public void Btn_JoinRandomRoom()
        {
            PhotonNetwork.JoinRandomRoom();
        }
    
        void OnGUI()
        {
            //显示连接信息
            GUILayout.Label(PhotonNetwork.NetworkClientState.ToString(),GUILayout.Width(300),GUILayout.Height(100));
        }
    }
    
    

    7.将脚本付给Main Camera(任意一个场景中的对象就行),然后新建3个按钮,绑定事件:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    8.Cube预制体Apply一下,然后从场景中删除,运行:
    在这里插入图片描述

    API解析

    连接和回调

    ConnectUsingSettings 建立连接

    PhotonNetwork.ConnectUsingSettings();
    

    PUN 使用回调让你知道客户什么时候建立了连接,加入了一个房间等等。

    例如:IConnectionCallbacks.OnConnectedToMaster.

    为了方便起见,可以继承MonoBehaviourPunCallbacks接口,它实现了重要的回调接口并自动注册自己,只需覆盖特定的回调方法

    public class YourClass : MonoBehaviourPunCallbacks
    {
        public override void OnConnectedToMaster()
        {
            Debug.Log("Launcher: 连接到主客户端");
        }
    }
    

    加入和创建房间

    加入房间

    PhotonNetwork.JoinRoom("someRoom");
    

    加入存在的随机房间

    PhotonNetwork.JoinRandomRoom();
    

    创建房间

    PhotonNetwork.CreateRoom("MyMatch");
    

    如果想跟朋友一起玩,可以编一个房间名称,并使用JoinOrCreateRoom创建房间,将IsVisible 设为false,那么就只能使用房间名来加入(而不是随机加入创建的房间了)

    RoomOptions roomOptions = new RoomOptions();
    roomOptions.IsVisible = false;
    roomOptions.MaxPlayers = 4;
    PhotonNetwork.JoinOrCreateRoom(nameEveryFriendKnows, roomOptions, TypedLobby.Default);
    

    游戏逻辑

    可以使用PhotonView组件将游戏对象实例化为“联网游戏对象”,它标识对象和所有者(或控制器)更新状态给其他人

    需要添加一个PhotonView组件选择Observed组件并使用PhotonNetwork.Instantiate若要创建实例,请执行以下操作。

    PhotonStream 负责写入(和读取)网络对象的状态,每秒钟几次,脚本需要继承接口IPunObservable,它定义了OnPhotonSerializeView。看起来是这样的:

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            Vector3 pos = transform.localPosition;
            stream.Serialize(ref pos);
        }
        else
        {
            Vector3 pos = Vector3.zero;
            stream.Serialize(ref pos); 
        }
    }
    

    远程过程调用

    Remote Procedure Calls (RPC)使你可以调用”networked GameObjects”上的方法,对由用户输入等触发的不常用动作很有用。

    一个RPC会被在同房间里的每个玩家在相同的游戏对象上被执行,所以你可以容易地触发整个场景效果就像你可以修改某些GameObject。

    作为RPC被调用的方法必须在一个带PhotonView组件的游戏对象上。该方法自身必须要被[PunRPC]属性标记。

    [PunRPC] 
    void ChatMessage(string a, string b) 
    { 
          Debug.Log("ChatMessage " + a + " " + b); 
    }
    

    要调用该方法,先访问到目标对象的PhotonView组件。而不是直接调用目标方法,调用PhotonView.RPC()并提供想要调用的方法名称:

    PhotonView photonView = PhotonView.Get(this); 
    photonView.RPC("ChatMessage", PhotonTargets.All, "jup", "and jup!");
    

    回调函数

    接口 解释
    IConnectionCallbacks 与连接相关的回调。
    IInRoomCallbacks 房间内发生的回调
    ILobbyCallbacks 与游戏大厅有关的回调。
    IMatchmakingCallbacks 与配对有关的回调
    IOnEventCallback 对接收到的事件进行一次回拨。这相当于C#事件OnEventReceived.
    IWebRpcCallback 一个用于接收WebRPC操作响应的回调。
    IPunInstantiateMagicCallback 实例化双关预制板的单个回调。
    IPunObservable PhotonView序列化回调。
    IPunOwnershipCallbacks 双关所有权转让回调。

    更多API参考:
    https://doc-api.photonengine.com/en/pun/v2/namespace_photon_1_1_pun.html

    四、案例

    1.简单的多人游戏

    1.新建Launcher.cs脚本

    using UnityEngine;
    using Photon.Pun;
    
    
    namespace Com.MyCompany.MyGame
    {
        public class Launcher : MonoBehaviour
        {
            #region Private Serializable Fields
    
    
            #endregion
    
    
            #region Private Fields
    
    
            /// <summary>
            /// This client's version number. Users are separated from each other by gameVersion (which allows you to make breaking changes).
            /// </summary>
            string gameVersion = "1";
    
    
            #endregion
    
    
            #region MonoBehaviour CallBacks
    
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during early initialization phase.
            /// </summary>
            void Awake()
            {
                // #Critical
                // this makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
                PhotonNetwork.AutomaticallySyncScene = true;
            }
    
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during initialization phase.
            /// </summary>
            void Start()
            {
                Connect();
            }
    
    
            #endregion
    
    
            #region Public Methods
    
    
            /// <summary>
            /// Start the connection process.
            /// - If already connected, we attempt joining a random room
            /// - if not yet connected, Connect this application instance to Photon Cloud Network
            /// </summary>
            public void Connect()
            {
                // we check if we are connected or not, we join if we are , else we initiate the connection to the server.
                if (PhotonNetwork.IsConnected)
                {
                    // #Critical we need at this point to attempt joining a Random Room. If it fails, we'll get notified in OnJoinRandomFailed() and we'll create one.
                    PhotonNetwork.JoinRandomRoom();
                }
                else
                {
                    // #Critical, we must first and foremost connect to Photon Online Server.
                    PhotonNetwork.ConnectUsingSettings();
                    PhotonNetwork.GameVersion = gameVersion;
                }
            }
    
    
        #endregion
    
    
        }
    }
    

    打开PhotonServerSettings:
    在这里插入图片描述
    2.扩展MonoBehaviourPunCallback
    修改MonoBehaviour为MonoBehaviourPunCallbacks
    加using Photon.Realtime;命名空间
    添加以下两个方法:

    public class Launcher : MonoBehaviourPunCallbacks
    {
    
    #region MonoBehaviourPunCallbacks Callbacks
    
    
    public override void OnConnectedToMaster()
    {
        Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN");
        PhotonNetwork.JoinRandomRoom();
    }
    
    
    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause);
    }
    
    
    #endregion
    

    当我们有效地加入一个房间时,它将通知您的脚本:

    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");
    
        // #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
        PhotonNetwork.CreateRoom(null, new RoomOptions());
    }
    
    public override void OnJoinedRoom()
    {
        Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");
    }
    

    新建字段:

    /// <summary>
    /// The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created.
    /// </summary>
    [Tooltip("The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created")]
    [SerializeField]
    private byte maxPlayersPerRoom = 4;
    

    然后修改PhototonNetwork.CreateRoom()调用并使用这个新字段

    // #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
    PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = maxPlayersPerRoom });
    

    在这里插入图片描述
    3.UI界面搭建
    开始按钮
    新建一个Button,命名为Play Button,绑定事件Launcher.Connect()
    打卡脚本Launcher.cs,移除Start()函数

    在这里插入图片描述
    4.玩家名字
    创建PlayerNameInputField.cs脚本:

    using UnityEngine;
    using UnityEngine.UI;
    
    
    using Photon.Pun;
    using Photon.Realtime;
    
    
    using System.Collections;
    
    
    namespace Com.MyCompany.MyGame
    {
        /// <summary>
        /// Player name input field. Let the user input his name, will appear above the player in the game.
        /// </summary>
        [RequireComponent(typeof(InputField))]
        public class PlayerNameInputField : MonoBehaviour
        {
            #region Private Constants
    
    
            // Store the PlayerPref Key to avoid typos
            const string playerNamePrefKey = "PlayerName";
    
    
            #endregion
    
    
            #region MonoBehaviour CallBacks
    
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during initialization phase.
            /// </summary>
            void Start () {
    
    
                string defaultName = string.Empty;
                InputField _inputField = this.GetComponent<InputField>();
                if (_inputField!=null)
                {
                    if (PlayerPrefs.HasKey(playerNamePrefKey))
                    {
                        defaultName = PlayerPrefs.GetString(playerNamePrefKey);
                        _inputField.text = defaultName;
                    }
                }
    
    
                PhotonNetwork.NickName =  defaultName;
            }
    
    
            #endregion
    
    
            #region Public Methods
    
    
            /// <summary>
            /// Sets the name of the player, and save it in the PlayerPrefs for future sessions.
            /// </summary>
            /// <param name="value">The name of the Player</param>
            public void SetPlayerName(string value)
            {
                // #Important
                if (string.IsNullOrEmpty(value))
                {
                    Debug.LogError("Player Name is null or empty");
                    return;
                }
                PhotonNetwork.NickName = value;
    
    
                PlayerPrefs.SetString(playerNamePrefKey,value);
            }
    
    
            #endregion
        }
    }
    

    5.为玩家的名字创建UI
    在场景中新建UI—InputField,添加事件On Value Change (String),拖动PlayerNameInputField附加到对象上,选择SetPlayerName方法
    在这里插入图片描述
    6.连接信息显示
    使用“GameObject/UI/Panel”菜单创建UI面板,命名为Control Panel,
    拖放Play Button和Name InputField在Control Panel
    新建一个text用作信息显示,命名为Progress Label
    在这里插入图片描述
    7.打开Launcher.cs脚本
    添加以下两个属性

    [Tooltip("The Ui Panel to let the user enter name, connect and play")]
    [SerializeField]
    private GameObject controlPanel;
    [Tooltip("The UI Label to inform the user that the connection is in progress")]
    [SerializeField]
    private GameObject progressLabel;
    

    添加到Start()方法:

    progressLabel.SetActive(false);
    controlPanel.SetActive(true);
    

    添加到Connect()方法:

    progressLabel.SetActive(true);
    controlPanel.SetActive(false);
    

    添加到OnDisconnected()方法:

    progressLabel.SetActive(false);
    controlPanel.SetActive(true);
    

    7.创建不同的场景
    创建一个新场景,保存它并命名Room for 1
    新建一个Plane,缩放到20,1,20
    新建4个Cube:
    Cube1
    在这里插入图片描述
    Cube2
    在这里插入图片描述
    Cube3
    在这里插入图片描述
    Cube4
    在这里插入图片描述
    8.新建c#脚本GameManager.cs

    using System;
    using System.Collections;
    
    
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    
    using Photon.Pun;
    using Photon.Realtime;
    
    
    namespace Com.MyCompany.MyGame
    {
        public class GameManager : MonoBehaviourPunCallbacks
        {
    
    
            #region Photon Callbacks
    
    
            /// <summary>
            /// Called when the local player left the room. We need to load the launcher scene.
            /// </summary>
            public override void OnLeftRoom()
            {
                SceneManager.LoadScene(0);
            }
    
    
            #endregion
    
    
            #region Public Methods
    
    
            public void LeaveRoom()
            {
                PhotonNetwork.LeaveRoom();
            }
    
    
            #endregion
        }
    }
    

    9.退出房间按钮
    新建一个面板Top Panel,设置锚点
    在这里插入图片描述
    添加一个退出按钮,命名为Leave Button,绑定事件Game Manager的LeaveRoom()
    在这里插入图片描述
    10.创造其他场景
    2人场景:
    Cube1:
    在这里插入图片描述
    Cube2:
    在这里插入图片描述
    Cube3:
    在这里插入图片描述
    Cube4:
    在这里插入图片描述
    3人场景:
    Cube1:
    在这里插入图片描述
    Cube2:
    在这里插入图片描述
    Cube3:
    在这里插入图片描述
    Cube4:
    在这里插入图片描述
    4人场景:
    Floor 比例尺:60,1,60
    Cube1:
    在这里插入图片描述
    Cube2:
    在这里插入图片描述
    Cube3:
    在这里插入图片描述
    Cube4:
    在这里插入图片描述
    11.生成设置场景列表
    File/Build Settings拖放所有场景
    在这里插入图片描述
    12.加载场景
    打开GameManager.cs
    添加新方法:

    #region Private Methods
    
    
    void LoadArena()
    {
        if (!PhotonNetwork.IsMasterClient)
        {
            Debug.LogError("PhotonNetwork : Trying to Load a level but we are not the master Client");
        }
        Debug.LogFormat("PhotonNetwork : Loading Level : {0}", PhotonNetwork.CurrentRoom.PlayerCount);
        PhotonNetwork.LoadLevel("Room for " + PhotonNetwork.CurrentRoom.PlayerCount);
    }
    
    
    #endregion
    

    13.检测其他玩家的加入:

    #region Photon Callbacks
    
    
    public override void OnPlayerEnteredRoom(Player other)
    {
        Debug.LogFormat("OnPlayerEnteredRoom() {0}", other.NickName); // not seen if you're the player connecting
    
    
        if (PhotonNetwork.IsMasterClient)
        {
            Debug.LogFormat("OnPlayerEnteredRoom IsMasterClient {0}", PhotonNetwork.IsMasterClient); // called before OnPlayerLeftRoom
    
    
            LoadArena();
        }
    }
    
    
    public override void OnPlayerLeftRoom(Player other)
    {
        Debug.LogFormat("OnPlayerLeftRoom() {0}", other.NickName); // seen when other disconnects
    
    
        if (PhotonNetwork.IsMasterClient)
        {
            Debug.LogFormat("OnPlayerLeftRoom IsMasterClient {0}", PhotonNetwork.IsMasterClient); // called before OnPlayerLeftRoom
    
    
            LoadArena();
        }
    }
    
    
    #endregion
    

    14.加入游戏大厅
    将下列内容附加到OnJoinedRoom()方法

    // #Critical: We only load if we are the first player, else we rely on `PhotonNetwork.AutomaticallySyncScene` to sync our instance scene.
    if (PhotonNetwork.CurrentRoom.PlayerCount == 1)
    {
        Debug.Log("We load the 'Room for 1' ");
    
    
        // #Critical
        // Load the Room Level.
        PhotonNetwork.LoadLevel("Room for 1");
    }
    

    打开场景Launcher运行它。点击“Play”但如果你离开房间,你会注意到当你回到大厅时,它会自动重新加入要解决这个问题,我们可以修改Launcher.cs脚本

    添加新属性:

    /// <summary>
    /// Keep track of the current process. Since connection is asynchronous and is based on several callbacks from Photon,
    /// we need to keep track of this to properly adjust the behavior when we receive call back by Photon.
    /// Typically this is used for the OnConnectedToMaster() callback.
    /// </summary>
    bool isConnecting;
    

    Connect()方法添加:

    // keep track of the will to join a room, because when we come back from the game we will get a callback that we are connected, so we need to know what to do then
    isConnecting = PhotonNetwork.ConnectUsingSettings();
    

    结果:

    public void Connect()
    {
        progressLabel.SetActive(true);
        controlPanel.SetActive(false);
        if (PhotonNetwork.IsConnected)
        {
            PhotonNetwork.JoinRandomRoom();
        }
        else
        {
            isConnecting = PhotonNetwork.ConnectUsingSettings();
            PhotonNetwork.GameVersion = gameVersion;
        }
    }
    

    OnConnectedToMaster()方法加入:

    // we don't want to do anything if we are not attempting to join a room.
    // this case where isConnecting is false is typically when you lost or quit the game, when this level is loaded, OnConnectedToMaster will be called, in that case
    // we don't want to do anything.
    if (isConnecting)
    {
        // #Critical: The first we try to do is to join a potential existing room. If there is, good, else, we'll be called back with OnJoinRandomFailed()
        PhotonNetwork.JoinRandomRoom();
        isConnecting = false;
    }
    

    15.玩家设置
    模型在Assets\Photon\PhotonUnityNetworking\Demos\Shared Assets\Models
    Kyle Robot.fbx
    新建一个空场景,拖入Kyle Robot.fbx进入场景,将模型拖入Resources文件夹,做成一个预制体:

    双击My Kyle Robot修改碰撞器:
    在这里插入图片描述
    动画设置
    在这里插入图片描述
    配这个Kyle Robot我们的控制器预制件,只需设置属性Controller要指向的动画组件的Kyle Robot
    在这里插入图片描述
    使用控制器参数
    新建PlayerAnimatorManager.cs脚本:

    using UnityEngine;
    using System.Collections;
    
    
    namespace Com.MyCompany.MyGame
    {
        public class PlayerAnimatorManager : MonoBehaviour
        {
            #region MonoBehaviour Callbacks
    
    
            // Use this for initialization
            void Start()
            {
            }
    
    
            // Update is called once per frame
            void Update()
            {
            }
    
    
            #endregion
        }
    }
    

    创建变量:

    private Animator animator;
    // Use this for initialization
    void Start()
    {
        animator = GetComponent<Animator>();
        if (!animator)
        {
            Debug.LogError("PlayerAnimatorManager is Missing Animator Component", this);
        }
    }
    // Update is called once per frame
    void Update()
    {
        if (!animator)
        {
            return;
        }
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        if (v < 0)
        {
            v = 0;
        }
        animator.SetFloat("Speed", h * h + v * v);
    }
    

    动画管理员脚本:方向控制
    编辑脚本PlayerAnimatorManager

    #region Private Fields
    
    
    [SerializeField]
    private float directionDampTime = 0.25f;
    
    
    #endregion
    

    Update里面添加:

    animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime);
    

    动画管理员脚本:跳跃
    编辑脚本PlayerAnimatorManager

    // deal with Jumping
    AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
    // only allow jumping if we are running.
    if (stateInfo.IsName("Base Layer.Run"))
    {
        // When using trigger parameter
        if (Input.GetButtonDown("Fire2"))
        {
            animator.SetTrigger("Jump");
        }
    }
    

    结果:

    using UnityEngine;
    using System.Collections;
    
    namespace Com.MyCompany.MyGame
    {
        public class PlayerAnimatorManager : MonoBehaviour
        {
            #region Private Fields
    
            [SerializeField]
            private float directionDampTime = .25f;
            private Animator animator;
    
            #endregion
    
            #region MonoBehaviour CallBacks
    
            // Use this for initialization
            void Start()
            {
                animator = GetComponent<Animator>();
                if (!animator)
                {
                    Debug.LogError("PlayerAnimatorManager is Missing Animator Component", this);
                }
    
            }
    
            // Update is called once per frame
            void Update()
            {
                if (!animator)
                {
                    return;
                }
                // deal with Jumping
                AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
                // only allow jumping if we are running.
                if (stateInfo.IsName("Base Layer.Run"))
                {
                    // When using trigger parameter
                    if (Input.GetButtonDown("Fire2"))
                    {
                        animator.SetTrigger("Jump");
                    }
                }
                float h = Input.GetAxis("Horizontal");
                float v = Input.GetAxis("Vertical");
                if (v < 0)
                {
                    v = 0;
                }
                animator.SetFloat("Speed", h * h + v * v);
                animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime);
            }
    
            #endregion
        }
    }
    

    摄像机设置
    添加组件CameraWork到My Kyle Robot预制件

    PhotonView组件
    给模型添加一个PhotonView组件:
    设置Observe Option到Unreliable On Change

    增加武器射线
    点击模型,打开层级列表,找到头部:
    在这里插入图片描述
    设置两个Cube为射线,然后父对象为Head:
    在这里插入图片描述
    在这里插入图片描述
    控制射线:
    创建一个新的脚本:PlayerManager.cs

    using UnityEngine;
    using UnityEngine.EventSystems;
    
    using Photon.Pun;
    
    using System.Collections;
    
    namespace Com.MyCompany.MyGame
    {
        /// <summary>
        /// Player manager.
        /// Handles fire Input and Beams.
        /// </summary>
        public class PlayerManager : MonoBehaviourPunCallbacks
        {
            #region Private Fields
    
            [Tooltip("The Beams GameObject to control")]
            [SerializeField]
            private GameObject beams;
            //True, when the user is firing
            bool IsFiring;
            #endregion
    
            #region MonoBehaviour CallBacks
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity during early initialization phase.
            /// </summary>
            void Awake()
            {
                if (beams == null)
                {
                    Debug.LogError("<Color=Red><a>Missing</a></Color> Beams Reference.", this);
                }
                else
                {
                    beams.SetActive(false);
                }
            }
    
            /// <summary>
            /// MonoBehaviour method called on GameObject by Unity on every frame.
            /// </summary>
            void Update()
            {
    
                ProcessInputs();
    
                // trigger Beams active state
                if (beams != null && IsFiring != beams.activeInHierarchy)
                {
                    beams.SetActive(IsFiring);
                }
            }
    
            #endregion
    
            #region Custom
    
            /// <summary>
            /// Processes the inputs. Maintain a flag representing when the user is pressing Fire.
            /// </summary>
            void ProcessInputs()
            {
                if (Input.GetButtonDown("Fire1"))
                {
                    if (!IsFiring)
                    {
                        IsFiring = true;
                    }
                }
                if (Input.GetButtonUp("Fire1"))
                {
                    if (IsFiring)
                    {
                        IsFiring = false;
                    }
                }
            }
    
            #endregion
        }
    }
    

    生命值
    打开PlayerManager剧本
    增加一个公众Health属性

    [Tooltip("The current Health of our player")]
    public float Health = 1f;
    

    以下两个方法添加到MonoBehaviour Callbacks区域。

    /// <summary>
    /// MonoBehaviour method called when the Collider 'other' enters the trigger.
    /// Affect Health of the Player if the collider is a beam
    /// Note: when jumping and firing at the same, you'll find that the player's own beam intersects with itself
    /// One could move the collider further away to prevent this or check if the beam belongs to the player.
    /// </summary>
    void OnTriggerEnter(Collider other)
    {
        if (!photonView.IsMine)
        {
            return;
        }
        // We are only interested in Beamers
        // we should be using tags but for the sake of distribution, let's simply check by name.
        if (!other.name.Contains("Beam"))
        {
            return;
        }
        Health -= 0.1f;
    }
    /// <summary>
    /// MonoBehaviour method called once per frame for every Collider 'other' that is touching the trigger.
    /// We're going to affect health while the beams are touching the player
    /// </summary>
    /// <param name="other">Other.</param>
    void OnTriggerStay(Collider other)
    {
        // we dont' do anything if we are not the local player.
        if (! photonView.IsMine)
        {
            return;
        }
        // We are only interested in Beamers
        // we should be using tags but for the sake of distribution, let's simply check by name.
        if (!other.name.Contains("Beam"))
        {
            return;
        }
        // we slowly affect health when beam is constantly hitting us, so player has to move to prevent death.
        Health -= 0.1f*Time.deltaTime;
    }
    

    在公共字段区域中添加此变量

    public static GameManager Instance;
    

    Start()方法添加:

    void Start()
    {
        Instance = this;
    }
    

    Update函数添加:

    if (Health <= 0f)
    {
        GameManager.Instance.LeaveRoom();
    }
    

    16.联网
    Transform 同步

    添加组件PhotonTransformView

    动画同步
    添加组件PhotonAnimatorView

    在这里插入图片描述
    16.用户输入管理
    打开PlayerAnimatorManager.cs
    Update添加

    if (photonView.IsMine == false && PhotonNetwork.IsConnected == true)
    {
        return;
    }
    

    17.摄像机控制
    打开PlayerManager剧本

    /// <summary>
    /// MonoBehaviour method called on GameObject by Unity during initialization phase.
    /// </summary>
    void Start()
    {
        CameraWork _cameraWork = this.gameObject.GetComponent<CameraWork>();
    
    
        if (_cameraWork != null)
        {
            if (photonView.IsMine)
            {
                _cameraWork.OnStartFollowing();
            }
        }
        else
        {
            Debug.LogError("<Color=Red><a>Missing</a></Color> CameraWork Component on playerPrefab.", this);
        }
    }
    

    禁用Follow on Start
    在这里插入图片描述
    18.开火控制

    打开脚本PlayerManager

    if (photonView.IsMine)
    {
        ProcessInputs ();
    }
    

    添加接口IPunObservable

    public class PlayerManager : MonoBehaviourPunCallbacks, IPunObservable
    {
        #region IPunObservable implementation
    
    
        public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
        {
        }
    
    
        #endregion
    

    IPunObservable.OnPhotonSerializeView添加以下代码

    if (stream.IsWriting)
    {
        // We own this player: send the others our data
        stream.SendNext(IsFiring);
    }
    else
    {
        // Network player, receive data
        this.IsFiring = (bool)stream.ReceiveNext();
    }
    

    将PlayerManager组件拖入PhotonView组件

    在这里插入图片描述
    19.生命值同步
    打开脚本PlayerManager

    if (stream.IsWriting)
    {
        // We own this player: send the others our data
        stream.SendNext(IsFiring);
        stream.SendNext(Health);
    }
    else
    {
        // Network player, receive data
        this.IsFiring = (bool)stream.ReceiveNext();
        this.Health = (float)stream.ReceiveNext();
    }
    

    20.实例化玩家

    打开GameManager脚本
    在公共字段区域中添加以下变量

    [Tooltip("The prefab to use for representing the player")]
    public GameObject playerPrefab;
    

    在Start()方法,添加以下内容

    if (playerPrefab == null)
    {
        Debug.LogError("<Color=Red><a>Missing</a></Color> playerPrefab Reference. Please set it up in GameObject 'Game Manager'",this);
    }
    else
    {
        Debug.LogFormat("We are Instantiating LocalPlayer from {0}", Application.loadedLevelName);
        // we're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
        PhotonNetwork.Instantiate(this.playerPrefab.name, new Vector3(0f,5f,0f), Quaternion.identity, 0);
    }
    

    在这里插入图片描述
    21.跟随玩家
    打开PlayerManager脚本
    在“公共字段”区域中,添加以下内容

    [Tooltip("The local player instance. Use this to know if the local player is represented in the Scene")]
    public static GameObject LocalPlayerInstance;
    

    在Awake()方法,添加以下内容

    // #Important
    // used in GameManager.cs: we keep track of the localPlayer instance to prevent instantiation when levels are synchronized
    if (photonView.IsMine)
    {
        PlayerManager.LocalPlayerInstance = this.gameObject;
    }
    // #Critical
    // we flag as don't destroy on load so that instance survives level synchronization, thus giving a seamless experience when levels load.
    DontDestroyOnLoad(this.gameObject);
    

    将实例化调用包围在if条件

    if (PlayerManager.LocalPlayerInstance == null)
    {
        Debug.LogFormat("We are Instantiating LocalPlayer from {0}", SceneManagerHelper.ActiveSceneName);
        // we're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
        PhotonNetwork.Instantiate(this.playerPrefab.name, new Vector3(0f, 5f, 0f), Quaternion.identity, 0);
    }
    else
    {
        Debug.LogFormat("Ignoring scene load for {0}", SceneManagerHelper.ActiveSceneName);
    }
    

    22.管理场景外的玩家
    打开PlayerManager脚本

    #if UNITY_5_4_OR_NEWER
    void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode loadingMode)
    {
        this.CalledOnLevelWasLoaded(scene.buildIndex);
    }
    #endif
    

    在Start()方法,添加以下代码

    #if UNITY_5_4_OR_NEWER
    // Unity 5.4 has a new scene management. register a method to call CalledOnLevelWasLoaded.
    UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
    #endif
    

    在“MonoBehaviour回调”区域中添加以下两个方法

    #if !UNITY_5_4_OR_NEWER
    /// <summary>See CalledOnLevelWasLoaded. Outdated in Unity 5.4.</summary>
    void OnLevelWasLoaded(int level)
    {
        this.CalledOnLevelWasLoaded(level);
    }
    #endif
    
    
    void CalledOnLevelWasLoaded(int level)
    {
        // check if we are outside the Arena and if it's the case, spawn around the center of the arena in a safe zone
        if (!Physics.Raycast(transform.position, -Vector3.up, 5f))
        {
            transform.position = new Vector3(0f, 5f, 0f);
        }
    }
    

    覆盖OnDisable方法如下

    #if UNITY_5_4_OR_NEWER
    public override void OnDisable()
    {
        // Always call the base to remove callbacks
        base.OnDisable ();
        UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
    }
    #endif
    

    23.玩家UI 血条和名字预设体
    在场景中新建UI,Slider,锚点,中间位置,rect宽度80高度15,背景设置成红色,加一个CanvasGroup组件,设置Interactable和Blocks Raycast为false,拖入到Prefab文件夹,删除场景中的实例,我们不再需要它了

    创建一个新的C#脚本PlayerUI.cs

    using UnityEngine;
    using UnityEngine.UI;
    
    
    using System.Collections;
    
    
    namespace Com.MyCompany.MyGame
    {
        public class PlayerUI : MonoBehaviour
        {
            #region Private Fields
    
    
            [Tooltip("UI Text to display Player's Name")]
            [SerializeField]
            private Text playerNameText;
    
    
            [Tooltip("UI Slider to display Player's Health")]
            [SerializeField]
            private Slider playerHealthSlider;
    
    
            #endregion
    
    
            #region MonoBehaviour Callbacks
    
    
            #endregion
    
    
            #region Public Methods
    
    
            #endregion
    
    
        }
    }
    

    添加属性:

    private PlayerManager target;
    

    添加此公共方法

    public void SetTarget(PlayerManager _target)
    {
        if (_target == null)
        {
            Debug.LogError("<Color=Red><a>Missing</a></Color> PlayMakerManager target for PlayerUI.SetTarget.", this);
            return;
        }
        // Cache references for efficiency
        target = _target;
        if (playerNameText != null)
        {
            playerNameText.text = target.photonView.Owner.NickName;
        }
    }
    

    添加此方法

    void Update()
    {
        // Reflect the Player Health
        if (playerHealthSlider != null)
        {
            playerHealthSlider.value = target.Health;
        }
    }
    

    24.实例化
    打开脚本PlayerManager
    添加一个公共字段以保存对Player UI预置的引用,如下所示:

    [Tooltip("The Player's UI GameObject Prefab")]
    [SerializeField]
    public GameObject PlayerUiPrefab;
    

    将此代码添加到Start()方法

    if (PlayerUiPrefab != null)
    {
        GameObject _uiGo =  Instantiate(PlayerUiPrefab);
        _uiGo.SendMessage ("SetTarget", this, SendMessageOptions.RequireReceiver);
    }
    else
    {
        Debug.LogWarning("<Color=Red><a>Missing</a></Color> PlayerUiPrefab reference on player Prefab.", this);
    }
    

    将此添加到Update()功能

    // Destroy itself if the target is null, It's a fail safe when Photon is destroying Instances of a Player over the network
    if (target == null)
    {
        Destroy(this.gameObject);
        return;
    }
    

    将此代码添加到CalledOnLevelWasLoaded()方法

    GameObject _uiGo = Instantiate(this.PlayerUiPrefab);
    _uiGo.SendMessage("SetTarget", this, SendMessageOptions.RequireReceiver);
    

    在“MonoBehaviour回调”区域中添加此方法

    void Awake()
    {
        this.transform.SetParent(GameObject.Find("Canvas").GetComponent<Transform>(), false);
    }
    

    在“公共字段”区域中添加此公共属性

    [Tooltip("Pixel offset from the player target")]
    [SerializeField]
    private Vector3 screenOffset = new Vector3(0f,30f,0f);
    

    将这四个字段添加到“私有字段”区域

    float characterControllerHeight = 0f;
    Transform targetTransform;
    Renderer targetRenderer;
    CanvasGroup _canvasGroup;
    Vector3 targetPosition;
    

    将此添加到Awake方法域

    _canvasGroup = this.GetComponent<CanvasGroup>();
    

    将下列代码追加到SetTarget()后法_target已经设定好了。

    targetTransform = this.target.GetComponent<Transform>();
    targetRenderer = this.target.GetComponent<Renderer>();
    CharacterController characterController = _target.GetComponent<CharacterController> ();
    // Get data from the Player that won't change during the lifetime of this Component
    if (characterController != null)
    {
    characterControllerHeight = characterController.height;
    }
    

    在“MonoBehaviour回调”区域中添加此公共方法

    void LateUpdate()
    {
    // Do not show the UI if we are not visible to the camera, thus avoid potential bugs with seeing the UI, but not the player itself.
        if (targetRenderer!=null)
        {
            this._canvasGroup.alpha = targetRenderer.isVisible ? 1f : 0f;
        }
    
    
    // #Critical
    // Follow the Target GameObject on screen.
    if (targetTransform != null)
    {
        targetPosition = targetTransform.position;
        targetPosition.y += characterControllerHeight;
        this.transform.position = Camera.main.WorldToScreenPoint (targetPosition) + screenOffset;
    }
    }
    

    2.游戏大厅与等待房间

    1.游戏大厅

    // 用于连接Cloud并进入游戏大厅
    
    PhotonNetwork.ConnectUsingSettings(string version)
    
    //进入游戏大厅的默认回调函数
    
    void OnJoinedLobby()
    
    //显示连接日志
    
    GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString())
    

    2.创建等待房间

    //设置房间属性并创建房间
    
    RoomOptions ro = new RoomOptions();
    
    ro.IsOpen = true;ro.IsVisible = true;
    
    //设置最大玩家数 为了简单就从2个人开始做起吧 可以随意设置
    
    ro.MaxPlayers = 2;
    
    PhotonNetwork.CreateRoom(srting roomName, ro, TypedLobby.Default);
    
    //创建房间失败的回调函数
    
    void OnPhotonCreateRoomFailed()
    

    3.加入等待房间

    //随机加入房间
    
    PhotonNetwork.JoinRandomRoom();
    
    //随机进入房间失败(可能是因为没有空房间)的回调函数
    
    //默认的回调函数一定不能眼花写错!!!
    
    void OnPhotonRandomJoinFailed()
    
    {可以调用PhotonNetwork.CreateRoom创建一个}
    
    //进入房间的回调函数
    
    void OnJoinedRoom()
    {
    	StartCoroutine(this.ChangeToWaitScene());
    	//写一个协程 当成功进入房间后就加载等待房间的场景
    }
    IEnumerator ChangeToWaitScene()
     {
    	//切换场景期间中断与photon服务器的网络信息传输 
    	//(加载场景尚未完成的情况下 服务器传递的网络信息可能会引发不必要的错误)
    	PhotonNetwork.isMessageQueueRunning = false;
    	//加载场景
    	AsyncOperation ao = SceneManager.LoadSceneAsync("RoomForWait");
    	 yield return ao;
    }
    

    加入房间的同时最好将玩家姓名PhotonNetwork.player.NickName读取或者设置,可以与PlayerPrefs连用实现数据的持久化。

    4.房间列表的显示
    UGUI里面的Grid Layout Group和 Horizontal Layout Group就是针对于这种情况设计的。我们可以将一个房间列表存储成一个预设,每次有新房间生成就生成一个预设。上面这俩组件可以帮助你把这些房间列表预设排列得整齐划一。

    需要用到的prefab都要存在根目录下的Resources文件夹。硬性规定。

    //只有在大厅里的房间有玩家进入的时候才会执行 接收房间列表

    void OnReceivedRoomListUpdate()
        {
    		//给单个房间列表的预设增加标签
            GameObject[] a = GameObject.FindGameObjectsWithTag("OneRoom");
            for (int i = 0; i < a.Length; i++)
                Destroy(a[i].gameObject);
            //每次接收房间列表前把旧的预设销毁 这样就能更新在线人数和房间总人数
            //利用接收房间目录信息的函数生成单个列表预设 
            //PhotonNetwork.GetRoomList()可以获取房间列表里的房间数组
            foreach (RoomInfo _room in PhotonNetwork.GetRoomList(){
               //接收房间列表
                GameObject room = (GameObject)Instantiate(OneRoom);
                room.transform.SetParent(RoomList.transform, false);
                roomData rd = room.GetComponent<roomData>();
                rd.roomName = _room.Name;
                rd.connectPlayer = _room.PlayerCount;
                rd.maxPlayers = _room.MaxPlayers;
                rd.DisplayRoomData();//把数据都获取并设置好了就显示在面板上
            }
        }
    

    至于roomData脚本里面存储的就是房间名、房间人数、最大容纳人数等基本信息,同时最好根据房间人数是否满来设置加入房间的按钮interactable= ture还是false。

    这时候如果点击房间列表上的Join 就能进入房间了。

    大致效果如下(那个NO.是我给房间用随机数字命名的房间名。场景中其实还有个输入玩家姓名的输入框,如果玩家没有输入姓名就自动随机给个数字当名称。)
    在这里插入图片描述

    展开全文
  • 本文为原创,转载请注明出处! 想到哪写到哪,主要是给自己做一个记录,记性不太好。教程实在是少的可怜 学习PhotonServer这个服务器引擎已经有一段时间了。一直想做一个带大厅的棋牌类型游戏,所以最近又把...

    本文为原创,转载请注明出处!


    想到哪写到哪,主要是给自己做一个记录,记性不太好。教程实在是少的可怜


    学习PhotonServer这个服务器引擎已经有一段时间了。一直想做一个带大厅的棋牌类型游戏,所以最近又把Photon捡起来了,有了新的体会。


    photon是可以客户端和服务端都用Lite来做,客户端有ExitGames.Client.Photon.Lite命名空间,服务器也有对应的Lite,这种好处在于,代码很干净,比较好管理,Photon本身的代码和自己写的代码可以分的很清楚。但是有个弊端,游戏里的物体同步需要自己去写,我还没发现有这个API。

    所以我客户的选择用Photon PUN  这个是在PHOTON的基础上再次封装,写法和Unity本身的网络很像,因为这个插件里有个很重要的组件 PhotonView ,可以直接丢到物体上。这个物体就可以和别的客户端同步了。不要太爽。

    IPunCallbacks 接口,里面包含了PUN本身设定的一些回调函数,和UNITY3D本身的Start等方法一样,是当别的事件触发了以后调用。

    客户端用了PUN,服务端是有Photon提供的云服务器,但是都在国外,卡的很,其实是可以连接到本地的LoadBalancing服务器。并且其他服务不能启用。


    以下是一些API的使用方法:


    //连接到主服务器

     PhotonNetwork.ConnectToMaster("127.0.0.1", 5055, "许可证", "游戏版本号");

    //是否自动加入大厅,默认是

    PhotonNetwork.autoJoinLobby = false;

    //是否自动销毁玩家离开前生成的物体,默认是
    PhotonNetwork.autoCleanUpPlayerObjects = false;

    //加入或者创建房间

     PhotonNetwork.JoinOrCreateRoom("房间1", new RoomOptions() { maxPlayers = 10 },  TypedLobby);    TypedLobby是大厅类,如果只有一个大厅就NULL

    //实例化一个物体,让它同步

     PhotonNetwork.Instantiate("Cube", new Vector3(0, 0, 0), Quaternion.identity, 0);  

     “Cube”是prefab的名字,必须放在Resources文件下面,并且要加上一个photonView组件。PhotonView同步只支持位置大小和旋转,是有一个PhotonRIgidbody View组件,看起来是刚体的同步,但是感觉没用。

    //服务器时间

     PhotonNetwork.time 这个返回过来的秒,但是感觉不对。


    //给其他客户的发送Event      这个是用的photonPun来写,在大厅不能调用,必须要再房间里面,其实底层就是PhotonServer,为了跟UNITY3D的写法一样又封装了。

    先添加一个PhotonView组件,而且脚本要继承自Photon.MonoBehaviour


        public void Hello()
        {     
           this.photonView.RPC("DoHello",PhotonTargets.All,null);   //第一个函数名,第二个接受对象为全部玩家,第三个为参数
        }


        [RPC]                                    //方法名上必须加 [RPC]    
        void DoHello()
        {
            tips.text = "Hello";
        }

    //给其他客户的发送Event      这个是用的photonServer来写

                RaiseEventOptions options=new RaiseEventOptions ();              
                options.TargetActors=new int[]{1,2,3};      //接受客户的的ID
                EventData evnetdate = new EventData();
                Dictionary<byte, object> dic = new Dictionary<byte, object>();      
                dic.Add(1, "这是从客户端"+PhotonNetwork.playerName+"发来的事件消息");    
                evnetdate.Code = 77;
                evnetdate.Parameters = dic;
                PhotonNetwork.RaiseEvent(evnetdate.Code, dic, true, options);    //一个参数是操作码,第二个是事件内容字典,第三个是否可靠 ,第四个是 RaiseEventOptions 

    //接收Event

    PhotonNetwork.OnEventCall += this.OnEventRaised;    //注册回调的函数

        public void OnEventRaised(byte eventCode, object content, int senderID)   //byte eventCode,其实是个字典或者Hastable
        {
          
            switch (eventCode)
            {
                case 77:
                    eventString =( (Dictionary<byte, object>)content) [1].ToString();
        
                    break;
            }


    //给服务器发送请求

    这个我并没有发现PUN里面有这个API,需要自己去写,在 PhotonNetwork里自己写一个函数 OpCustom发送请求。服务器也要相应的写一个收到请求的处理

    服务器的相关处理主要修改MasterServer / MasterClientPeer.cs 里面


    //返回请求响应

    这个也没有,可以按照IPunCallbacks 接口里面的函数写法来写,修改这个接口,添加一个返回函数到接口里面,在NetworkPeer里面OnOperationResponse判断,如果请求代码小于200就调用我们自己写的返回响应的函数。(因为Photon本身的操作码都在200--255之间)

            if (operationResponse.OperationCode<200)
            {
                SendMonoMessage(PhotonNetworkingMessage.OnReceiveOpCustomResponse, operationResponse); 
                return;
            }

     PhotonNetworkingMessage.OnReceiveOpCustomResponse这个函数是我自己加的,这样客户端只要一发送请求,服务器返回回来就用自动调用一个函数,然后我们可以去那个函数里去处理接受到的响应。



    其他的一些API都蛮简单,看看就会了。


    总得来说,我感觉PUN比photon Lite好用很多,而且自己修改好和服务器通讯的代码以后,客户端代码很干净,层次很清楚。服务端还需要再研究研究。


    本文为原创,转载请注明出处!

    展开全文
  • 由于这段时间比较忙,所以也很久没发布过新的教程,这几天刚好要为一个项目写服务端程序,所以顺便也在Unity3d里面实现了一个简单的客户端,多个客户端一同使用就是一个简单的公共聊天室了。服务端为一个控制台程序...
  • 动作与特效同步的小插件!!! 支持延时。 使用直接把脚本放入Unity工程下即可。
  • 使用unity3d2019加photon开发的网络对战小游戏,游戏玩法借鉴经典游戏,提供4种场景。游戏素材均来自于网络与unity官方案例资源,若有侵权请联系删除。游戏仅供参考学习使用。如果有人想要完整开发工程包的可以联系...
  • Photon Unity Networking(PUN)的简单使用 博客原文http://blog.csdn.net/xiaoge132/article/details/65628220
  • Photon Unity Networking(首字母缩写PUN)是一个Unity多人游戏插件包。它提供了身份验证选项、匹配,以及快速、可靠的通过我们的Photon后端实现的游戏内通信。 二、原文 原文地址:...
  • Unity3D 加载场景有很多种方式,做一些小的 DEMO 的时候往往是直接使用--文章出处【狗刨学习网】   Application.LoadLevel 或者 Application.LoadLevelAsync 加载场景,,但是这种办法不适合在真正的 ...
  • PUN插件的话,就在OnJoinedRoom()回调函数里,表示加入房间,可以实例化角色,GameObject go=PhotonNetwork.Instantiate(prefabPlayer.name, new Vector3(241,0.2f,253), Quaternion.identity, 0);这里的...
  • RPC Details 远程过程调用(RPC)让你调用一个远程计算机的函数。就好像调用一个普通函数一样容易,但也需要理解 一些重要的不同。 ...只要你喜欢,RPC调用可以有很多参数。...你可以轻松调用
  • Photon和KBEngineunity3d是最适用Unity3d游戏开发的两个服务器引擎
  • 本文将把常用的Unity插件分成11大类进行归档,方便各位Unity开发者查找使用。 整理成了一个电子书方便大家查阅,下载地址:Asset Store寻宝书百度网盘,提取码:dfun 一:用Unity开发手机游戏的常用插件 越来越多...
  • unity3d中实现聊天功能

    2015-03-11 19:01:30
    【狗刨学习网】 using UnityEngine; using System.Collections; public class Chat : MonoBehaviour {  bool usingChat = false;  bool showChat = false;... string inputField = "";... Vector2 scr
  • 路线节点:[unity3d-->ugui-->playmaker-->pun-->Photon3Unity3d.dll-->photon master server-->mysql] 步骤: 1、【客户端】用户输入用户名、密码。设置PhotonNetwork.AuthValues。    -->AccountLogin.cs
  • Photon引擎是什么? Photon是集产品、服务于一体的网络引擎,也是最快、最灵活、最容易上手的网络引擎,可在PC、Mac、浏览器、移动终端、控制台等多个平台上建立可扩展的MMOG、FPS及任何多人在线网络游戏和应用。...
  • 紧接着上一篇博客,上一篇博客中,我们已经能够分别移动角色,并且控制他射击了,而且还稍微区分了一下不同的角色。这篇博客中我们继续讲解后面的内容。 既然角色都已经可以射击了,那肯定还得需要一个血量对吧,...
1 2 3 4 5 6
收藏数 111
精华内容 44
热门标签