Recommended reading

  • CSDN home page
  • GitHub open source address
  • Unity3D plugin sharing
  • Jane’s address book
  • My personal blog
  • QQ group: 1040082875

One, foreword

Photon Unity Networking (PUN) is a Unity package for multiplayer games. Flexible matching allows players to enter rooms and synchronize objects across the network. Fast and reliable communication is done through a dedicated Photon server, so there is no need for 1-to-1 client connections.

Second, refer to the article

【PUN】Photon Unity Networking(PUN) is easy to use

【Unity3D】 Photon multiplayer Game Development tutorial 3, PUN Introduction

Photon Unity Networking

5. Unity3D uses Photon to implement real-time networking for PUN SDK

7. Update the Networking version of Player for Photon Unity Networking

Using Photon Unity Networking to develop multiplayer online games (I) : Lobby and waiting room

Third, the body

Quickly build

1. Download PUN from:Doc.photonengine.com/en-us/pun/c… Jump to the AssetStore store:It should be noted that the version should be above Unity2017.4.7. If it is a previous version, PUN1.0 can be installed

Or just go to the store in Unity with Alt+9 and search for PUN

2. Then open Photon’s official website and register an account.Dashboard.photonengine.com/Account/Sig… Once logged in, click create APP:For Chat rooms, select Photon Chat and Photon PUNCopy the App ID, to Unity in the project by Photon/PhotonUnityNetworking/Resources/PhotonServerSettings Realtim App ID3. Create a new scene, create a Plane and a Cube, set the Cube to a prefab and put it in the Resouces folder:4. Add the Photon View component to the Cube. This component is a must if you want to synchronizeDrag Cube’s Transform into Observed Components 5. Create a new script, ClickFloor, and pay the script to the 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); }}}}Copy the code

6. Create a new script PhotonConnect.cs

using UnityEngine;
using Photon.Pun;// Import the Photon namespace
using Photon.Realtime;

public class PhotonConnect : MonoBehaviour
{
    void Start()
    {
        // Initialize the version number
        PhotonNetwork.ConnectUsingSettings();
        PhotonNetwork.GameVersion = "1";
    }

    // Button event creates room
    public void Btn_CreateRoom(string _roomName)
    {
        
        // Set the room properties
        RoomOptions m_Room = new RoomOptions { IsOpen = true, IsVisible = true, MaxPlayers = 4 };
        PhotonNetwork.CreateRoom(_roomName, m_Room);
    }

    // Add the room according to the room name
    public void Btn_JoinRoom(string _roomName)
    {
        PhotonNetwork.JoinRoom(_roomName);
    }

    // Randomly add a room that has been created
    public void Btn_JoinRandomRoom()
    {
        PhotonNetwork.JoinRandomRoom();
    }

    void OnGUI()
    {
        // Displays connection information
        GUILayout.Label(PhotonNetwork.NetworkClientState.ToString(),GUILayout.Width(300),GUILayout.Height(100)); }}Copy the code

7. Pay the script to the Main Camera(any object in the scene will do) and create 3 new buttons to bind the event: 8. Apply the Cube prefab, then delete it from the scene and run:

API parsing

Connect and call back

ConnectUsingSettings Establishes a connection

PhotonNetwork.ConnectUsingSettings();
Copy the code

PUN uses callbacks to let you know when a customer has made a connection, joined a room, etc.

For example: IConnectionCallbacks OnConnectedToMaster.

For convenience, can inherit MonoBehaviourPunCallbacks interface, it implements the important callback interface and automatic registration, only need to cover specific callback methods

public class YourClass : MonoBehaviourPunCallbacks
{
    public override void OnConnectedToMaster()
    {
        Debug.Log("Launcher: Connect to the main client"); }}Copy the code

Join and create rooms

To join the room

PhotonNetwork.JoinRoom("someRoom");
Copy the code

Join existing random rooms

PhotonNetwork.JoinRandomRoom();
Copy the code

Create a room

PhotonNetwork.CreateRoom("MyMatch");
Copy the code

If you want to play with your friends, create a room name and use JoinOrCreateRoom to create the room, set IsVisible to false, then you can only use the room name to join (not randomly create the room).

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

The game logic

Gameobjects can be instantiated as “networked gameobjects” using the PhotonView component, which identifies the object and its owner (or controller) to update their status to others

Need to add a PhotonView components selection Observed components and use PhotonNetwork. Instantiate to create an instance, do the following.

The PhotonStream is responsible for writing (and reading) the state of the network object several times per second. The script needs to inherit the interface IPunObservable, which defines OnPhotonSerializeView. It looks like this:

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(refpos); }}Copy the code

Remote procedure call

Remote Procedure Calls (RPCS) enable you to call methods on “networked GameObjects,” which are useful for infrequent actions triggered by user input, etc.

An RPC will be executed on the same GameObject for every player in the same room, so you can easily trigger the entire scene effect just as you can modify some GameObject.

Methods called as RPC must be on a gameobject with a PhotonView component. The method itself must be marked by the [PunRPC] property.

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

To call this method, first access the PhotonView component of the target object. Instead of calling the target method directly, call PhotonView.rpc () and provide the name of the method you want to call:

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

The callback function

interface explain
IConnectionCallbacks Connection-related callbacks.
IInRoomCallbacks Callbacks that occur in the room
ILobbyCallbacks Callbacks related to game halls.
IMatchmakingCallbacks Pair-related callbacks
IOnEventCallback Call back the received event. This is equivalent to the C# event OnEventReceived.
IWebRpcCallback A callback to receive WebRPC action responses.
IPunInstantiateMagicCallback Instantiate a single callback for a pun prefab.
IPunObservable PhotonView serialization callback.
IPunOwnershipCallbacks Punning transfer of ownership callback.

More API reference: doc-api.photonengine.com/en/pun/v2/n…

Four cases,

1. Simple multiplayer

1. Create the Launcher. Cs script

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}}Copy the code

Open PhotonServerSettings:

2. Extend MonoBehaviourPunCallback modify MonoBehaviour for MonoBehaviourPunCallbacks using Photon. The Realtime. Add the following two methods to the namespace:

public class Launcher : MonoBehaviourPunCallbacks
{
Copy the code
#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
Copy the code

When we effectively join a room, it will notify your script:

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

New field:

/// <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;
Copy the code

Then change PhototonNetwork. CreateRoom () call and use this new field

// #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 });
Copy the code

Create a Button, name it Play Button, bind the event Launcher.Connect() to the script Launcher. Cs, and remove the Start() function

4. Create playerNameInputField. cs script with player name:

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}}Copy the code

Create a UI for the player’s name. Create a UI for the scene –InputField, add the event On Value Change (String), drag PlayerNameInputField to attach to the object, select SetPlayerNameUse the “GameObject/UI/Panel” menu to create a UI Panel, Name it Control Panel, drag and drop Play Button and Name InputField and create a new Text in the Control Panel for information display. I’ll call it Progress Label7. Open the Launcher. Cs script and add the following two properties

[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;
Copy the code

Add to the Start() method:

progressLabel.SetActive(false);
controlPanel.SetActive(true);
Copy the code

Add to Connect() method:

progressLabel.SetActive(true);
controlPanel.SetActive(false);
Copy the code

Add to the OnDisconnected() method:

progressLabel.SetActive(false);
controlPanel.SetActive(true);
Copy the code

Create a new scene, save it and name it Room for 1. Create a new Plane, scale to 20,1,20 and create 4 cubes:

Cube1

Cube2

Cube3

Cube4

8. Create the c# script 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}}Copy the code

Create a new Top Panel and set the anchor pointAdd an exit Button named Leave Button and bind LeaveRoom() to the event Game Manager

10. Create other scenarios

Two-person scene:

Cube1:

Cube2:

Cube3:

Cube4:

3 people scene: Cube1:

Cube2:

Cube3:

Cube4:

Floor scale: 60,1,60 Cube1:

Cube2:

Cube3:

Cube4:

11. Create a list of Settings File/Build Settings and drop all the Settings

12. Load scenario Open Gamemanager.cs add new method:

#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
Copy the code

13. Detect other players joining:

#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 OnPlayerLeftRoomLoadArena(); }}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 OnPlayerLeftRoomLoadArena(); }}#endregion
Copy the code

Add the following to the OnJoinedRoom() method

// #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");
}
Copy the code

