צור משחק מרובה משתתפים ב-Unity באמצעות PUN 2

תהיתם פעם מה צריך כדי ליצור משחק מרובה משתתפים בתוך Unity?

בניגוד למשחקים לשחקן יחיד, משחקים מרובי משתתפים דורשים שרת מרוחק שממלא את תפקיד הגשר, המאפשר ללקוחות המשחק לתקשר אחד עם השני.

כיום שירותים רבים דואגים לאירוח שרתים. שירות אחד כזה הוא Photon Network, שבו נשתמש במדריך זה.

PUN 2 היא המהדורה העדכנית ביותר של ה-API שלהם אשר שופרה מאוד בהשוואה לגרסה הישנה.

בפוסט זה, נעבור על הורדת הקבצים הדרושים, הגדרת Photon AppID ותכנות דוגמה פשוטה של ​​מרובה משתתפים.

Unity גרסה בשימוש במדריך זה: Unity 2018.3.0f2 (64 סיביות)

חלק 1: הגדרת PUN 2

השלב הראשון הוא הורדת חבילת PUN 2 מה-Asset Store. הוא מכיל את כל הסקריפטים והקבצים הדרושים לשילוב מרובה משתתפים.

  • פתח את פרוייקט Unity שלך ואז עבור אל Asset Store: (חלון -> כללי -> AssetStore) או הקש Ctrl+9
  • חפש "PUN 2- Free" ואז לחץ על התוצאה הראשונה או לחץ כאן
  • ייבא את חבילת PUN 2 לאחר סיום ההורדה

  • לאחר ייבוא ​​החבילה עליך ליצור מזהה אפליקציית Photon, זה נעשה באתר שלהם: https://www.photonengine.com/
  • צור חשבון חדש (או היכנס לחשבון הקיים שלך)
  • עבור אל דף היישומים על ידי לחיצה על סמל הפרופיל ולאחר מכן על "Your Applications" או עקוב אחר הקישור הזה: https://dashboard.photonengine.com/en-US/PublicCloud
  • בדף יישומים לחץ "Create new app"

  • בדף היצירה, עבור Photon Type בחר "Photon Realtime" ועבור שם, הקלד כל שם ולאחר מכן לחץ "Create"

כפי שאתה יכול לראות, היישום כברירת מחדל לתוכנית החינמית. תוכל לקרוא עוד על תוכניות תמחור כאן

  • לאחר יצירת האפליקציה, העתק את מזהה האפליקציה שנמצא מתחת לשם האפליקציה

  • חזור לפרויקט Unity שלך ואז עבור אל חלון -> Photon Unity Networking -> אשף PUN
  • ב-PUN Wizard לחץ על "Setup Project", הדבק את מזהה האפליקציה שלך ואז לחץ "Setup Project"

  • ה-PUN 2 מוכן כעת!

חלק 2: יצירת משחק מרובה משתתפים

כעת נעבור לחלק בו אנו יוצרים למעשה משחק מרובה משתתפים.

הדרך שבה מתנהל מרובה משתתפים ב-PUN 2 היא:

  • ראשית, אנו מתחברים לאזור הפוטון (לדוגמה ארה"ב מזרח, אירופה, אסיה וכו') אשר ידוע גם בשם הלובי.
  • ברגע שנכנסים ללובי, אנו מבקשים את כל החדרים שנוצרו באזור, ואז נוכל להצטרף לאחד מהחדרים או ליצור חדר משלנו.
  • לאחר ההצטרפות לחדר אנו מבקשים רשימה של השחקנים המחוברים לחדר ומציגים את מופעי הנגן שלהם, אשר לאחר מכן מסונכרנים עם המופעים המקומיים שלהם באמצעות PhotonView.
  • כאשר מישהו עוזב את החדר, המופע שלו מושמד והוא מוסר מרשימת השחקנים.

1. הקמת לובי

נתחיל ביצירת סצנת לובי שתכיל את היגיון הלובי (גלישה בחדרים קיימים, יצירת חדרים חדשים וכו'):

  • צור סקריפט C# חדש וקרא לו PUN2_GameLobby
  • צור סצנה חדשה וקרא לה "GameLobby"
  • בסצנת GameLobby צור GameObject חדש. קרא לזה "_GameLobby" והקצה לו את הסקריפט PUN2_GameLobby

כעת פתח את הסקריפט PUN2_GameLobby:

ראשית, אנו מייבאים את מרחבי השמות של Photon על ידי הוספת השורות למטה בתחילת הסקריפט:

using Photon.Pun;
using Photon.Realtime;

כמו כן, לפני שנמשיך, עלינו להחליף את ברירת המחדל של MonoBehaviour ב-MonoBehaviourPunCallbacks. שלב זה נחוץ כדי להיות מסוגל להשתמש בהתקשרות חוזרת של Photon:

public class PUN2_GameLobby : MonoBehaviourPunCallbacks

לאחר מכן, אנו יוצרים את המשתנים הדרושים:

    //Our player name
    string playerName = "Player 1";
    //Users are separated from each other by gameversion (which allows you to make breaking changes).
    string gameVersion = "0.9";
    //The list of created rooms
    List<RoomInfo> createdRooms = new List<RoomInfo>();
    //Use this name when creating a Room
    string roomName = "Room 1";
    Vector2 roomListScroll = Vector2.zero;
    bool joiningRoom = false;

לאחר מכן אנו קוראים ל-ConnectUsingSettings() ב-Void Start(). זה אומר שברגע שהמשחק נפתח, הוא מתחבר לשרת הפוטונים:

    // Use this for initialization
    void Start()
    {
        //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;

        if (!PhotonNetwork.IsConnected)
        {
            //Set the App version before connecting
            PhotonNetwork.PhotonServerSettings.AppSettings.AppVersion = gameVersion;
            // Connect to the photon master-server. We use the settings saved in PhotonServerSettings (a .asset file in this project)
            PhotonNetwork.ConnectUsingSettings();
        }
    }

כדי לדעת אם החיבור לפוטון הצליח, עלינו ליישם את ההתקשרויות הבאות: OnDisconnected(DisconnectCause cause), OnConnectedToMaster(), OnRoomListUpdate(List<RoomInfo> roomList)

    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.Log("OnFailedToConnectToPhoton. StatusCode: " + cause.ToString() + " ServerAddress: " + PhotonNetwork.ServerAddress);
    }

    public override void OnConnectedToMaster()
    {
        Debug.Log("OnConnectedToMaster");
        //After we connected to Master server, join the Lobby
        PhotonNetwork.JoinLobby(TypedLobby.Default);
    }

    public override void OnRoomListUpdate(List<RoomInfo> roomList)
    {
        Debug.Log("We have received the Room list");
        //After this callback, update the room list
        createdRooms = roomList;
    }

הבא הוא החלק של ממשק המשתמש, שבו מתבצעות הגלישה בחדר ויצירת החדר:

    void OnGUI()
    {
        GUI.Window(0, new Rect(Screen.width / 2 - 450, Screen.height / 2 - 200, 900, 400), LobbyWindow, "Lobby");
    }

    void LobbyWindow(int index)
    {
        //Connection Status and Room creation Button
        GUILayout.BeginHorizontal();

        GUILayout.Label("Status: " + PhotonNetwork.NetworkClientState);

        if (joiningRoom || !PhotonNetwork.IsConnected || PhotonNetwork.NetworkClientState != ClientState.JoinedLobby)
        {
            GUI.enabled = false;
        }

        GUILayout.FlexibleSpace();

        //Room name text field
        roomName = GUILayout.TextField(roomName, GUILayout.Width(250));

        if (GUILayout.Button("Create Room", GUILayout.Width(125)))
        {
            if (roomName != "")
            {
                joiningRoom = true;

                RoomOptions roomOptions = new RoomOptions();
                roomOptions.IsOpen = true;
                roomOptions.IsVisible = true;
                roomOptions.MaxPlayers = (byte)10; //Set any number

                PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, TypedLobby.Default);
            }
        }

        GUILayout.EndHorizontal();

        //Scroll through available rooms
        roomListScroll = GUILayout.BeginScrollView(roomListScroll, true, true);

        if (createdRooms.Count == 0)
        {
            GUILayout.Label("No Rooms were created yet...");
        }
        else
        {
            for (int i = 0; i < createdRooms.Count; i++)
            {
                GUILayout.BeginHorizontal("box");
                GUILayout.Label(createdRooms[i].Name, GUILayout.Width(400));
                GUILayout.Label(createdRooms[i].PlayerCount + "/" + createdRooms[i].MaxPlayers);

                GUILayout.FlexibleSpace();

                if (GUILayout.Button("Join Room"))
                {
                    joiningRoom = true;

                    //Set our Player name
                    PhotonNetwork.NickName = playerName;

                    //Join the Room
                    PhotonNetwork.JoinRoom(createdRooms[i].Name);
                }
                GUILayout.EndHorizontal();
            }
        }

        GUILayout.EndScrollView();

        //Set player name and Refresh Room button
        GUILayout.BeginHorizontal();

        GUILayout.Label("Player Name: ", GUILayout.Width(85));
        //Player name text field
        playerName = GUILayout.TextField(playerName, GUILayout.Width(250));

        GUILayout.FlexibleSpace();

        GUI.enabled = (PhotonNetwork.NetworkClientState == ClientState.JoinedLobby || PhotonNetwork.NetworkClientState == ClientState.Disconnected) && !joiningRoom;
        if (GUILayout.Button("Refresh", GUILayout.Width(100)))
        {
            if (PhotonNetwork.IsConnected)
            {
                //Re-join Lobby to get the latest Room list
                PhotonNetwork.JoinLobby(TypedLobby.Default);
            }
            else
            {
                //We are not connected, estabilish a new connection
                PhotonNetwork.ConnectUsingSettings();
            }
        }

        GUILayout.EndHorizontal();

        if (joiningRoom)
        {
            GUI.enabled = true;
            GUI.Label(new Rect(900 / 2 - 50, 400 / 2 - 10, 100, 20), "Connecting...");
        }
    }

ולבסוף, אנו מיישמים עוד 4 התקשרויות: OnCreateRoomFailed(short returnCode, הודעה מחרוזת), OnJoinRoomFailed(short returnCode, הודעה מחרוזת), OnCreatedRoom(), ו OnJoinedRoom().

התקשרויות חוזרות אלו משמשות כדי לקבוע אם הצטרפנו/יצרנו את החדר או אם היו בעיות כלשהן במהלך החיבור.

הנה התסריט האחרון של PUN2_GameLobby.cs:

using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

public class PUN2_GameLobby : MonoBehaviourPunCallbacks
{

    //Our player name
    string playerName = "Player 1";
    //Users are separated from each other by gameversion (which allows you to make breaking changes).
    string gameVersion = "0.9";
    //The list of created rooms
    List<RoomInfo> createdRooms = new List<RoomInfo>();
    //Use this name when creating a Room
    string roomName = "Room 1";
    Vector2 roomListScroll = Vector2.zero;
    bool joiningRoom = false;

    // Use this for initialization
    void Start()
    {
        //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;

        if (!PhotonNetwork.IsConnected)
        {
            //Set the App version before connecting
            PhotonNetwork.PhotonServerSettings.AppSettings.AppVersion = gameVersion;
            // Connect to the photon master-server. We use the settings saved in PhotonServerSettings (a .asset file in this project)
            PhotonNetwork.ConnectUsingSettings();
        }
    }

    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.Log("OnFailedToConnectToPhoton. StatusCode: " + cause.ToString() + " ServerAddress: " + PhotonNetwork.ServerAddress);
    }

    public override void OnConnectedToMaster()
    {
        Debug.Log("OnConnectedToMaster");
        //After we connected to Master server, join the Lobby
        PhotonNetwork.JoinLobby(TypedLobby.Default);
    }

    public override void OnRoomListUpdate(List<RoomInfo> roomList)
    {
        Debug.Log("We have received the Room list");
        //After this callback, update the room list
        createdRooms = roomList;
    }

    void OnGUI()
    {
        GUI.Window(0, new Rect(Screen.width / 2 - 450, Screen.height / 2 - 200, 900, 400), LobbyWindow, "Lobby");
    }

    void LobbyWindow(int index)
    {
        //Connection Status and Room creation Button
        GUILayout.BeginHorizontal();

        GUILayout.Label("Status: " + PhotonNetwork.NetworkClientState);

        if (joiningRoom || !PhotonNetwork.IsConnected || PhotonNetwork.NetworkClientState != ClientState.JoinedLobby)
        {
            GUI.enabled = false;
        }

        GUILayout.FlexibleSpace();

        //Room name text field
        roomName = GUILayout.TextField(roomName, GUILayout.Width(250));

        if (GUILayout.Button("Create Room", GUILayout.Width(125)))
        {
            if (roomName != "")
            {
                joiningRoom = true;

                RoomOptions roomOptions = new RoomOptions();
                roomOptions.IsOpen = true;
                roomOptions.IsVisible = true;
                roomOptions.MaxPlayers = (byte)10; //Set any number

                PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, TypedLobby.Default);
            }
        }

        GUILayout.EndHorizontal();

        //Scroll through available rooms
        roomListScroll = GUILayout.BeginScrollView(roomListScroll, true, true);

        if (createdRooms.Count == 0)
        {
            GUILayout.Label("No Rooms were created yet...");
        }
        else
        {
            for (int i = 0; i < createdRooms.Count; i++)
            {
                GUILayout.BeginHorizontal("box");
                GUILayout.Label(createdRooms[i].Name, GUILayout.Width(400));
                GUILayout.Label(createdRooms[i].PlayerCount + "/" + createdRooms[i].MaxPlayers);

                GUILayout.FlexibleSpace();

                if (GUILayout.Button("Join Room"))
                {
                    joiningRoom = true;

                    //Set our Player name
                    PhotonNetwork.NickName = playerName;

                    //Join the Room
                    PhotonNetwork.JoinRoom(createdRooms[i].Name);
                }
                GUILayout.EndHorizontal();
            }
        }

        GUILayout.EndScrollView();

        //Set player name and Refresh Room button
        GUILayout.BeginHorizontal();

        GUILayout.Label("Player Name: ", GUILayout.Width(85));
        //Player name text field
        playerName = GUILayout.TextField(playerName, GUILayout.Width(250));

        GUILayout.FlexibleSpace();

        GUI.enabled = (PhotonNetwork.NetworkClientState == ClientState.JoinedLobby || PhotonNetwork.NetworkClientState == ClientState.Disconnected) && !joiningRoom;
        if (GUILayout.Button("Refresh", GUILayout.Width(100)))
        {
            if (PhotonNetwork.IsConnected)
            {
                //Re-join Lobby to get the latest Room list
                PhotonNetwork.JoinLobby(TypedLobby.Default);
            }
            else
            {
                //We are not connected, estabilish a new connection
                PhotonNetwork.ConnectUsingSettings();
            }
        }

        GUILayout.EndHorizontal();

        if (joiningRoom)
        {
            GUI.enabled = true;
            GUI.Label(new Rect(900 / 2 - 50, 400 / 2 - 10, 100, 20), "Connecting...");
        }
    }

    public override void OnCreateRoomFailed(short returnCode, string message)
    {
        Debug.Log("OnCreateRoomFailed got called. This can happen if the room exists (even if not visible). Try another room name.");
        joiningRoom = false;
    }

    public override void OnJoinRoomFailed(short returnCode, string message)
    {
        Debug.Log("OnJoinRoomFailed got called. This can happen if the room is not existing or full or closed.");
        joiningRoom = false;
    }

    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        Debug.Log("OnJoinRandomFailed got called. This can happen if the room is not existing or full or closed.");
        joiningRoom = false;
    }

    public override void OnCreatedRoom()
    {
        Debug.Log("OnCreatedRoom");
        //Set our player name
        PhotonNetwork.NickName = playerName;
        //Load the Scene called GameLevel (Make sure it's added to build settings)
        PhotonNetwork.LoadLevel("GameLevel");
    }

    public override void OnJoinedRoom()
    {
        Debug.Log("OnJoinedRoom");
    }
}

