Buat Game Multiplayer di Unity menggunakan PUN 2

Pernah bertanya-tanya apa yang diperlukan untuk membuat game multipemain di dalam Unity?

Tidak seperti game pemain tunggal, game multipemain memerlukan server jarak jauh yang berperan sebagai jembatan, yang memungkinkan klien game untuk berkomunikasi satu sama lain.

Saat ini banyak layanan yang menangani server hosting. Salah satu layanan tersebut adalah Photon Network, yang akan kita gunakan untuk tutorial ini.

PUN 2 adalah rilis terbaru API mereka yang telah ditingkatkan secara signifikan dibandingkan dengan versi lawas.

Dalam posting ini, kita akan mulai mengunduh file yang diperlukan, mengatur Photon AppID, dan memprogram contoh multipemain sederhana.

Unity versi yang digunakan dalam tutorial ini: Unity 2018.3.0f2 (64-bit)

Bagian 1: Menyiapkan PUN 2

Langkah pertama adalah mendownload paket PUN 2 dari Asset Store. Ini berisi semua skrip dan file yang diperlukan untuk integrasi multipemain.

  • Buka proyek Unity Anda lalu buka Asset Store: (Window -> General -> AssetStore) atau tekan Ctrl+9
  • Cari "PUN 2- Gratis" lalu klik hasil pertama atau klik di sini
  • Impor paket PUN 2 setelah Download selesai

  • Pada halaman pembuatan, untuk Photon Type pilih "Photon Realtime" dan untuk Name, ketikkan nama apa saja lalu klik "Create"

Seperti yang Anda lihat, Aplikasi defaultnya adalah paket Gratis. Anda dapat membaca lebih lanjut tentang Paket Harga di sini

  • Setelah Aplikasi dibuat, salin ID Aplikasi yang terletak di bawah nama Aplikasi

  • Kembali ke proyek Unity Anda lalu buka Window -> Photon Unity Networking -> PUN Wizard
  • Di PUN Wizard klik "Setup Project", paste App ID Anda lalu klik "Setup Project"

  • PUN 2 sekarang siap!

Bagian 2: Membuat game multipemain

Sekarang mari beralih ke bagian di mana kita sebenarnya membuat game multipemain.

Cara penanganan multipemain di PUN 2 adalah:

  • Pertama, kita terhubung ke Wilayah Foton (mis. AS Timur, Eropa, Asia, dll.) yang juga dikenal sebagai Lobi.
  • Setelah berada di Lobi, kita meminta semua Ruangan yang dibuat di Wilayah, dan kemudian kita dapat bergabung dengan salah satu Ruangan atau membuat Ruangan kita sendiri.
  • Setelah bergabung dengan ruangan, kami meminta daftar pemain yang terhubung ke Ruangan dan membuat instance Player mereka, yang kemudian disinkronkan dengan instance lokal mereka melalui PhotonView.
  • Ketika seseorang meninggalkan Ruangan, instance mereka dihancurkan dan mereka dihapus dari Daftar Pemain.

1. Menyiapkan Lobi

Mari kita mulai dengan membuat adegan Lobi yang berisi logika Lobi (Menelusuri ruangan yang ada, membuat ruangan baru, dll.):

  • Buat skrip C# baru dan beri nama PUN2_GameLobby
  • Buat Adegan baru dan beri nama "GameLobby"
  • Dalam adegan GameLobby buat GameObject baru. Sebut saja "_GameLobby" dan tetapkan skrip PUN2_GameLobby padanya

Sekarang buka skrip PUN2_GameLobby:

Pertama, kita mengimpor namespace Photon dengan menambahkan baris di bawah ini di awal skrip:

using Photon.Pun;
using Photon.Realtime;

Juga sebelum melanjutkan, kita perlu mengganti MonoBehaviour default dengan MonoBehaviourPunCallbacks. Langkah ini diperlukan untuk dapat menggunakan panggilan balik Foton:

public class PUN2_GameLobby : MonoBehaviourPunCallbacks

Selanjutnya, kita membuat variabel yang diperlukan:

    //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;

Kemudian kita memanggil ConnectUsingSettings() di bagian void Start(). Artinya, segera setelah game dibuka, game tersebut akan terhubung ke Server Foton:

    // 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();
        }
    }