Open the scene Launcher and run it. Click “Play” but if you leave the room, you’ll notice that when you return to the lobby, it automatically rejoins. To fix this, we can modify the Launcher

Add new properties:

/// <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;
Copy the code

The Connect() method adds:

// 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();
Copy the code

Results:

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

The OnConnectedToMaster() method adds:

// 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;
}
Copy the code

15. Create a new empty scene in Assets\Photon\PhotonUnityNetworking\Demos\Shared Assets\Models Kyle Robot. FBX and drag Kyle Robot. FBX into the scene. Drag the model into the Resources folder to make a prefab:

Double click on My Kyle Robot to modify collider:Animation SettingsWith this Kyle Robot for our Controller prefab, just set the property Controller to point to Kyle Robot of the animated componentCreate a playerAnimatorManager.cs script with controller parameters:

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}}Copy the code

Create a variable:

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);
}
Copy the code

Animation Manager Script: Direction control editor script PlayerAnimatorManager

#region Private Fields


[SerializeField]
private float directionDampTime = 0.25 f;


#endregion
Copy the code

Update to add:

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

Animation Manager Script: Jump edit script 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"); }}Copy the code

Results:

using UnityEngine;
using System.Collections;

namespace Com.MyCompany.MyGame
{
    public class PlayerAnimatorManager : MonoBehaviour
    {
        #region Private Fields

        [SerializeField]
        private float directionDampTime = 25.f;
        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}}Copy the code

Camera Settings add the component CameraWork to My Kyle Robot prefab

Add a PhotonView component to the model: Set Observe Option to send across On Change

Add a weapon ray click on the model, open the hierarchy list, and find the head:Set the two cubes as rays, and then the parent object as Head:

Control Ray: Create a new script: 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}}Copy the code

Health opens PlayerManager playbook to add a public Health attribute

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

The following two methods are added to the MonoBehaviour Callbacks area.

/// <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.1 f;
}
/// <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.1 f*Time.deltaTime;
}
Copy the code

Add this variable to the public field area

public static GameManager Instance;
Copy the code

The Start() method adds:

void Start()
{
    Instance = this;
}
Copy the code

Update function adds:

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

16. Network Transform synchronization

Add a component PhotonTransformView

Animation synchronization adds a component PhotonAnimatorView

16. User input Management open playerAnimatorManager.cs Update to add

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

17. Camera control opens PlayerManager script

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

Disable Follow on Start

18. Fire control

Open the script PlayerManager

if (photonView.IsMine)
{
    ProcessInputs ();
}
Copy the code

Add interface IPunObservable

public class PlayerManager : MonoBehaviourPunCallbacks.IPunObservable
{
    #region IPunObservable implementation


    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){}#endregion
Copy the code

Add the following code 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();
}
Copy the code

Drag the PlayerManager component into the PhotonView component

19. Health synchronization opens script 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();
}
Copy the code

20. Instantiate the player

Open the GameManager script and add the following variables to the public field area

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

In the Start() method, add the following

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);
}
Copy the code

21. Follow the player

Open the PlayerManager script. In the Public Fields area, add the following

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

In the Awake() method, add the following

// #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);
Copy the code

Surround the instantiation call with an if condition

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);
}
Copy the code

22. Open the PlayerManager script for managing players outside the scene

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

In the Start() method, add the following code

#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
Copy the code

Add the following two methods to the “MonoBehaviour Callback” area

#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); }}Copy the code

Override OnDisable as follows

#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
Copy the code

Create a new UI in the scene, Slider, anchor point, middle position, rect width 80 height 15, background set to red, add a CanvasGroup component, set Interactable and Blocks Raycast to false. Drag it into the Prefab folder and delete the instance from the scene, we don’t need it anymore