2. יצירת נגן טרומי

במשחקים מרובי משתתפים, למופע Player יש 2 צדדים: מקומי ומרוחק

מופע מקומי נשלט באופן מקומי (על ידינו).

מופע מרוחק, לעומת זאת, הוא ייצוג מקומי של מה שהשחקן השני עושה. זה לא אמור להיות מושפע מהקלט שלנו.

כדי לקבוע אם המופע הוא מקומי או מרוחק אנו משתמשים ברכיב PhotonView.

PhotonView פועל כשליח שמקבל ושולח את הערכים שצריך לסנכרן, למשל מיקום וסיבוב.

אז בואו נתחיל ביצירת מופע הנגן (אם כבר יש לכם מופע הנגן מוכן, תוכלו לדלג על שלב זה).

במקרה שלי, מופע ה-Player יהיה קובייה פשוטה שמוזזת עם המקשים W ו-S ומסובב עם המקשים A ו-D.

הנה תסריט בקר פשוט:

SimplePlayerController.cs

using UnityEngine;

public class SimplePlayerController : MonoBehaviour
{

    // Update is called once per frame
    void Update()
    {
        //Move Front/Back
        if (Input.GetKey(KeyCode.W))
        {
            transform.Translate(transform.forward * Time.deltaTime * 2.45f, Space.World);
        }
        else if (Input.GetKey(KeyCode.S))
        {
            transform.Translate(-transform.forward * Time.deltaTime * 2.45f, Space.World);
        }

        //Rotate Left/Right
        if (Input.GetKey(KeyCode.A))
        {
            transform.Rotate(new Vector3(0, -14, 0) * Time.deltaTime * 4.5f, Space.Self);
        }
        else if (Input.GetKey(KeyCode.D))
        {
            transform.Rotate(new Vector3(0, 14, 0) * Time.deltaTime * 4.5f, Space.Self);
        }
    }
}

השלב הבא הוא הוספת רכיב PhotonView.

  • הוסף רכיב PhotonView למופע Player.
  • צור סקריפט C# חדש וקרא לו PUN2_PlayerSync (סקריפט זה ישמש לתקשורת באמצעות PhotonView).

פתח את סקריפט PUN2_PlayerSync:

ב-PUN2_PlayerSync הדבר הראשון שעלינו לעשות הוא להוסיף מרחב שמות Photon.Pun ולהחליף את MonoBehaviour עם MonoBehaviourPun וגם להוסיף ממשק IPunObservable.

MonoBehaviourPun נחוץ כדי להיות מסוגל להשתמש במשתנה photonView המאוחסן במטמון, במקום להשתמש ב-GetComponent<PhotonView>().

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable

לאחר מכן, נוכל לעבור ליצירת כל המשתנים הדרושים:

    //List of the scripts that should only be active for the local player (ex. PlayerController, MouseLook etc.)
    public MonoBehaviour[] localScripts;
    //List of the GameObjects that should only be active for the local player (ex. Camera, AudioListener etc.)
    public GameObject[] localObjects;
    //Values that will be synced over network
    Vector3 latestPos;
    Quaternion latestRot;

לאחר מכן ב-void Start(), אנו בודקים אם הנגן הוא מקומי או מרוחק באמצעות photonView.IsMine:

    // Use this for initialization
    void Start()
    {
        if (photonView.IsMine)
        {
            //Player is local
        }
        else
        {
            //Player is Remote, deactivate the scripts and object that should only be enabled for the local player
            for (int i = 0; i < localScripts.Length; i++)
            {
                localScripts[i].enabled = false;
            }
            for (int i = 0; i < localObjects.Length; i++)
            {
                localObjects[i].SetActive(false);
            }
        }
    }

