1
\$\begingroup\$

I've built a Server / Client environment for my Unity game which is pretty simple. It is written in C# with asynchronous sockets. To keep it simple, the player sends his input data and the server broadcasts it to every connected player.

My message packets have to the following strucutre:

Username|Command|Scene ID|X Position|Y Position|Horizontal Axis Input|Vertical Axis Input

An example packet for player movement / position data would look like this:

PlayerA|P|0|0.12341123|1.3423424|-1|0

My first approach was to sent the data every frame. As you can imagine that was too often and the game was laggy with only two players moving simultaneously. Then I've read about interpolation. So I reduced sending the data. I now send only position data if a certain distance was walked or after a certain amount of milliseconds. The rest is calculated via interpolation. Unfortunately I got my interpolation code from a tutorial using the Unity multiplayer functionality.

Now the result I get is that the player kind of jumps. I have a guess why. The player sends his position when he reached it. I think that is too late. But I have no idea how I could fix that problem.

I have two objects. A player object, which is using the script PlayerMovement and a prefab which is using the CharacterMovement for in game movement. I think these two scripts are the important once to help me, so here they are:

PlayerMovement.cs

using System;
using UnityEngine;
using UnityEngine.SceneManagement;

public class PlayerMovement : MonoBehaviour
{
    Rigidbody2D Body;
    Animator Animator;

    private bool sendStopWalking = false;
    public float Threshold = 0.5f;

    DateTime lastSentDateTime = DateTime.Now;

    private Vector3 lastSentPosition;

    private void Awake()
    {
        DontDestroyOnLoad(transform.gameObject);
    }

    void Start()
    {
        Body = GetComponent<Rigidbody2D>();
        Animator = GetComponent<Animator>();
    }

    void Update()
    {
        // GetAxisRaw Returns 0 / 1 / -1
        Vector2 movementVector = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));

        if(movementVector != Vector2.zero)
        {
            Animator.SetBool("IsWalking", true);
            Animator.SetFloat("InputX", movementVector.x);
            Animator.SetFloat("InputY", movementVector.y);

            if (NetworkManagement.IsConnected)
            {
                Vector3 currentPosition = Body.transform.position;
                if(isOverThreshold(lastSentPosition, currentPosition, Threshold) || isTimeToSend(500))
                {
                    lastSentDateTime = DateTime.Now;
                    NetworkManagement.ClientSocket.Send(string.Format("P|{0}|{1}|{2}|{3}|{4}", SceneManager.GetActiveScene().buildIndex, currentPosition.x, currentPosition.y, Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")));
                    lastSentPosition = Body.transform.position;
                    sendStopWalking = true;
                }

            }
        }
        else
        {
            Animator.SetBool("IsWalking", false);

            if(sendStopWalking && NetworkManagement.IsConnected)
            {
                NetworkManagement.ClientSocket.Send(string.Format("P|{0}|{1}|{2}|{3}|{4}", SceneManager.GetActiveScene().buildIndex, Body.transform.position.x, Body.transform.position.y, Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")));
                sendStopWalking = false;
            }
        }

        Body.MovePosition(Body.position + movementVector * Time.deltaTime);
    }

    private bool isOverThreshold(Vector3 oldPosition, Vector3 currentPosition, float threshold)
    {
        return Vector3.Distance(oldPosition, currentPosition) > threshold;
    }

    private bool isTimeToSend(double milliseconds)
    {
        return ((DateTime.Now - lastSentDateTime).TotalMilliseconds > milliseconds);
    }
}

CharacterMovement.cs

using UnityEngine;

public class CharacterMovement : MonoBehaviour
{
    Rigidbody2D Body;
    Animator Animator;
    public float AxisRawHorizontal;
    public float AxisRawVertical;

    public Vector3 ServerPosition;

    public float LerpValue = 4;

    void Start()
    {
        Body = GetComponent<Rigidbody2D>();
        Animator = GetComponent<Animator>();
    }

    void Update()
    {
        Vector2 movementVector = new Vector2(AxisRawHorizontal, AxisRawVertical);

        if (movementVector != Vector2.zero)
        {
            Animator.SetBool("IsWalking", true);
            Animator.SetFloat("InputX", movementVector.x);
            Animator.SetFloat("InputY", movementVector.y);
        }
        else
        {
            Animator.SetBool("IsWalking", false);
        }
        interpolate();

        //Body.MovePosition(Body.position + movementVector * Time.deltaTime);
    }

    private void interpolate()
    {
        Vector3 position = Vector3.Lerp(this.transform.position, ServerPosition, Time.deltaTime * LerpValue);
        this.transform.position = position;
    }
}

I seriously have no more idea and unfortunately I never have been good in maths and physics, so I hope someone can help me here. If there is any more information needed, please tell me. I tried to reduce the question to the most important things.

\$\endgroup\$
1
  • \$\begingroup\$ OK so in the meantime I changed the server / client, so that the server broadcasts message not immediately when they are received. Instead, the server broadcasts the message every 33 ms. The client sends every 50 ms. The movement is now better but still kind of laggy and when two players walk the same path, the local player arrives the destination first. \$\endgroup\$
    – hapablap
    Commented May 8, 2017 at 22:00

1 Answer 1

1
\$\begingroup\$

Before starting,

Period: The time it takes to complete one full turn of a repetitive event. So, Period = 1/Frequency.


The method you are using can work pretty robust for simple movement, but when it comes to special actions like jumping, casting spells, attacking an enemy etc; you need to consider a question:

Does the period of the action is greater than the communication period? (That is; is the time it takes for the action to complete, greater than the time between two network packets we send?)

If period of movement is lower than period of your messages, than you cannot rely on a regular broadcast, because as you mentioned in your question, the action will actually end before you can send a second packet of information.

So, you can force a packet send at the very beginning of the action (and maybe also at the end, depending on the type of action). This way, you can notify the server that you are "jumping", instead of just saying "I am floating in the air".

Then, when you receive the action in the client, instead of interpolating between two positions, you can actually play the "jump" action on the character. This way you can actually make the remote character jump, just like the way the local character does, instead of ascending it to mid air via interpolation. And when the remote character hits the ground, then you can simply continue to interpolate.

If you want more precision, you can interpolate the "heading" of remote character mid-air, so that it doesn't land somewhere far far away fron the actual landing point.


And lastly, I suggest you to follow MSDN naming conventions for C# in your code. It will make your coding life really easier.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .