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

Make a script server only

Discussion in 'Multiplayer' started by dominicpoppe, Feb 17, 2018.

  1. dominicpoppe

    dominicpoppe

    Joined:
    Feb 10, 2018
    Posts:
    21
    Hello,

    I did attach a script to a weapon which is spawned with every player prefab.
    It is basically a collision detection (Box Collider), which only should get called server-side.

    At the moment, if the trigger enables, it calls the script and processes all no matter if it is the server or the client. That's the issue. I want to do the collision detection and all what happens with it on the server only. So I basically did this:

    Code (CSharp):
    1. void OnTriggerEnter(Collider otherAgent) {
    2. if (Network.isServer)
    3. {
    4.    return;
    5. }
    6. Debug.Log("test");
    7. }
    So, if I collide with my weapon at something, "Test" is debugged on the server and on the client which means it is client and serverside called. How can I change that?

    At start, I thought all was fine until I started to send a ClientRPC command which should play an audio file at a position. At that I got the error null reference... Then I tried to send a Cmd, which did display me a "NetworkIdendity" is missing.

    So all I want is, that this script only runs server-side and basically is able to send ClientRPC commands.

    I really hope someone can help me out, I'm stuck on this since hours.
     
  2. ItsaMeTuni

    ItsaMeTuni

    Joined:
    Jan 19, 2015
    Posts:
    44
    The problem I'm seeing here is that you're returning the function if it is the server and printing to the console only if it is the client.
    To send RPCs the class needs to be a NetworkBehaviour (don't forget to add a NetworkIdentity component to the object that uses this class). Then you can send RPCs and use isServer instead of Network.isServer.
     
    Last edited: Feb 18, 2018
    dominicpoppe likes this.
  3. dominicpoppe

    dominicpoppe

    Joined:
    Feb 10, 2018
    Posts:
    21
    The player has the weapon on him. Basically -> Player -> Arms -> Left Hand -> Weapon
    on this the script is basically added. The player has a Network Identity, so it should be on the weapon aswell or does the weapon need a identity for itself aswell?

    public class HitDetector : NetworkBehaviour {

    that should be fine right? With importing Unity.Network.
     
  4. ItsaMeTuni

    ItsaMeTuni

    Joined:
    Jan 19, 2015
    Posts:
    44
    Every object that has a NetworkBehaviour component needs a NetworkIdentity to work. So yes, even if it is a child it has to have a NetworkIdentity. I just tested that out and it wouldn't let me remove the NetworkIdentity from a child object that used a NetworkBehaviour script, even though its parent has a NetworkIdentity.
     
    dominicpoppe likes this.
  5. dominicpoppe

    dominicpoppe

    Joined:
    Feb 10, 2018
    Posts:
    21
    Okay so I added the NetworkIdentity to the weapon child object aswell.
    Following warnings I get now:

    Code (CSharp):
    1. The prefab 'Player' has multiple NetworkIdentity components. There can only be one NetworkIdentity on a prefab, and it must be on the root object.
    2. UnityEngine.Networking.NetworkManagerHUD:OnGUI()
    also, when I do RPC calls inside the script I get following:

    Code (CSharp):
    1. TargetRpc call on un-spawned object
    2. UnityEngine.Networking.NetworkBehaviour:SendTargetRPCInternal(NetworkConnection, NetworkWriter, Int32, String)
    3. HitDetector:CallTargetSyncToClient(NetworkConnection, Int32, Int32)
    4. HitDetector:OnTriggerEnter(Collider) (at Assets/HitDetector.cs:83)
    5.  
    Code (CSharp):
    1. ClientRpc call on un-spawned object
    2. UnityEngine.Networking.NetworkBehaviour:SendRPCInternal(NetworkWriter, Int32, String)
    3. HitDetector:CallRpcplayAudioOnClients(Vector3, Int32)
    4. HitDetector:OnTriggerEnter(Collider) (at Assets/HitDetector.cs:89)
    5.  
    which I don't understand.

    From my side, it says, I'm trying to do a RPC on a not spawned RPC prefab or something like that?
    Even tho it is spawned for the server & the clients.

    Also when I debug log isServer in both ways with a if it would display "I'm the client" even tho it is the server/host?

    Code (CSharp):
    1. if(isServer) { Debug.Log("I am server"); } else { Debug.Log("I am the client"); }
     
  6. ItsaMeTuni

    ItsaMeTuni

    Joined:
    Jan 19, 2015
    Posts:
    44
    Well, that seems to be a problem... I didn't know about that. I've seen people with this same problem here. One of the workarounds that I see them doing are adding the NetworkBehaviour component, commenting out the networked code, switching to MonoBehaviour, compiling the code, removing the NetworkIdentity component that was automatically added to the object by Unity, un-commenting the code and compiling again. The second workaround would be to "route" the commands to the player class (that is the root object).

    If there's really no solution to this it seems UNET was poorly designed. I like this engine because of it's ease of use and fast development speed, but seeing something as fundamental as this broken makes me think about switching back to UE4...
     
    dominicpoppe likes this.
  7. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    In UNET the NetworkIdentity is supposed to be on the root gameobject, and all NetworkBehavior scripts are supposed to be on the same GameObject as the NetworkIdentity component (so on the root gameobject as well). Trying to use them other than intended is a road trip to unsupported land. Might work, might break, good luck.

    For something like checking isServer on a child gameobject, I'd have it just call a method on the root gameobject where the networkbehaviours are to check that.

    So on the root gameobject
    Code (csharp):
    1. public bool CheckIfServer()
    2. {
    3.     return isServer;
    4. }
    And a Monobehavior on the child, where rootNetworkBehavior is a reference to the above NetworkBehavior script on the root gameobject
    Code (csharp):
    1. if (rootNetworkBehavior.CheckIfServer())
    2. {
    3.     Debug.Log("Hey, I know I'm a server, and I'm not even a NetworkBehavior!");
    4. }
     
    dominicpoppe likes this.
  8. dominicpoppe

    dominicpoppe

    Joined:
    Feb 10, 2018
    Posts:
    21

    That helped me a lot, thanks!
     
  9. dominicpoppe

    dominicpoppe

    Joined:
    Feb 10, 2018
    Posts:
    21
    I thought I can make it work but it seems to be impossible.

    The hit detector script is on the sword which is a child of the player prefab.
    The player prefab has all like Network Identity, Transform and so on.

    When I hit, and the collision trigger, calls hit detector basically, I am trying to send a command but it says no reference exception.
    Also if I do:

    Code (CSharp):
    1.         if (!isLocalPlayer)
    2.         {
    3.             return;
    4.         }
    it will say:

    There is no NetworkIdentity on this object. Please add one.
    UnityEngine.Networking.NetworkBehaviour:get_isLocalPlayer()

    I am really confused and I don't really know what to do.
    Code (CSharp):
    1.  
    2.     void OnTriggerEnter(Collider otherAgent) {
    3.         if (!isLocalPlayer)
    4.         {
    5.             return;
    6.         }
    7.         if (gameObject.name == "weapon") {
    8.                 int uniqueiddefender = otherAgent.transform.root.GetComponent<MovementController> ().myuniqueid;
    9.                 int uniqueidattacker = gameObject.transform.root.GetComponent<MovementController> ().myuniqueid;
    10.  
    that's the code itself, more it doesn't do except Debug.Log the uniqueid's of the attacker and defender.
    That works, but, how do I change it so it is only executed either on the server or on the client?
    I prefer server-side only for better handling.

    Your topic helped me to understand some parts, but if I call stuff in this function it doesn't work.


    Or do I have to move all Commands/RPC and so on to the main script of the player prefab which is a child next to the Network Behaviour and then link it as a public void?


    The scripts has to do:
    - Detect collision (if weapon collides with a player)
    - Check if the player blocked it or not
    - Apply damage or cancel block depending on what happens
    - Play a sound for everyone in that position where the hit happened

    Collision detection works, check if block or not aswell.
    Issue is now how to apply damage and play a hit sound for everyone now for example?

    Also I have to sync some stuff from server to client here. I'm really confused about UNET right now.
     
  10. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    If your script is on a child object, you shouldn't make it a NetworkBehavior. If your script is not a NetworkBehavior then you shouldn't try using things like isLocalPlayer directly in that script. Though you can call another NetworkBehavior to get that same information, just as I showed in my previous example.

    Just replace my use of isServer with isLocalPlayer for the method you put on the NetworkBehavior script on the root gameobject with the networkidentity attached, and you can call that method through a reference to it from any MonoBehavior on any child object to tell if this object isLocalPlayer or not.
     
  11. dominicpoppe

    dominicpoppe

    Joined:
    Feb 10, 2018
    Posts:
    21
    Why I shouldn't do that? I don't understand it?
    If the root script is NetworkBehavior why the script in a child component shouldn't be NetworkBehavior aswell?

    The reason why I currently have it on NetworkBehavior is because I need to send Commands towards the server like if a hit did actually hit -> I have to send a hit request to the server which will apply the damage then so it's not locally on the local player done.
     
  12. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    https://docs.unity3d.com/Manual/UNetSpawning.html

    https://docs.unity3d.com/ScriptReference/Networking.NetworkIdentity.html

    Its also the whole reason NetworkTransformChild exists, since you're not supposed to put NetworkIdentity, NetworkBehavior, NetworkTransform, etc, on child objects.

    So according to the docs a NetworkIdentity needs to be on the root gameobject, it is unsupported to have multiple NetworkIdentity components on the same gameobject and its children, and it is unsupported to have NetworkBehaviour scripts on any gameobject other than the root gameobject with the NetworkIdentity.

    If you need to do something in a child script that has to be in a NetworkBehavior, just have your child MonoBehaviour script call a NetworkBehavior on the root gameobject to do it. For example, if you need to send a hit from the child object, have your child object MonoBehavior call a send hit method on a NetworkBehavior, have that method in the NetworkBehaviour first check if isLocalPlayer, etc, and fire off the Command to the server.
     
    Last edited: Feb 21, 2018
  13. dominicpoppe

    dominicpoppe

    Joined:
    Feb 10, 2018
    Posts:
    21
    How would I pass data then to the root network behaviour?
    If my monhobehaviour script in the weapon detects a collision and detects that it is a hit.
    How do I call a public void function in the root object of the player networkbehaviour and tell it who did hit who?
    I can't pass NetworkIdentity or anything like that so - how do I inform the server now Player A did hit Player B?
     
  14. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    Pass data to a NetworkBehavior on the root gameobject just like calling a function on any other script component. Its no different. You can just pass that Collider otherAgent to the NetworkBehaviour and deal with it all there, you could just ask the NetworkBehavior if this !isLocalPlayer and then do all your logic locally, and then again call the NetworkBehaviour to send something back to the server. Lots of ways you can do it.
     
    dominicpoppe likes this.