Unity Netcode for GameObjects multiplayer online (source file)

1. Install Netcode for Gameobjects
Netcode for Gameobjects is a new online solution launched by Unity. This solution is currently in its infancy, and the related functions are not very perfect, but it is not bad to use. The following introduces the related usage.
First download and install Netcode for Gameobjects. Its official website is Netcode for Gameobjects . The installation method is also very simple. Create a new Unity project, open the Window-Package Manager in the menu bar, click the plus sign to add Package from git Url, enter the following address, click add and wait The installation is complete.

com.unity.netcode.gameobjects

insert image description here
If an error occurs, it is a version problem first, and another version of Unity needs to be replaced.

2. NetworkManager component
Create a new empty object, name it NetworkManager, and add the NetworkManager component.
insert image description here
At the same time, click the Selec transport drop-down option and select Unity Transport.
insert image description here
In the NetworkManager component, there are several important parameters:
Player Prefab: player prefab
Network Prefab: network prefab
TickRate: the frequency of compiling and sending data, generally the default is 30, and the sending interval is 1/30s
in the UnityTransport component Next, there are several parameters to pay attention to:
insert image description here
Address: IP address
Port: network port number
Server Listen Adress: server listening address

3. Create the login interface
Create the following interface in Unity:
insert image description here
Note that the EventSystem component cannot be missing.
insert image description here

Create server: Create a game server, which is only used as a server, not as a client.
Join server: Join an existing server as a client.
Input box: Enter an ip address to join
4. Create a scene and
create a player role in the scene Just a ground and some squares
insert image description here
In the Unity resource store, import the third-person free resource package
insert image description here
Find the following resource package in the Assets folder, drag it into the scene, add the parent object Player for the two prefabs and
insert image description here
cancel the prefab association of the two prefabs at the same time .
insert image description here
Add network component NetworkObject for Player.
insert image description here
Drag the Player into the Prefabs folder to make a prefab, and then delete the Player in the scene.
insert image description here
Drag the prefab Player to the player prefab of NetworkManager.
insert image description here
In order to make the online connection more interesting, we create the effect of shooting bullets to reduce blood for players, so add a three-dimensional UI canvas above the player's head, as shown in the figure below: At the same time, in order to shoot bullets, add two empty objects
insert image description here
as Bullets are spawned and shot in the direction of the object, then save the prefab.
insert image description here
Create a sphere as a bullet, and add the following components to the sphere, where Bullet is the new script created.
insert image description here

Delete MainCamera, create a new Camera, adjust to a suitable viewing angle in Scene, then select Camera and click Align With View to align the viewing angle. The camera is used as a server-side camera, if it is a client-side camera, the camera will be hidden.
insert image description here
5. Create a server and join the server

First create a script NetMain, hang it under NetworkManager, the main code is as follows:

using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
using UnityEngine.UI;

public class NetMain : MonoBehaviour
{
    
    
    /// <summary>
    /// 单例
    /// </summary>
    public static NetMain instance;
    /// <summary>
    /// 创建服务器按钮
    /// </summary>
    public Button createServerBtn;
    /// <summary>
    /// 加入服务器按钮
    /// </summary>
    public Button joinServerBtn;
    /// <summary>
    /// 输入IP地址框
    /// </summary>
    public InputField ipinput;
    //玩家生成点
    public Transform playerSpawnPos;
    //默认相机
    public GameObject _camera;

    /// <summary>
    /// 测试面板
    /// </summary>
    public GameObject testPanel;
    /// <summary>
    /// 本地玩家
    /// </summary>
    public NetPlayer localPlayer;
    private void Awake()
    {
    
    
        instance = this;
    }
    void Start()
    {
    
    
        //绑定按钮事件
        createServerBtn.onClick.AddListener(CreateServerBtnClick);
        joinServerBtn.onClick.AddListener(JoinServerBtnClick);
    }

    // Update is called once per frame
    void Update()
    {
    
    
        
    }

