Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Why NetworkServer.Spawn doesn't pass the ScriptableObject data in the GameObject to the client

Discussion in 'Multiplayer' started by quiet00903, Jul 25, 2017.

  1. quiet00903

    quiet00903

    Joined:
    May 18, 2017
    Posts:
    19
    Hi, everyone. I'm learning the UNET from yesterday. Before using the UNET, I used the ScriptableObject to save data as assets in the game and I instantiated the GameObject which would read the data from the SO asset to show its appearance. However,when I want to use the UNET I meet the problem. When I start the game as a host then I instantiate the GameObject from the SO asset it works well. But when I use the NetworkServer to spawn these GameObject and I start the game from the client which only get the GameObject without appearance and its position seems to be not correct either. I'm new to the UNET and also new to the network, hope someone could give me some suggestion about the problem. Thanks in advance.
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    It should be working. Make sure that both client and server got the same SO instance.
    Without the code it's impossible to tell what's you're doing wrong.
     
  3. quiet00903

    quiet00903

    Joined:
    May 18, 2017
    Posts:
    19
    Thanks for your reply! This is my ScriptableObject files.
    Code (CSharp):
    1. [CreateAssetMenu(menuName ="Building/Data")]
    2. public class BuildingData : ScriptableObject {
    3.     public string description;
    4.  
    5.     [Header("Visual")]
    6.     public Sprite buildingSprite;
    7.     public string buildingSpriteName;
    8.     public int width;
    9.     public int height;
    10.     public GameObject buildingPrefab;
    11.     public GameObject tilePrefab;
    12.     public string buildingInfo;
    13.  
    14.     [Header("Cost")]
    15.     public int moneyCost;
    16.     public int crystalCost;
    17.  
    18.     [Header("CoolDown")]
    19.     public float coolDownTime;
    20.     public bool isCoolingDown = false;
    21.     public float startCoolDownTime;
    22.  
    23.     [Header("Effect")]
    24.     public BuildingEffect[] effects;
    25. }
    And the gameObject's part codes which contains the SO data is like this:
    Code (CSharp):
    1. public class BuildingLayout : NetworkBehaviour {
    2.  
    3.     [HideInInspector]
    4.     public Transform[,] tiles;
    5.     public GameObject tilePrefab;
    6.     [HideInInspector]
    7.     public Collider2D coll;
    8.     [SyncVar]
    9.     [HideInInspector]
    10.     public int width;
    11.     [SyncVar]
    12.     [HideInInspector]
    13.     public int height;
    14.     [SyncVar]
    15.     [HideInInspector]
    16.     public string spriteName;
    17.     private SpriteRenderer spriteRenderer;
    18.  
    19.     private BuildingData data;
    20.     public BuildingData Data
    21.     {
    22.         get { return data; }
    23.         set
    24.         {
    25.             data = value;
    26.             width = data.width;
    27.             height = data.height;
    28.             //tilePrefab = data.tilePrefab;
    29.             spriteName = data.buildSpriteName;
    30.             spriteRenderer.sprite = data.buildingSprite;
    31.             Initial();
    32.         }
    33.     }
    34. }
    Now I seems to find a way to solve the problem, even though I don't think it's a proper way because of my poor knowledge about UNET. I will tell what I do from before to now. At beginning, the variable width and height and tilePrefab is private variable and they get the data from BuildingData. But when the NetworkServer spawns the BuildingData lost and the width and height becomes to 0 and the tilePrefab becomes to null. Then I read the manual at unity3d and I wonder if the variables in the BuildingLayout should have the attribute [SyncVar] so that the client gameobject can initial properly (I'll appreciate if you have the answer). So I try to add the [SyncVar] to the BuildingData, however it seems that the BuildingData have Sprite so it can't be serialized by the UNET. I have no choice but to make the width and height to be [SyncVar] and I add a string variable called spriteName as [SyncVar] to get Sprite by Resource.Load. And it magically to work properly even though I don't know why ! But there is one more problem about the tilePrefab. At beginning I want to make it to be [SyncVar] too, and I make the tilePrefab asset add the NetworkIdentity component because by doing like this, the GameObject tilePrefab seems can be pass from Server to Client when I read the manual at unity3d. However, it didn't work! So I have to make it a public variable and drag the prefab to the BuildingLayout even though it's not a good idea.
    That's all I have done by now. And I still have many questions about the UNET. First, what does the Server Authority, Local Player Authority, Client Authority means and at which situation I should use them. Second , how the data transport between server and clients and why the GameObject which I have added the NetworkIdentity didn't transport as my expectation. If anyone have the idea to tell me I will be very appreciated. And I will keep learning the basic knowledge about the UNET and wish someday I could answer the questions by myself.
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Glad you've figured SO issue on your own, it's okay to do it that manner.

    Ok, that's a lot of stuff going on. I'll try to answer some of these.
    SyncVars (in theory) is the way to sync basic types of data from server to all clients. That's why it worked, since the server told clients what information must be in those fields.

    Second, if you want for prefab to be spawned, add a NetworkIdentity component to it, and also add the prefab to the spawn prefab list in the NetworkManager. Then do something like this:
    Code (CSharp):
    1. ...
    2. Prefab pref = Instantiate(YourPrefab);
    3. // Do something initializing, like
    4. pref.DoWhatEver();
    5. pref.ImProperty = 10;
    6. // etc.
    7.  
    8. // Then use
    9. NetworkServer.Spawn(pref);
    Server will issue a spawn message to all clients connected, instantiating prefab on them with the changes you've made.
    Don't add NetworkIdentity components in runtime. It's bad. Like whole-client-desync bad.

    Also, make sure all NetworkIdentity components are enabled on scene change, will understand why in a sec.

    NetworkIdentity:
    Server Authority (I think you've meant Server Only) - it makes sure this object is disabled on the clients, but remains enabled on the server. Very usefull for Server Logic;
    Local Player Authority - means that player can issue command to this object, and manipulate it on a client.

    If you have any other questions, feel free to ask.
     
  5. quiet00903

    quiet00903

    Joined:
    May 18, 2017
    Posts:
    19
    Ha ha, thanks for your reply, VergilUa. You know, I really have other questions need help. Well, I've made my game objects have a proper appearance and a hp slider bar to work consistently on both the client and the host as my expectation: Now the remote client and local client have the same position, and the building lose hp consistently when the warrior move closely.
    upload_2017-7-28_0-0-57.png

    However, I've no idea when it comes to send some complex data. For example, in my game I have a item "金钱" means money to show the money the player have, and I have another item "金钱每秒增速" means the money increase speed per second. Because I make a my building a prefab (You can treat the three icons below as building prefab, in fact they are another prefab called buildngItem and they instantiate the building prefab when clicked), and the prefab get all the data when they're instantiated from my ScriptalbeObject which is BuilidingData I mentioned before. In fact, in order to make different players click their UI indepently, I make the UI built on their clients, not on the server. So the UI icons can hold the BuilidngData on client, and when I click the icon the icon instantiate a building prefab which isn't the server spawned prefab until I click the building on the ground then the server spawn the prefab. So before I clicked the building on the ground, the building instantiated on the client still have the BuildingData as the icon does. And in order to pass the data to server I had done what I mentioned before. Everything works well when I send the simple data. But there is a problem. As you see, I have a sunflower icon, which can make the Player to add 2 point money increase speed per second. Because maybe I want to add more buildings which can add money increase speed someday and all the effect can overlay perfectly, I think I can use a buffsystem-like Class to do it. Then I make another abstract ScriptableObject called BuildingEffect which have only two method:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public abstract class BuildingEffect : ScriptableObject {
    6.  
    7.     public abstract void AddEffect(Player player);
    8.  
    9.     public abstract void RemoveEffect(Player player);
    10. }
    11.  
    Then I implement it with a class called AddSpeedEffect which well add themselves to the Player Object when they are built and remove themselves when they are destroyed.
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. [CreateAssetMenu(menuName ="Effect/AddSpeed")]
    7. public class AddSpeedEffect : BuildingEffect
    8. {
    9.     public Status status;
    10.     public float addSpeed;
    11.  
    12.     public override void AddEffect(Player player)
    13.     {
    14.         player.effects.Add(this);
    15.        
    16.         switch(status)
    17.         {
    18.             case Status.MoneyUpSpeed:
    19.                 player.PlayerMoneySpeed += addSpeed;
    20.                 break;
    21.             case Status.CrystalUpSpeed:
    22.                 player.PlayerCrystalSpeed += addSpeed;
    23.                 break;
    24.             default:
    25.                 break;
    26.         }
    27.     }
    28.  
    29.  
    30.     public override void RemoveEffect(Player player)
    31.     {
    32.         switch (status)
    33.         {
    34.             case Status.MoneyUpSpeed:
    35.                 player.PlayerMoneySpeed -= addSpeed;
    36.                 break;
    37.             case Status.CrystalUpSpeed:
    38.                 player.PlayerCrystalSpeed -= addSpeed;
    39.                 break;
    40.             default:
    41.                 break;
    42.         }
    43.  
    44.         player.effects.Remove(this);
    45.     }
    46. }
    47.  
    My BuildingData maintains my EffectData but the BuilidingData can't be sent to the Server. Neither the BuildingData nor EffectData can use [SyncVar], I have no choice but to add the effect just when I click the building instantiated on client on the ground like this:
    Code (CSharp):
    1. if (canDropDown && Input.GetMouseButtonDown(0))
    2.         {
    3.             foreach (Transform tile in layout.tiles)
    4.             {
    5.                 tile.GetComponent<SpriteRenderer>().color = Color.clear;
    6.             }
    7.  
    8.             //Assume player money and crystal
    9.             Player.GetLocalPlayer().PlayerMoney -= layout.Data.moneyCost;
    10.             Player.GetLocalPlayer().PlayerCrystal -= layout.Data.crystalCost;
    11.  
    12.             if (layout.Data.effects != null)
    13.             {
    14.                 foreach (BuildingEffect effect in layout.Data.effects)
    15.                 {
    16.                     effect.AddEffect(Player.GetLocalPlayer());
    17.                 }
    18.             }
    19.  
    20.             SelectToBuild.chosenBuildingItem.CoolDown();
    21.             SelectToBuild.chosenBuildingItem = null;
    22.             coll.enabled = true;
    23.             enabled = false;
    24.  
    25.             Player[] players = FindObjectsOfType<Player>();
    26.  
    27.             for (int i = 0;i < players.Length;i++)
    28.             {
    29.                 if(players[i].isLocalPlayer)
    30.                 {
    31.                     //This is what I do when I can't send the BuildingData
    32.                     players[i].CmdAssignBuilding(layout.width, layout.height, layout.spriteName
    33.                        , status.MaxLife , layout.transform.position, players[i].gameObject);
    34.                     break;
    35.                 }
    36.             }
    37.             Destroy(gameObject);
    38.         }
    The layout.Data is another SO seems called StatusData or something similarly, which contains the basic info like money, money speed, crystal and so on, and the building built on client will destroy itself when I spawn it on server. As you see, I use CmdAssignBuilding to spawn the prefab and use many arguments to replace the BuildingData. However, because I can't send the EffectData to the Server and the Effect.Add() is about the prefab built on client, not the prefab built on server, I can't remove the Effect out of the player because:1. the building is not the same. 2. the EffectData on the server building is null.
    Now it finally comes to my question, I think I should still send the SO EffectData which is maintained by BuildingData to the Server. SO is there any method to implement my expectation? Is the custom network serialization can works for ScriptableObject and how to code it?
    Thanks for your reply again. I really appreciate everyone who give me suggestions and advice. It's my forth month after my decision to learn game development. I'll keep on studying for my dream :)
     
  6. quiet00903

    quiet00903

    Joined:
    May 18, 2017
    Posts:
    19
    I just found that I did a stupid things. Now that the SO is an asset, and it exists both in the Client and Server, I could directly use Resource.Load to get the ScriptableObject just by sending the file path string. And it turns out to be working properly! Ah, what can I say? Life is full of surprise! :rolleyes: