复习上一章物体运动

玩家按下W键向前移动,此时Update()中调用Move()函数,更新PlayerController类中的velocity、xRot、yRot值,而PlayerController类中的FixedUpdate()函数会均匀调用,实现玩家移动。

PlayerInput模块:Update()中获取x,y得到速度velocity,调用controler.Move()更新PlayerController模块中的速度velocity

PlayerController模块:FixUpdate()均匀检测(0.02s)速度velocity是否为0,如果不为0,更改位置坐标,使角色移动

控制鼠标运动同理

实现联机的两种方式

Server判断:Client处理用户输入,将用户输入信息传给Server,Server控制角色移动,将结果返回给所有Client【延迟大;不易作弊】

Client模式:Client处理用户用户输入以及角色移动,然后将结果发给Server,Server仅同步结果给其他Client【延迟小;易作弊】

击中与未击中判定:采取攻击者视角。在射击者看来,是否击中目标自己很明确,因为准星是否瞄准到对方身上自己能看到,而从被攻击者的视角来看,被攻击者并不知道自己是否被瞄准且击中。

联机实现

安装网络包

com.unity.netcode.gameobjects(需要更新到Unity 2021.3及以后版本才能看到)

- 工具栏:Windows->Package Manager

- 左上角改为: Unity Registry,找到对应的包,右下角install(已经安装则显示Remove)

配置网络组件

  1. 创建空Object,命名为NetworkManager管理所有需要联机同步的物体,添加组件NetwortkManager ,NetworkManager会有两个警告:安装包 Multiplaers Tools(按照上文方法);选择NetworkTrnasform后即可消除。

  2. 添加需要同步的物体 Player

  1. 设置需要同步的信息,Player自身

玩家的部分信息是不需要同步的,比如Scale等,因为Scale是不变的。

开局创建角色与UI

创建角色:在NetWorkManager中设置 Player Prefab。

创建开局相机:【否则开局黑屏】,全局创建并调整即可,命名为SceneCamera。

UI创建:

  1. 创建 Canvas 命名为 MenuUI【如果提示import包则import即可】

  2. 添加 Button组件

  1. 添加事件逻辑:在NetworkManager中添加脚本NetWorkManagerUI

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

public class NetworkManagerUI : MonoBehaviour
{
    [SerializeField]
    private Button hostButton;

    [SerializeField]
    private Button serverButton;

    [SerializeField]
    private Button clientButton;

    void Start()
    {
        hostButton.onClick.AddListener(() =>
        {
            NetworkManager.Singleton.StartHost();
        });

        serverButton.onClick.AddListener(() =>
        {
            NetworkManager.Singleton.StartServer();
        });

        clientButton.onClick.AddListener(() =>
        {
            NetworkManager.Singleton.StartClient();
        });
    }
}

禁用非本地玩家的组件

此时直接运行 hostclient,会发现两个角色会同时移动,并且unity中显示有多个Audio被使用的警告。

原因是在一个Player中,没有禁用其他Player的输入控制和Camera;并且开始菜单的Camera和本地Player的Cameara也会有冲突。

在Player中添加一段脚本PlayerSetup解决此问题。

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

public class PlayerSetup : NetworkBehaviour
{

    [SerializeField]
    private Behaviour[] componentsToDisable;

    private Camera sceneCamera;

    void Start()
    {
        if (!IsLocalPlayer)
        {
            for (int i = 0; i < componentsToDisable.Length; i++)
            {
                componentsToDisable[i].enabled = false;
            }
        }
        else
        {
            sceneCamera = Camera.main;
            if (sceneCamera != null)
            {
                sceneCamera.gameObject.SetActive(false);
            }
        }
    }

    private void OnDisable()
    {
        if (sceneCamera != null)
        {
            sceneCamera.gameObject.SetActive(true);
        }
    }
}

SceneCamera无法拖动赋值,因为Player不在场景里,要设置为MainCemera来获取

控制不同玩家移动

此时,不会同时控制多个Player 但是,打开两个窗口时,只能控制其中一个。

原因是Unity默认不信任客户端,没有授权,需要自己写一段脚本打开授权

创建脚本 ClientNetworkTransform

using Unity.Netcode.Components;
using UnityEngine;

namespace Unity.Multiplayer.Samples.Utilities.ClientAuthority
{
    [DisallowMultipleComponent]
    public class ClientNetworkTransform : NetworkTransform
    {
        protected override bool OnIsServerAuthoritative()
        {
            return false;
        }
    }
}

代码含义:重载NetWorkTransform中的授权函数

所以,要将用到NetWorkTransform的地方改为ClientNetWorkTransform