Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

uNet networking help

Discussion in 'UNet' started by VirusPunk, Feb 24, 2016.

  1. VirusPunk

    VirusPunk

    Joined:
    Aug 21, 2015
    Posts:
    25
    I am pretty savvy now when it comes to singleplayer coding. I recently took up an interest in trying to convert my singleplayer Resident Evil clone to multiplayer. I read the docs, and tried to come up with a little tech demo already. While it works somewhat how I want it there are quite a few problems...

    Problems:
    1) For some reason, the ClientRPC's only work for the host, but do not work for the client connecting to the host. Adding identical Commands seems to do nothing.

    2) Using Network Transform; the host's movements are extremely laggy / choppy. He sort of just teleports around.

    3) The camera doesn't affect the individual players, despite being apart of the prefab, when a client connects the camera will follow the client on the host's window instead of the host when he connects...

    4) Audio works only for the host.

    5) Animations only display for the host character on both the client and host windows, the animations do not work for the client except from his own perspective.

    I'm sure it has something to do with my code, but I have no idea how to change it so it'll reflect my singleplayer implementations for multiplayer... This code works 100% perfectly for offline play, just not online.

    My code:
    Code (CSharp):
    1. // Resident Evil style network controls by VirusPunk
    2. using UnityEngine;
    3. using UnityEngine.Networking;
    4.  
    5. public class PlayerScript : NetworkBehaviour
    6. {
    7.     private Animation anim;
    8.     private CharacterController controller;
    9.     private float speed;
    10.     private float turnSpeed;
    11.     private float idleTimeOut = 10.0f;
    12.     private float idleTimeOutTimer = 0.0f;
    13.     private Vector3 mydir;
    14.     private bool isWalking = false;
    15.     private bool isRunning = false;
    16.     public AudioClip[] walk;
    17.     public AudioClip[] run;
    18.     public Camera myCam;
    19.  
    20.     public override void OnStartLocalPlayer()
    21.     {
    22.         GameObject.Find("Object_0").GetComponent<MeshRenderer>().material.color = Color.red;
    23.     }
    24.  
    25.     void Start()
    26.     {
    27.         anim = GetComponent<Animation>();
    28.         controller = GetComponent<CharacterController>();
    29.         myCam = Camera.current;
    30.  
    31.         if (isLocalPlayer)
    32.         {
    33.             myCam.enabled = true;
    34.         }
    35.  
    36.         if (!isLocalPlayer)
    37.         {
    38.             myCam.enabled = false;
    39.         }
    40.     }
    41.  
    42.     // Update is called once per frame
    43.     void Update()
    44.     {
    45.         if (!isLocalPlayer)
    46.         {
    47.             return;
    48.         }
    49.  
    50.         float hor = Mathf.Abs(Input.GetAxis("Dpad_Horizontal"));
    51.         float ver = Mathf.Abs(Input.GetAxis("Dpad_Vertical"));
    52.  
    53.         // Movement
    54.         if (Input.GetKey(KeyCode.Joystick1Button0) && ver > 0.1
    55.             && Input.GetAxis("Dpad_Vertical") < 0)
    56.         {
    57.             isWalking = false; isRunning = true;
    58.             speed = 170f;
    59.             turnSpeed = 230f;
    60.             transform.Rotate(0, Input.GetAxis("Dpad_Horizontal") * turnSpeed
    61.                     * Time.deltaTime, 0);
    62.             mydir = transform.forward * Input.GetAxis("Dpad_Vertical") * speed;
    63.             controller.Move(mydir * Time.deltaTime - Vector3.up * 0.2f);
    64.         }
    65.  
    66.         else
    67.         {
    68.             isWalking = true; isRunning = false;
    69.             speed = 70f;
    70.             turnSpeed = 230f;
    71.  
    72.             if (anim["WalkBack"].enabled)
    73.             {
    74.                 speed = 55f;
    75.             }
    76.  
    77.             transform.Rotate(0, Input.GetAxis("Dpad_Horizontal") * turnSpeed
    78.                         * Time.deltaTime, 0);
    79.             mydir = transform.forward * Input.GetAxis("Dpad_Vertical") * speed;
    80.             controller.Move(mydir * Time.deltaTime - Vector3.up * 0.2f);
    81.         }
    82.  
    83.         // Animations
    84.         if (ver < 0.1 && hor < 0.1 && isWalking)
    85.         {
    86.             RpcIdleAnim();
    87.             idleTimeOutTimer += Time.deltaTime;
    88.  
    89.             if (idleTimeOutTimer > idleTimeOut)
    90.             {
    91.                 anim.CrossFade("Idle2");
    92.             }
    93.  
    94.             else if (idleTimeOutTimer < idleTimeOut)
    95.             {
    96.                 anim.CrossFade("Idle");
    97.             }
    98.         }
    99.  
    100.         else
    101.         {
    102.             RpcWalkAnim();
    103.             idleTimeOutTimer = 0.0f;
    104.             anim.CrossFade("Walk");
    105.         }
    106.  
    107.         if (isRunning)
    108.         {
    109.             RpcRunAnim();
    110.             idleTimeOutTimer = 0.0f;
    111.             anim.CrossFade("Run", 0.1f);
    112.         }
    113.  
    114.         if (Input.GetAxis("Dpad_Vertical") > 0)
    115.         {
    116.             RpcWalkBackAnim();
    117.             idleTimeOutTimer = 0.0f;
    118.             anim.CrossFade("WalkBack");
    119.         }
    120.  
    121.         // Audio
    122.         if (isRunning && ver > 0.1 && !anim["Idle"].enabled)
    123.         {
    124.             Invoke("RunningFootstepTimer", 0.32f);
    125.         }
    126.  
    127.         if (isWalking && ver > 0.1 && !anim["Idle"].enabled
    128.             || isWalking && hor > 0)
    129.         {
    130.             Invoke("WalkingFootstepTimer", 0.48f);
    131.         }
    132.     }
    133.  
    134.     [ClientRpc]
    135.     void RpcIdleAnim()
    136.     {
    137.         idleTimeOutTimer += Time.deltaTime;
    138.  
    139.         if (idleTimeOutTimer > idleTimeOut)
    140.         {
    141.             anim.CrossFade("Idle2");
    142.         }
    143.  
    144.         else if (idleTimeOutTimer < idleTimeOut)
    145.         {
    146.             anim.CrossFade("Idle");
    147.         }
    148.     }
    149.  
    150.     [ClientRpc]
    151.     void RpcWalkAnim()
    152.     {
    153.         idleTimeOutTimer = 0.0f;
    154.         anim.CrossFade("Walk");
    155.     }
    156.  
    157.     [ClientRpc]
    158.     void RpcRunAnim()
    159.     {
    160.         idleTimeOutTimer = 0.0f;
    161.         anim.CrossFade("Run", 0.1f);
    162.     }
    163.  
    164.     [ClientRpc]
    165.     void RpcWalkBackAnim()
    166.     {
    167.         idleTimeOutTimer = 0.0f;
    168.         anim.CrossFade("WalkBack");
    169.     }
    170.  
    171.     // Timers
    172.     void RunningFootstepTimer()
    173.     {
    174.         if (anim["Run"].enabled)
    175.         {
    176.             GetComponent<AudioSource>().PlayOneShot(run[0]);
    177.         }
    178.  
    179.         CancelInvoke();
    180.     }
    181.  
    182.     void WalkingFootstepTimer()
    183.     {
    184.         if (anim["Walk"].enabled)
    185.         {
    186.             GetComponent<AudioSource>().PlayOneShot(walk[0]);
    187.         }
    188.  
    189.         CancelInvoke();
    190.     }
    191. }
     
  2. foxifi

    foxifi

    Joined:
    Dec 20, 2015
    Posts:
    55
    1. I think the RPC's only can be called by the host. The clients call Commands to run methods on the server. Your host is your server, so this is likely the issue. You'll have to rethink how you send messages to the server and get back reports through rpc's.

    2. Network transform is pretty choppy by default. I ended up writing my own way of syncing the movement across, getting rpc's of the position to move to and lerping to the new position for smoothness. This is also where I added client side prediction so you don't have to wait on reports from the server, increasing responsiveness while keeping the server authoritative. There's some posts here about how to do that, as well as some nice Valve posts about client prediction and reconciliation. It's pretty complicated, or it was for me, but if you're using rigidbodiespecially and don't need that, there are settings in the network transform to sync rigidbody movement which is far smoother.

    3. I feel the camera issue is something with just using the current camera to target players. I'm not entirely sure how this would behave with multiple cameras in a scene.

    4. Not experienced with audio at all :/

    5. If your code is solid then this is likely got to do with commands and rpc's as listed above. However, I prefer to decouple my animations from inputs, and make them independent entirely. So my animations play based on movement. If my player gameobject moves, I get the change in position to calculate a velocity, and play animations based solely on that. Not saying you have to do it this way, but it made it simple as all I had to do was get the movement working over networking and I don't have to send anything over the network to handle animation, as it's all handled locally with movement.

    Hopefully some of my input helps a little. I'm new to unet and I'm not that experienced in Unity or c# either, so forgive me if I'm wrong about something. Best of luck.
     
  3. VirusPunk

    VirusPunk

    Joined:
    Aug 21, 2015
    Posts:
    25
    Thanks for the reply. I looked up on syncing movements manually since other people complained that the default NetworkTransform is laggy as well, and came across this:

    http://forum.unity3d.com/threads/syncing-position-without-network-transform.336013/

    Its starting to make a little more sense, so I might be able to do something with this information now, at least alot better than before.

    As far as the camera goes, it was mostly a temporary implementation. The final goal will be to have static fixed cameras per room just like in Resident Evil (already accomplished ages ago in single). As far as online goes, each player will be able to freely explore the rooms, the camera will switch according to a trigger that the player steps in, and will be independent for each player based on where he is at. The best example I can give is to lookup multiplayer videos of Resident Evil: Outbreak on Youtube that uses the same system. However, some cameras are dynamic in that game. Whereas in mine, every camera will be static and fixed according to the room layouts like in the original classic RE games.

    Now this brings me to my next big point. I think the best approach for a system like this in general is to have both a separate server and a client instead of the regular host-client / client-host system. It just seems like something of this caliber would work better on a global scale. So if two people wanted to play, they would need to connect to a server hosted on a machine separate from the two clients (most likely as it's own separate server executable).

    Which brings me to my next question; how do I go about doing it? Would all of the important data be server dependent, whereas client data is graphically dependent? How would a server know to relay the actions/information of what all the other players are doing?
     
  4. foxifi

    foxifi

    Joined:
    Dec 20, 2015
    Posts:
    55
    Alright, I'll try my best to explain what I know clearly, but I'm on mobile so I'm sorry for the lack of links. There's a decent amount of information around if you look for it.

    I think first you need to decide the importance of server authority. If you aren't concerned with "hackability" or whatever you want to call it, then things are a little more lax.

    However, I think unet is kind of centered around the server being authoritative. I also think it's a great thing to have, so you don't rely on the client always being honest to make things fair or work properly.

    This means that whatever you do on screen needs to be justified by the server first, as far as the player goes.

    I'm sure there are a lot of ways to accomplish what you want, and I don't know much yet, but what I do is try my best to only send inputs to the server. I also try to keep my server code and client code as separate as possible.

    For example, when I want to move my player, in my client script I handle things normally except for the actual movement. I keep track of the inputs, and I send these to the server using a [command]. Commands are only called on the server, so regardless of whether you put server/client code in one script or separate, you have to have a way to get that information to the server, and to the server script.
    Keep in mind that likely both your scripts will be present on both players, as they are duplicates of themselves until you modify something once they are created.

    This means that my inputs change only on the local player, and you need to check that it is the local player in the client script. Once those inputs change, you call a command with those inputs, and on the server that command will be called, and those inputs will be present. You cant send every data type, I think its limited to Serializable data types? I'm not entirely sure. But you can send all basic types and vectors and things of that nature.

    So now you've got a command on the server side client script called. I like to then just pass that received value over to the server script I made, so the client script doesn't contain any of the logic the server does with it.

    The server script then gets a method called with that received information, and you move the player on the server. Success.

    However, since you aren't using the network transform, the client doesn't see any movement. This is where you would then send the resulting position after the move back to the client, using an RPC. It works very similarly to the commands, only running on the client.

    Once you get that result back over to the client's client script, you can lerp your player to the correct position.

    This is pretty complex stuff to try to explain and I'm not the best, I hope it makes at least a little sense. Also this is the most basic way to handle movement like this. In reality, you want to consider how many times a second you want to send information. For me, I work in fixed update, and collect 5 inputs. At that rate, I have those 5 inputs after 0.1 seconds, and then I send that packet of inputs to the server in an array.

    So server side I can replicate that movement and send back the result after it moves. I also send a tick for each movement, which is just an integer representing the number of updates since the player was created. I can use that to ensure that when I get the resulting position back from the server, I can compare that position with where I was when I was supposed to be where the server said I was. It's confusing, but this system let's you freely move the player client side without having to wait on a message back from the server, which solves latency issues. No matter the latency, I always know where I was and where I was supposed to be. If these get out of sync you get the snapping you see in games sometimes, but I very rarely see it. It also means what you see is in the future relative to other players, and you see other players in the past. To me this is a good tradeoff for smooth movement.

    An important note doing things like I'm doing is that I don't think you can do it using rigidbodies, as they will get out of sync too easily, and must wait for the physics update to handle collisions, and to do this properly you have to basically have to "rewind" by setting your position to the server position once you get it, and apply the rest of your changes all in one go so you don't see that change. If you can do this using rigidbodies I would love to know how.

    Anyway, I know this is extremely complicated but hopefully you see now at least how you handle getting info to the server and back. It works inside the script so each player will automatically only get its own information sent and received.

    Also, with your cameras, that should work just fine, but you want to ensure that the only player that triggers them focusing on the player is the actual local player.
     
  5. VirusPunk

    VirusPunk

    Joined:
    Aug 21, 2015
    Posts:
    25
    Update: Just thought I'd let you know even though this thread is fairly old now, I managed to figure out how to sync animations manually in code with Commands/ClientRPC's properly for legacy animations. It's kind of messy and redundant, but it gets the job done either way. If you need (or anyone needs) help feel free to ask. Animations sync both on client and host for both players. The same principles can be applied for anything you're trying to do. Thanks for the help though anyway.
     
    Last edited: Jun 22, 2016