前置知識(shí),安裝,及簡(jiǎn)單UI
- 【Unity工具,簡(jiǎn)單學(xué)習(xí)】PUN 2,多人在線游戲開(kāi)發(fā),初步使用
- 需要有一定 UNITY 使用經(jīng)驗(yàn)的開(kāi)發(fā)者可以順利閱讀。
大廳
- 簡(jiǎn)單搭建一下大廳UI。
給Laucher
節(jié)點(diǎn)一個(gè)Launcher
腳本 -
Launcher
腳本如下,具體功能看注釋
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using Photon.Realtime;
public class Launcher : MonoBehaviourPunCallbacks
{
public string RoomName = "Room"; // 設(shè)置房間名,確保每次連接到同一個(gè)房間
private string gameVersion = "1";
[SerializeField]
private const byte maxPlayersPerRoom = 4; // 設(shè)置單房間最多玩家數(shù)
[SerializeField]
private GameObject controlPanel;
[SerializeField]
private GameObject progressLabel;
void Awake()
{
// Then let master server can use PhotonNetwork.LoadLevel()
// Everyone will see the same level
PhotonNetwork.AutomaticallySyncScene = true; // 確保該變量為 true,否則無(wú)法同步
progressLabel.SetActive(false);
controlPanel.SetActive(true);
}
public void Connect()
{
progressLabel.SetActive(true);
controlPanel.SetActive(false);
if (PhotonNetwork.IsConnected) // 若已連接,則直接加入到房間
{
PhotonNetwork.JoinOrCreateRoom(RoomName, new RoomOptions() { MaxPlayers = maxPlayersPerRoom }, default);
}
else
{
PhotonNetwork.ConnectUsingSettings(); // 用 PhotonServerSettings 來(lái)連接
PhotonNetwork.GameVersion = gameVersion;
}
}
public override void OnJoinRandomFailed(short returnCode, string message) // 若加入隨機(jī)房間失敗
{
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(RoomName, new RoomOptions() { MaxPlayers = maxPlayersPerRoom });
}
public override void OnJoinedRoom() // 若加入了某房間,則加載聊天室場(chǎng)景,不要使用 UNITY的加載場(chǎng)景方法
{
Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");
PhotonNetwork.LoadLevel("ChatingRoom");
}
public override void OnConnectedToMaster() // 運(yùn)行ConnectUsingSettings()后會(huì)先連接到 Master節(jié)點(diǎn),再創(chuàng)建或加載房間
{
Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN");
PhotonNetwork.JoinOrCreateRoom(RoomName, new RoomOptions() { MaxPlayers = maxPlayersPerRoom }, default);
}
public override void OnDisconnected(DisconnectCause cause) // 若失去連接后
{
progressLabel.SetActive(false);
controlPanel.SetActive(true);
Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause);
}
}
- 需要注意的是
PhotonNetwork.JoinOrCreateRoom(RoomName, new RoomOptions() { MaxPlayers = maxPlayersPerRoom }, default);
方法,若該房間名的房間不存在則創(chuàng)建,否則加載該房間。 - 在輸入框中,把該字符串賦值給
PhotonNetwork.NickName
即表示該玩家的名稱(chēng),需非空PhotonNetwork.NickName = defaultName;
聊天室
- 聊天室的UI如下
添加一個(gè)GameManagerPUN
的物體并給予它對(duì)應(yīng)的腳本組件 -
GameManagerPUN
腳本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Photon.Pun;
using Photon.Realtime;
public class GameManagerPUN : MonoBehaviourPunCallbacks
{
public string DialogueText = "DialogueText";
[SerializeField]
private InputField _inputField;
public GameObject Content;
void Start()
{
GameObject go = PhotonNetwork.Instantiate("Sphere", Vector3.zero, Quaternion.identity);
go.GetComponent<ChangeRandomPosition>().change(); // 用 PhotonNetwork.Instantiate 創(chuàng)建一個(gè)物體,讓它的位置隨機(jī)變一下,代表顯示該房間內(nèi)的玩家個(gè)數(shù)
}
public override void OnLeftRoom()
{
SceneManager.LoadScene("LobbyScene");
}
public void LeaveRoom() // 退出按鈕的監(jiān)聽(tīng)器直接調(diào)用該代碼即可調(diào)用 OnLeftRoom() 方法,退出大廳
{
PhotonNetwork.LeaveRoom();
}
public override void OnPlayerEnteredRoom(Player other) // 自動(dòng)監(jiān)聽(tīng)是否有玩家進(jìn)入
{
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
}
}
public override void OnPlayerLeftRoom(Player other) // 自動(dòng)監(jiān)聽(tīng)是否有玩家退出
{
Debug.LogFormat("OnPlayerLeftRoom() {0}", other.NickName); // seen when other disconnects
if (PhotonNetwork.IsMasterClient)
{
Debug.LogFormat("OnPlayerLeftRoom IsMasterClient {0}", PhotonNetwork.IsMasterClient); // called before OnPlayerLeftRoom
}
}
public void SendMessage() // 發(fā)送消息,記錄發(fā)送消息者名稱(chēng),與它發(fā)送的消息,然后創(chuàng)建一個(gè)UI物體,加載到滾動(dòng)content中
{
string res = PhotonNetwork.NickName + " : " + _inputField.text;
GameObject obj = PhotonNetwork.Instantiate(DialogueText, Vector3.zero, Quaternion.identity);
obj.GetComponent<Text>().text = res;
obj.GetComponent<SentenceAsync>().str = res;
obj.transform.SetParent(Content.transform);
_inputField.text = ""; // 發(fā)送玩后清空輸入框
}
}
-
同步文本信息腳本
SentenceAsync
如下,需要實(shí)現(xiàn)IPunObservable
接口的OnPhotonSerializeView
方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using UnityEngine.UI;
public class SentenceAsync : MonoBehaviourPunCallbacks, IPunObservable
{
public string str;
public GameManagerPUN gm;
public GameObject pa;
void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
stream.SendNext(str);
}
else
{
str = (string)stream.ReceiveNext();
if (pa == null) // 用于在不同客戶(hù)端的同步處理
{
pa = GameObject.Find("Content");
this.gameObject.transform.SetParent(pa.transform);
this.GetComponent<Text>().text = str;
}
}
}
}
- 沒(méi)錯(cuò),該同步腳本糾結(jié)了我好幾個(gè)小時(shí),才成功處理對(duì)。為什么這么麻煩呢?
簡(jiǎn)單同步
- 同步的基本原理比較復(fù)雜, 所幸該
Photon + Pun2
給我們封裝的差不多了,我們只需要知道基礎(chǔ)的內(nèi)容即可 - 簡(jiǎn)單來(lái)說(shuō),假設(shè)有客戶(hù)端
A,B,C
,他們都連接服務(wù)器X
在客戶(hù)端A
處,玩家創(chuàng)建了一個(gè)球,若只是普通創(chuàng)建,則只有A
處的玩家能看到該球。
若希望每個(gè)客戶(hù)端都能看到該球,首先需要使用PhotonNetwork.Instantiate(str, Vector3, Quaternion)
方法進(jìn)行同步創(chuàng)建。注意創(chuàng)建物體通過(guò)給定它的字符串str
,該預(yù)設(shè)體的路徑必須在該PhotonUnityNetworking / Resources
文件夾下: - 第二步,指定該物體需要同步什么信息。對(duì)于該球,我們只需要同步它的位置信息即可。
物體若需要同步信息,則必須創(chuàng)建Photon View
腳本。
物體若需要同步信息,則必須創(chuàng)建Photon View
腳本。
物體若需要同步信息,則必須創(chuàng)建Photon View
腳本。
重要的信息重復(fù)三次
物體若需要同步transform
信息,則直接給它Photon Transform View
腳本即可 - 這里,
Ownership
表示該物體的所有權(quán)是否轉(zhuǎn)移,比如A
創(chuàng)建了該物體,是否允許B
更改它的信息。這里還設(shè)計(jì)到主機(jī)與從機(jī)的區(qū)別。但這里我們不更改別的信息,設(shè)置 fixed` 即可 - 這里
Synchronization
選擇Unreliable On Change
即可,Observable Search
選擇同步的觀察信息,可以直接選擇Auto Find Active
或者設(shè)置Manual
然后手動(dòng)給予它需要同步信息的腳本即可。 - 能被觀察到的腳本必須實(shí)現(xiàn)
IPunObservable
接口,實(shí)現(xiàn)IPunObservable.OnPhotonSerializeView
方法。
較復(fù)雜同步
- 我們搜索一下已經(jīng)有的腳本,發(fā)現(xiàn)官方直接支持我們?nèi)缦碌耐侥_本:
- 也就是說(shuō),若需要同步的觀察信息只有
Animator, Rigidbody, Transform
那么可以直接掛載相應(yīng)的腳本進(jìn)行同步。 - 那如果我們需要同步某數(shù)值信息,比如
int, float, string
呢?該三種信息同步相應(yīng)來(lái)說(shuō)比較容易
仍然,我們需要實(shí)現(xiàn)IPunObservable
接口,實(shí)現(xiàn)IPunObservable.OnPhotonSerializeView
方法
void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting) // 若流正在寫(xiě),我們發(fā)送接下來(lái)要同步的信息 string str
{
stream.SendNext(str);
}
else
{
str = (string)stream.ReceiveNext(); // 接受流的下一個(gè)輸出,并轉(zhuǎn)化為 (string) 類(lèi)型
if (pa == null) // (1)
{
pa = GameObject.Find("Content");
this.gameObject.transform.SetParent(pa.transform);
this.GetComponent<Text>().text = str;
}
}
}
- 我們發(fā)現(xiàn),其他腳本都很好懂,那么
(1)
處是什么意思呢?
這里每行對(duì)話(huà)物體,由于掛載在Canvas
中,且掛載Text
組件,所以必須為Rect Transform
組件
盡管Rect Transform
組件是繼承自Transform
組件,但是它的信息與Transform
組件截然不同,所以官方提供的Photon Transform View
不能使用(你掛載就會(huì)出bug,可以F12查看該腳本原碼) - 難道我們?nèi)?shí)現(xiàn)能同步
RectTransform
或者Text
信息的腳本?寫(xiě)起來(lái)有點(diǎn)麻煩…
但是我們轉(zhuǎn)念一想,我們需要同步的信息只有 發(fā)送信息的字符串而已
所以我們只同步字符串。由于PhotonNetwork.Instantiate()
方法已經(jīng)為我們創(chuàng)建了該同步物體,所以別的組件都是創(chuàng)建出來(lái)的,只是其中的信息沒(méi)有同步而已 - 我們還需要同步哪個(gè)信息?
RectTransform?
不需要,由于我們使用ScrollView
,自動(dòng)處理它的各種坐標(biāo)信息Text?
我們只需給予其Text.text = str
即可,其他的字體啥信息都默認(rèn)即可
還有一個(gè)隱藏的需要同步的信息 —— 該物體的父對(duì)象
。父對(duì)象由于我們沒(méi)有同步,所以它默認(rèn)會(huì)創(chuàng)建在世界根節(jié)點(diǎn)的下面,不會(huì)顯示在ScrollView
下。
由于該ScrollView
下面的那些默認(rèn)物體是每個(gè)玩家一開(kāi)始就是一樣的,所以我們只需要單純通過(guò)(1)
指定,把自己掛載到該父對(duì)象上即可。
自定義同步
- 若需要自定義同步,由于
stream.ReceiveNext()
強(qiáng)轉(zhuǎn)類(lèi)型只支持默認(rèn)的那三個(gè)int, float, string
,所以你想同步比如說(shuō)Rect Transform
或者XXXscript
腳本,就需要去里面注冊(cè)某物體類(lèi)型 - 很麻煩,不怎么推薦,相當(dāng)于指定某個(gè)類(lèi)型的序列化和反序列化的規(guī)則,可以自己去搜集信息。
最終效果
- 我和室友,使用的是兩臺(tái)筆記本的兩個(gè)客戶(hù)端,在里面進(jìn)行聊天
- 經(jīng)過(guò)測(cè)試發(fā)現(xiàn),若某玩家退出后,由該
owner
通過(guò)PhotonNetwork.Instantiate
創(chuàng)建的同步物體會(huì)自動(dòng)Destroy
。猜測(cè)可以通過(guò)轉(zhuǎn)移所有權(quán)的方式保留聊天記錄。
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-472372.html
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-472372.html
到了這里,關(guān)于【Unity工具,簡(jiǎn)單應(yīng)用】Photon + PUN 2,做一個(gè)簡(jiǎn)單多人在線聊天室的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!