将多个套接字实例绘制到 canvas 并在移动时更新(多人游戏)

Drawing multiple Socket Instances to canvas and update on Move (Multiplayer)

计划

我决定开始尝试 Node.js 和 Socket.io 并着手制作一款游戏(或者只是一个将每个玩家绘制并显示为一个块的大厅)。每次客户端加入时,服务器都应该获取 class 并实例化一个新对象,其中包含服务器提供的播放器详细信息(随机)。

然后服务器将使用 socket.io 并将新对象发送给连接的客户端,这样他们就可以拥有该对象,然后可以使用它做一些事情(在本例中为 move)。

客户端首先要做的是将所有用户拉到canvas,然后将当前客户端也拉到canvas。


问题

我已经到了工作正常的地步,服务器创建了一个新的播放器对象,然后将其发送给客户端,然后客户端将能够使用属性将自己绘制到 canvas,但是在我移动播放器之前,我似乎无法让客户端被发送到其他用户的 canvas 上。

当我打开多个浏览器选项卡时,它会按预期方式绘制客户端,如下所示:

但是当我移动其中一个时,一个客户确实成功地吸引了其他客户,但其他客户都失去了那一刻给他们的对象,直到他们移动然后它成功地移动到其他地方但是然后其他客户端都失去了屏幕状态,您可以在其中看到它们的位置。简而言之,他们都在屏幕上,并相应地移动到所有浏览器中的正确位置,但是当它发生时你永远看不到所有玩家它一次只显示一个玩家,那就是那个移动的人(在你第一次加入你的地方之后也只能看到自己,直到有人移动,然后你才能看到所有玩家,只有最后移动,直到有人或你移动)。 查看下图

上面显示我聚焦中间选项卡并移动,日志显示发生了什么(注意其他玩家都消失了,只显示移动的玩家)


代码

我只有 2 个正在使用的文件,它们的填充如下。

服务器-nodejs

var express = require('express'),
    app = express(),
    http = require('http').createServer(app),
    io = require('socket.io').listen(http);

http.listen(8000);

//server connections and routing
app.use(express.static(__dirname + '/public'), function(request, response){
    if(request.url !== '/public') {
        response.sendFile( __dirname +'/error/index.html');
        console.log('Error 404 request, User tried accessing: ' + __dirname + request.url);
    }
});

var players = [];

//Lets create a function which will help us to create multiple players
function newPlayer() {
    this.name;
    this.id = 1;
    this.x = Math.random() * 500;
    this.y =  Math.random() * 500;
    //Random colors
    var r = Math.random()*255>>0;
    var g = Math.random()*255>>0;
    var b = Math.random()*255>>0;
    this.color = "rgba(" + r + ", " + g + ", " + b + ", 0.5)";

    //Random size
    this.radius = Math.random()*20+20;
    this.speed =  5;

    return {'name' : this.name,"x" : this.x,"y" : this.y,"color" : this.color, "radius" : this.radius,"speed" : this.speed}
}

//calls to the server and tracking connection of each new user
io.sockets.on('connection', function(socket){
    var currentPlayer = new newPlayer(); //new player made
    players.push(currentPlayer); //push player object into array

    //create the player
    socket.broadcast.emit('createUsers', players);
    socket.emit('currentUser', currentPlayer);

    //user moved
    socket.on('moved', function(data){
        console.log(data);
        socket.broadcast.emit('moving', data);
    });

    //disconnected
    socket.on('disconnect', function(){
        var i = players.indexOf(currentPlayer);
        players.splice(i, 1);
        socket.broadcast.emit('user left','User: ' + currentPlayer.name + ' Left');
        console.log(players);
    });

});

console.log('NodeJS Server started on port 8000...');

客户端 - HTML

<!doctype html>
<html>
<head>
    <title>Game Dev JS - 1</title>
    <link href="./ui.css" rel="stylesheet">
</head>
<body>
    <canvas id="canvas" width="500" height="500">
        Your Browser Does Not Support Canvas and HTML5
    </canvas>
</body>
    <script type="text/javascript" src="/socket.io/socket.io.js"></script>
    <script type="text/javascript" src="/js/engine.js"></script>

</html>

客户端 - CSS

body{
    margin: 0 auto;
    width: auto;
    background: #fff;
    text-align: center;
}

#canvas {
    margin: 15px;
    background: #000;
}

客户端 - Javascript

var socket = io.connect(); //add socket object

//initializing the canvas
var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext('2d'),
    W = window.innerWidth,
    H = window.innerHeight;

var keys = {};