    /// <summary>
    /// 创建服务器按钮绑定事件
    /// </summary>
    private void CreateServerBtnClick()
    {
    
    
        //获取Unity传输组件
        UnityTransport unityTransport = NetworkManager.Singleton.GetComponent<UnityTransport>();
        //设置ip地址和端口,0.0.0.0代表任意ip地址
        unityTransport.SetConnectionData("0.0.0.0", 7777);
        //启动服务器
        NetworkManager.Singleton.StartServer();
        //隐藏UI界面
        createServerBtn.transform.parent.gameObject.SetActive(false);
        testPanel.SetActive(true);
    }

    /// <summary>
    /// 加入服务器按钮点击事件
    /// </summary>
    private void JoinServerBtnClick()
    {
    
    
        //获取输入的ip地址
        string ip = ipinput.text;
        //判断ip地址是否为空
        if (ipinput.text.Equals(""))
        {
    
    
            //如果为空,默认ip地址为127.0.0.1
            ip = "127.0.0.1";
            print("未输入IP地址,使用默认地址");
        }
        //获取Unity传输组件
        UnityTransport unityTransport = NetworkManager.Singleton.GetComponent<UnityTransport>();
        //设置要连接的ip地址和端口
        unityTransport.SetConnectionData(ip, 7777);
        //启动连接到服务器,以客户端的身份
        NetworkManager.Singleton.StartClient();
        //隐藏UI界面
        joinServerBtn.transform.parent.gameObject.SetActive(false);
        //关闭消息调试面板
        testPanel.SetActive(true);
    }
}


6. Synchronize the player and the fired bullet
Create a new NetPlayerSync script and hang it on the player of the third-person controller. The main code is as follows:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using UnityEngine.UI;
using Invector.vCharacterController;

public class NetPlayerSync : NetworkBehaviour
{
    
    
    /// <summary>
    /// 动画组件
    /// </summary>
    private Animator _animator;

    /// <summary>
    /// 需要进行服务端更新的参数,包含发射和接收
    /// </summary>
    NetworkVariable<float> _SyncInputHorizontal = new NetworkVariable<float>();
    NetworkVariable<float> _SyncInputVertical = new NetworkVariable<float>();
    NetworkVariable<float> _SyncInputMagnitude = new NetworkVariable<float>();
    NetworkVariable<bool> _SyncIsGrounded = new NetworkVariable<bool>();
    NetworkVariable<bool> _SyncIsStrafing = new NetworkVariable<bool>();
    NetworkVariable<bool> _SyncIsSprinting = new NetworkVariable<bool>();
    NetworkVariable<float> _SyncGroundDistance = new NetworkVariable<float>();

    NetworkVariable<Vector3> _SyncPosition = new NetworkVariable<Vector3>();
    NetworkVariable<Quaternion> _SyncRotation = new NetworkVariable<Quaternion>();
    NetworkVariable<float> _SyncHealth = new NetworkVariable<float>();


    #region Variables       

    [Header("Controller Input")]
    public string horizontalInput = "Horizontal";
    public string verticallInput = "Vertical";
    public KeyCode jumpInput = KeyCode.Space;
    public KeyCode strafeInput = KeyCode.Tab;
    public KeyCode sprintInput = KeyCode.LeftShift;

    [Header("Camera Input")]
    public string rotateCameraXInput = "Mouse X";
    public string rotateCameraYInput = "Mouse Y";

    [HideInInspector] public vThirdPersonController cc;
    [HideInInspector] public vThirdPersonCamera tpCamera;
    [HideInInspector] public Camera cameraMain;

    #endregion

    public Image fillImage;
    public float currentHealth;
    public float maxHealth = 100;

    public NetPlayer mPlayer;



    private void Start()
    {
    
    
        _animator = GetComponent<Animator>();
        maxHealth = 100;
        currentHealth = maxHealth;
        mPlayer.playerSync = this;


        InitilizeController();
        InitializeTpCamera();

    }

