Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback for docs/basics/scenemanagement/custom-management.md #1053

Closed
Laumania opened this issue May 12, 2023 · 52 comments
Closed

Feedback for docs/basics/scenemanagement/custom-management.md #1053

Laumania opened this issue May 12, 2023 · 52 comments
Assignees
Labels
no docs required (yet) Issues that don't currently need docs work but can't be closed yet

Comments

@Laumania
Copy link

When doing custom scene management, I assume (at least in my case), it's because you are already doing some more advanced/complex stuff. With that in mind, can you please add a more advanced sample or link to a github sample, of how to handle in-scene prefabs when scene management is turned off.

The sample you have, with copy GlobalObjectIdHash and paste into Network Prefabs, Im sure is fine. However, as mentioned, when you do more advanced stuff where thigns are loaded dynamically etc. this doesn't really fly.

So really I'm after some samples on how to get in-scene network prefab instances and setup via code so it have correct overrides to work on client etc.
https://docs-multiplayer.unity3d.com/netcode/current/basics/scenemanagement/custom-management/#building-your-own-scene-management-solution

@Laumania
Copy link
Author

Laumania commented May 24, 2023

Sara on Discord asked me to elaborate a little more on this, so I made a little video where I try to explain where I'm coming from.

As I also state in the video, I'm not interested help specifically to my game, it's the general "handling of in-scene NetworkObject/Prefabs in Custom Scenemanagement" that I'm after - and I think more than me can benefit from more information about that on the docs :)

https://www.youtube.com/watch?v=1L2c868WHmM

@Laumania
Copy link
Author

Just some more info, now that I was rereading the page again: https://docs-multiplayer.unity3d.com/netcode/current/basics/scenemanagement/custom-management/

The two links that looks interesting in relation to my challenges here, are both "dead". Just goes to a 404 page.

image

image

image

@NoelStephensUnity
Copy link
Contributor

NoelStephensUnity commented May 27, 2023

@Laumania
I am working on putting together a small sample to help you out, but figured I would help you with those broken links. This could be due to some recent updates in the documentation server and links might have gotten broken (those did work at some point).
They "should" have pointed to:

However, watching your video makes me believe you might want to get a little fancier with your approach since I think the brick wall you are running up against is the fact that the prefabs you are spawning aren't in the list of prefabs...which makes both of the above approaches not very helpful.

The small example I am working on for you leverages from the NetworkBehaviour.OnSynchronize method where the very basic concept is this:

  • Create a "GenericPrefab" that has:
    • A GameObject
    • A NetworkObject
    • A modified version (specific to your project) of the below "TemplatedPrefab" NetworkBehaviour

Where you need to make sure your server and client have the GenericPrefab in their prefab list so that will always spawn no matter what. Then it is up to your implementation of the TemplatedPrefab as to how that constructs the unique attributes to the "generic" object.

NetworkBehaviour.OnSynchronize Order of Operations
NetworkBehaviour.OnSynchronize is invoked when a server sends the spawn data for client synchronization (per NetworkObject spawned) or when the server spawns a new NetworkObject dynamically. OnSynchronize allows you to add custom serialization data per NetworkBehaviour.

  • Server:
    • New Spawn: After the NetworkObject has been fully spawned and it is serializing information about the NetworkObject, for each NetworkBehaviour associated with the NetworkObject the NetworkBehaviour.OnSynchronize method is invoked and provides you with the opportunity to include custom data.
    • Client Synchronization: This is the same as spawning but happens for every spawned NetworkObject while the server is generating the entire client synchronization data block for everything.
  • Client:
    • For each NetworkObject being spawned, after the associated GameObject has been instantiated with some basic information, the NetworkBehaviour.OnSynchronize method is invoked. This happens before the NetworkObject is fully spawned on the Client (i.e. the NetworkBehaviour.OnNetworkSpawn has not been invoked yet).

Below is just one potential approach and is really pseudo code to provide you with the fundamental concept of what you most likely would want to do.

public interface ITemplatedPrefabHandler
{
    // Using a generic as what you want to return is up to you.
    // This could be JSON formated information or whatever you want.
    // You don't need to use a generic here (is is symbolic and pseudo code)
    G GetTemplateData<G>();
    void SetTemplateData<G>(G data);
}


public class TemplatedPrefab : NetworkBehaviour, ITemplatedPrefabHandler
{
    // The unqiue prefab you could make as a child under this instance (only as an example)
    // Not having viewed your project in detail, not sure what kind of information/approach makes the most sense.
    private GameObject ActualUniquePrefab;

    public G GetTemplateData<G>()
    {
        // Server gets this when spawning the object
        // This information defines the "unique attributes" of the
        // prefab. This could be JSON format (or whatever) to define the unique aspects of this prefab

        return ActualUniquePrefabInfo;
    }

    public void SetTemplateData<G>(G data)
    {
        // Client sets this during OnSynchronize and would determine what "thing" to instantiate and/or add
        // You could make this a normal prefab that implements an interface to invoke generic methods (i.e LightFireWork or the like)
        // or you might think of a more clever way to convey the specifics of the firework(s) associated with this NetworkObject.
        var stringData = data as string;
        if (string.IsNullOrEmpty(stringData))
        {
            // Error
        }
        // Otherwise, construct the object and apply it to this generic prefab
    }

    protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
    {
        // As an example, if you were adding additional custom JSON data with your NetworkObject's spawn message you would use a string
        var data = string.Empty;

        // Server always writes when sending spawn information (i.e. client synchronization or just newly spawned object)
        if (serializer.IsWriter)
        {
            // Get the unique data for this generic
            data = GetTemplateData<string>();
        }

        // Serialize/Deserialize (depending upon context)
        serializer.SerializeValue(ref data);

        // Client always reads when spawning locally
        if (serializer.IsReader)
        {
            // Set and apply the unique data of this generic
            SetTemplateData<string>(data);
        }

        base.OnSynchronize(ref serializer);
    }
}

So, the general idea is to use a "GenericPrefab" (or more than one depending upon what makes the most sense for your project) that will be configured by the server but is something that the client "already knows about" without having to know about any of the unique/specific attributes of the object itself. The unique information is sent by the server to the client using custom serialized data via the NetworkBehaviour.OnSynchronize method, which removes the need to have a unique GlobalObjetIdHash value for each spawned NetworkObject (i.e. they would all be the GenericPrefab's GlobalObjectIdHash).

Will post the example I have been putting together...might be a few days but the above is the general direction I am using...so you might look at the above and see if that makes sense to you and if it does then possibly prototype an implementation more specific to your project.

@Laumania
Copy link
Author

Laumania commented May 29, 2023

@NoelStephensUnity

Thanks for the updated links 👌

About your generic approach - it's nice information and something I might need for something else, but don't think it's what I'm after here.
In my video I might have included a bit much info and can see I'm not clear enough about my actual issue. Also I have thought more about it after and think I can simplify my question.

So I'll try a bit simpler now :D

...is the fact that the prefabs you are spawning aren't in the list of prefabs...which makes both of the above approaches not very helpful.

Actually I can count on all the prefabs being in the networkprefabs list on both host and client.

What I'm essentially asking is:

"How to do I make this work when I'm using custom scenemanagement?"
image
https://docs-multiplayer.unity3d.com/netcode/current/basics/scenemanagement/inscene-placed-networkobjects/#creating-in-scene-placed-network-prefab-instances

I know how to make a prefab, it's the part that replaces the in-scene prefab, with a dynamically spawned instance of the same prefab that is "network enabled" so it's synced.

:)

@NoelStephensUnity
Copy link
Contributor

NoelStephensUnity commented May 29, 2023

@Laumania
Ahhh,
I see now. So, you want to have the original network prefab registered and you want to create a hash value override entry in your prefab list for each in-scene placed NetworkObject. For each hash value override entry, you want to copy and paste the in-scene placed NetworkObject's GlobalObjectIdHash value and set the target network prefab to the original network prefab:
image
Notes:

  • You will want to pin the properties of your prefab list for easy access when setting up the hash value override entries.
  • I keep the unique in-scene placed hash value overrides under the original registered prefab for convenience.

Below you will find a small project, for reference purposes, that also provides you with a some in-scene placed parenting examples when scene management is disabled. With scene management disabled you have to handle parenting in-scene placed NetworkObjects yourself on the client side of things. The two examples I provided are in-scene placed NetworkObjects parented under:

  • A standard GameObject
    • You have to handle finding the correct GameObject (I used just the name, but look to the next example for an alternate way)
  • Another in-scene placed NetworkObject:
    • This uses OnSynchronize to pass the information from the server to the client to handle parenting

The caveat to this is only if you have an in-scene placed NetworkObject parented under a dynamically spawned NetworkObject. Under this scenario, since both are dynamically spawned on the client side, the parenting should be handled for you.

image

Here is the small example project for reference purposes:
Custom-SM-InScenePlaced.zip

Why the multiple registrations?

Since in-scene placed NetworkObjects are given unique GlobalObjectIdHash values (to distinguish them from other instances within the same scene) and since disabling scene management will make the server just simply provide the spawn information for all NetworkObjects as if they are dynamically spawned, you need to create the hash value override in order to provide the client with a "look up table" that links to the original network prefab. It isn't the greatest approach, but basically this is what scene management is handling internally (it builds its own version of a table to know "which in-scene placed NetworkObject" is being spawned). It is definitely an area we could improve upon, but that is basically "how to get clients spawning in-scene placed NetworkObjects" when scene management is disabled.

Let me know if you have any more questions about this.

Cheers! 👍

@Laumania
Copy link
Author

Laumania commented May 30, 2023

Hi @NoelStephensUnity

Ok, now we start talking - we are getting closer to what I'm after 👍.

My problem is that I cannot do the above upfront in the editor, as I don't know these things ahead of time.

So, simply put, how would you do what you just showed above, but in code?
Doesn't have to be a full working sample, just snippets of how to do the basic things, then I figure out how to put it together.

:)

@NoelStephensUnity
Copy link
Contributor

@Laumania
Based on what I know so far, the following pseudo component would need to be on each in-scene placed NetworkObject prior to the scene being loaded or prior to the connection approved message that contains all of the (serialized) NetworkObjects to spawn:

public class InSceneFireworkHandler : NetworkBehaviour
{
    [HideInInspector]
    [SerializeField]
    private GameObject m_SourcePrefab;

#if UNITY_EDITOR
    /// <summary>
    /// Get a reference to the source prefab asset
    /// </summary>
    private void OnValidate()
    {
        m_SourcePrefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(gameObject);
    }
#endif

    /// <summary>
    /// Synchronize Firework's Unique Settings
    /// Since the NetworkObject is being dynamically spawned, a new instance will
    /// be created which means the unique settings will no longer exist. This might
    /// be the best way to handle passing this information along to the client.
    /// </summary>
    protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
    {
        if (serializer.IsWriter)
        {
            // Server would set the unique firework configuration settings
            SetConfigurationSetttings();
        }

        serializer.SerializeNetworkSerializable(ref m_UniqueFireworkSettings);

        if (serializer.IsReader)
        {
            // Client would apply the unique firework configuration settings
            ApplyConfigurationSettings();
        }


        base.OnSynchronize(ref serializer);
    }

    /// <summary>
    /// Server side
    /// </summary>
    private void SetConfigurationSetttings()
    {
        // Add any pertinent/unique information about the in-scene placed firework object
        // to the UniqueFireworkSettings struct (as an example)
    }

    /// <summary>
    /// Client side
    /// </summary>
    private void ApplyConfigurationSettings()
    {
        // Apply the UniqueFireworkSettings information to the newly instantiated firework object
    }

    /// <summary>
    ///  The unique firework configuration settings (pseudo)
    /// </summary>
    private struct UniqueFireworkSettings : INetworkSerializable
    {
        public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
        {

        }
    }

    private UniqueFireworkSettings m_UniqueFireworkSettings = new UniqueFireworkSettings();

    /// <summary>
    /// Start is invoked during scene loading which happens prior to spawning.
    /// This is a good place to register your override entry for the in-scene
    /// placed NetworkObject's GlobalObjectIdHash value to the source prefab's
    /// GlobalObjectIdHash value.
    /// </summary>
    /// <remarks>
    /// If you can't have this component on each in-scene placed NetworkObject
    /// prior to the mod being loaded, then it needs to be added before the client
    /// receives the connection approved message (which contains the NetworkObjects
    /// to spawn).
    /// </remarks>
    private void Start()
    {
        // Add the override on the client side (using singleton because the instance is not yet spawned)
        if (m_SourcePrefab != null && !NetworkManager.Singleton.IsServer)
        {
            var instanceGlobalObjectIdHash = GetComponent<NetworkObject>().PrefabIdHash;
            var networkPrefab = new NetworkPrefab()
            {
                SourceHashToOverride = instanceGlobalObjectIdHash,
                OverridingTargetPrefab = m_SourcePrefab,
                Prefab = gameObject,
            };
            if (!NetworkManager.Singleton.NetworkConfig.Prefabs.Contains(gameObject))
            {
                NetworkManager.Singleton.NetworkConfig.Prefabs.Add(networkPrefab);
            }
        }
    }
}

There are two areas of focus:

  1. InSceneFireworkHandler.Start adds the prefab hash override to the client side at runtime. This does require the component to already be on the in-scene placed NetworkObject when created in the editor so it can set the source prefab.
  2. You will need to handle serializing the unique properties of the in-scene placed firework since the instance spawned on the client will be effectively a completely new instance (the original is deleted on the client side when the client receives the connection approved message).

The second area of focus I think will be the more complicated aspect of this since you are having to reconstruct the mod's unique configurations.

@Laumania
Copy link
Author

Hi @NoelStephensUnity

Now we are really talking! This looks very much like something I can use! 🤓

Really excited to give this a try - I'll try it out over the next few days and will get back.

@NoelStephensUnity
Copy link
Contributor

@Laumania
Good to hear! 👍
Also, I forgot to add that you would only need to synchronize the settings that are not already synchronized via NetworkVariables as those will still be synchronized. Additionally, you want to add the NetworkPrefab overrides prior to the connection approved message is received by the client to assure the client side has those when trying to instantiate the in-scene placed NetworkObjects.

@Laumania
Copy link
Author

Laumania commented May 31, 2023

@NoelStephensUnity Yeah I figured that. Right now I don't think I have a need to "sync" like that, as all is on the prefab itself and assume NetworkTransform, NetworkRigidbody and other NetworkVariables are synced out of the box as normally.

I cannot make sure all is in the networkprefab list on the client prior to connection, because as you might remember from my video, the first thing that happens after the connection is done is that the host tells the client what mods to install and load and what scene to load.
I think it would be ok, because I can make sure everything is prepared on the client side before actually loading the scene on the client.

But lets see what issues I run into - as said will look into it during this week I hope.

UPDATE (2023-06-15): Just wanted to let you know that I haven't forgot, just working on preparing something in my game for an event, so I will have to look into this a bit later. I will be back with an update when I have given this a try :)

@Laumania
Copy link
Author

Laumania commented Jul 12, 2023

@NoelStephensUnity I have a little time to test this out, but haven't gotten it to work yet. But I need to try some more stuff.

However, while I'm doing that, just a little question.

As I understand it, as soon as a client connection is approved, it sends all the networkobject ids to sync.
I think part of my problem is, that it's too early for my client to receive that data.
My connection/load flow looks something like this:

Client --> Attempt to connect --> Host
Client <-- Sends info about what mods to install and what custom map (from one of the mods to load) <-- Host
Client --> Download and install provided mods --> mod.io
Client --> Loads provided mods and the custom map
Client --> Tell Host all is loaded and it's ready to have it's player spawned --> Host
Host --> Spawns the playerprefab for the given Client

So its there a way to kind of postphone that sync to happen somehow or am I approaching this wrongly?

