Author Topic: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles  (Read 8140 times)

Falagard

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 5
    • View Profile
Hi, sorry if these have been answered before, I tried searching and reading existing posts.

I'm building a game where the player moves from scene to scene as they move through the overall game world. Each scene will be obviously be a different channel.

The vast majority of objects in a scene won't be network aware, but some objects in a scene need to be networked because they have states such as a treasure chest which might be opened or closed. I'm planning on using Asset Bundles.

1. My assumption is that TNObject IDs should be unique across all channels, so the best way of handling my situation rather than assigning manual IDs is instantiating the network aware prefabs using something like the AutoCreate script so their IDs are automatically generated. That way in the editor I can place a treasure chest, add a script, and not worry about it. Does this sound like the right way to handle it?

2. Each player will obviously be a networked object and will also have state. For example, a player may have their color changed to red through a saved RFC, which will set an internal variable called "color" to "red". If the current player leaves a scene and joins another scene, what process should I use to keep that state?

My assumption is that saved RFCs are tied to a channel and that the player's TNObject should be non persistent, so it will be destroyed as I leave one channel and join the next. For this reason I should store state information (color = red) separately, join a different channel, then dynamically create a new object, and call a saved RPC on it to set its state again (or perhaps use parameters passed to the Create function - I need to investigate). Does this sound correct?

3. This might be a tricky question since not many people have a good grasp of Asset Bundles, but here's my plan.

I can't use the Resources folder to load objects, and I also can't add all prefabs to TNManager.objects list in the start scene.

I'm using Asset Bundles to be able to add new content without patching the game. Instead of adding all possible prefabs to the TNManager's objects list, I'm hoping to dynamically add and remove objects from this list before I attempt to create an object using TNManager.Create.

For asset bundles, I'll modify TNManager so instead of calling Application.LoadLevelAsync in LoadLevelCoroutine it calls a delegate to a different function I'll create which will download the necessary asset bundles then calls Application.LoadLevelAsync when they're ready.

Before the current player joins a scene, I'll call TNManager.LeaveChannel (if in a channel) to leave the current channel, and empty the TNManager.objects array, load a staging level which will which shows loading progress bars, etc. then call TNManager.JoinScene, updates progress bars downloading asset bundles and loading the level, etc. and finally the level is loaded via the modified TNManager LoadLevelCoroutine.

The scene will have its own list of prefabs that need to be dynamically instanced by the TNManager, so I'll create something like a TNSceneManager with its own objects list, that is only within that scene (destroyed when the scene is unloaded). The TNSceneManager Start it will set the TNManager's objects list to the same values as those on the TNSceneManager so any calls to TNManager.Create will subsequently use this prefab list.

Since the RequestCreate/ResponseCreate send the index into the TNSceneManager's object list, and they will always be sent within a channel, I don't think this will cause a problem.

I'll delay the creation of dynamic TNManager.Create'd objects until after the TNManager's objects are set properly for the current scene.

Am I crazy?

Thanks,
Clay







ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #1 on: December 10, 2015, 03:29:07 PM »
1. If your scenes are created by hand, you will have to have unique TNObject IDs on your objects that are present in the scene. TNet will warn you if you don't have unique IDs. If your objects are instantiated at run-time, then you don't need to worry about it as TNet will take care of assigning IDs at the time of instantiation as long as you use TNManager.Create.

2. Storing this data on the player is a bad idea. Each player should be a dynamically instantiated object (TNManager.Create) set to not be persistent (disappears when the player disconnects). If you need to store persistent data, store it alongside player's data -- for example:
  1. TNManager.playerDataNode.Set("color", Color.red);
  2. TNManager.SyncPlayerData();
You can save the entire DataNode as a file (even as text if you like) via TNManager.playerDataNode.Write, and read it via DataNode.Read. You can even save this on the server via TNManager.SaveFile and load it via TNManager.LoadFile. Load the player file as soon as you connect to the server, parse it as a DataNode and set TNManager.playerDataNode, then sync the player data. There you go, persistent player information.

3. Asset bundles are a very bad idea as they differ with each platform. Instead, again use DataNode. DataNode is fully capable of serializing entire game object hierarchies -- complete with all the necessary resources like textures and materials. It will even serialize meshes for you. If the referenced resources are in the Resources folder, it will save references to them instead so you can control which things will get bundled and which will simply be referenced. DataNode is a one-stop solution. TNet is fully capable of loading these DataNode-exported prefabs natively as if they were just regular prefabs, which makes this perfect for adding modding support to your games. Check this video I made for Windward for an example: https://www.youtube.com/watch?v=UE40pLt1VLw

NOTE: You will need the upcoming version of TNet for this however as not all functionality is there in the Asset Store version. You can drop me an email (to support [at] tasharen.com) with your OR# and I'll send you a pre-release version.

Falagard

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 5
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #2 on: December 10, 2015, 04:33:11 PM »
Thanks for the answers ArenMook.

1. My scenes are created by hand in the editor, however I'll be using something similar to the AutoCreate script meaning that they will be instantiated dyamically. You didn't really answer my question here about TNObject IDs being unique across all channels, but I dug into the code and determined they're unique within a channel.

2. Ah thanks I wasn't aware of playerDataNode, I've taken a look at it now. Am I correct in assuming that every player in a channel has all other player's data synced as well? So for example, I can get some other player's data using playerID?

3. "Asset bundles are a very bad idea as they differ with each platform."

While I don't like all the decisions Unity made with Asset Bundles and I'll likely hit some major problems down the road with them, I don't think that the biggest problem is that they differ for each platform. To me that's pretty minor as I can simply build bundles for every platform and then based on the platform download the platform specific bundles. The biggest problem is integrating with assets such as TNet that assumes I'm not going to use Asset Bundles :-)

I may take a look at using DataNode, but I doubt I'll use it. I've investigated writing my own scene serializer, as well as using existing ones (Easy Save 2, etc.), and thrown out the idea due to complications. A simple example is saving any custom types of objects, maintaining references between instances, or even something as simple as NavMesh data that I use in my levels for pathfinding, or Lightmaps (which I don't use but it's another example of potential problems). I could change my workflow to use different libraries such as Aron Grenberg's AStar, or work around the problems but why should I if I can use the existing .scene format with Asset Bundles instead?

So, assuming I need to use Asset Bundles, does the solution I presented make sense?

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #3 on: December 10, 2015, 10:57:31 PM »
2. That is correct. All players have access to each other's entire Player Data.

3. Well with TNet it's simple. Select the root game object of whatever you want to export in the scene, Assets -> DataNode -> Export Selected, and choose whether to export it as text or binary (or if you have LZMA -- compressed as well -- get C# from http://www.7-zip.org/sdk.html and define "LZMA"). DataNode export will keep references for you -- assuming they are references to a sub-object or a prefab -- just like any Unity prefab would. As a rule of thumb, if you want something referenced like a material or texture, keep it in the Resources folder somewhere. If you want it to be embedded, move it somewhere outside the Resources.

Main problem with asset bundles for custom content from what I recall was them embedding certain things that wasn't always desired, then instantiating extra copies of loaded content (shader code for example, possibly textures) resulting in multiples of the same object when you import different bundles. For example you have 5 different buildings, all sharing the same material / texture. With DataNode when you do an export, it will export the material and texture (assuming they are not in Resources), but when loading it will check -- does this texture and material already exist? If yes, just use those instances.

DataNode will automatically save all custom scripts. You don't need to write any serialization for them. If Unity can save it, so can DataNode.

Falagard

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 5
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #4 on: December 11, 2015, 11:06:23 AM »
It's not that simple.

The whole purpose of Asset Bundles is to update game content without patching the game. I could simply patch the game frequently to add new assets to the Resources folder, or embedded in the game data, but I'm trying to avoid that so I can potentially target non-patchable platforms like mobile. My whole architecture is based on the ability to have game logic in assets using FSMs/Behavior Trees, so I can include them in Asset Bundles and update actual game logic without patching the game executable.

I wouldn't want to embed binary assets, such as Meshes, Textures, Sounds, Particle Systems, etc. in an external scene format like NodeData or any other system, I'd prefer to reference them using prefabs or paths to the resource folder, like you mentioned, but I can't update the resources folder without patching the game.

You are correct that there is a problem with embedding shaders, but it's on Unity's list of things to fix as per this thread:

