2
votes

I am having an issue getting the network latency to be low for my multiplayer shooter game made with Unity. I am using UDP to send player positions from the game client to my amazon server and back to another game client. My game clients are sending 60 byte UDP packets to the amazon server at a rate of 8 packets per second.

When I play the game on two separate iOS devices (iPhone 7 and iPad mini) the network latency is very low and the players are able to see each other move instantly. However, if I run the game on my iPhone 7 and play against another player who is using a samsung galaxy s4 running android, which is a lower powered device, I experience 5 second latency on the android device when receiving player positions from the iOS device. The odd thing is that the iOS device can receive player positions from the android device instantly. The same issue happens when playing on the iPhone 7 against a client on mac, except the iPhone 7 experiences the 5 second latency when receiving player positions from the mac client. The issue also happens when playing on the Samsung Galaxy S4 against a client on mac, where the galaxy s4 experiences the 5 second latency.

I have tried to increase the udp receive buffer size to 16 MB but it didn't change anything.

Here is a sample of my game client code where I send player positions to the amazon server:

    void Update(){
    // manually constrain rotation because rigidbody constraints don't work 
    this.transform.rotation = Quaternion.Euler(new Vector3(0, this.transform.rotation.eulerAngles.y, 0));
    this.transform.position = new Vector3(this.transform.position.x, boatHeightConstant, this.transform.position.z);


    // Speed Boost Handling
    if(isSpeedBoosting == true){
        tiltFactor = tiltModifier * (VelocityRatio() + 1.0f);
        speedBoostTimer += Time.deltaTime;
    }
    else{
        tiltFactor = VelocityRatio() + 1.0f;
    }
    if(speedBoostTimer >= speedBoostDuration){
        isSpeedBoosting = false;
        speedBoostTimer = 0f;
        endSpeedBoost = true;
    }
    if(endSpeedBoost == true){
        GetComponentInChildren<MainWeapon>().EndFireRateBoost();
        endSpeedBoost = false;
    }

    // Attack Boost Handling
    if(isAttackBoosting == true){
        attackBoostTimer += Time.deltaTime;
    }
    if(attackBoostTimer >= attackBoostDuration){
        isAttackBoosting = false;
        attackBoostTimer = 0f;
        endAttackBoost = true;
    }
    if(endAttackBoost == true){
        GetComponentInChildren<MainWeapon>().ResetDamage();
        GetComponentInChildren<MainWeapon>().ResetHeatUpRate();
        endAttackBoost = false;
    }

    if (GetComponent<InputManager>().GetInputType() == 0 || GetComponent<InputManager>().GetInputType() == 1 )
    {
        if (isSpeedBoosting == true)
        {
            Move(speedModifier);

        }
        else
        {

            Move(1f);
        }


        if (syncTimer <= 0f) {
            syncTimer = networkRefreshRate;
            SyncTransform ();
        } else {
            syncTimer -= Time.deltaTime;
        }

    }
    else
    {
        NetworkMove();

    }


}

/// <summary>
/// This function is constantly called to upload the local player's position to the server so the server is 
/// aware of this player's movement and can share this local players current position with other players in the game.
/// </summary>
public void SyncTransform() {

    if (isLocalPlayer == true && client != null && client.IsConnected()) {
        client.SendPlayerTransform(SharedData.storage["userId"], transform.position, transform.rotation, currentLife);
    }
}

Here is a sample of the UDP sender class in my game client:

public void SendPlayerTransform(string playerId, Vector3 position, Quaternion rotation, int currentLife) {

    Message.Writer writer = new Message.Writer(Message.MessageType.PlayerTransform, udpClient, remoteEndPoint);
    // Write the timestamp of this message
    writer.WriteLong(DateTime.UtcNow.Ticks);

    // write the player id
    writer.WriteString(SharedData.storage["userId"]);

    // write the position vector
    writer.WriteFloatArray(CommonGameFunctions.ConvertVectorToFloatArray(position));

    // write the rotation vector
    writer.WriteFloatArray(CommonGameFunctions.ConvertQuaternionToFloatArray(rotation));

    writer.WriteInt (currentLife);

    // Now send the message
    writer.Send();

}

Here is a sample of where I receive the UDP messages on the game client:

public int HandleUdpMessages() {
    if (udpTimerStarted == false) {
        lastUdpMessageReceivedTime = DateTime.Now;
        udpTimerStarted = true;
    } else if (udpTimerStarted == true && udpClient.Available == 0){
        TimeSpan t = DateTime.Now - lastUdpMessageReceivedTime;
        if (t.Seconds >= 10f) {
            // no message received for last 10 seconds then throw IO exception
            //throw new SocketException();
        }
    }

    if (udpClient.Available > 0) {
        var messageReader = new Message.Reader (udpClient);
        messageReader.BlockingRead (ref localEndPoint, UdpReceiveTimeout);
        var messageType = messageReader.ReadMessageTypeUdp ();

        lastUdpMessageReceivedTime = DateTime.Now;
        Debug.Log ("Received udp message: " + messageType);

        switch (messageType) {

        // Player position update message
        case Message.MessageType.PlayerTransform:
            HandlePlayerTransform (messageReader);
            break;
        case Message.MessageType.PlayerScore:
            HandlePlayerScore (messageReader);
            break;
        case Message.MessageType.RockHealth:
            HandleRockHealth (messageReader);
            break;
        case Message.MessageType.PlayerHealth:
            HandlePlayerHealth (messageReader);
            break;
        case Message.MessageType.ShieldHealth:
            HandleShieldHealth (messageReader);
            break;
        default:
            Debug.LogError ("Unhandled message " + messageType);
            break;

        }

    }

    return 0;

}
public void HandlePlayerTransform(Message.Reader reader)
{

    long timeStamp = reader.ReadLong ();
    string playerId = reader.ReadString();

    if (playerMessageTimeStamps [playerId].latestPlayerTransform > timeStamp)
        return;

    Vector3 position = new Vector3();
    Quaternion rotation = new Quaternion();

    // read position
    position = CommonGameFunctions.ConvertFloatArrayToVector3(reader.ReadFloatArray(3));

    rotation = CommonGameFunctions.ConvertFloatArrayToQuaternion(reader.ReadFloatArray(4));


    // Now update the transform of the right player

    Player player = Player.playerTable[playerId];

    if (player == null) {
        return;
    }

    player.SetNetworkPostion(position);
    player.SetNetworkRotation(rotation);
}

On my server this is the main game loop which runs on its own dedicated thread.

    // Now all the players are connected to the server
    // We can start the main game loop
    while (gameRunning == true) {

        HandlePlayersWhoDroppedOut ();

        if (PlayersLeftGame () == true) {
            DisconnectAllPlayers ();
            Debug.LogError ("Player's left match, returning from thread");
            return;
        } else {
            foreach (NetworkPlayer p in participants) {

                try {
                    p.HandleTcpMessages ();
                    p.HandleUdpMessages ();
                } catch (IOException e) {
                    droppedPlayers.Add (p);
                }
            }

            try {
                RespawnRocksIfDestroyed ();
            } catch (System.IO.IOException e) {
                DisconnectAllPlayers ();
                return;
                Debug.LogError ("Failed to spawn rocks");
            }
        }
    }
1
5 seconds is too much. You need to post a sample of your UDP code and where you are calling it from.Programmer

1 Answers

0
votes

So I figured out the problem with my code. I was reading exactly one udp message in each iteration of the udp message handler function. I changed my function to read ALL the available udp messages in the buffer and the lag reduced by 80%. UDP messages queue up in the buffer quicker than the message handler function repeats so this is why the problem was happening.