window.addEventListener('keydown', function(e){
    keys[e.keyCode] = true; 
}, false);

//check if ke is not being pressed or has lifted up
window.addEventListener('keyup', function(e){
    delete keys[e.keyCode];
}, false);

socket.on('currentUser', function(newUser){
    ctx.fillStyle = newUser.color;
    ctx.fillRect(newUser.x, newUser.y, 25, 25);

    function update(){
        //moving player
        if(keys[38]){
            newUser.y -= newUser.speed;
            socket.emit('moved', newUser);
            console.clear();
            console.log('You Moving');
            console.log(newUser);
            ctx.clearRect(0, 0, W, H);
            ctx.fillStyle = newUser.color;
            ctx.fillRect(newUser.x, newUser.y, 25, 25);
        } 
        if(keys[40]){
            newUser.y += newUser.speed;
            socket.emit('moved', newUser);
            console.clear();
            console.log('You Moving');
            console.log(newUser);
            ctx.clearRect(0, 0, W, H);
            ctx.fillStyle = newUser.color;
            ctx.fillRect(newUser.x, newUser.y, 25, 25);
        } 
        if(keys[37]){
            newUser.x -= newUser.speed;
            socket.emit('moved', newUser);
            console.clear();
            console.log('You Moving');
            console.log(newUser);
            ctx.clearRect(0, 0, W, H);
            ctx.fillStyle = newUser.color;
            ctx.fillRect(newUser.x, newUser.y, 25, 25);
        } 
        if(keys[39]){
            newUser.x += newUser.speed;
            socket.emit('moved', newUser);
            console.clear();
            console.log('You Moving');
            console.log(newUser);
            ctx.clearRect(0, 0, W, H);
            ctx.fillStyle = newUser.color;
            ctx.fillRect(newUser.x, newUser.y, 25, 25);
        }
        window.requestAnimationFrame(update);
    }
    update();
});

//moving player and updating on other clients
socket.on('moving', function(moveTo){
    ctx.clearRect(0, 0, W, H);
    ctx.fillStyle = moveTo.color;
    ctx.fillRect(moveTo.x, moveTo.y, 25, 25);
    console.clear();
    console.log('A Player Moved');
    console.log(moveTo);
});

备注

我会把它放在 fiddle 的某个地方供你玩,但我不知道有什么可以让我用 node.js 服务器测试并让你看看在代码中同时喜欢 JS fiddle。我还是 Node 的新手,Socket.io 这是我的第一次尝试,任何帮助对我来说都是奖励。一旦主要功能有序,我将致力于重构代码。

如果你安装了 npm 并且你安装了 socket.io 那么你应该能够复制和粘贴它并且它应该可以工作。我的文件夹结构如下 - 这没有任何问题,因为一切都正确加载

提前致谢

moving 事件的客户端侦听器将永远只允许客户端一次绘制一个玩家的位置。您正在清除整个屏幕,擦除所有其他位置,但随后只重新绘制移动的玩家。

我推荐阅读 Robert Nystrom 的优秀游戏编程模式电子书 http://gameprogrammingpatterns.com/contents.html。它不涵盖这种特定情况,但您可以从 Observer、Game Loop 和 Update 部分推断出很多内容。在您的情况下,您可能希望服务器 运行 权威游戏循环和状态,使用观察者在 client/server 之间更新状态,并且客户端使用自己的独立游戏循环向用户显示所述状态。

看来我从来没有得到帮助验证我的问题的回复。我设法通读了这本书(或上面@JCD 的部分内容)并设法查看了其他应用程序以及它们如何使用节点管理它们的应用程序。

我知道在节点服务器上进行任何计算都不是好的做法,因此我重新查看了我的客户端和服务器的代码并更新了它。


判决


客户端:

我决定让客户端只显示当前客户端及其属性以及连接到服务器的其他客户端。使用循环,客户端将循环遍历服务器生成的 JSON 对象数组(所有连接到服务器的套接字)。然后客户端将使用 canvas 将对象绘制到 canvas。当新客户端加入时,阵列也会自动更新,这使得该客户端自动出现在其他客户端的正确位置。我正在使用请求动画帧来检查客户端是否按下了一个键,然后它发送并更新客户端属性,而服务器只是相应地广播(客户端将循环绘制到 canvas 的更新数组)。

服务器端:

服务器将负责等待客户端向服务器发送密钥代码,如果满足条件,它将使用新属性更新分配给该客户端的对象(正确时移动密钥通过 - 通过 RequestAnimationFrame()).

发送到服务器

