前言
生成一个网络对象有多种办法,但始终
- 只能由Server/Host生成/销毁
- 必须有network object组件
- 要在NetworkManager中的NetworkPrefebList中注册
建议先看完第一章:unity netcode for gameobject(NGO)逻辑代码教程-CSDN博客
最简单直接的方法
简单的Debug程序:
private void Update() {
if (!IsOwner) {
return;
}
if (Input.GetKeyDown(KeyCode.F)) {
Spawn();
}
if (Input.GetKeyDown(KeyCode.G)) {
DeSpawn();
}
}
生成
// NetworkObject networkObject;
private void Spawn() {
//只有服务器可以创建/销毁对象
if (IsServer) {
//先在本地实例化
GameObject prefebInstance = Instantiate(SpawnPrefeb);
//获取NetworkObject组件
networkObject = prefebInstance.GetComponent<NetworkObject>();
//调用Spawn函数,它的参数含义为是否随着场景销毁而销毁,默认为false
networkObject.Spawn();
}
}
销毁
注意Despawn会默认销毁对象,可以理解为在其后调用了Destroy(networkObject.gameObject)
之后我们会修改这个特点让它对象池化
private void DeSpawn() {
//只有服务器可以创建/销毁对象
if (IsServer) {
Debug.Log("Despawn");
networkObject.Despawn();
}
}
演示:注意到右边Hierarchy中NetObject的销毁
,
使用对象池
INetworkPrefabInstanceHandler接口介绍
首先,你可以很轻松地验证:在host/server中调用networkObject.Despawn()后,不仅networkObject消失(despawn,不应该理解为摧毁),networkObject.gameObject摧毁。
那么INetworkPrefabInstanceHandler允许你取消networkObject.gameObject摧毁的过程,改为networkObject.gameObject.SetActive(false)。
这样的过程就像是:物体在主机生成本地实例,出池时networkObject.Spawn()在网络上生成,入池时networkObject.Despawn(),在网络上消失,同时主机本地实例隐藏,但他仍然存在,你可以激活该实例然后重复这个过程。
//官方定义
namespace Unity.Netcode {
public interface INetworkPrefabInstanceHandler {
//network object调用Despawn()后,在Host和Client中调用
void Destroy(NetworkObject networkObject);
//当networkObject.Spawn()调用后,在Client中调用,注意不会在Host和Server中调用。
//为什么不在Host中调用?我的解释是Host中存在network.gameobject的Inactive实体,Host在本地调用Spawn()就够了
NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation);
}
}
我们直接来看实际使用场景。
单物体对象池
unity官方给出了单物体对象池的示例代码,它的通用性不强,建议了解就好,完全可以用多物体对象池替代
官方代码
public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstanceHandler
{
public GameObject PrefabToSpawn;
public bool SpawnPrefabAutomatically;
private GameObject m_PrefabInstance;
private NetworkObject m_SpawnedNetworkObject;
private void Start()
{
// Instantiate our instance when we start (for both clients and server)
m_PrefabInstance = Instantiate(PrefabToSpawn);
// Get the NetworkObject component assigned to the Prefab instance
m_SpawnedNetworkObject = m_PrefabInstance.GetComponent<NetworkObject>();
// Set it to be inactive
m_PrefabInstance.SetActive(false);
}
private IEnumerator DespawnTimer()
{
yield return new WaitForSeconds(2);
m_SpawnedNetworkObject.Despawn();
StartCoroutine(SpawnTimer());
yield break;
}
private IEnumerator SpawnTimer()
{
yield return new WaitForSeconds(2);
SpawnInstance();
yield break;
}
/// <summary>
/// Invoked only on clients and not server or host
/// INetworkPrefabInstanceHandler.Instantiate implementation
/// Called when Netcode for GameObjects need an instance to be spawned
/// </summary>
public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation)
{
m_PrefabInstance.SetActive(true);
m_PrefabInstance.transform.position = transform.position;
m_PrefabInstance.transform.rotation = transform.rotation;
return m_SpawnedNetworkObject;
}
/// <summary>
/// Client and Server side
/// INetworkPrefabInstanceHandler.Destroy implementation
/// </summary>
public void Destroy(NetworkObject networkObject)
{
m_PrefabInstance.SetActive(false);
}
public void SpawnInstance()
{
if (!IsServer)
{
return;
}
if (m_PrefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned)
{
m_PrefabInstance.SetActive(true);
m_SpawnedNetworkObject.Spawn();
StartCoroutine(DespawnTimer());
}
}
public override void OnNetworkSpawn()
{
// We register our network Prefab and this NetworkBehaviour that implements the
// INetworkPrefabInstanceHandler interface with the Prefab handler
NetworkManager.PrefabHandler.AddHandler(PrefabToSpawn, this);
if (!IsServer || !SpawnPrefabAutomatically)
{
return;
}
if (SpawnPrefabAutomatically)
{
SpawnInstance();
}
}
public override void OnNetworkDespawn()
{
if (m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned)
{
m_SpawnedNetworkObject.Despawn();
}
base.OnNetworkDespawn();
}
public override void OnDestroy()
{
// This example destroys the
if (m_PrefabInstance != null)
{
// Always deregister the prefab
NetworkManager.Singleton.PrefabHandler.RemoveHandler(PrefabToSpawn);
Destroy(m_PrefabInstance);
}
base.OnDestroy();
}
}
我们把目光放在INetworkPrefebInstanceHandler接口的2个函数:Instantiate,Destroy
以及2个控制循环生成销毁对象的协程
private IEnumerator DespawnTimer() {
yield return new WaitForSeconds(2);
m_SpawnedNetworkObject.Despawn();
//Destroy在这里自动调用
StartCoroutine(SpawnTimer());
yield break;
}
private IEnumerator SpawnTimer() {
yield return new WaitForSeconds(2);
SpawnInstance();
yield break;
}
public void SpawnInstance() {
if (!IsServer) {
return;
}
if (prefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned) {
prefabInstance.SetActive(true);
m_SpawnedNetworkObject.Spawn();
//Instantiate在这里自动调用
StartCoroutine(DespawnTimer());
}
}
想象一下:Host/Server 实例化一个对象->Spawn(Sever and Client) ,SetAcitve(true)(Server)->Despawn(Sever and Client),SetActive(false)(Server),Not Destroy
之后重复这个过程
演示
协程等待时间修改成了1秒
多物体对象池
Untiy 官方给出了一份多物体网络对象池的通用代码,我们来看看它如何使用
Object Pooling | Unity Multiplayer Networking (unity3d.com)
官方代码
这是一份通用性很强的组件,你可以复制并且通过一点点的配置就能让它管理你的对象
using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Pool;
namespace Unity.BossRoom.Infrastructure {
/// <summary>
/// Object Pool for networked objects, used for controlling how objects are spawned by Netcode. Netcode by default
/// will allocate new memory when spawning new objects. With this Networked Pool, we're using the ObjectPool to
/// reuse objects.
/// Boss Room uses this for projectiles. In theory it should use this for imps too, but we wanted to show vanilla spawning vs pooled spawning.
/// Hooks to NetworkManager's prefab handler to intercept object spawning and do custom actions.
/// </summary>
public class NetworkObjectPool : NetworkBehaviour {
public static NetworkObjectPool Singleton { get; private set; }
[SerializeField]
List<PoolConfigObject> PooledPrefabsList;
//已生成对象池的预制体
HashSet<GameObject> m_Prefabs = new HashSet<GameObject>();
//预制体与管理它的对象池
Dictionary<GameObject, ObjectPool<NetworkObject>> m_PooledObjects = new Dictionary<GameObject, ObjectPool<NetworkObject>>();
public void Awake() {
if (Singleton != null && Singleton != this) {
Destroy(gameObject);
}
else {
Singleton = this;
}
}
public override void OnNetworkSpawn() {
// Registers all objects in PooledPrefabsList to the cache.
foreach (var configObject in PooledPrefabsList) {
RegisterPrefabInternal(configObject.Prefab, configObject.PrewarmCount);
}
}
public override void OnNetworkDespawn() {
// Unregisters all objects in PooledPrefabsList from the cache.
foreach (var prefab in m_Prefabs) {
// Unregister Netcode Spawn handlers
NetworkManager.Singleton.PrefabHandler.RemoveHandler(prefab);
m_PooledObjects[prefab].Clear();
}
m_PooledObjects.Clear();
m_Prefabs.Clear();
}
//这段代码会在你试图添加一个非网络对象时报错
public void OnValidate() {
for (var i = 0; i < PooledPrefabsList.Count; i++) {
var prefab = PooledPrefabsList[i].Prefab;
if (prefab != null) {
Assert.IsNotNull(prefab.GetComponent<NetworkObject>(), $"{nameof(NetworkObjectPool)}: Pooled prefab \"{prefab.name}\" at index {i.ToString()} has no {nameof(NetworkObject)} component.");
}
}
}
/// <summary>
/// Gets an instance of the given prefab from the pool. The prefab must be registered to the pool.
/// </summary>
/// <remarks>
/// To spawn a NetworkObject from one of the pools, this must be called on the server, then the instance
/// returned from it must be spawned on the server. This method will then also be called on the client by the
/// PooledPrefabInstanceHandler when the client receives a spawn message for a prefab that has been registered
/// here.
/// </remarks>
/// <param name="prefab"></param>
/// <param name="position">The position to spawn the object at.</param>
/// <param name="rotation">The rotation to spawn the object with.</param>
/// <returns></returns>
public NetworkObject GetNetworkObject(GameObject prefab, Vector3 position, Quaternion rotation) {
var networkObject = m_PooledObjects[prefab].Get();
var noTransform = networkObject.transform;
noTransform.position = position;
noTransform.rotation = rotation;
return networkObject;
}
/// <summary>
/// Return an object to the pool (reset objects before returning).
/// </summary>
public void ReturnNetworkObject(NetworkObject networkObject, GameObject prefab) {
m_PooledObjects[prefab].Release(networkObject);
}
/// <summary>
/// Builds up the cache for a prefab.
/// </summary>
void RegisterPrefabInternal(GameObject prefab, int prewarmCount) {
NetworkObject CreateFunc() {
return Instantiate(prefab).GetComponent<NetworkObject>();
}
void ActionOnGet(NetworkObject networkObject) {
networkObject.gameObject.SetActive(true);
}
void ActionOnRelease(NetworkObject networkObject) {
networkObject.gameObject.SetActive(false);
}
void ActionOnDestroy(NetworkObject networkObject) {
Destroy(networkObject.gameObject);
}
m_Prefabs.Add(prefab);
// Create the pool
m_PooledObjects[prefab] = new ObjectPool<NetworkObject>(CreateFunc, ActionOnGet, ActionOnRelease, ActionOnDestroy, defaultCapacity: prewarmCount);
// Populate the pool
var prewarmNetworkObjects = new List<NetworkObject>();
for (var i = 0; i < prewarmCount; i++) {
prewarmNetworkObjects.Add(m_PooledObjects[prefab].Get());
}
foreach (var networkObject in prewarmNetworkObjects) {
m_PooledObjects[prefab].Release(networkObject);
}
// Register Netcode Spawn handlers
NetworkManager.Singleton.PrefabHandler.AddHandler(prefab, new PooledPrefabInstanceHandler(prefab, this));
}
}
[Serializable]
struct PoolConfigObject {
public GameObject Prefab;
public int PrewarmCount;
}
//PooledPrefabInstanceHandler will handle it on the client(s) when the network object's Spawn or Despawn method is called,
//via its Instantiate and Destroy methods. Inside those methods, the PooledPrefabInstanceHandler simply calls the pool to get the corresponding object, or to return it.
class PooledPrefabInstanceHandler : INetworkPrefabInstanceHandler {
GameObject m_Prefab;
NetworkObjectPool m_Pool;
public PooledPrefabInstanceHandler(GameObject prefab, NetworkObjectPool pool) {
m_Prefab = prefab;
m_Pool = pool;
}
//你可以很轻易的验证这2个函数的调用时机
//当networkObject.Spawn()调用后,在Client中调用,注意不会在Host和Server中调用。
NetworkObject INetworkPrefabInstanceHandler.Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) {
Debug.Log(" NetworkObject INetworkPrefabInstanceHandler.Instantiate");
return m_Pool.GetNetworkObject(m_Prefab, position, rotation);
}
//network object调用Despawn()后,在Host和Client中调用
void INetworkPrefabInstanceHandler.Destroy(NetworkObject networkObject) {
Debug.Log("void INetworkPrefabInstanceHandler.Destroy");
m_Pool.ReturnNetworkObject(networkObject, m_Prefab);
}
}
}
配置
创建一个空物体,挂载脚本,添加NetworkObject组件
设置池化预制体,它需要在NetworkPrefebList中注册。
PrewarmCount指的是预制体初始时拥有多少实例,他们在生成时都会被设置成Inactive
使用它
在unity官方示例游戏中,它的使用非常简单
以下是我写的Debug版本
生成对象:从池中取出对象,再调用Spawn()即可
private void Spawn() {
//只有服务器可以创建/销毁对象
if (IsServer) {
//public GameObject SpawnPrefeb;
networkObject= NetworkObjectPool.Singleton.GetNetworkObject(SpawnPrefeb, transform.position, Quaternion.identity);
Debug.Log("Spawn");
networkObject.Spawn();
}
}
销毁对象:获得networkObject的引用,直接调用Despawn()
注意NetworkObjectPoo.ReturnNetworkObject是不需要的,它应该被改成private,只为内部的INetworkPrefabInstanceHandler服务。
private void DeSpawn() {
//只有服务器可以创建/销毁对象
if (IsServer) {
Debug.Log("Despawn");
networkObject.Despawn();
}
}
演示
在控制栏中能够看到各个函数的调用情况,INetworkPrefabInstanceHandler.Instantiate明显没有在Host中调用,可以验证它会在client中调用,这里不再展示
RPC实现法?
RPC也能够实现,但是不清楚性能怎么样
基本思路是把SetAcitve同步给其他客户端就行
这里就留个悬念吧,日后有空尝试一下。文章来源:https://www.toymoban.com/news/detail-784962.html
如果有人能有兴趣实现,请一定联系我,我会毫不吝啬地加上你的大名!文章来源地址https://www.toymoban.com/news/detail-784962.html
到了这里,关于unity NGO 代码教程:网络上动态生成物体的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!