What I generally do right now to avoid stuff to be synced until client is ready is to have this (or a variation of this) on my NetworkObjects:

this.NetworkObject.CheckObjectVisibility += ((clientId) =>
            {
                var client = NetworkManager.Singleton.ConnectedClients[clientId];

                if (client == null || client.PlayerObject == null)
                    return false;

                return true;
            });

@Laumania
Copy link
Author

Laumania commented Sep 1, 2023

Hi @NoelStephensUnity

Finally I got back to look into this and have tried various things to make it work and I simply can't.

So I have made a simple sample that I hope you could maybe make work, or send to someone in Unity that can show how this is done. Because it's not working even in this super simple sample.
https://github.com/Laumania/ngo-inscene-networkobject-sample
If you/or another, could do a pull request or explain in here what I'm missing - I would be really happy. Then I could try and take that knowledge and make it work in my game, which is a bit more complex.

I made a little video too to show it:
https://youtu.be/xQnIrco8Sx0

Thanks in advance.

@Laumania
Copy link
Author

Laumania commented Sep 7, 2023

Hi @s-omeilia-unity

Could you maybe help out here, see my comment above - or is @NoelStephensUnity already on it?

Thanks in advance.

@Laumania
Copy link
Author

Hi @NoelStephensUnity

Any updates on this one?

@NoelStephensUnity
Copy link
Contributor

NoelStephensUnity commented Sep 21, 2023

@Laumania
Thank you for your patience and persistence on this! 👍

Getting your ngo-inscene-networkobject-sample to work

In regards to getting your project to work, with custom scene management you need to create overrides for your in-scene placed NetworkObjects:
image

This attached modified version of your repository (thank you for making that) gets your simple project working properly:
In-SceneCustomManagement.zip

Why do you need to do this?

With custom scene management, all NetworkObject instances are treated as if they are dynamically spawned. Since each in-scene placed NetworkObject has a unique GlobalObjectIdHash, you need to create the override to link the instance to the original prefab instance in order for it be dynamically spawned. Each client will actually delete any in-scene prefab instance before processing the list of NetworkObjects to be spawned (to avoid duplication).

Regarding GlobalObjectIdHash Generation & In-Scene Placed NetworkObjects:

If you created your prefab and created in-scene placed prefab instances before adding a NetworkObject to the source prefab itself, then that would cause an issue with the generation of the GlobalObjectIdHash value for the in-scene placed NetworkObject. This issue is resolved in PR-2707.

How the fix in PR-2707 works:

If you already have a bunch of in-scene placed prefab instances in one or more scenes prior to wanting to make the prefab a "network prefab" (i.e. add a NetworkObject to it), then after adding the NetworkObject you can right click on the NetworkObject of the source prefab and select "Refresh In-Scene Prefab Instances" that will basically:

  • First check the currently active/loaded scene for any changes to the GlobalObjectIdHash of in-scene placed network prefab instances.
    • If changes are detected, it marks the scene dirty, saves the scene, but leaves the currently active scene open
  • Next it will individually load (additively) all enabled scenes in the scenes in build list
  • If the loaded scene has any in-scene placed network prefab instances that had their GlobalObjectIdHash value change:
    • It marks the scene dirty and saves the scene
  • It then closes the currently additively loaded scene and moves on to the next scene in the scenes in build list until all scenes have been checked and updated (if needed).

I tested this using NGO's test project that has quite a few scenes in it and it is a pretty fast process (takes like ~1 second on an average PC).

How to avoid this issue until PR-2707 makes it into an update

If you create the prefab and add a NetworkObject instance to it prior to making the in-scene placed NetworkObject, then the GlobalObjectIdHash value for the in-scene placed prefab instance will be correct for each instance.

What I still have no good answer to:

Client --> Download and install provided mods --> mod.io
Client --> Loads provided mods and the custom map

Now, regarding the downloading of the mods... it depends on what is considered in-scene placed vs dynamically spawned. If a mod has existing prefab instances of a source prefab that is specific to the mod, then you would need to add prefab entries with hash overrides during runtime... but the unfortunate part of this is that adding a NetworkObject to a prefab or instance of a prefab during runtime will not generate a unique GlobalObjectIdHash value for that prefab since that needs to be done in edit mode.

@Laumania
Copy link
Author

Hi @NoelStephensUnity

Thanks for getting back - I have updated my sample project with your changes and now it works out of the box for me too. So far so good.

Now that the most basic version of this works - I will try and add another sample where I add these overrides dynamically, to get closer to the challenge I need to solve in my game.

I'm aware of the GlobalObjectIdHash issue, or a variant of it as I already ran into that in the beginning when I started adding multiplayer. So I have am following the issue on github about it too - however - due to the other "in scene networkobject" issues I was facing, I put that aside and focused on the scenes coming in via mods, which was oddly enough easier to get working - as they didn't have in scene networkobjects :P

About the last part of the downloading and loading of mods - lets not worry about that, because if I can get dynamic adding these overrides to work - then that isn't a problem as I already have that part working.

I'll get back when I have something that works - or run my head against a wall :D

@Laumania
Copy link
Author

Oh one more thing, that "NetworkManagerHelper" was actually only confusing, as it didn't add anything in relation to the issue - right? It was only to be able to start Host and Client - and you can really do that right from the NetworkManager in the editor.

@Laumania
Copy link
Author

Laumania commented Sep 21, 2023