הסנכרון בפועל נעשה באמצעות התקשרות חוזרת של ה-PhotonView: OnPhotonSerializeView(זרם PhotonStream, מידע PhotonMessageInfo):

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            //We own this player: send the others our data
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            //Network player, receive data
            latestPos = (Vector3)stream.ReceiveNext();
            latestRot = (Quaternion)stream.ReceiveNext();
        }
    }

במקרה זה, אנו שולחים לשחקן רק מיקום וסיבוב, אבל אתה יכול להשתמש בדוגמה למעלה כדי לשלוח כל ערך שנדרש לסנכרון דרך הרשת, בתדירות גבוהה.

הערכים שהתקבלו מוחלים לאחר מכן בעדכון void():

    // Update is called once per frame
    void Update()
    {
        if (!photonView.IsMine)
        {
            //Update remote player (smooth this, this looks good, at the cost of some accuracy)
            transform.position = Vector3.Lerp(transform.position, latestPos, Time.deltaTime * 5);
            transform.rotation = Quaternion.Lerp(transform.rotation, latestRot, Time.deltaTime * 5);
        }
    }
}

הנה הסקריפט הסופי של PUN2_PlayerSync.cs:

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable
{

    //List of the scripts that should only be active for the local player (ex. PlayerController, MouseLook etc.)
    public MonoBehaviour[] localScripts;
    //List of the GameObjects that should only be active for the local player (ex. Camera, AudioListener etc.)
    public GameObject[] localObjects;
    //Values that will be synced over network
    Vector3 latestPos;
    Quaternion latestRot;

    // Use this for initialization
    void Start()
    {
        if (photonView.IsMine)
        {
            //Player is local
        }
        else
        {
            //Player is Remote, deactivate the scripts and object that should only be enabled for the local player
            for (int i = 0; i < localScripts.Length; i++)
            {
                localScripts[i].enabled = false;
            }
            for (int i = 0; i < localObjects.Length; i++)
            {
                localObjects[i].SetActive(false);
            }
        }
    }

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            //We own this player: send the others our data
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            //Network player, receive data
            latestPos = (Vector3)stream.ReceiveNext();
            latestRot = (Quaternion)stream.ReceiveNext();
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (!photonView.IsMine)
        {
            //Update remote player (smooth this, this looks good, at the cost of some accuracy)
            transform.position = Vector3.Lerp(transform.position, latestPos, Time.deltaTime * 5);
            transform.rotation = Quaternion.Lerp(transform.rotation, latestRot, Time.deltaTime * 5);
        }
    }
}

עכשיו בואו נקצה סקריפט חדש שנוצר:

  • צרף את הסקריפט PUN2_PlayerSync ל-PlayerInstance.
  • גרור ושחרר את PUN2_PlayerSync לתוך הרכיבים שנצפו ב-PhotonView.
  • הקצה את SimplePlayerController ל-"Local Scripts" והקצה את ה-GameObjects (שאותם רוצים לבטל עבור שחקנים מרוחקים) ל- "Local Objects"

  • שמור את PlayerInstance ל-Prefab והעבר אותו לתיקיה שנקראת Resources (אם אין תיקיה כזו, צור אחת). שלב זה נחוץ כדי להיות מסוגל להריץ אובייקטים מרובי משתתפים ברשת.

3. יצירת רמת משחק

GameLevel היא סצנה שנטענת לאחר ההצטרפות לחדר ושם מתרחשת כל האקשן.

  • צור סצנה חדשה וקרא לה "GameLevel" (או אם אתה רוצה לשמור שם אחר, הקפד לשנות את השם בשורה זו PhotonNetwork.LoadLevel("GameLevel"); ב-PUN2_GameLobby.cs).