Create a new C# script, 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}}Copy the code

Add attributes:

private PlayerManager target;
Copy the code

Add this public method

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; }}Copy the code

Add this method

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

24. Instantiate open script PlayerManager to add a public field to save a reference to Player UI presets as follows:

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

Add this code to the Start() method

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);
}
Copy the code

Add this to the Update() function

// 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;
}
Copy the code

Add this code to the CalledOnLevelWasLoaded() method

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

Add this method in the “MonoBehaviour Callback” area

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

Add this public property in the Public Fields area

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

Add these four fields to the Private Fields area

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

Add this to the Awake method field

_canvasGroup = this.GetComponent<CanvasGroup>();
Copy the code

The _target method is set after appending the following code to your SetTarget().

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;
}
Copy the code

Add this public method in the “MonoBehaviour Callback” area

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; }}Copy the code

2. Game hall and waiting room

1. Game hall

// Connect to Cloud and enter the game hall

PhotonNetwork.ConnectUsingSettings(string version)

// Enter the game hall default callback function

void OnJoinedLobby()

// Displays connection logs

GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString())
Copy the code

2. Create a waiting room

// Set the room properties and create the room

RoomOptions ro = new RoomOptions();

ro.IsOpen = true; ro.IsVisible =true;

// Set the maximum number of players. For simplicity, start with 2 players

ro.MaxPlayers = 2;

PhotonNetwork.CreateRoom(srting roomName, ro, TypedLobby.Default);

// Create room failed callback function

void OnPhotonCreateRoomFailed()
Copy the code

3. Join the waiting room

// Randomly add rooms

PhotonNetwork.JoinRandomRoom();

// Random room entry failed (possibly because there was no room available)

// The default callback function must not be written wrong!!

void OnPhotonRandomJoinFailed(){You can create one by calling PhotonNetwork.createroom}// Enter the room callback function

void OnJoinedRoom()
{
	StartCoroutine(this.ChangeToWaitScene());
	// Write a coroutine that loads the waiting room scenario when it successfully enters the room
}
IEnumerator ChangeToWaitScene()
 {
	// Interrupts network information transfer with Photon server during switching scenarios
	// (Network information passed by the server may cause unnecessary errors while the loading scenario is not complete)
	PhotonNetwork.isMessageQueueRunning = false;
	// Load the scene
	AsyncOperation ao = SceneManager.LoadSceneAsync("RoomForWait");
	 yield return ao;
}
Copy the code

Joined the room. At the same time, it is best to the player’s name PhotonNetwork player. The NickName read or Settings, can be realized with a laparoscope, usually connected to a PlayerPrefs data persistence.

4. The Grid Layout Group and Horizontal Layout Group in UGUI are designed for this situation. We can store a list of rooms as a preset, which is generated every time a new room is created. These two components will help you to arrange the room list neatly by default.

All the PreFab you need should be stored in the Resources folder under the root directory. Hard and fast.

// The receive room list is executed only if the room in the lobby is entered by the player

void OnReceivedRoomListUpdate()
    {
		// Add labels to presets for single room lists
        GameObject[] a = GameObject.FindGameObjectsWithTag("OneRoom");
        for (int i = 0; i < a.Length; i++)
            Destroy(a[i].gameObject);
        // Destroy the old presets every time you receive the room list so that you can update the number of people online and the total number of rooms
        // Generate a single list preset using functions that receive room catalog information
        / / PhotonNetwork GetRoomList () can get the rooms in the room list array
        foreach (RoomInfo _room inPhotonNetwork. GetRoomList ()) {// Receive the room list
            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();// Get all the data and set it up and display it on the panel}}Copy the code

The roomData script stores basic information such as the room name, the number of people in the room, the maximum number of people in the room, and it is best to set the interactable button to true or false depending on whether the room is full.

Click Join on the room list to enter the room.

The NO. Is the name of the room I named with random numbers. There is also an input box for the player’s name, and if the player does not enter a name, a random number is automatically given as the name.