目录

艾尔登复刻Ep1客户端制作场景切换网络控制

艾尔登复刻Ep1——客户端制作、场景切换、网络控制

需要添加的插件内容

Netcode for GameObjects :是一个为 Unity 游戏开发提供高级网络功能的 SDK。它的主要作用是允许开发者在其 GameObject 和 MonoBehaviour 工作流中集成网络功能,并且可以与多种底层传输层协议兼容。

具体内容请看:

ParrelSync :ParrelSync 是一个 Unity 编辑器扩展,旨在帮助开发者在没有构建项目的情况下测试多人游戏玩法。通过使用 ParrelSync,开发者可以在多个 Unity 编辑器窗口中同时运行项目,从而快速测试多人游戏的功能和同步问题。

具体内容请看:

客户端制作

Network manager

添加了Net for work脚本后,可以给物体挂载Network manager脚本。我们需要创建一个空物体,并将该脚本挂载上。

https://i-blog.csdnimg.cn/direct/3edf0c3cd8c9496cbe81d3da9fa68e9c.png

编写客户端控制脚本

这段脚本代码的作用是管理游戏的标题屏幕,提供两个主要功能:

  1. 启动网络会话作为主机( StartNetworkAsHost ),允许其他客户端连接。
  2. 开始一个新游戏( StartNewGame ),加载游戏的初始状态。
using SG;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;


namespace SG
{
    public class TitleScreenManager : MonoBehaviour
    {
        // Start is called before the first frame update
        public void StartNetworkAsHost()
        {
            NetworkManager.Singleton.StartHost();
        }

        public void StartNewGame()
        {
            StartCoroutine(WorldSaveManager.instance.LoadNewGame());
        }

    }

}

命名空间:

  • namespace SG :定义了一个命名空间,用于组织代码,避免命名冲突。

    • 团队开发中,不同开发者可能会使用相同的标识符名称(如函数、类、变量等),导致命名冲突。命名空间通过为标识符添加一个前缀(即命名空间名称),将标识符限定在一个特定的作用域内,从而避免了全局作用域中的命名冲突。

StartNetworkAsHost方法:

  • 这个方法的作用是启动一个网络主机(Host)。在Unity Netcode中,主机既是服务器又是客户端,可以允许其他客户端连接到它
  • NetworkManager.Singleton.StartHost(); :
    • NetworkManager 是Unity Netcode(以前称为UNet)中的一个单例类,用于管理网络会话。
    • SingletonNetworkManager 的单例实例,确保在整个应用程序中只有一个 NetworkManager 对象。
    • StartHost()NetworkManager 的一个方法,用于启动主机模式。当用户调用 StartHost() 时,实际上是让用户的电脑设备承担了主机的角色,同时运行服务器和客户端的功能。

StartNewGame 方法:

  • 这个方法用于开始一个新游戏。
    • WorldSaveManager.instance :假设 WorldSaveManager 是一个单例模式的管理器类, instance 是其唯一的实例。
    • LoadNewGame() :这是一个协程方法,用于加载新游戏。协程在Unity中用于执行需要分多个帧完成的操作,通常用于避免主线程阻塞。

注意,这个挂载Network Manager脚本的空物体要加入预制体,在Unity中,将脚本挂载在空物体上作为单例管理器是一种常见的设计模式,这种模式能够确保在整个游戏或应用中只有一个实例存在,并且提供了一个全局的访问点,要添加至Asset—prefabs中。

挂载unity import

https://i-blog.csdnimg.cn/direct/454e81d2c8b14c9fb714c714235c8368.png

将 Unity Transport 挂载在 NetworkManager 物体上,是为了让 NetworkManager 使用它来处理网络连接和数据传输。Unity Transport 作为 NetworkManager 的传输层,负责实际的网络通信工作,使得 NetworkManager 能够通过网络与其他客户端或服务器进行交互。

https://i-blog.csdnimg.cn/direct/0d7ae9541310496ca75bb78c6cc6fa12.png

将脚本Unity Transport挂载到Network Transport

编写切换大世界场景脚本

用于Unity游戏引擎中的世界保存管理器(WorldSaveManager)。它的主要功能是管理游戏世界的加载。

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

namespace SG
{
    public class WorldSaveManager : MonoBehaviour
    {
        // Start is called before the first frame update
        public static WorldSaveManager instance;
        [SerializeField] int worldSceneIndex = 1;

        private void Awake()
        {
            if (instance == null)
            {
                instance = this;
            }
            else
            {
                Destroy(gameObject);
            }
        }

        private void Start()
        {
            DontDestroyOnLoad(gameObject);
        }

        public IEnumerator LoadNewGame()
        {

            AsyncOperation loadOperation = SceneManager.LoadSceneAsync(worldSceneIndex);
            yield return null;
        }
    }
}
[SerializeField] int worldSceneIndex = 1;
  • 这是一个Unity提供的特性(Attribute),用于指定一个私有字段(private field)应该在Unity编辑器的Inspector面板中显示并可编辑。
  • 通常情况下,私有字段不会在Inspector面板中显示,但添加了 [SerializeField] 特性后,该字段就会在Inspector中可见,允许你在编辑器中直接修改它的值。
private void Start()
{
    DontDestroyOnLoad(gameObject);
}

这是Unity的另一个生命周期方法,在 Awake 之后调用。在这里,它调用了 DontDestroyOnLoad 方法,确保 WorldSaveManager 实例在场景切换时不会被销毁。

public IEnumerator LoadNewGame()
{
    AsyncOperation loadOperation = SceneManager.LoadSceneAsync(worldSceneIndex);
    yield return null;
} 