Ok @NoelStephensUnity
Already I'm running into something that could end up being a problem.
I'm not even started on trying to do this dynamically.

As a test duplicated the in scene instance of the prefab. It got what looks to be a new unique GLobalHashId, so all looked good.
I add it to the override list and start the game. And right away I get this error:

NetworkPrefab ("CubeNetworkObjectPrefab") has a duplicate GlobalObjectIdHash target entry value of: 2155859350!
(2155859350 is the globalhashid of the prefab)

Pretty clear what the error is - however it doesn't really make sense that I cannot have more than one in scene networkobject og each prefab? That sounds pretty useless, so I'm sure I'm doing something wrong - but this just seemed so obvious to be able to do like this.

image

@NoelStephensUnity
Copy link
Contributor

@Laumania
I think there is a regression bug that I am working on fixing and publishing a PR for this.

@Laumania
Copy link
Author

@NoelStephensUnity Ok, yeah because if you cannot have more than one, it doesnt make sense in a lot of scenarios :)

@NoelStephensUnity
Copy link
Contributor

NoelStephensUnity commented Sep 22, 2023

@Laumania
Ok,
I am pointing this updated version of your sample project to a WIP branch that is going to remove some restrictions and (TBD) legacy code that I think no longer applies (still need to run a series of tests on this work).
Here is a project that has a slightly modified version of the InSceneFireworkHandler with some quick notes as to the "why" in certain places.
In-SceneCustomManagement-2.zip

image
I verified the 5 in-scene placed instances were synchronized by running the host in the editor and a client as a runtime build...then in the scene view just selected an instance and dragged it around by the transform gizmo tool.

Here are the NGO changes so far in the wip branch

The current requirements for these changes are:

  • All in-scene placed prefab instances should be based on existing registered network prefabs
    • This is temporary as there is another PR up that allows you to update (editor only) existing in-scene prefab instances of a prefab that originally did not have a NetworkObject component but you decide to add it after creating instances in several scenes.
    • You won't be able to add NetworkObject and NetworkBehaviour components to an in-scene prefab instance during runtime (i.e. the only real/recommended way to get a valid GlobalObjectIdHash value is if they are generated in the editor)
  • Any override target prefab must be registered on both the client and server side prior to registering any network prefabs with override types other than NetworkPrefabOverride.None.
  • I cannot guarantee that the GlobalObjectIdHash value updates will perfectly synchronize using parrel sync (sometimes the AssetDatabase between instances can become out of synch etc).
    • I can guarantee that under an editor and runtime build type of scenario this does work.

See if using those minor adjustments in that branch work out better for your project's needs?
Let me know if you run into any issues while "kicking the tires". 👍

@Laumania
Copy link
Author

Hi @NoelStephensUnity

Ok I got your sample working - but only when I build like the client and run the host in the editor. Guess this "Parallel Sync" issue have fooled me up until now :( Must see if I can find a fix for that, as it makes a lot easier to test using Parallel Sync.

This is temporary as there is another PR up that allows you to update (editor only) existing in-scene prefab instances of a prefab that originally did not have a NetworkObject component but you decide to add it after creating instances in several scenes.

Update of the GlobalObjectHashId for existing prefabs that were placed in a scene prior to getting a NetworkObject component added to their prefab (which is the case for all my in-scene I assume) I can use the work around for now I guess, as it's a "one time thing" I assume.

You won't be able to add NetworkObject and NetworkBehaviour components to an in-scene prefab instance during runtime (i.e. the only real/recommended way to get a valid GlobalObjectIdHash value is if they are generated in the editor)

For my game, it's ok to have in-scene prefabs instances added in the editor to a scene. So that's no problem.

You won't be able to add NetworkObject and NetworkBehaviour components to an in-scene prefab instance during runtime (i.e. the only real/recommended way to get a valid GlobalObjectIdHash value is if they are generated in the editor)

That shouldn't be a problem as far as I see right now.

Any override target prefab must be registered on both the client and server side prior to registering any network prefabs with override types other than NetworkPrefabOverride.None.

Just to be clear, you mean the "Original prefab" needs to be registered prior to dynamically adding the overrides for all the instances of that prefab in the scene - right?

Thank you for your help so far! Much much appreciated!

Next step for me is to see if I can make it work with Parallel Sync, to speed up things and then after do a simple sample in my own game to see it work there too 🤞

@Laumania
Copy link
Author

Hi @NoelStephensUnity

Ok I "kicked tires" on your latest sample here to attempt and figure out why it was not working in Parallel Sync - however as I see it, things go "wrong" before we even get to the Parallel Sync part.

In "Editor mode" all seems linked up correctly. The instance in the scene have a reference to the prefab (I removed the [HideInInspector] to see this in the editor). In general all is fine at this point.
image

Now lets enter Play Mode and nothing more. Not starting a host, not joining as client or anything. And right away I see a problem.
image
Now the instance in the scene, points back to itself, which result in the globalhashId to be the same for the instance and for the TargetPrefab (because the TargetPrefab is in PlayMode the instance itself).

The confusion is real :D

PS: I pushed a version with your changes to here: https://github.com/Laumania/ngo-inscene-networkobject-sample/tree/dynamic-sample

@NoelStephensUnity
Copy link
Contributor

NoelStephensUnity commented Sep 29, 2023

@Laumania
Yeah you can't make it public as it will point back to itself and will "override" the correct prefab set in the InSceneFireworkHandler.OnValidate method... and why that property can't be visible to the inspector.

Regarding Parrel Sync, you might try the Multiplayer Play Mode and see if you have better luck with that...

Otherwise if that isn't an option...like you mentioned in your merge of the changes, if you build the client it will work just fine but if you try it in parrel sync it possibly fails (I am guessing GlobalObjectIdHash issue?), which would make sense...for kicks and giggles...try updating the OnValidate method to this:

    private void OnValidate()
    {
        var originalSource = PrefabUtility.GetCorrespondingObjectFromOriginalSource(gameObject);
        if (originalSource != null)
        {
            TargetPrefab = originalSource;
            
            // If this is a prefab instance
            if (PrefabUtility.IsPartOfAnyPrefab(this))
            {
                // Mark the prefab instance as "dirty"
                PrefabUtility.RecordPrefabInstancePropertyModifications(this);
            }
        }
    }

See if that gets Parrel Sync to recognize the property update for the prefab instance?

@Laumania
Copy link
Author

@NoelStephensUnity

Ok fair enough, it needs to be hidden.

Regarding Parrel Sync, you might try the Multiplayer Play Mode and see if you have better luck with that...

I can't use that as I'm sticking to LTS right now, which is 2022.x and that Multiplayer Play Mode is 2023.x only it seems.

I tried something else...and even with the TargetPrefab being hidden and all as was in your sample, I still think it might be a "Unity/GlobalHash issue" at least I see this by just running the gam ein Play Mode.
image

So, I'm debugging out the globalhash value, as at edit time I can clearly see they are not the same. However, as soon as I enter play mode - bum they are the same. So "something" happens.

image

To my understanding, even in Editor these should continue to be different and if they were, I think this might actually work.
Because this happens in the Parallel Sync version too "on the client" and these global ids have to be different for it to register the override, and as they are the same, its not registered and therefore it doesn't work.

I tried adding this too, as an attempt to try and avoid some validate/editor stuff - but same result:
image

@Laumania
Copy link
Author

Ok tried something else...and I'm completely puzzled now... its really frustrating that something, I as a user of NGO shouldn't really think about, is such a huge issue.

Anyway, look at this:
image

And when I run it I get this in the console.
image

How can the TargetPrefab clearly be set to the right one, the actual prefab (2155859350) and then, without OnValidate being called again - it's suddently in Start() set to the globalHashid of the instance it self?

:)