    private void FixedUpdate()
    {
    
    
        if (IsLocalPlayer)
        {
    
    
            cc.UpdateMotor();               // updates the ThirdPersonMotor methods
            cc.ControlLocomotionType();     // handle the controller locomotion type and movespeed
            cc.ControlRotationType();       // handle the controller rotation type
        }
    }
    private void Update()
    {
    
    
        if (IsLocalPlayer)
        {
    
    
            InputHandle();                  // update the input methods
            cc.UpdateAnimator();            // updates the Animator Parameters
            UpdateAnimator();
            SetHealthToServerRpc(currentHealth);
        }
    }
    private void LateUpdate()
    {
    
    
        if (!IsLocalPlayer)
        {
    
    
            SyncInput();
            UpdateOtherPlayerHealth();
        }
        UpdateHealthImage();
    }
    public virtual void OnAnimatorMove()
    {
    
    
        if (cc!=null)
        {
    
    
            cc.ControlAnimatorRootMotion(); // handle root motion animations 
        }
    
    }

    private void UpdateAnimator()
    {
    
    
        float h = _animator.GetFloat("InputHorizontal");
        float v = _animator.GetFloat("InputVertical");
        float m = _animator.GetFloat("InputMagnitude");
        bool ground = _animator.GetBool("IsGrounded");
        bool straf = _animator.GetBool("IsStrafing");
        bool sprint = _animator.GetBool("IsSprinting");
        float grounddistance = _animator.GetFloat("GroundDistance");
        //上面先获取到当前参数,然后发给服务端,服务端再发给各个客户端进行同步
        UpdatePlayerAnimatorServerRpc(h, v, m, ground, straf, sprint, grounddistance);
        UpdatePlayerMovementServerRpc(transform.position, transform.rotation);

    }

    [ServerRpc]
    private void UpdatePlayerAnimatorServerRpc(float h, float v, float m, bool ground, bool straf, bool sprint, float grounddistance)
    {
    
    
        _SyncInputHorizontal.Value = h;
        _SyncInputVertical.Value = v;
        _SyncInputMagnitude.Value = m;
        _SyncIsGrounded.Value = ground;
        _SyncIsStrafing.Value = straf;
        _SyncIsSprinting.Value = sprint;
        _SyncGroundDistance.Value = grounddistance;


    }
    [ServerRpc]
    private void UpdatePlayerMovementServerRpc(Vector3 pos, Quaternion rot)
    {
    
    
        _SyncPosition.Value = pos;
        _SyncRotation.Value = rot;
    }

    [ServerRpc]
    private void SetHealthToServerRpc(float health)
    {
    
    
        _SyncHealth.Value = health;
    }
    /// <summary>
    /// 如果不是本地玩家,只需要接收服务端更新的数据
    /// </summary>
    private void SyncInput()
    {
    
    
        _animator.SetFloat("InputHorizontal", _SyncInputHorizontal.Value);
        _animator.SetFloat("InputVertical", _SyncInputVertical.Value);
        _animator.SetFloat("InputMagnitude", _SyncInputMagnitude.Value);
        _animator.SetBool("IsGrounded", _SyncIsGrounded.Value);
        _animator.SetBool("IsStrafing", _SyncIsStrafing.Value);
        _animator.SetBool("IsSprinting", _SyncIsSprinting.Value);
        _animator.SetFloat("GroundDistance", _SyncGroundDistance.Value);

        transform.position = _SyncPosition.Value;
        transform.rotation = _SyncRotation.Value;
    }
    private void UpdateOtherPlayerHealth()
    {
    
    
        currentHealth = _SyncHealth.Value;
    }

    private void UpdateHealthImage()
    {
    
    
        fillImage.fillAmount = currentHealth / maxHealth;
    }



    #region Basic Locomotion Inputs

    protected virtual void InitilizeController()
    {
    
    
        cc = GetComponent<vThirdPersonController>();

        if (cc != null)
            cc.Init();
    }

    protected virtual void InitializeTpCamera()
    {
    
    
        if (tpCamera == null)
        {
    
    
            tpCamera = FindObjectOfType<vThirdPersonCamera>();
            if (tpCamera == null)
                return;
            if (tpCamera)
            {
    
    
                tpCamera.SetMainTarget(this.transform);
                tpCamera.Init();
            }
        }
    }

