Search Unity

Unity Multiplayer UNET: Saving & Networking Character Data

Discussion in 'Connected Games' started by Zullar, Oct 17, 2016.

  1. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    621
    I am working on a top-down ARPG using UNET. Here's an example of the inventory.


    Your hero character's data is saved locally on your hardDrive. When you connect to a host it passes the host your heroData and which spawns your hero on the server. Then the information is sent back to the original client via UNET NetworkBehaviour scripts. Doing this seems to work OK, but it got pretty complicated and I'm checking here to see if there's a cleaner way. Here's the method I'm using.

    Client Menu: Selecting/Creating Hero

    1: Skim all files on hardDrive in "HeroData" folder.
    2: Deserialize each file and extract string heroName from the file
    3: Populate GUI list of heroNames
    4: Select a hero from the GUI list

    Client Connect to Server & Spawn Player
    1: CLIENT: MessageHeroData is created which contains byte[] byteArray of all heroData
    2: CLIENT: During NetworkManager.OnClientConnect the client calls ClientScene.AddPlayer(conn, 0, messageHeroData)
    3: SERVER: NetworkManager.OnServerAddPlayer with additional networkMessage is received
    4: SERVER: messageHeroData with byte[] byteArray is read
    5: SERVER: Hero gameObject is instantiated locally
    6: SERVER: byteArray from messageHeroData is deserialized and applied to hero scripts.
    7: SERVER: Hero gameObject is spawned to clients using NetworkServer.AddPlayerForConnection(...)
    8: SERVER: The heroObject is completely serialized using OnSerialize(initialState == true)
    9: CLIENT: HeroObject is spawned and OnDeserialize(initialState == true) is called for each script

    Runtime Incremental Updates
    1: All incremental changes are sent using NetworkMessages (In the future I may be able to use OnSerialize/Deserialize but cant at the moment due to various UNET bugs)

    Saving Data
    1: SERVER: Call Save(). This completely serializes the heroGameObject into a byte[] byteArray
    2: SERVER: Create a networkMessage messageHeroData containing byte[] byteArray. Send to client owner.
    3: CLIENT: Receive and read messageHeroData. Write byte[] byteArray to hardDrive.

    Does this seem like the proper way to do things? It seems to work, but seems really complicated.

    One Issue I have is a chicken & egg scenario. How to create a new hero data file? I need the heroData file to initialize a hero gameObject... but I need a hero gameObject to generate the data file! Which one do I make first?! Not sure the best way to handle this. What I've done is created a limited data file when making a new hero. Then during gameObject initialization I let scripts know they do not have any data and then the script can initialize itself w/o data (i.e. inventory adds starting equipment).

    Thanks in advance.
     
  2. Whippets

    Whippets

    Joined:
    Feb 28, 2013
    Posts:
    1,775
    If you store the character data locally on the player's machine and send it to the server when the player logs in; that could leave the door open to people changing their character data, and/or sending the server almost anything.
     
    Zullar likes this.
  3. wobes

    wobes

    Joined:
    Mar 9, 2013
    Posts:
    738
    You can easily save data on SERVER, if you saving it on CLIENT it can be hacked, if you Save data on SERVER, like Position, rotation, inventory items, then you can easily put it on method OnServerAddPlayer, and load on server position and rotation, then use RPC or TargetRPC for giving the client Items from inventory, I'm already implement it on my project. Try use Binary Serialization.
     
    Zullar likes this.
  4. Whippets

    Whippets

    Joined:
    Feb 28, 2013
    Posts:
    1,775
    You're using uNet, so make every player have a username and password - your server can handle logging on very easily. Once logged on, the server can feed back a list of characters that go with the account. After selecting one, the client can join the game.
     
    Zullar likes this.
  5. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    926
    The guys are right. You should save player data on some server either by implementing it on your own server or using services like playfab and gamesparks. Otherwise what you are doing is fine but if you want to do it locally at least always encrypt the data using AES or some other algorithm and then save it. better workflow is.

    Use a login process and save the data on a server , if you are using uNet's match making and don't host servers. then at least use playfab or gamesparks and save and access player data from game servers only so
    1- player wants to make a hero. send a command to server
    2- server connects to playfab/gamesparks and saves the hero
    3- changes are done and synced to client
    4- server saves player data in plafab/gamesparks
    5- next time player just connects and logins in using playfab/gamesparks and server retrieves data from those services.

    I'm not advertising any of those services. Those are served just as examples.
     
    Zullar likes this.
  6. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    621
    I understand that saving data to hard drive opens the game up for hacking/cheating. That's OK for this game (some games like MMO's or FPS's need to be hack proof, other games like friendly co-op strategy don't necessary need to be hack proof).

    The reason I do not want to save on server is because I do not have a standalone server. The host will temporarily be the server. I'm waiting on NAT punchthrough and STEAM support *crosses fingers*.

    I want the files local to each player so that if client PlayerA plays with host PlayerB, but then PlayerB logs off... PlayerA can still use his character.

    @Wobes I am using messages instead of RPC's because RPC's aren't robust. If you are using UNET HLAPI for inventory management stop now!!!! I've gone down that road and the UNET HLAPI is too buggy and unrobust for deterministic things like inventory where messages can not be missed even once or else the inventory between server/client will be de-synced. I wasted a lot of time! Here's a few bugs I've found that prevent HLAPI from being used on inventory
    -RPC's can fail during a scene transition because NetworkIdenity observers temporarily drop out
    -Commands/RPC's can be sent to wrong scripts if the UNET hash messes up (inheritance, scripts with same method names). No joke... I attacked with my sword and a fireball popped out!!!
    -SyncVar/SyncList hook/callbacks aren't always called due to scene changes
    -SyncVar can be de-synced between server and client due to other connecting clients (OnSerialize clears dirty bits)
    -SyncList callbacks have insufficient information (i.e. you don't know what was removed from the list)

    Regarding serialization... this one hangs me up. Unity has 2 forms of serialization
    1: Unity prefab/scene serialization. i.e. public fields on MonoBehaviour scripts
    2: Unity UNET serialization. i.e. NetworkBehaviour.OnSerialize/OnDeserialize and SyncVars
    and then there's the...
    3: .NET binary serialization.

    .NET binary serialization works fine for reading/writing to hard drive files, but isn't part of UNET. And if you change anything (rename a field, add a field) it seems to break.
    Unity's serialization works with UNET, but doesn't support reading/writing to hard drive files.

    The big question I have. Is it best to use 2 different forms of serialization (1 for networking and 1 for reading/writing to file)? Or is it best to create a new serialization and then use this for both networking and reading/writing to file?

    What I've done is created a new custom serialization. But it's been a total pain and I'm not sure if it was the right thing to do... I feel like I'm re-inventing the wheel.

    What did you guys do?
     
    Last edited: Oct 17, 2016
    wobes likes this.
  7. wobes

    wobes

    Joined:
    Mar 9, 2013
    Posts:
    738

    I'm not using SyncList's and SyncVars for my Inventory, what about your saving, you can try save localy to .bin file, this will looks like this: but then for protecting it, try to simple do check of saves by nickname or something, for be safe about someone giving saved game to friend.
     
    Last edited: Oct 17, 2016
    Zullar likes this.
  8. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    289
    I currently use 2 different encoders to (a) save player inventories and (b) sync them across network.

    (a) I save player data of clients in local JSON-based files with AES-encryption. I guess this is a quite straightforward and simple approach. For local saves, I need a little more data, like positions of particular items in the inventory, which I would not need to pass over the network. While local saves are not as secure as server-side data, I guess this approach should still be reasonable for now since it is a coop game. As far as I know, even Borderlands 2 uses local savegames.

    (b) For syncronization of player data across the network, I try to keep the bandwidth consumption as low as possible. Therefore, I use a byte array to sync player inventories which uses a custom code to store the data. For example, in my case I need 13 bytes for a single item at worst, so, for an inventory with size of 20, I would need 260 bytes at max.

    As it concerns later changes to player inventories (e.g. pickup, drop, equip, unequip, etc.), I also sycronize these state changes incrementally.
     
    Last edited: Oct 31, 2016
    Zullar likes this.
  9. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    621
    Ah what you did seems to be the best way.

    I've used the same serialization I write to network to also write to file (2A and 3 use the same serialize method). While this avoids duplication of code it doesn't seem robust to any changes (i.e. if I add a new field then it will no longer know how to deserialize and read my data file).

    I think JSON is more robust to changes? (i.e. it can deal with fields that don't exist if you expand your game and add in new fields that didn't exist in the old data file). Is that correct?

    Serialization Types
    1A: UNITY monobehaviour prefab serialization
    1B: UNITY monobehaviour scene object serialization (1A and 1B are lumped together for UNITY)
    2A: Network full serialization (when spawning a new object, or when a player connects)
    2B: Network incremental serialization (only partial data... just the changes)
    3: Read/Write to file serialization
     
  10. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    289
    Good question. From my feeling, I think it would work, but I did not have explicitely tested this case yet. Maybe someone else can confirm this.
     
    Zullar likes this.
  11. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    Hi Zullar. Thanks for making your post so clean! I'm working on implementing these nine steps in my project. I'm held up on creating the MessageBase inherited class that contains the byte[] byteArray of the heroData. Would you post an example of how you are accomplishing this?
    I have found ways to send a message using a MessageBase inherited class but I'd like to send it through ClientScene.AddPlayer() so It can be used in OnServerAddPlayer().
     
  12. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    Hi @Zullar

    I've got the server instantiating the clients character prefab, then it loads its character data from GameSparks, then applies it to a script attached to the character prefab. The server then Spawns the character on the clients using NetworkServer.AddPlayerForConnection().

    The issue I'm having now is that when the character spawns on the client, the script that holds the characters data is not set. Is this where Serialize / Deserialize comes in to play? How is this accomplished? Are you using Sync Vars for this or custom serialization?

    Thanks, Ian.
     
    Last edited: Jan 1, 2017
  13. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    621
    @MMOERPG

    What you are doing seems correct.
    -Instantiate prefab on server
    -Assign data on server
    -AddPlayerForConnection on server (This calls OnSerialize and serializes all networked data)

    Then on Client it will generate the object. The data will be set during OnDeserialize (which comes after Awake but before Start). So do not access any data before OnDeserialize is called or else the data won't be set yet.

    I am not using SyncVars anymore due to a variety of UNET bugs. I am using custom serialization.

    Let me know if you have any more questions.
     
unityunity