My head is about to explode :D

@NoelStephensUnity
Copy link
Contributor

@Laumania
I went ahead and cloned your repository to see if I could get it working properly from your commit.
There were some adjustments that might have gotten lost in your original merge, so I forked your repo and created PR-1.

What I noticed was missing in your repository's main branch:

  • The InSceneFireworkHandler didn't seem to have the changes I had made, so I went ahead and added those.
  • The DefaultNetworkPrefabs asset only needs to include the original (source) network prefab (i.e. it does not need to be a prefab override as you create those at runtime in the InSceneFireworkHandler.Start method).
  • The manifest was still pointing to NGO v1.5.2 which this will only work with the changes made in the fix/prefab-override-hash-value branch.
  • I then created 4 more duplicates of the original in-scene placed NetworkObject (CubeNetworkObjectPrefab) and spaced them out from the center.

From there, this is what I did:

  • Created a stand alone build to test (since we don't support Parrel Sync officially I am testing with a stand alone build).
  • Started a host in the editor
  • Started an instance of the stand alone build and connected as a client
  • In the editor, switched to the scene view and selected one of the cubes and just dragged it around to make sure the client-side instances were spawned and "working as expected".

You might try merging that PR and seeing if you have better luck?
image
I validated all 5 of the in-scene placed NetworkObjects were spawned properly (moving each from their original position on the host/editor side).

@Laumania
Copy link
Author

Laumania commented Oct 2, 2023

@NoelStephensUnity
Cool! I'll take a look - did you try having the build version be the host and the editor the client?

@NoelStephensUnity
Copy link
Contributor

@Laumania
I see... 🤯
Let me look at that issue when the client is running in the editor.

@Laumania
Copy link
Author

Laumania commented Oct 2, 2023

@NoelStephensUnity Yeah "something" behaves differently in the Editor - but it's not a problem for the Host only the Client, why the client doesn't work running in Unity - which I think is also the reason Parallel Sync is not working (know you don't support it ;) )

@Laumania
Copy link
Author

Laumania commented Oct 3, 2023

@NoelStephensUnity As I wrote in the PR too, you missed the part where I wrote that all this "dynamic" stuff is pushed to another branch: https://github.com/Laumania/ngo-inscene-networkobject-sample/tree/dynamic-sample
This branch also points to your "hot fix" changes etc.

That version I have there works, but only if client is build and Editor is Host, but not the other way around as I also discovered.

@NoelStephensUnity
Copy link
Contributor

NoelStephensUnity commented Oct 3, 2023

@Laumania
Ok, I noticed your comment about the branch after I made additional changes to PR-1 so I will need to migrate the changes over.
There is definitely a "funky" thing that happens to the prefab GameObject reference from the editor side when entering into play mode (i.e. it ends up pointing to itself seemingly automatically). I haven't been able to track this down to the exact reason why this happens, but I am pretty sure this isn't an NGO specific thing.

Until I find another pocket of time to migrate the changes into the dynamic branch, take a look at the changes I made to the InSceneFireworkHandler. Since the "funky" issue seems to be editor specific, I went ahead and just stored the target prefab's GlobalObjectIdHash value (which is correct during OnValidate) and then within the Start method I just use the m_TargetGlobalObjectIdHash to find the GameObject of the already registered target network prefab and return the GameObject (Prefab).

This works with both the client or host running in the editor.

Some things you might want to think about when scene management is disabled:

  • The in-scene placed NetworkObjects are deleted prior to spawning their replacement clones from the host provided list of NetworkObjects to spawn.
    • This means if you disconnect the client and try to reconnect the client without reloading the scene with the in-scene placed NetworkObjects it will fail.
    • I provided one way to handle this by using additive scene loading (SceneManager not NetworkSceneManager) and separating the in-scene placed NetworkObjects from the SampleScene (in the In-SceneObjects scene).
  • The manual deleting of the in-scene placed NetworkObjects is only required when the client-side disconnects from its side and the active scene is not unloaded upon disconnecting (but figured it was worth pointing it out in this sample).

@Laumania
Copy link
Author

Laumania commented Oct 3, 2023

@NoelStephensUnity Oh thats cool I'll take a look.
I manually handle loading and unloading of scenes etc. so that shouldn't be a problem.
I know doing like this requires me to code more myself, and I have no problems with that as that also gives me more control.
My problem was I couldn't even get a simple sample to work as you saw :)

Looking forward to try out your sample and poke around :)

@Laumania
Copy link
Author

Laumania commented Oct 4, 2023

@NoelStephensUnity Thanks! Now it seems to work - and funny enough (as I said) its working in Parallel Sync now too :D

I'll commit to my branch https://github.com/Laumania/ngo-inscene-networkobject-sample/tree/dynamic-sample

I'll poke a bit more around and see if it holds up.

Thank you very very much so far :)

@NoelStephensUnity
Copy link
Contributor

NoelStephensUnity commented Oct 4, 2023

@Laumania
Glad to hear it!
I will get a PR together for the the branch with the fix. It won't make it in the coming v1.7.0 update, but it will definitely make it in the next update. 👍

(That PR will include a link to the documentation updates for custom scene management that will use bunch of the discovery in this thread as reference. Hopefully that will help any other users that want to handle scene management themselves.)

@Laumania
Copy link
Author

Laumania commented Oct 5, 2023

@NoelStephensUnity No worries with the fix not being there in 1.7.0 - I can use your patched version while poking around with this in my actual game and I'm sure 1.8.0 will be out before I have something to show and test with players anyway.

I'll get back if I run into issues related to this.

However, as the original issue said, I really think you should add a sample like this to the documentation, so others have an easier change to figure it out without having to be in direct contact with you like I have been here :)

@NoelStephensUnity
Copy link
Contributor

@Laumania

However, as the original issue said, I really think you should add a sample like this to the documentation, so others have an easier change to figure it out without having to be in direct contact with you like I have been here :)

Yep! That will be part of the updated documentation that provides:

  • Additional guidance on what you have to handle yourself vs what the integrated scene management handles for you.
  • Definitely a section covering in-scene placed NetworkObjects
    • This will include some examples from simple to advanced (similar to your InSceneFireworkHandler without the added serialization and automatically moves the objects around a bit...or something like that to show "everything is working as expected")
  • Scene management tips/suggestions
    • i.e. Possibly use a combination of custom messaging and NetworkVariables to maintain loaded scene states

Stuff like that...

@NoelStephensUnity
Copy link
Contributor

@Laumania
Finally got around to being able to update PR-2710 a bit more.
There is a new method, NetworkSpawnManager.InstantiateAndSpawn, that makes spawning a prefab less complicated (especially if you have overrides).
Should be done with tests by tomorrow and this should be out in the next minor version release of NGO!
👍

@Laumania
Copy link
Author

@NoelStephensUnity Oh that's nice! Looking forward try it out when it's out :)

@NoelStephensUnity
Copy link
Contributor

@Laumania
So I went ahead and added some additional functionality to help reduce the painful process of having to register each in-scene placed NetworkObject when scene management is disabled. :godmode:

Using the same branch of PR-2710 unless you want to specify a unique network prefab for your in-scene placed fireworks, you no longer have to handle creating the overrides as this is now automatically handled for you when scene management is disabled. Of course, if you want to dynamically change the final prefab you could still use the same scripting approach in your InSceneFireworkHandler but as far as your sample project is setup it is no longer needed.

To validate this with the way your project is currently setup and using the most recent updates to PR-2710 you should be able to:

  • Remove/Comment out all of the OnValidate code.
  • Remove/Comment out all of the Start code.

Then take it for a spin. The client side should "automatically" know what source prefab asset it needs to spawn for each in-scene placed prefab instance. 👍

As well, you can also still register the same prefab with an actual override (for dynamic spawning) which will yield the following behavior:

  • If you drop an instance of the overridden prefab (the original not the overriding target) into your scene those will still be spawned as the original and not the override defined in your NetworkPrefabList.
  • If you dynamically spawn the overridden prefab (recommend using NetworkSpawnManager.InstantiateAndSpawn) it will result in the overridden prefab getting spawned (both on host and client).

I figured someone might want to still be able to have an in-scene placed instance of a prefab while also being able to dynamically spawn that same instance but with an override applied.