    protected virtual void InputHandle()
    {
    
    
        MoveInput();
        CameraInput();
        SprintInput();
        StrafeInput();
        JumpInput();
    }

    public virtual void MoveInput()
    {
    
    
        cc.input.x = Input.GetAxis(horizontalInput);
        cc.input.z = Input.GetAxis(verticallInput);
    }

    protected virtual void CameraInput()
    {
    
    
        if (!cameraMain)
        {
    
    
            if (!Camera.main) Debug.Log("Missing a Camera with the tag MainCamera, please add one.");
            else
            {
    
    
                cameraMain = Camera.main;
                cc.rotateTarget = cameraMain.transform;
            }
        }

        if (cameraMain)
        {
    
    
            cc.UpdateMoveDirection(cameraMain.transform);
        }

        if (tpCamera == null)
            return;

        var Y = Input.GetAxis(rotateCameraYInput);
        var X = Input.GetAxis(rotateCameraXInput);

        tpCamera.RotateCamera(X, Y);
    }

    protected virtual void StrafeInput()
    {
    
    
        if (Input.GetKeyDown(strafeInput))
            cc.Strafe();
    }

    protected virtual void SprintInput()
    {
    
    
        if (Input.GetKeyDown(sprintInput))
            cc.Sprint(true);
        else if (Input.GetKeyUp(sprintInput))
            cc.Sprint(false);
    }

    /// <summary>
    /// Conditions to trigger the Jump animation & behavior
    /// </summary>
    /// <returns></returns>
    protected virtual bool JumpConditions()
    {
    
    
        return cc.isGrounded && cc.GroundAngle() < cc.slopeLimit && !cc.isJumping && !cc.stopMove;
    }

    /// <summary>
    /// Input to trigger the Jump 
    /// </summary>
    protected virtual void JumpInput()
    {
    
    
        if (Input.GetKeyDown(jumpInput) && JumpConditions())
            cc.Jump();
    }

    #endregion
}

Create a new NetPlayer script and hang it on the Player prefab.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using Invector.vCharacterController;
using Invector.Utils;
using UnityEngine.UI;

public class NetPlayer : NetworkBehaviour
{
    
    
  

    /// <summary>
    /// 子物体玩家
    /// </summary>
    public GameObject _player;
    /// <summary>
    /// 子物体第三人称相机
    /// </summary>
    public GameObject _camera;

    //子弹出生点或者说生成点
    public Transform spawnPoint;
    //子弹发射方向
    public Transform endpoint;
    //子弹预制体
    public GameObject bullet;
    //玩家同步脚本
    public NetPlayerSync playerSync;

    /// <summary>
    /// 当玩家生成时
    /// </summary>
    public override void OnNetworkSpawn()
    {
    
    
        //显示调试信息
        NetMain.instance.testPanel.GetComponentInChildren<Text>().text += OwnerClientId;
        //设置玩家名字
        gameObject.name = OwnerClientId.ToString();

        //如果是本地玩家
        if (IsLocalPlayer)
        {
    
    
            //初始化参数
            NetMain.instance.localPlayer = this;
            NetMain.instance._camera.SetActive(false);
            transform.position = NetMain.instance.playerSpawnPos.position;
           

        }

        //不是本地玩家
        if (!IsLocalPlayer)
        {
    
    
            //删除部分组件
            if (_player.GetComponent<vThirdPersonController>()!=null)
            {
    
    
                Destroy(_player.GetComponent<vThirdPersonController>());
            }
            if (_player.GetComponent<vComment>()!=null)
            {
    
    
                Destroy(_player.GetComponent<vComment>());
            }
            if (_player.GetComponent<vThirdPersonInput>()!=null)
            {
    
    
                Destroy(_player.GetComponent<vThirdPersonInput>());
            }
            if (_camera!=null)
            {
    
    
                Destroy(_camera);
            }
         
        }

    }

    private void Update()
    {
    
    
        //如果是本地玩家,才可以进行发射子弹,其他玩家收到服务器控制,不接受本地的控制
        if (IsLocalPlayer)
        {
    
    
            TestAttack();

        }

    }


