Syncing NPC behaviour in a game. Using socket.io/node.js. Unity2D

Viewed 998

I am making a 2d topdown with 3d elements game in unity. It is a multiplayer game and the way I am setting it up is by having a master client (server) that relays information to a backend server through socket.io/ node.js.

I have been able to sync the movement of players, their healthbars and rotationgs and the flipping of their sprites with a great measure of suceess considering it is the first time I try such a feat.

However now I am venturing into make monsters on this server and I run into a problem I did not have with the syncing the players. The way I did them was to have the player controllers relay information to the backend server which then gets broadcast to the other players. But with NPCs I have simply attached a controller to the spawned mob, that performs actions on the "server" client which is then relayed to all of the clients in order to ensure that everyone "agrees" on what is happening and when.

This seems to work very well if there is only 1 monster but the second I add 2 or more they are obviously conflicting their information. So I see I cannot do it the same I did with the players. And for this I require some feedback if at all possible.

The signals are sent as follows:

command to spawn monster for test purposes:

    void CreateMonster()
{
    var selectedSpawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)];

    var spawnedEnemy = Instantiate(Enemy[Random.Range(0, Enemy.Length)], selectedSpawnPoint.transform.position, selectedSpawnPoint.transform.rotation);

    Debug.Log("im at " + spawnedEnemy.transform.position);

    socket.Emit("enemySpawned", Network.VectorToJson(spawnedEnemy.transform.position));
}

server receives that signal:

socket.on('enemySpawned', function(data){

        var thisEnemyId = shortid.generate(); 
        var currentTarget = '';        

        var Enemy = {                                              
                    id: thisEnemyId, x:0, y:0, target:currentTarget,  
        };

        Enemies[thisEnemyId] = Enemy;

        Enemy.x = data.x;
        Enemy.y = data.y;

        data.id = thisEnemyId;

        console.log("enemySpawned position: ", data);

    socket.broadcast.emit('enemySpawned', data);

back to the server and relayed to the spawner:

    public void SpawnEnemy(SocketIOEvent e)
{
    var id = e.data["id"].str;

    var spawnPosition = new Vector3(e.data["x"].n, e.data["y"].n, 0);

    var enemy = Instantiate(orcShaman, spawnPosition, Quaternion.identity) as GameObject;

    enemy.name = "Enemy " + id;

    enemies.Add(id, enemy);
}

so far so good. everything is working fine and the clients are seeing the mob in the correct position at the correct time.

Monster moves his position after aggroing a player:

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

    void CheckForMove()
{
    if (lastPos != transform.position)
    {
        socket.Emit("enemyMove", Network.VectorToJson(transform.position));

        lastPos = transform.position;
    }
}

Server receives information to move:

            socket.on('enemyMove', function(data){



        Enemy.x = data.x;                                          
        Enemy.y = data.y;

        data.id = thisEnemyId;

            console.log('enemy is moving', data);

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

    });

game receives the data:

    private void OnEnemyMove(SocketIOEvent e)
{
        var position = new Vector3(e.data["x"].n, e.data["y"].n, 0);

        var enemy = enemySpawner.FindEnemy(e.data["id"].str);

        var enemyNavigator = enemy.GetComponent<EnemyNavigator>();

        enemyNavigator.MoveThisEnemy(position);
}

the individual monster should now receive the information to move to said position:

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

now this all works wonderfully for only 1 monster, but with 2 all hell breaks loose and they start to look like they are standing the same place and only 1 is shown at a time jumping back and forth between them. which makes me think that the server keeps moving one at a time and cant do them seamlessly at the same time. Even tho the command promt is showing their data being sent very well 10 times a second.

Im unsure why this happens and I am wondering if I have maybe fundamentally misunderstood something. Perhaps I need to redesign my communication with the server.

Any feedback would be wonderful and I am relatively new to all of this but I have built all of this up with just a few tuturials and then kinda winging from there on my own so please go easy on me if this looks like amateur hour at the apollo =(

thanks for reading.


I have narrowed the problem down and made the client able to see all the spawned mobs and their locations. However when moving the same thing happens. I am quite confident the problem arrises because I am storing the Identifying numbers for the individual monsters wrong and sending this data wrong. I dont know the correct way to do this so I am linking my server code here in hopes that someone does, and can indicate what seems to cause it:

var express  = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);

var shortid = require('shortid');
app.use(express.static(__dirname));


var players = [];


io.on('connection', function(socket){

var thisPlayerId = shortid.generate(); 
var maxHealthValue = 0;
var currentHealthValue = 10;

var player = {                                              
    id: thisPlayerId, x:0, y:0, currentHealth:currentHealthValue, maxHealth: maxHealthValue, UpSideDown:0   
};


players[thisPlayerId] = player;

console.log('client connected', thisPlayerId);
console.log('list of current players: ', players);

socket.emit('register', { id: thisPlayerId });
socket.broadcast.emit('otherPlayerConnected', player);


for(var everyoneButMe in players){
    if(everyoneButMe == thisPlayerId)    // for everyone but me
        continue;

    console.log('not a main player joining');
    socket.emit('otherPlayerConnected', players[everyoneButMe]);
    socket.emit('register', { id: thisPlayerId });
    socket.broadcast.emit('requestData');
}


for(var mainPlayer in players){
    if(mainPlayer == thisPlayerId){            // for me
        console.log('main player joining');
        socket.emit('spawn', player);

    }
};





    // bunch of functions for the players which work very well removed to make this more viser friendly



    // Section for Enemy logic from Master Client
    // This is where the problems arrise

        var Enemies = [];                                   // Array to store Enemies
        var currentTarget = ''; 
        var Enemy = {                                              
            id: thisEnemyId, x:0, y:0, target:currentTarget,  
        };

        var thisEnemyId = 0;



    socket.on('enemySpawned', function(data){

        thisEnemyId = Enemies.length ++        

        Enemies[thisEnemyId] = Enemy;

        Enemy.x = data.x;
        Enemy.y = data.y;

        data.id = thisEnemyId;

        console.log("enemySpawned position: ", data, thisEnemyId);

    socket.broadcast.emit('enemySpawned', data);

   });

    socket.on('enemyMove', function(data){



        Enemy.x = data.x;                                          
        Enemy.y = data.y;

        data.id = thisEnemyId;

        console.log('enemy is moving', data);

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

    });


 });
 http.listen(process.env.PORT || 3000, function(){;
 console.log('listening on *:3000');

 });

 console.log('-----------server started----------');
1 Answers
Related