代码:

客户端(HTML - 没有改变):

<!doctype html>
<html>
<head>
    <title>Game Dev JS - 1</title>
    <link href="./ui.css" rel="stylesheet">
</head>
<body>
    <canvas id="canvas" width="500" height="500">
        Your Browser Does Not Support Canvas and HTML5
    </canvas>
</body>
    <script type="text/javascript" src="/socket.io/socket.io.js"></script>
    <script type="text/javascript" src="/js/engine.js"></script>
</html>

客户端(JS - 更新):

var socket = io.connect(); //add socket object

//initializing the canvas
var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext('2d'),
    W = window.innerWidth,
    H = window.innerHeight;

var keys = {};

window.addEventListener('keydown', function(e){
    keys[e.keyCode] = true; 
}, false);

//check if key is not being pressed or has lifted up
window.addEventListener('keyup', function(e){
    delete keys[e.keyCode];
}, false);

//game loop to make the game smoother
function gameLoop() {
    if(keys[38]) {
        socket.emit('pressed', 38);
        console.log('You are UP');
    }
    if(keys[40]) {
        socket.emit('pressed', 40);
        console.log('You are DOWN');
    }
    if(keys[37]) {
        socket.emit('pressed', 37);
        console.log('You are LEFT');
    }
    if(keys[39]) {
        socket.emit('pressed', 39);
        console.log('You are RIGHT');
    }
    window.requestAnimationFrame(gameLoop);
}
window.requestAnimationFrame(gameLoop);

//the connected user joins and gets all the players on server
socket.on('welcome', function(currentUser, currentUsers){
    console.log(currentUser);

    ctx.globalCompositeOperation = "source-over";
    //Lets reduce the opacity of the BG paint to give the final touch
    ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
    ctx.fillRect(0, 0, W, H);

    //Lets blend the particle with the BG
    ctx.globalCompositeOperation = "lighter";

    //players in lobby
    for(var i = 0; i < currentUsers.length; i++){

        ctx.beginPath();

        //Time for some colors
        var gradient = ctx.createRadialGradient(currentUsers[i].x, currentUsers[i].y, 0, currentUsers[i].x, currentUsers[i].y, currentUsers[i].radius);
        gradient.addColorStop(0, "white");
        gradient.addColorStop(0.4, "white");
        gradient.addColorStop(0.4, currentUsers[i].color);
        gradient.addColorStop(1, "black");

        ctx.fillStyle = gradient;
        ctx.arc(currentUsers[i].x, currentUsers[i].y, currentUsers[i].radius, Math.PI*2, false);
        ctx.fill();
    }

    //player
    ctx.beginPath();
    //Time for some colors
    var gradient = ctx.createRadialGradient(currentUser.x, currentUser.y, 0, currentUser.x, currentUser.y, currentUser.radius);
    gradient.addColorStop(0, "white");
    gradient.addColorStop(0.4, "white");
    gradient.addColorStop(0.4, currentUser.color);
    gradient.addColorStop(1, "black");

    ctx.fillStyle = gradient;
    ctx.arc(currentUser.x, currentUser.y, currentUser.radius, Math.PI*2, false);
    ctx.fill();
});

//other users get updated with new players when teh new player joins
socket.on('currentUsers', function(currentUsers){
    ctx.globalCompositeOperation = "source-over";
    //Lets reduce the opacity of the BG paint to give the final touch
    ctx.fillStyle = "rgba(0, 0, 0, 0.3)";

    //Lets blend the particle with the BG
    ctx.globalCompositeOperation = "lighter";

    for(var i = 0; i < currentUsers.length; i++){

        ctx.beginPath();

        //Time for some colors
        var gradient = ctx.createRadialGradient(currentUsers[i].x, currentUsers[i].y, 0, currentUsers[i].x, currentUsers[i].y, currentUsers[i].radius);
        gradient.addColorStop(0, "white");
        gradient.addColorStop(0.4, "white");
        gradient.addColorStop(0.4, currentUsers[i].color);
        gradient.addColorStop(1, "black");

        ctx.fillStyle = gradient;
        ctx.arc(currentUsers[i].x, currentUsers[i].y, currentUsers[i].radius, Math.PI*2, false);
        ctx.fill();
    }
    console.log('A new User has joined');
});