    private void TestAttack()
    {
    
    
        if (Input.GetKeyDown(KeyCode.F))
        {
    
    
            SpawnBulletServerRpc();
        }

    }

    /// <summary>
    /// 客户端向服务器发射消息
    /// </summary>
    [ServerRpc]
    private void SpawnBulletServerRpc()
    {
    
    
        SpawnBulletToClientRpc();
    }

    /// <summary>
    /// 服务端向客户端发射消息
    /// </summary>
    [ClientRpc]
    private void SpawnBulletToClientRpc()
    {
    
    
        if (IsLocalPlayer)
        {
    
    
            GameObject go = NetworkManager.Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);
            go.GetComponent<Bullet>().dir = endpoint.position - spawnPoint.position;
        }
        else
        {
    
    
            GameObject go = NetworkManager.Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);
            go.GetComponent<Bullet>().dir = endpoint.position - spawnPoint.position;
            
        }
        
    }

    /// <summary>
    /// 本地测试发射子弹
    /// </summary>
    private void LocalSpawnBullet()
    {
    
    
        GameObject go = NetworkManager.Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);
        go.GetComponent<Bullet>().dir = endpoint.position - spawnPoint.position;
    }


    /// <summary>
    /// 向服务端发射消息:更新被射中的玩家的血量
    /// </summary>
    /// <param name="id"></param>
    [ServerRpc]
    public void SendHealthInfoToServerRpc(ulong id)
    {
    
    
        SetHealthChangeToClientRpc(id);
    }


    /// <summary>
    /// 通知所有玩家,判断是否为射中的玩家,如果是减血量
    /// </summary>
    /// <param name="id"></param>
    [ClientRpc]
    public void SetHealthChangeToClientRpc(ulong id)
    {
    
    
        if (id == NetMain.instance.localPlayer.OwnerClientId)
        {
    
    
            NetMain.instance.testPanel.GetComponentInChildren<Text>().text += "\n当前的id为" + id;
            NetMain.instance.localPlayer.playerSync.currentHealth -= 1;
        }
    }

}

Create a new HealthImage script and hang it on the player's health bar so that the health bar always faces the camera.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HealthImage : MonoBehaviour
{
    
    
    //朝向相机
    private Transform Camtarget;
    private void Update()
    {
    
    
        //不断查找所有的摄像机,判断哪个是已启用的,把已启用的摄像机设置为目标摄像机
        GameObject[] gos = GameObject.FindGameObjectsWithTag("MainCamera");
        for (int i = 0; i < gos.Length; i++)
        {
    
    
            if (gos[i].activeInHierarchy)
            {
    
    
                Camtarget = gos[i].transform;
                break;
            }
        }
        if (Camtarget!=null)
        {
    
    
            transform.rotation = Camtarget.rotation;
        }
    }
}

Bullet code:

using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;

public class Bullet : NetworkBehaviour
{
    
    
    public Vector3 dir;
    Rigidbody rigid;

    

    private void Start()
    {
    
    
        rigid = GetComponent<Rigidbody>();
        Destroy(gameObject, 5);
    }

    private void FixedUpdate()
    {
    
    
        rigid.velocity = dir * Time.deltaTime * 5000;
    }

    private void OnCollisionEnter(Collision collision)
    {
    
    
        if (collision.collider.CompareTag("Player"))
        {
    
    
            NetPlayerSync mps = collision.collider.GetComponent<NetPlayerSync>();
            if (!mps.mPlayer.IsLocalPlayer)
            {
    
    
                print(mps.OwnerClientId);
             NetMain.instance.localPlayer.SendHealthInfoToServerRpc(mps.OwnerClientId);
                Destroy(gameObject);
            }
        }
    }



}

7. Test and summary
Game objects that need to be manually dragged after the code is completed:
insert image description here
insert image description here
insert image description here
the effect is as follows:
insert image description here
project source file, the version used is 2020.3.28
Link: https://pan.baidu.com/s/1d8zuQzzekIefJGgQvJKfXw
Extraction code: yrpc

Guess you like

Origin blog.csdn.net/qq_14942529/article/details/128435664