Untuk mengetahui apakah koneksi ke Photon berhasil, kita perlu mengimplementasikan callback berikut: 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;
    }

Berikutnya adalah bagian UI, tempat penjelajahan Ruangan dan pembuatan Ruangan dilakukan:

    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...");
        }
    }

Dan terakhir, kami menerapkan 4 callback lainnya: OnCreateRoomFailed(short returnCode, string message), OnJoinRoomFailed(short returnCode, string message), OnCreatedRoom(), dan DiJoinedRoom().

Callback ini digunakan untuk menentukan apakah kita Bergabung/Membuat ruangan atau apakah ada masalah selama koneksi.

Berikut skrip terakhir 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. Membuat cetakan Player

Dalam game Multipemain, instance Player memiliki 2 sisi: Lokal dan Jarak Jauh

Sebuah instance lokal dikontrol secara lokal (oleh kami).

Sebaliknya, instance jarak jauh adalah representasi lokal dari apa yang dilakukan pemain lain. Seharusnya tidak terpengaruh oleh masukan kita.

Untuk menentukan apakah instance tersebut Lokal atau Jarak Jauh, kami menggunakan komponen PhotonView.

PhotonView bertindak sebagai pembawa pesan yang menerima dan mengirimkan nilai yang perlu disinkronkan, misalnya posisi dan rotasi.

Jadi mari kita mulai dengan membuat instance pemain (Jika instance pemain Anda sudah siap, Anda dapat melewati langkah ini).

Dalam kasus saya, instance Player akan berupa Kubus sederhana yang dipindahkan dengan tombol W dan S dan diputar dengan tombol A dan D.

Berikut ini skrip pengontrol sederhana:

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);
        }
    }
}

Langkah selanjutnya adalah menambahkan komponen PhotonView.

  • Tambahkan komponen PhotonView ke Instance Player.
  • Buat skrip C# baru, dan beri nama PUN2_PlayerSync (skrip ini akan digunakan untuk berkomunikasi melalui PhotonView).

Buka skrip PUN2_PlayerSync:

Di PUN2_PlayerSync hal pertama yang perlu kita lakukan adalah menambahkan namespace Photon.Pun dan mengganti MonoBehaviour dengan MonoBehaviourPun dan juga menambahkan antarmuka IPunObservable.

MonoBehaviourPun diperlukan untuk dapat menggunakan variabel photonView yang di-cache, daripada menggunakan GetComponent<PhotonView>().

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable

Setelah itu, kita dapat melanjutkan untuk membuat semua variabel yang diperlukan:

    //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;

Kemudian di kekosongan Start(), kami memeriksa apakah pemutarnya Lokal atau Jarak Jauh dengan menggunakan 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);
            }
        }
    }

Sinkronisasi sebenarnya dilakukan melalui panggilan balik PhotonView: OnPhotonSerializeView(aliran PhotonStream, info 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();
        }
    }

Dalam hal ini, kami hanya mengirimkan Posisi dan Rotasi pemain, tetapi Anda dapat menggunakan contoh di atas untuk mengirimkan nilai apa pun yang perlu disinkronkan melalui jaringan, pada frekuensi tinggi.

Nilai yang diterima kemudian diterapkan dalam kekosongan Update():

    // 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);
        }
    }
}

Berikut ini skrip PUN2_PlayerSync.cs terakhir:

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);
        }
    }
}

Sekarang mari kita tetapkan skrip yang baru dibuat:

  • Lampirkan skrip PUN2_PlayerSync ke PlayerInstance.
  • Seret dan lepas PUN2_PlayerSync ke dalam Komponen yang Diamati PhotonView.
  • Tetapkan SimplePlayerController ke "Local Scripts" dan tetapkan GameObjects (yang ingin Anda nonaktifkan untuk pemutar jarak jauh) ke "Local Objects"

  • Simpan PlayerInstance ke Prefab dan pindahkan ke folder bernama Resources (Jika tidak ada folder seperti itu, buatlah folder tersebut). Langkah ini diperlukan untuk dapat menelurkan Objek multipemain melalui Jaringan.

3. Membuat Level Game