//if a player leaves, everyone gets new set of players
socket.on('playerLeft', function(currentUsers){
    ctx.fillRect(0, 0, W, H);
    ctx.globalCompositeOperation = "source-over";
    //Lets reduce the opacity of the BG paint to give the final touch
    ctx.fillStyle = "rgba(0, 0, 0, 0.3)";

    //Lets blend the particle with the BG
    ctx.globalCompositeOperation = "lighter";

    for(var i = 0; i < currentUsers.length; i++){

        ctx.beginPath();

        //Time for some colors
        var gradient = ctx.createRadialGradient(currentUsers[i].x, currentUsers[i].y, 0, currentUsers[i].x, currentUsers[i].y, currentUsers[i].radius);
        gradient.addColorStop(0, "white");
        gradient.addColorStop(0.4, "white");
        gradient.addColorStop(0.4, currentUsers[i].color);
        gradient.addColorStop(1, "black");

        ctx.fillStyle = gradient;
        ctx.arc(currentUsers[i].x, currentUsers[i].y, currentUsers[i].radius, Math.PI*2, false);
        ctx.fill();
    }
    console.log('A Player Has left');
});


socket.on('PlayersMoving', function(players){
    ctx.globalCompositeOperation = "source-over";
    //Lets reduce the opacity of the BG paint to give the final touch
    ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
    ctx.fillRect(0, 0, W, H);

    //Lets blend the particle with the BG
    ctx.globalCompositeOperation = "lighter";

    var players = players;
    var i = 0;
    function allPlayers(){
        for(i; i < players.length; i ++) {

        ctx.beginPath();

        //Time for some colors
        var gradient = ctx.createRadialGradient(players[i].x, players[i].y, 0, players[i].x, players[i].y, players[i].radius);
        gradient.addColorStop(0.5, "white");
        gradient.addColorStop(0.5, players[i].color);
        gradient.addColorStop(1, "black");

        ctx.fillStyle = gradient;
        ctx.arc(players[i].x, players[i].y, players[i].radius, Math.PI*2, false);
        ctx.fill();
        }
    }
    allPlayers();

});

服务器端(NodeJS - 更新):

var express = require('express'),
    app = express(),
    http = require('http').createServer(app),
    io = require('socket.io').listen(http);

http.listen(3000);

//server connections and routing
app.use(express.static(__dirname + '/public'), function(request, response){
    if(request.url !== '/public') {
        response.sendFile( __dirname +'/error/index.html');
        console.log('Error 404 request, User tried accessing: ' + __dirname + request.url);
    }
});

var players = [];

//Lets create a function which will help us to create multiple players
function newPlayer() {
    this.name;
    this.id = 1;
    this.x = Math.random() * 500;
    this.y =  Math.random() * 500;
    //Random colors
    var r = Math.random()*255>>0;
    var g = Math.random()*255>>0;
    var b = Math.random()*255>>0;
    this.color = "rgba(" + r + ", " + g + ", " + b + ", 0.5)";

    //Random size
    this.radius = Math.random()*20+20;
    this.speed =  5;

    return {'name' : this.name,"x" : this.x,"y" : this.y,"color" : this.color, "radius" : this.radius,"speed" : this.speed}
}


//calls to the server and tracking connection of each new user
io.sockets.on('connection', function(socket){
    var currentPlayer = new newPlayer(); //new player made
    players.push(currentPlayer); //push player object into array

    //create the players Array
    socket.broadcast.emit('currentUsers', players);
    socket.emit('welcome', currentPlayer, players);

        //disconnected
    socket.on('disconnect', function(){
        players.splice(players.indexOf(currentPlayer), 1);
        console.log(players);
        socket.broadcast.emit('playerLeft', players);
    });

    socket.on('pressed', function(key){
        if(key === 38){
            currentPlayer.y -= currentPlayer.speed;
            socket.emit('PlayersMoving', players);
            socket.broadcast.emit('PlayersMoving', players);
        } 
        if(key === 40){
            currentPlayer.y += currentPlayer.speed;
            socket.emit('PlayersMoving', players);
            socket.broadcast.emit('PlayersMoving', players);
        } 
        if(key === 37){
            currentPlayer.x -= currentPlayer.speed;
            socket.emit('PlayersMoving', players);
            socket.broadcast.emit('PlayersMoving', players);
        } 
        if(key === 39){
            currentPlayer.x += currentPlayer.speed;
            socket.emit('PlayersMoving', players);
            socket.broadcast.emit('PlayersMoving', players);
        }
    });
});

console.log('NodeJS Server started on port 3000...');

例子


我知道这不是最干净的做事方式,但这是一个原型,我希望它能在不久的将来帮助某人成为设法 运行 解决相同问题而不是解决问题的垫脚石必须经历同样的麻烦。

谢谢