这段代码的作用是启动一个异步操作,用于加载指定索引的场景。加载过程不会阻塞主线程,游戏可以继续运行,同时场景在后台加载。

  • SceneManager.LoadSceneAsync(worldSceneIndex) :使用Unity的场景管理器异步加载指定索引的场景(这里是 worldSceneIndex )。

    • SceneManager.LoadSceneAsync :这是Unity引擎中 SceneManager 类的一个静态方法,用于异步加载场景。
    • AsyncOperation : 这是一个返回值类型,表示异步操作的对象。 通过这个对象,可以监听页面加载的进度和状态。
  • yield return null :这是一个协程的暂停点,表示在下一帧继续执行。这里可能需要进一步的逻辑来处理场景加载的完成,比如等待加载完成后再继续执行其他操作

Q1:什么是异步操作?

A1:异步操作是指一个操作在启动后,不会立即阻塞当前线程的执行,而是允许当前线程继续处理其他任务,直到该操作完成。

异步操作通常用于执行耗时的任务,例如文件读写、网络请求、场景加载等,以避免主线程被阻塞,导致应用程序响应迟缓或卡顿。

Q2:为什么这里要用异步操作?

A2:在Unity中,加载场景是一个耗时的操作,特别是当场景包含大量资源(如模型、纹理、动画等)时。如果使用同步加载(即 SceneManager.LoadScene ),主线程会被阻塞,直到场景加载完成。这会导致游戏在加载期间出现卡顿,甚至完全冻结,严重影响用户体验。

通过使用异步加载(即 SceneManager.LoadSceneAsync ),场景的加载过程会在后台进行,而主线程可以继续处理其他任务,例如更新UI、播放加载动画、响应用户输入等。这样可以确保游戏在加载场景时仍然保持流畅的运行。

注意,这个挂载world Save Manager脚本的空物体要加入预制体

如何设置场景序号

1、先将当前客户端场景——save as——保存到Asset——Scene中(相当于另存一份),再删去多余的一份。

2、为场景添加序号

https://i-blog.csdnimg.cn/direct/74921598d16840c1a328e339ec0561c6.png

进入当前场景后,进入Building Setting界面,点击Add Open Scenes

为客户端设置按钮

https://i-blog.csdnimg.cn/direct/97d503d52e6e4589bc4a482f79a224ae.png

1、button的第一个设定:使网络会话作为主机( StartNetworkAsHost ),允许其他客户端连接。

2、button的第二个设定:隐藏Start Game游戏栏

3、button的第三个设定:显示New Game游戏栏

4、button的第四个设定: Select 方法会将按钮设置为选中状态,这通常会触发按钮的选中效果(如高亮设置等视觉效果)

https://i-blog.csdnimg.cn/direct/126a0e6e826c40eda1302ca4bf8c05af.png

将Screen Manager脚本挂载至Screen Canvas

https://i-blog.csdnimg.cn/direct/e097bdec431f449b91c06f5e02593eed.png

将Screen Canvas物体挂载到New Game游戏栏的按钮上,即点击New Game栏时,进入游戏场景

设置游戏角色管理脚本

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


namespace SG
{
    public class CharacterManger : MonoBehaviour
    {
        public static CharacterManger instance;

        [Header("NETWORK JOIN")]
        [SerializeField] bool startGameAsClient;

        private void Awake()
        {
            if (instance == null)
            {
                instance = this;
            }
            else
            {
                Destroy(gameObject);
            }
        }

        private void Start()
        {
            DontDestroyOnLoad(gameObject);
        }

        private void Update()
        {
            if (startGameAsClient)
            {
                startGameAsClient = false;
                NetworkManager.Singleton.Shutdown();
                NetworkManager.Singleton.StartClient();
            }
        }
    }
}
[Header("NETWORK JOIN")]
[SerializeField] bool startGameAsClient;
  • bool startGameAsClient; :一个布尔变量,用于决定游戏启动时是否作为客户端加入游戏

  • [Header("NETWORK JOIN")] 的作用是将下面的变量 [SerializeField] bool startGameAsClient; 归类到"NETWORK JOIN"这一部分

    这样在Unity编辑器中,这个变量会显示在检查器面板的"NETWORK JOIN"标题下。

  • https://i-blog.csdnimg.cn/direct/2bd261c594ff4aa7b326ae0100d5c664.png

if (startGameAsClient)
{
    startGameAsClient = false;
    NetworkManager.Singleton.Shutdown();
    NetworkManager.Singleton.StartClient();
}
  1. if (startGameAsClient)

    • 检查 startGameAsClient 是否为 true 。如果是,表示需要以客户端模式加入游戏。
  2. startGameAsClient = false;

    • startGameAsClient 设置为 false ,以确保这个逻辑只执行一次,避免重复触发。
  3. NetworkManager.Singleton.Shutdown();

    • 调用 NetworkManager 的单例实例的 Shutdown 方法。
    • 作用 :关闭当前的网络管理器,清理网络状态。
    • 目的 :确保在重新启动客户端连接之前,清除任何现有的网络连接和状态,避免冲突或资源泄漏。
  4. NetworkManager.Singleton.StartClient();

    • 调用 NetworkManager 的单例实例的 StartClient 方法。
    • 作用 :以客户端模式启动网络连接,使游戏客户端连接到服务器。

使动画角色对应网络连接

https://i-blog.csdnimg.cn/direct/91be875367aa414b9935a7cb52ec4f36.png

安装了网络插件后,为了让场景角色也有网络效应,我们要给物体加上该组件

https://i-blog.csdnimg.cn/direct/87b2502920ef4d26b393f20149206f14.png

将物体挂载到NetworkManager的Player Prefab上, 但需要注意:不同场景下执行挂载操作产生的效果不同