0
\$\begingroup\$

I am building a 2D top-down MMO that's very lightweight.

I have a master client that determines everything's position and actions, and sends this information to all other clients.

Now I have made a great deal of different solutions to the movement of my NPCs across the network, and some solutions are better than others, but none have been perfect. So I would love some feedback on this.

Master client sends information to server:

 void Start () {
    InvokeRepeating("CheckForMove", 1.0f, 1f);
 }

 void CheckForMove()
 {
    var distance = Vector3.Distance(lastPos, transform.position);
    if (distance > 0.02)  // Dont send information if movement is very small i.e bumping into a wall repeatedly etc
    {
        socket.Emit("enemyMove", Network.MobIdAndLocationToJson(gameObject.name, transform.position));
        moveEmit = true;
        lastPos = transform.position;
        spawnScript.spawnedEnemies[gameObject.name] = transform.position;
    }
}

Server reveives the information:

socket.on('enemyMove', function(data){
    
    // moves the monster that matches the given ID        
    console.log('enemy is moving', data);

    socket.broadcast.emit('enemyMove', data);    
});

Player Clients receive the move information:

private void OnEnemyMove(SocketIOEvent e)
{
        var position = new Vector3(e.data["x"].n, e.data["y"].n, -0.5f);
        var enemy = enemySpawner.FindEnemy(e.data["id"].str);
        var enemyNavigator = enemy.GetComponent<EnemyNavigator>();
        enemyNavigator.MoveThisEnemy(position);
}

Now the question becomes: what is the best way to go from here to smooth this movement as best I can?

The solutions I have tried are as follows:

------------------ solution 1 ----------------------

public void MoveThisEnemy(Vector3 position)
{
    anim.SetBool("walk", true);

    startTime = Time.time;
    DistanceToDest = Vector3.Distance(transform.position, position);
    
    destPos = position;
    isMoving = true;
}

and in update:

if (isMoving)
{
    if (currentTarget != null)
    {
        float currentDuration = Time.time - startTime;
        float journeyFraction = currentDuration / totalDistanceToDestination;
        transform.position = Vector3.Lerp(transform.position, destPos, journeyFraction);
    }
}

Although this works fine, the NPCs' movement is wonky: it finishes its movement faster than the next update can come no matter what, so despite lerping, it seems to move faster than I can provide it with updates. Imagine a cartoon slug that moves fast, stops, moves again, stops. So the NPC is always there and not just teleporting but still not the effect I am looking for.

------------------ solution 2 ----------------------

Here I simply try to just reproduce on the player client what I am doing on the master client but I make sure to correct the position of the NPC ever so often to guarantee its in the correct position.

public void MoveThisEnemy(Vector3 position)
{
    anim.SetBool("walk", true);
    transform.position = position;
    isMoving = true;
}

and in update:

if (isMoving)
{
    if (currentTarget != null)
    {
        var targetDistance = Vector3.Distance(transform.position, currentTarget.transform.position);

        if (targetDistance > minDist)
        {
            Vector3 direction = (currentTarget.transform.position - transform.position).normalized;
            rigid.MovePosition(transform.position + direction * speed * Time.deltaTime);
        }
    }
}

This is actually a very good solution as I can get the position correct with minimal amounts of data sent over the network. But since I have obstacles in my game, it gets very weird as moving the NPC's transform doesn't include collisions and it can perform weird movements when colliding. Furthermore, it seems to teleport to its position because of the transform.position updates when playing in full screen. But that's barely visible in smaller resolution for some reason.

I have also tried using rigidbody.MovePosition in the MoveThisEnemy function, but this just means it doesn't get updated correctly - it seems to be less precise than transform.position.

Any feedback would be greatly appreciated.

\$\endgroup\$

2 Answers 2

0
\$\begingroup\$

First of all, I might missunderstanded your question but it looks like your clients are updating the mobs position, this shouldn't happen. Game logic should be managed by the master server for several reasons from security (dont trust the client, ever) to math consistency (diferent platforms, different float precision)

About the smooth movement, a widely used approach is using movement prediction, checkpoints and interpolation.

Movement prediction

If object A that is in P point moves in V direction at S speed, you know that, in 10 frames, that object A will be 10*S units from P in V direction.

Knowing this, you can say from your server to your clients to move #12742 mob in (0.3, 1, 0.1) direction at speed 3 until point X is reached (f.e.), then, your clients can predict and update the position based on the server input.

Checkpoints

Your mob moves dumbly for a given values from server, but if your server keep sending position and movement updates every server tick the client can update the position and the movement stuff with the correct one.

Interpolation

This could lead to bumping, to fix it, you want to artificially lag a little the game and interpolate the position and movement between the last two recieved updates, by doing this and averaging with the last updates (and taking latency in mind) you can achieve smooth movement.

\$\endgroup\$
1
  • \$\begingroup\$ Hi and thanks for your input. In solution 1, I dont think I am allowing clients to determine NPC movement. all they are doing is positioning the NPC based on the information sent from the master client through the server. I understand that I could make player movement is a similar way by sending the "I am pressing down the move right key" to the master client and then simply have the server determine where they actually are in the world and then update it once they stop moving makeing the server authorative. I need a way to update the client with some smoothe movement for the NPCs. \$\endgroup\$
    – Svp
    May 26, 2017 at 9:43
0
\$\begingroup\$

Your interpolation in solution 1 is completely wrong.

  • You're dividing a time by a distance, which gets you a nonsensical value in units of seconds-per-meter, not appropriate to use as an interpolation blend factor.

  • You're lerping from the current position to the destination, not from the start position. That gives you a Zeno's paradox-style ease-out movement, where you move x% of the distance one frame, then x% of the remainder the next, then x% of the remaining remainder...

  • You're relying on absolute timestamps, which will function worse the longer your game is running.

This would be a more correct approach:

float progressSinceUpdate;
const float SERVER_UPDATES_PER_SECOND = 1f/INVOKE_REPEATING_DELAY;
Vector3 startPos;

public void MoveThisEnemy(Vector3 position)
{
    anim.SetBool("walk", true);

    profressSinceUpdate = 0f;
    
    startPos = transform.position;
    destPos = position;
    isMoving = true;
}

and in update:

if (isMoving)
{
    if (currentTarget != null)
    {
        progressSinceUpdate += Time.deltaTime * SERVER_UPDATES_PER_SECOND;

        if (progressSinceUpdate >= 1f) {
            progressSinceUpdate = 1f;
            isMoving = false;
        }

        transform.position = Vector3.Lerp(startPos, destPos, progressSinceUpdate);
    }
}

This will move in a straight line at a constant speed to reach the destination about the time that the next server update should arrive if the object is moving continuously.

Currently, if a packet is late, it will stop at its destination and wait. Alternatively, you can allow it to overshoot, assuming it will keep its current velocity, which can look smoother. That will require implementing an explicit "stop" signal so you can distinguish "next movement packet might just be late, keep moving" from "movement is finished".

\$\endgroup\$

You must log in to answer this question.

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