http://forum.unity3d.com/threads/loading-assets-from-assetbundles-makes-assets-appear-pink-in-editor.326541/

Apparently the problem is that shaders get precompiled per platform, and it is loading the wrong shader in the editor. I think this problem will go away soon due to shaders no longer being precompiled and instead the shader source will be included and compiled dynamically on each platform.

As far as assets referencing assets in other bundles, my understanding is that as long as each asset belongs to an asset bundle, a reference and dependency is used instead. For example, a texture in bundle A is used in a material on a mesh in bundle B, the second bundle will not embed the texture but instead have a reference to the texture in bundle a and there is then a dependency to bundle A added to bundle B, such that when bundle B is downloaded it also downloads bundle A. The system should work like this, and if it doesn't then it's a bug and will eventually get fixed, hopefully long before I launch my game.

So, can we leave that alone for now and answer my questions about how to work within the constraints of my game architecture?

Can I dynamically add and remove prefabs from the TNManager.objects list for each channel I join? So, before joining a channel, I populate the objects list with one that is specific to the scene that will be loaded?

I think the answer is yes, with caveats that if the object list changes that any TNManager.Create'd objects already in the channel will have problems, so I'll avoid that.

I also believe I'll be able to integrate the AssetBundleManager.LoadLevelAsync call to replace the Application.LoadLevelAsync call within TNManager.

Thanks, and hopefully I don't sound too confrontational. I really like TNet's design, I bought it quite a while back and have been basing my networking system on it from a design perspective, so now I just have to go in and start implementing it.

To be clear, I'm making a persistent multiplayer world, and TNet's channels, saved RFCs, etc. are all very elegant for handling a non authoritative multiple scene based world. Most of my logic executes on the client, and the idea of having a semi-authoritative host is perfect for what I need. Much better than other networking systems because of how simple yet powerful it is. I'm using my own system for persistence, using REST calls to a web server to query and store persistent data and also perform some of the more secure logic like applying experience points, leveling up characters, and giving out loot to the player, and am also using socket.io for sending messages back to the client from the web server and for chat.

I should also say that I've been lurking for a while, and some time back you mentioned the idea of having sub channels within a channel that would allow you to join multiple sub channels to segregate messages. I can't remember the exact terminology you used, but the idea is that as you walk around in a scene, you could be joining and leaving channels based on your location so you only receive messages from objects within your vicinity. Now, to me this sounds like a great idea but I'm not sure how you'd handle "host" migration because at the moment I think you base the migration on the assumption that every player has all data synchronized, so a client can take over as "host" at any time. Anyhow, the concept sounded great - any progress on this?

My game wouldn't be possible without TNet, so thanks!
« Last Edit: December 11, 2015, 11:26:57 AM by Falagard »

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #5 on: December 11, 2015, 01:50:43 PM »
The whole purpose of Asset Bundles is to update game content without patching the game. I could simply patch the game frequently to add new assets to the Resources folder, or embedded in the game data, but I'm trying to avoid that so I can potentially target non-patchable platforms like mobile. My whole architecture is based on the ability to have game logic in assets using FSMs/Behavior Trees, so I can include them in Asset Bundles and update actual game logic without patching the game executable.
Yes, I understood that. That's exactly what DataNode was designed for. If you watch that vid I linked, I add a new playable vehicle to the game without patching the game itself. The exported file is what you want it to be -- be it an entire model with all the necessary assets or the entire scene.

Quote
As far as assets referencing assets in other bundles, my understanding is that as long as each asset belongs to an asset bundle, a reference and dependency is used instead. For example, a texture in bundle A is used in a material on a mesh in bundle B, the second bundle will not embed the texture but instead have a reference to the texture in bundle a and there is then a dependency to bundle A added to bundle B, such that when bundle B is downloaded it also downloads bundle A. The system should work like this, and if it doesn't then it's a bug and will eventually get fixed, hopefully long before I launch my game.
That sounds like it will get hairy quickly... bundles referencing other bundles? What happens when you update bundle A forgetting about all the other bundles that depend on it? Better design is to have self-contained modules that have everything they need inside them, and when loading check to see if the asset within has already been loaded from somewhere else, and if so -- simply skip loading it and reuse the existing one (which is what DataNode serialization does).

Quote
Can I dynamically add and remove prefabs from the TNManager.objects list for each channel I join? So, before joining a channel, I populate the objects list with one that is specific to the scene that will be loaded?
The objects list isn't needed, and is only there as a legacy functionality. Just use the resource paths as if you were loading stuff via Resources.Load. TNet is designed in such a way that you can even place external assets (think My Documents/Your Game/Prefabs or something similar) and TNet will load them the same as if they were a part of the Resources folder. This includes textures (.png), text assets (.txt), binary assets (.bytes) and of course DataNode exports.

Quote
I should also say that I've been lurking for a while, and some time back you mentioned the idea of having sub channels within a channel that would allow you to join multiple sub channels to segregate messages. I can't remember the exact terminology you used, but the idea is that as you walk around in a scene, you could be joining and leaving channels based on your location so you only receive messages from objects within your vicinity. Now, to me this sounds like a great idea but I'm not sure how you'd handle "host" migration because at the moment I think you base the migration on the assumption that every player has all data synchronized, so a client can take over as "host" at any time. Anyhow, the concept sounded great - any progress on this?
It's on the TODO list for the near future as I need this functionality for my next game. I expect it to be done within the next 2 months.

Falagard

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 5
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #6 on: December 11, 2015, 03:45:49 PM »
Quote
Yes, I understood that. That's exactly what DataNode was designed for. If you watch that vid I linked, I add a new playable vehicle to the game without patching the game itself. The exported file is what you want it to be -- be it an entire model with all the necessary assets or the entire scene.

I've watched the video, it looked very good, but as I've explained it is unfortunately not what I need.

Quote
That sounds like it will get hairy quickly... bundles referencing other bundles? What happens when you update bundle A forgetting about all the other bundles that depend on it? Better design is to have self-contained modules that have everything they need inside them, and when loading check to see if the asset within has already been loaded from somewhere else, and if so -- simply skip loading it and reuse the existing one (which is what DataNode serialization does).

The basic idea is that you rebuild all your asset bundles any time you want to deploy changes. The asset bundle build system only rebuilds bundles that have changed, and keeps track of dependencies. You re-upload your asset bundles to the server. There is no "forgetting" bundles that depend on other bundles, as they rebuild themselves as necessary and keep track of versions. This is all built into Unity's new Asset Bundle system.

In my system (Unity's asset bundle system actually), the scene I'm loading only ever has serialized data with references to assets, not the assets embedded themselves. Each scene is in an asset bundle itself that only contains the scene file and no additional assets. To reiterate, there's an asset bundle per scene that only includes the scene. It should be the size of the scene file itself and nothing else, but it also contains metadata that indicates what other asset bundles it depends on.

When I request to load a scene, the asset bundle manager checks locally to see if it has already downloaded the scene's asset bundle. If not, it fetches it from the server. The scene asset bundle has a list of dependent asset bundles based on which assets were used in the scene. It then downloads those asset bundles as well if they don't already exist locally.

After all dependent asset bundles have been downloaded, it loads the scene.

This means the scene file itself doesn't have meshes, textures, etc. in it.

Now, if I was going to go the route of using my own asset loading system, I would do what I've done in the past for a C++ game I wrote, which is a similar two step process. The custom scene file would only have references, it would be downloaded, then each reference would be checked to see if it exists locally and if not, it would be downloaded.

I definitely would not embed assets in the custom scene file itself.

Long story short, Unity already has the system I want and I'm going to use it. It might bite me in the ass, we'll see.

Quote
The objects list isn't needed, and is only there as a legacy functionality. Just use the resource paths as if you were loading stuff via Resources.Load. TNet is designed in such a way that you can even place external assets (think My Documents/Your Game/Prefabs or something similar) and TNet will load them the same as if they were a part of the Resources folder. This includes textures (.png), text assets (.txt), binary assets (.bytes) and of course DataNode exports.

I personally can't load data using Resources.Load and need to continue using the Objects list or something similar. I could perhaps modify TNet to support loading from asset bundles directly though via an asset reference. Though in the meantime I hope you don't remove the legacy functionality.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #7 on: December 11, 2015, 05:27:38 PM »
You can certainly expand TNet to load data from custom sources. Just change the resource loading functions in TNet.UnityTools -- LoadBinary, LoadResource, LoadResourceEx. They're all delegates you can set to your own custom functions. So are UnityTools.GetType / GetTypeEx for that matter.

devomage

  • Sr. Member
  • ****
  • Thank You
  • -Given: 7
  • -Receive: 67
  • Posts: 250
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #8 on: December 11, 2015, 06:56:51 PM »
It's on the TODO list for the near future as I need this functionality for my next game. I expect it to be done within the next 2 months.

along the same thought:  subchannels within a channel.  it would be great if there was a player list available for each channel.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #9 on: December 11, 2015, 08:27:13 PM »
You have a list of players in the channel you're in via TNManager.players. In Windward when I want to do a /who on the channel, I simply call a broadcast RFC passing the ID of the channel. Clients check to see if the ID matches their channel, and if so -- send a response. Responses populate the list of players.

devomage

  • Sr. Member
  • ****
  • Thank You
  • -Given: 7
  • -Receive: 67
  • Posts: 250
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #10 on: December 11, 2015, 08:49:54 PM »
You have a list of players in the channel you're in via TNManager.players. In Windward when I want to do a /who on the channel, I simply call a broadcast RFC passing the ID of the channel. Clients check to see if the ID matches their channel, and if so -- send a response. Responses populate the list of players.

yep - per several other similar threads ;p

still would be nice to have it dealt with.  for example:  clients that have high latency would delay immediate results.

Falagard

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 5
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #11 on: December 15, 2015, 10:39:05 AM »
Hi ArenMook,

Out of curiosity, does the new version of TNet support asynchronous loading of resources?

For example, instead of using Resources.Load, use Resources.LoadAsync?

If not, I may have to modify the source to support it.

With the existing code I have, it would involve making TNManager.OnCreateObject start a coroutine to execute its functionality, and then also make the onLoadGameObject delegate in TNManager return an IEnumerator so it can be called as a coroutine.

*Edit*

Yikes, it's worse than that, TNManager.objectOwnerID is being used as a sort of global loading variable to the owner id of the object currently being created and referenced from TNObject.Awake to set the object's owner, which assumes that Create is being called synchronously. I'll need to investigate more.

Couldn't I just make objectOwnerID have a setter, and then from the CreateGameObject function set the owner ID after it is created?

*End Edit*

I'd also have to make a delegate for OnLoadLevelCoroutine so I could assign my own level loading code.

And then I'd have to be change the code to load from asset bundles asynchronously for both level load and asset loading. But I'm guessing things have changed in your latest version since you mention UnityTools LoadBinary, LoadResource, etc. which I don't have in my version of TNet.

When is the latest version coming out?

Thanks!

« Last Edit: December 15, 2015, 11:13:45 AM by Falagard »

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #12 on: December 15, 2015, 07:38:54 PM »
TNManager.objectOwnerID is gone in TNet 3, replaced by TNManager.currentObjectOwner. It was used as a variable set prior to game object instantiation so that TNObject's ID can be set in its Awake() function. It's not something you will be modifying in any case. TNObject IDs for instantiated objects are dynamically assigned. You only get to choose IDs for static objects (objects existing in the scene that don't get instantiated via TNManager.Create).

Some things have to be loaded at once while others can be delayed. In Windward whenever I upload something to the server -- such as an exported DataNode containing a new ship -- its MD5 hash will be added to the server config under "Uploads" section. When new players connect they receive the server config and check their own files against the Uploads section. If there is a MD5 mismatch (or the file doesn't exist), this file gets downloaded. Only once all the files have been downloaded can the player actually play the game.

Some other things like textures are streamed automatically. When loading a texture a dummy texture is created and the download process is started by calling TNManager.LoadFile on the server. When the file loads the callback function is executed, updating the existing texture's data, thus replacing an existing texture.

I suggest you do something similar -- when connected to the server, download all the necessary bundles first before proceeding.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Dynamic Object IDs, changing channels and maintaining state, Asset Bundles
« Reply #13 on: December 15, 2015, 07:42:25 PM »
As for when it's coming... I'm still working on the features. I just added support for simultaneous multiple channel subscriptions which involved breaking backwards compatibility as many functions such as LeaveChannel now require a parameter with the channel's ID. You can request a preview of the coming version by emailing support [at] tasharen.com with your OR#.