Note: The one thing that is not supported by NGO when scene management is disabled is in-scene defined NetworkObject instances (i.e. not a network prefab instance). We might support that kind of functionality in the future, but there is a large chunk of code in NetworkSceneManager, when scene management is enabled, that handles synchronizing in-scene defined NetworkObjects and there are a lot of "opinionated" decisions that had to be made in order to get all of that working smoothly.

The PR is pretty much finished other than the documentation updates I need to apply, but it should make it out into the next minor version update of NGO (i.e. v1.8.0).

Let me know if you run into any issues with this update, but I think this should make life much easier for anyone wanting to handle scene synchronization themselves.

@Laumania
Copy link
Author

@NoelStephensUnity THIS IS AWESOME! Can't wait to try it out :)
However, it won't be until after 2nd of December, as I'm currently spending all my game dev time on getting ready for opening up to the public to join the multiplayer beta version of my game :)

I'll get back once I have tested it.

@brainwipe
Copy link

This is the very best of open source developing out in the open. I really like the non-scene management solution above. Great job.

@gord0
Copy link

gord0 commented Jan 23, 2024

I'm seeing in the NGO version drop down on the documentation page it goes up to v1.8.0, but I'm only seeing up to 1.7.1 available in package manager. I'm using Unity 2022.3.6f1 .

@jabbacakes
Copy link
Collaborator

Hey @NoelStephensUnity am I right in thinking we can close this issue now that changes to both docs and code have been delivered?

@Laumania
Copy link
Author

@jabbacakes As the creator of this issue, I would say it sounds like you have done what I was asking - however I sadly haven't had the chance yet to try it out. Hopefully I will soon.

If you could keep this open for a few weeks more I would be happy.
Or do you want me to create an new issue in case I find something that doesn't work as expected?

@jabbacakes
Copy link
Collaborator

Nah, if it's relevant to still keep it open, then we can ^_^ I'm just having a tidy up of old issues/PRs etc. in the repo.

Let us know how you get on with the new features!

@Laumania
Copy link
Author

@jabbacakes Cool! Yeah I'll for sure tag you, or simple just close the issue once I have tested the changes out.
Thanks! - Maybe you have a tag you can give this issue, so you guys are not wasting time looking into it as it's on myside in the current state.

@jabbacakes jabbacakes added no docs required (yet) Issues that don't currently need docs work but can't be closed yet and removed docs: content addition to do labels Apr 11, 2024
@Laumania
Copy link
Author

Hi @jabbacakes

So I'm getting back to this and are looking into it. Upgraded to NGO 1.8.1 and I see a lot have changed and made things easier.

Just dragging in a prefab (that have NetworkObject on it) and then load scene dynamically (custom scene management turned off) works fine - perfect!

However, if you turn custom scene management off, you do it for a reason. In my case I load dynamic scenes(maps) via mods.

Therefore, I would like to add all NetworkObject prefabs i can find in the scene, dynamically to the prefablist.

In the documentation it says:

Once you've registered your in-scene placed Network Prefabs with your NetworkPrefabList and assigned that to your NetworkManager...
https://docs-multiplayer.unity3d.com/netcode/1.8.1/basics/scenemanagement/custom-management/

I would suggest you add a code sample, just to give programmers an idea what methods we are playing with here.

What I'm doing now in my game is this, and it's now working:

var inSceneNetworkObjects = GameObject.FindObjectsByType<NetworkObject>(FindObjectsSortMode.None);
if (inSceneNetworkObjects.Length > 0)
{
    Messenger.Broadcast(new MessengerEventLoadSceneProgress($"Populating {inSceneNetworkObjects.Length} in-scene network prefabs...", 0.7f));
    await UniTask.Yield(token);

    foreach (var networkObject in inSceneNetworkObjects)
    {
        if (networkObject != null && NetworkManager.Singleton.NetworkConfig.Prefabs.Contains(networkObject.gameObject) == false)
        {
            NetworkManager.Singleton.AddNetworkPrefab(networkObject.gameObject);
            //Debug.Log($"Added In-Scene GameObject '{networkObject.gameObject.name}' to NetworkManager prefabs");
        }
    }
}

Some code to help devs in the right direction would be awesome there :)

@Laumania
Copy link
Author

Laumania commented May 24, 2024

Just a little note. The above code sample I provided and said working, is not correct. Well, yes it was working, but it was not due to the above code, because what that does it just add all the in-scene network objects to the NetworkManagers Prefab list - and that's not gonna work on the client, as all of these are destroyed upon joining a host.

The reason however its working, is because NGO now automatically adds prefabs that have NetworkObject component on it, to the DefaultNetworkPrefabs asset.
image

Here you need to add references to the actual prefab assets, that are instanced and placed in the scene.
image

So this seems to be working fine now - which I nice.

My actual scenario is a bit more advanced, as I need to add prefabs dynamically based on loaded mods etc. but I seem to have gotten it to work now :)

@Laumania
Copy link
Author

@jabbacakes Ok we can close this now, as it seems to be working :)

@jabbacakes
Copy link
Collaborator

Thanks @Laumania. It's a pleasure to work with engaged community members like yourself. ^_^

@Laumania
Copy link
Author

Likewise it's a pleasure working with guys like you @jabbacakes and @NoelStephensUnity.
Making Unity better is a win for all of us :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no docs required (yet) Issues that don't currently need docs work but can't be closed yet
Projects
None yet
Development

No branches or pull requests

6 participants