במקרה שלי, אני אשתמש בסצנה פשוטה עם מטוס:

  • כעת צור סקריפט חדש וקרא לו PUN2_RoomController (סקריפט זה יטפל בהיגיון בתוך החדר, כמו השראת השחקנים, הצגת רשימת השחקנים וכו').

פתח את סקריפט PUN2_RoomController:

בדומה ל-PUN2_GameLobby, אנו מתחילים בהוספת מרחבי שמות של Photon והחלפת MonoBehaviour ב-MonoBehaviourPunCallbacks:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks

כעת נוסיף את המשתנים הדרושים:

    //Player instance prefab, must be located in the Resources folder
    public GameObject playerPrefab;
    //Player spawn point
    public Transform spawnPoint;

כדי להפעיל את ה-Player Prefab אנו משתמשים ב-PhotonNetwork.Instantiate:

    // Use this for initialization
    void Start()
    {
        //In case we started this demo with the wrong scene being active, simply load the menu scene
        if (PhotonNetwork.CurrentRoom == null)
        {
            Debug.Log("Is not in the room, returning back to Lobby");
            UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
            return;
        }

        //We're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
        PhotonNetwork.Instantiate(playerPrefab.name, spawnPoint.position, Quaternion.identity, 0);
    }

וממשק משתמש פשוט עם כפתור "Leave Room" וכמה אלמנטים נוספים כמו שם החדר ורשימת השחקנים המחוברים:

    void OnGUI()
    {
        if (PhotonNetwork.CurrentRoom == null)
            return;

        //Leave this Room
        if (GUI.Button(new Rect(5, 5, 125, 25), "Leave Room"))
        {
            PhotonNetwork.LeaveRoom();
        }

        //Show the Room name
        GUI.Label(new Rect(135, 5, 200, 25), PhotonNetwork.CurrentRoom.Name);

        //Show the list of the players connected to this Room
        for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
        {
            //Show if this player is a Master Client. There can only be one Master Client per Room so use this to define the authoritative logic etc.)
            string isMasterClient = (PhotonNetwork.PlayerList[i].IsMasterClient ? ": MasterClient" : "");
            GUI.Label(new Rect(5, 35 + 30 * i, 200, 25), PhotonNetwork.PlayerList[i].NickName + isMasterClient);
        }
    }

לבסוף, אנו מיישמים התקשרות חוזרת נוספת של PhotonNetwork בשם OnLeftRoom() שנקראת כשאנחנו עוזבים את החדר:

    public override void OnLeftRoom()
    {
        //We have left the Room, return back to the GameLobby
        UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
    }

להלן התסריט האחרון של PUN2_RoomController.cs:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks
{

    //Player instance prefab, must be located in the Resources folder
    public GameObject playerPrefab;
    //Player spawn point
    public Transform spawnPoint;

    // Use this for initialization
    void Start()
    {
        //In case we started this demo with the wrong scene being active, simply load the menu scene
        if (PhotonNetwork.CurrentRoom == null)
        {
            Debug.Log("Is not in the room, returning back to Lobby");
            UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
            return;
        }

        //We're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
        PhotonNetwork.Instantiate(playerPrefab.name, spawnPoint.position, Quaternion.identity, 0);
    }

    void OnGUI()
    {
        if (PhotonNetwork.CurrentRoom == null)
            return;

        //Leave this Room
        if (GUI.Button(new Rect(5, 5, 125, 25), "Leave Room"))
        {
            PhotonNetwork.LeaveRoom();
        }

        //Show the Room name
        GUI.Label(new Rect(135, 5, 200, 25), PhotonNetwork.CurrentRoom.Name);

        //Show the list of the players connected to this Room
        for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
        {
            //Show if this player is a Master Client. There can only be one Master Client per Room so use this to define the authoritative logic etc.)
            string isMasterClient = (PhotonNetwork.PlayerList[i].IsMasterClient ? ": MasterClient" : "");
            GUI.Label(new Rect(5, 35 + 30 * i, 200, 25), PhotonNetwork.PlayerList[i].NickName + isMasterClient);
        }
    }

    public override void OnLeftRoom()
    {
        //We have left the Room, return back to the GameLobby
        UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
    }
}
  • צור GameObject חדש בסצנה 'GameLevel' וקרא לו "_RoomController"
  • צרף את הסקריפט PUN2_RoomController לאובייקט _RoomController
  • הקצה לו את ה-PlayerInstance הקדם ו-SpawnPoint Transform ואז שמור את הסצנה

  • הוסף גם MainMenu וגם GameLevel להגדרות ה-Build.

4. ביצוע מבחן Build

עכשיו הגיע הזמן ליצור מבנה ולבדוק אותו:

הכל עובד כמצופה!

מַעֲנָק

RPC

ב-PUN 2, RPC מייצג Remote Procedure Call, הוא משמש לקריאה לפונקציה בלקוחות מרוחקים שנמצאים באותו חדר (תוכל לקרוא עוד על זה כאן).

ל-RPC שימושים רבים, למשל, נניח שאתה צריך לשלוח הודעת צ'אט לכל השחקנים בחדר. עם RPC, קל לעשות זאת:

[PunRPC]
void ChatMessage(string senderName, string messageText)
{
    Debug.Log(string.Format("{0}: {1}", senderName, messageText));
}

שימו לב ל-[PunRPC] לפני הפונקציה. תכונה זו נחוצה אם אתה מתכנן לקרוא לפונקציה באמצעות RPCs.

כדי לקרוא לפונקציות המסומנות כ-RPC, אתה צריך PhotonView. שיחה לדוגמה:

PhotonView photonView = PhotonView.Get(this);
photonView.RPC("ChatMessage", RpcTarget.All, PhotonNetwork.playerName, "Some message");

טיפ מקצוען: אם תחליף את MonoBehaviour בסקריפט שלך ב-MonoBehaviourPun או MonoBehaviourPunCallbacks תוכל לדלג על PhotonView.Get() ולהשתמש ישירות ב-photonView.RPC().

מאפיינים מותאמים אישית

ב-PUN 2, מאפיינים מותאמים אישית הוא Hashtable שניתן להקצות לנגן או לחדר.

זה שימושי כאשר אתה צריך להגדיר נתונים קבועים שאין צורך לשנות לעתים קרובות (למשל שם צוות שחקן, מצב משחק בחדר וכו').

ראשית, עליך להגדיר Hashtable, אשר נעשה על ידי הוספת השורה למטה בתחילת הסקריפט:

//Replace default Hashtables with Photon hashtables
using Hashtable = ExitGames.Client.Photon.Hashtable;

הדוגמה להלן מגדירה את מאפייני החדר הנקראים "GameMode" ו-"AnotherProperty":

        //Set Room properties (Only Master Client is allowed to set Room properties)
        if (PhotonNetwork.IsMasterClient)
        {
            Hashtable setRoomProperties = new Hashtable();
            setRoomProperties.Add("GameMode", "FFA");
            setRoomProperties.Add("AnotherProperty", "Test");
            PhotonNetwork.CurrentRoom.SetCustomProperties(setRoomProperties);
        }

        //Will print "FFA"
        print((string)PhotonNetwork.CurrentRoom.CustomProperties["GameMode"]);
        //Will print "Test"
        print((string)PhotonNetwork.CurrentRoom.CustomProperties["AnotherProperty"]);

מאפייני הנגן מוגדרים באופן דומה:

            Hashtable setPlayerProperties = new Hashtable();
            setPlayerProperties.Add("PlayerHP", (float)100);
            PhotonNetwork.LocalPlayer.SetCustomProperties(setPlayerProperties);

            print((float)PhotonNetwork.LocalPlayer.CustomProperties["PlayerHP"]);

כדי להסיר מאפיין ספציפי פשוט הגדר את הערך שלו ל- null.

            Hashtable setPlayerProperties = new Hashtable();
            setPlayerProperties.Add("PlayerHP", null);
            PhotonNetwork.LocalPlayer.SetCustomProperties(setPlayerProperties);

הדרכות נוספות:

סנכרן גופים קשיחים ברשת באמצעות PUN 2

PUN 2 הוספת צ'אט חדר

מָקוֹר
PUN2Guide.unitypackage14.00 MB