GameLevel adalah Adegan yang dimuat setelah bergabung dengan Ruangan dan di situlah semua aksi terjadi.

  • Buat Scene baru dan beri nama "GameLevel" (Atau jika Anda ingin tetap menggunakan nama lain, pastikan untuk mengganti nama di baris ini PhotonNetwork.LoadLevel("GameLevel"); di PUN2_GameLobby.cs).

Dalam kasus saya, saya akan menggunakan Adegan sederhana dengan Pesawat:

  • Sekarang buat skrip baru dan beri nama PUN2_RoomController (Skrip ini akan menangani logika di dalam Ruangan, seperti memunculkan pemain, menampilkan daftar pemain, dll.).

Buka skrip PUN2_RoomController:

Sama seperti PUN2_GameLobby kita mulai dengan menambahkan namespace Photon dan mengganti MonoBehaviour dengan MonoBehaviourPunCallbacks:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks

Sekarang mari tambahkan variabel yang diperlukan:

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

Untuk membuat instance prefab Player kami menggunakan 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);
    }

Dan UI sederhana dengan tombol "Leave Room" dan beberapa elemen tambahan seperti nama Ruangan dan daftar Pemain yang terhubung:

    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);
        }
    }

Terakhir, kita mengimplementasikan callback PhotonNetwork lain yang disebut OnLeftRoom() yang dipanggil saat kita meninggalkan Ruangan:

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

Berikut adalah skrip PUN2_RoomController.cs terakhir:

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");
    }
}
  • Buat GameObject baru di adegan 'GameLevel' dan beri nama "_RoomController"
  • Lampirkan skrip PUN2_RoomController ke Objek _RoomController
  • Tetapkan prefab PlayerInstance dan SpawnPoint Transform ke dalamnya lalu simpan Scene

  • Tambahkan MainMenu dan GameLevel ke Pengaturan Build.

4. Membuat tes Membangun

Sekarang saatnya membuat build dan mengujinya:

Semuanya berfungsi seperti yang diharapkan!

Bonusnya

RPC

Di PUN 2, RPC adalah singkatan dari Remote Procedure Call, digunakan untuk memanggil fungsi pada klien Remote yang berada di ruangan yang sama (Anda dapat membaca lebih lanjut di sini).

RPC memiliki banyak kegunaan, misalnya Anda perlu mengirim pesan chat ke semua pemain di Room. Dengan RPC, mudah untuk melakukannya:

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

Perhatikan [PunRPC] sebelum fungsinya. Atribut ini diperlukan jika Anda berencana memanggil fungsi melalui RPC.

Untuk memanggil fungsi yang ditandai sebagai RPC, Anda memerlukan PhotonView. Contoh panggilan:

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

Kiat pro: Jika Anda mengganti MonoBehaviour di skrip Anda dengan MonoBehaviourPun atau MonoBehaviourPunCallbacks Anda dapat melewati PhotonView.Get() dan menggunakan photonView.RPC() secara langsung.

Properti Kustom

Di PUN 2, Properti Kustom adalah Hashtable yang dapat ditetapkan ke Pemain atau Ruangan.

Ini berguna ketika Anda perlu mengatur data persisten yang tidak perlu sering diubah (mis. Nama Tim Pemain, Mode Permainan Kamar, dll.).

Pertama, Anda harus mendefinisikan Hashtable, yang dilakukan dengan menambahkan baris di bawah ini di awal skrip:

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

Contoh di bawah ini menyetel properti Room yang disebut "GameMode" dan "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"]);

Properti pemain diatur dengan cara yang sama:

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

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

Untuk menghapus properti tertentu cukup setel nilainya menjadi nol.

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

Tutorial tambahan:

Sinkronkan Rigidbodies Melalui Jaringan Menggunakan PUN 2

PUN 2 Menambahkan Obrolan Ruang

Sumber
📁PUN2Guide.unitypackage14.00 MB
Artikel yang Disarankan
Sinkronkan Rigidbodies Melalui Jaringan Menggunakan PUN 2
Unity Menambahkan Obrolan Multipemain ke Ruang PUN 2
Kompresi Data Multi Pemain dan Manipulasi Bit
Buat Game Mobil Multi Pemain dengan PUN 2
Membangun Game Jaringan Multipemain dalam Unity
Panduan Pemula Jaringan Foton (Klasik).
Tutorial Papan Peringkat Online Unity