Sunday, 19 March 2017

Html5 Nodejs Socketio Multiplayer Game with bouncing balls

I am a beginner at multiplayer game development. I am trying to code a multiplayer game running on node.js socket.io html5. I have already the players which are created on server's side. Now I have a problem with creating the balls on server's side. Here's my scenario:

  1. Server creates an amount of balls
  2. Then it emits to all connected users
  3. Each user receives the balls
  4. Then the server tries to move the balls
  5. On each movement the server emits the new coordinates x,y of each ball to clients
  6. Client receives the balls and tries to draw them in the main loop

I can get the balls on each client's side but the balls are not changing their coordinates which means they are not updating the new values that the server had emitted before. Below is my code. If anyone could see it and tells me where I make mistake I would be so thankful.

the html code: simpleChat.html

<style>
#myCanvas {
  border: 1px solid red;
  background-color:pink;
}
</style>
<script src="jeu.js"></script>
<script src="/socket.io/socket.io.js"></script>

<script>
        var username = prompt("What's your name?");
        var conversation, data, datasend, users;

        var socket = io.connect();

        // on connection to server, ask for user's name with an anonymous callback
        socket.on('connect', function(){
                // call the server-side function 'adduser' and send one parameter (value of prompt)
                socket.emit('adduser', username);
        });

        // listener, whenever the server emits 'updatechat', this updates the chat body
        socket.on('updatechat', function (username, data) {
                var chatMessage = "<b>" + username + ":</b> " + data + "<br>";
                conversation.innerHTML += chatMessage;
        });

    // just one player moved
        socket.on('updatepos', function (newPos) {
    console.log('client: '+newPos.user +' has to update pos to ['+newPos.x+','+newPos.y+']');
                updatePlayerNewPos(newPos);
        });

        // listener, whenever the server emits 'updateusers', this updates the username list
        socket.on('updateusers', function(listOfUsers) {
                users.innerHTML = "";
                for(var name in listOfUsers) {
                var userLineOfHTML = '<div>' + name + '</div>';
                users.innerHTML += userLineOfHTML;
                }
        });

        // update the whole list of players, useful when a player
        // connects or disconnects, we must update the whole list
        socket.on('updatePlayers', function(listOfplayers) {
                updatePlayers(listOfplayers);
        });

        // on load of page
        window.addEventListener("load", function(){
                // get handles on various GUI components
                conversation = document.querySelector("#conversation");
                data = document.querySelector("#data");
                datasend = document.querySelector("#datasend");
                users = document.querySelector("#users");

                // Listener for send button
                datasend.addEventListener("click", function(evt) {
                sendMessage();
                });

                // detect if enter key pressed in the input field
                data.addEventListener("keypress", function(evt) {
                // if pressed ENTER, then send
                if(evt.keyCode === 13) {
                                this.blur();
                        sendMessage();
                }
                });

                // sends the chat message to the server
                function sendMessage() {
                        var message = data.value;
                        data.value = "";
                        // tell server to execute 'sendchat' and send along one parameter
                        socket.emit('sendchat', message);
                }
        });

</script>

<body onload="init();">
        <p>
<canvas width = 600 height = 400 id="myCanvas"></canvas>
<p>
<div style="float:left;width:100px;border-right:1px solid black;height:300px;padding:10px;overflow:scroll-y;">
        <b>USERS</b>
        <div id="users"></div>
</div>
<div style="float:left;width:300px;height:250px;overflow:scroll-y;padding:10px;">
        <div id="conversation"></div>
        <input id="data" style="width:200px;" />
        <input type="button" id="datasend" value="send" />
</div>

</body>
server's side server.js => in command prompt: node server.js

// We need to use the express framework: have a real web servler that knows how to send mime types etc.
var express = require('express');

// Init globals variables for each module required
var app = express()
        , http = require('http')
        , server = http.createServer(app)
        , io = require('socket.io').listen(server);

// launch the http server on given port
server.listen(8082);

// Indicate where static files are located. Without this, no external js file, no css...

app.use(express.static(__dirname + '/'));


// routing
app.get('/', function (req, res) {
    res.sendFile(__dirname + '/simpleChat.html');
});

// usernames which are currently connected to the chat
var usernames = {};
var listOfPlayers = {};
var canvas_d = {};
var oldTime = 0;
var joueurs = new Array();
var currentP = {};
var width = 600, height = 400;
var ballArray=[],ballArrayColor=[];
//var ballPos={};
function Balle(x, y, vx, vy, diameter, ballcol) {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.rayon = diameter / 2;
    this.bcolor=ballcol;
/*
    this.draw = function (ballcol) {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.rayon, 0, 2 * Math.PI);
        ctx.fillStyle = ballcol;
        ctx.fill();
//    console.log(ballcol);
    };*/
/*
    this.move = function () {

        this.x += this.vx;
        this.y += this.vy;
    };*/

}
function createBalls(numberOfBalls) {
    for (var i = 0; i < numberOfBalls; i++) {
        var balcolor = getRandomColor();
        // Create a ball with random position and speed
        var ball = new Balle(width * Math.random(),
                height * Math.random(),
                (10 * Math.random()) - 5,
                (10 * Math.random()) - 5,
                15, balcolor); // radius, change if ou like.

        // Add it to the array
        //ballArray[i] = ball;
        ballArray.push(ball);
        ballArrayColor[i] = balcolor;
        //console.log(i, balcolor);
    }

}
function getRandomColor() {
    var letters = '0123456789ABCDEF';
    var color = '#';
    for (var i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}
function getMax(arr) {
    var maxX = 0;
    var maxY = 0;
    for (var i in arr) {
        if (i !== null) {
            console.log('i not null kaj getMax');
            if (maxX <= arr[i].x) {
                maxX = arr[i].x + arr[i].width;
                if (maxX >= (canvas_d.width - arr[i].width)) {
                    maxX = arr[i].x;
                    if (maxY <= arr[i].y) {
                        maxY = arr[i].y + arr[i].height;
                    }
                }
            }

        } else {
            maxX = arr[i].x;
            maxY = arr[i].y;
        }


    }
    return {'maxX': maxX + 0.5, 'maxY': maxY + 0.5};
}


function checkCollisionWithAnotherPlayer(arr, usr, currentP) {

    for (var name in arr) {
        console.log('other player: ' + name);
        if (usr !== name) {
            if ((arr[usr].x < (arr[name].x + arr[name].width))
                    && ((arr[usr].x + arr[usr].width) > arr[name].x)
                    && ((arr[usr].y + arr[usr].height) > arr[name].y)
                    && (arr[usr].y < (arr[name].y + arr[name].height))) {
                console.log('collision');
                listOfPlayers[usr].x = currentP.x;
                listOfPlayers[usr].y = currentP.y;
            }

        }
    }
}

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

    // when the client emits 'sendchat', this listens and executes
    socket.on('sendchat', function (data) {
        // we tell the client to execute 'updatechat' with 2 parameters
        io.sockets.emit('updatechat', socket.username, data);
    });


    socket.on('canvas', function (d) {
        canvas_d.width = d.width;
        canvas_d.height = d.height;
    });
    // when the client emits 'sendupdate', this listens and executes
    socket.on('sendupdate', function (input) {
        console.log('socket on sendupdate from ' + input.user);
        currentP = {'x': listOfPlayers[input.user].x, 'y': listOfPlayers[input.user].y};
        oldTime = input.lastMove;
        var now = Date.now();
        console.log('now ' + now);
        //calculating delta time
        var delta = (now - oldTime) / 1000;
        console.log('delta ' + delta);
        listOfPlayers[input.user].speedX = listOfPlayers[input.user].speedY = 0;
        if (input.inputStates.left) {
            listOfPlayers[input.user].speedX = -listOfPlayers[input.user].v;
        } else if (input.inputStates.up) {
            listOfPlayers[input.user].speedY = -listOfPlayers[input.user].v;
        } else if (input.inputStates.right) {
            listOfPlayers[input.user].speedX = listOfPlayers[input.user].v;
        } else if (input.inputStates.down) {
            listOfPlayers[input.user].speedY = listOfPlayers[input.user].v;
        }
        listOfPlayers[input.user].x += delta * listOfPlayers[input.user].speedX;
        listOfPlayers[input.user].y += delta * listOfPlayers[input.user].speedY;

        //player restriction
        if (listOfPlayers[input.user].x < 0)
            listOfPlayers[input.user].x = 0;
        //up wall
        if (listOfPlayers[input.user].y < 0)
            listOfPlayers[input.user].y = 0;
        //down wall
        if (listOfPlayers[input.user].y + listOfPlayers[input.user].height > canvas_d.height)
            listOfPlayers[input.user].y -= delta * listOfPlayers[input.user].speedY;
        //right wall
        if (listOfPlayers[input.user].x + listOfPlayers[input.user].width > canvas_d.width)
            listOfPlayers[input.user].x -= delta * listOfPlayers[input.user].speedX;

        //collision detection with other players
        checkCollisionWithAnotherPlayer(listOfPlayers, input.user, currentP);
        console.log('socket emits to all players the updated position of player: ' + input.user + ' to position: ' + listOfPlayers[input.user].x + ' , ' + listOfPlayers[input.user].y);
        var newPos = {'user': input.user, 'x': listOfPlayers[input.user].x, 'y': listOfPlayers[input.user].y};
        io.sockets.emit('updatepos', newPos);

    });
    //when client emits 'drawBalls' the server will try to move each ball
    socket.on('drawBalls',function(){
        for(var i=0;i<ballArray;i++){
            ballArray[i].x += ballArray[i].vx+10;
            ballArray[i].y += ballArray[i].vy+10;
        }
        var ballPosit={
            'balls':ballArray,
            'colors':ballArrayColor
        };
        socket.emit('updateBallPos',ballPosit);
    });

    // when the client emits 'adduser', this listens and executes
    socket.on('adduser', function (username) {
        // we store the username in the socket session for this client
        // the 'socket' variable is unique for each client connected,
        // so we can use it as a sort of HTTP session
        socket.username = username;
        // add the client's username to the global list
        // similar to usernames.michel = 'michel', usernames.toto = 'toto'
        usernames[username] = username;

        // echo to the current client that he is connecter
        socket.emit('updatechat', 'SERVER', 'you have connected');
        // echo to all client except current, that a new person has connected
        socket.broadcast.emit('updatechat', 'SERVER', username + ' has connected');
        // tell all clients to update the list of users on the GUI
        io.sockets.emit('updateusers', usernames);

        // Create a new player and store his position too... for that
        // we have an object that is a "list of players" in that form
        // listOfPlayer = {'michel':{'x':0, 'y':0, 'v':0},
        //                                                      john:{'x':10, 'y':10, 'v':0}}
        // for this example we have x, y and v for speed... ?
        var player = {'x': 10, 'y': 10, 'v': 200, 'height': 100, 'width': 100, 'lastUpdate': null, 'name': username};
        var newPlayer;

        newPlayer = getMax(listOfPlayers);
        player.x = newPlayer.maxX;
        player.y = newPlayer.maxY;
        console.log(newPlayer.maxX + ' - ' + newPlayer.maxY);


        joueurs.push(player.username);

        listOfPlayers[username] = player;
        io.sockets.emit('updatePlayers', listOfPlayers);
        
        //creation of balls
        if(joueurs.length===1){
            createBalls(2);
        }
        console.log('taille of joueurs; '+joueurs.length);
        var ballPos={
            'balls':ballArray,
            'colors':ballArrayColor,
            'user':username
        };
       // socket.emit('createBalls',ballPos);
    });

    // when the user disconnects.. perform this
    socket.on('disconnect', function () {
        var index = joueurs.indexOf(socket.username);
        joueurs.splice(index, 1);
        // remove the username from global usernames list
        delete usernames[socket.username];
        // update list of users in chat, client-side
        io.sockets.emit('updateusers', usernames);

        // Remove the player too
        delete listOfPlayers[socket.username];
        io.sockets.emit('updatePlayers', listOfPlayers);

        // echo globally that this client has left
        socket.broadcast.emit('updatechat', 'SERVER', socket.username + ' has disconnected');
    });
});
client's side. jeu.js

var canvas, ctx, w, h;
var ballArray = [], ballArrayColor = [];
// for time based animation

// vars for handling inputs
var inputStates = {};

// Autres joueurs
var allPlayers = {};
var now;
function init() {
    console.log("init");
    canvas = document.querySelector("#myCanvas");
    // often useful
    w = canvas.width;
    h = canvas.height;
    ctx = canvas.getContext('2d');
    var canvasDimension = {'width': w, 'height': h};
    socket.emit('canvas', canvasDimension);
    // Les écouteurs
    //canvas.addEventListener("mousedown", traiteMouseDown);
    //canvas.addEventListener("mousemove", traiteMouseMove);
    //createBalls(2);
    socket.on('createBalls',function(data){
        ballArray=data.balls;
        ballArrayColor=data.colors;
    });
    
    window.addEventListener('keydown', movePlayer, false);


    anime();

}



function movePlayer(evt) {
    inputStates.left = false;
    inputStates.up = false;
    inputStates.right = false;
    inputStates.down = false;
    if (evt.keyCode === 37) {
        inputStates.left = true;
    } else if (evt.keyCode === 38) {
        evt.preventDefault();
        inputStates.up = true;
    } else if (evt.keyCode === 39) {
        inputStates.right = true;
    } else if (evt.keyCode === 40) {
        evt.preventDefault();
        inputStates.down = true;
    }
    now = Date.now();
    var input = {'user': username, 'inputStates': inputStates, 'lastMove': now};
    console.log('player' + username + ' wants to move with inputStates: ' + inputStates.up + ' ; ' + inputStates.down + ' ; ' + inputStates.left + ' ; ' + inputStates.right);
    socket.emit('sendupdate', input);
}

function updatePlayerNewPos(newPos) {
    for (var i in allPlayers) {
        if (i === newPos.user) {
            allPlayers[i].x = newPos.x;
            allPlayers[i].y = newPos.y;
            console.log('I have just moved!->  ' + newPos.x + ' ; ' + newPos.y);
        }

    }

}

// Mise à jour du tableau quand un joueur arrive
// ou se deconnecte
function updatePlayers(listOfPlayers) {
    allPlayers = listOfPlayers;
}

function drawPlayer(joueur) {
    // save the context
    ctx.save();
    // draw a red rectangle
    // head
    ctx.fillStyle = 'yellow';
    ctx.fillRect(joueur.x, joueur.y, 100, 100);
    ctx.fillStyle = "grey";
    ctx.fillRect(joueur.x, joueur.y, 100, 10);
    //eyebrows
    ctx.fillStyle = 'black';

    ctx.fillRect(joueur.x + 5, joueur.y + 15, 20, 5);
    ctx.fillRect(joueur.x + 75, joueur.y + 15, 20, 5);

    // eyes
    ctx.fillStyle = 'lightblue';
    ctx.fillRect(joueur.x + 10, joueur.y + 25, 10, 10);
    ctx.fillRect(joueur.x + 80, joueur.y + 25, 10, 10);
    // interior of eye
    ctx.fillStyle = 'blue';
    ctx.fillRect(joueur.x + 13, joueur.y + 27, 5, 5);
    ctx.fillRect(joueur.x + 83, joueur.y + 27, 5, 5);
    // Nose
    ctx.fillStyle = 'brown';
    ctx.fillRect(joueur.x + 50, joueur.y + 35, 8, 15);
    //Moustaches
    ctx.fillStyle = 'grey';
    ctx.fillRect(joueur.x + 43, joueur.y + 63, 25, 5);
    // Mouth
    ctx.fillStyle = 'red';
    ctx.fillRect(joueur.x + 35, joueur.y + 70, 40, 20);
    //teeth
    ctx.fillStyle = 'white';
    ctx.fillRect(joueur.x + 38, joueur.y + 73, 10, 15);
    ctx.fillRect(joueur.x + 50, joueur.y + 73, 10, 15);
    ctx.fillRect(joueur.x + 62, joueur.y + 73, 10, 15);
    // ctx.strokeText(joueur.name,joueur.x+50,joueur.y+50);
    ctx.restore();
}

function drawAllPlayers() {
    for (var name in allPlayers) {
        drawPlayer(allPlayers[name]);
    }
}

function getMousePos(canvas, evt) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: evt.clientX - rect.left,
        y: evt.clientY - rect.top
    };
}

function anime() {
    if (username !== undefined) {
        // 1 On efface l'écran
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 2 On dessine des objets
        drawAllPlayers();
          socket.on('updateBallPos',function(data){
        console.log('=== updating the pos of balls');
        ballArray=[];
        ballArrayColor=[];
        ballArray=data.balls;
        ballArrayColor=data.colors;
        
        
    });
    //drawing the balls
    for (var i = 0; i < ballArray.length; i++) {
        var balle = ballArray[i];
        var bc = ballArrayColor[i];
        console.log(balle.x, balle.y, bc);
        // 1) Move the ball
       // balle.move();

        // 2) collision test with walls
        //collisionTestWithWalls(balle);

        // 3) draw the ball
       // balle.draw(bc);
       ctx.beginPath();
        ctx.arc(balle.x, balle.y, balle.rayon, 0, 2 * Math.PI);
        ctx.fillStyle = bc;
        ctx.fill();
    }
    socket.emit('drawBalls','balls are drawn');
    }/*
    socket.on('updateBallPos',function(data){
        console.log('=== updating the pos of balls');
        ballArray=[];
        ballArrayColor=[];
        ballArray=data.balls;
        ballArrayColor=data.colors;
        
        
    });
    for (var i = 0; i < ballArray.length; i++) {
        var balle = ballArray[i];
        var bc = ballArrayColor[i];
        console.log(balle.x, balle.y, bc);
        // 1) Move the ball
       // balle.move();

        // 2) collision test with walls
        //collisionTestWithWalls(balle);

        // 3) draw the ball
       // balle.draw(bc);
       ctx.beginPath();
        ctx.arc(balle.x, balle.y, balle.rayon, 0, 2 * Math.PI);
        ctx.fillStyle = bc;
        ctx.fill();
    }
    socket.emit('drawBalls','balls are drawn');
*/
    //collisionTestBetweenBalls();
    // 4 On rappelle la fonction d'animation à 60 im/s

    requestAnimationFrame(anime);
}


via P Richmond

No comments:

Post a Comment