Advertisement

question about game networking design (and a few other questions)

Started by January 20, 2017 10:21 PM
3 comments, last by hplus0603 7 years, 9 months ago

Hello all! This is my first post so I hope i'm in the right place. :)

game client: web browser, html canvas & javascript with socket.io (client)

server: node.js & socket.io(server)

I am trying to make this game a multiplayer game (for now just have multiple characters on the same screen). I have removed the monster and it's functionality. I have sent the movement of my character to the server (via x,y coordinates) and then the server then emits them to all connected clients:

Untitled.png

Great! I've figured that out! :D

Right now if multiple people connect they all can control the character and see it live.

Now I am wanting to make it so I can generate the character once I am connected to the websocket server, so I can make more than one character on the screen. So my idea would be that once I connect I would then send a message to the server saying "hey, i'm a new character" the server would respond and send all clients a message to create a character at this location. I can't seem to find out what parts of my javascript code I should move around to do this... Could someone help me out with this? I would ask my teacher but she doesn't know websockets as much as I do...Besides that, is this the right type of thinking?

Also, it seems like I am sending too much coordinates to the server, I don't know how much is too much for the sockets to handle but I feel like it's unnecessary. I am not noticing much latency even from different ip's though so I am not sure. What is the right amount of times you have to send your location to the server per second?

If you guys have any recomendations on what I should do that'd be great! If not, do you have any good articles on how I should acheive this?

Thanks guys!

My client code:


var uname = prompt("Please enter a username!");
var socket = io.connect('http://192.168.2.5:8654');
socket.emit('username', { name: uname });
var i = 0;
// Create the canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 512;
canvas.height = 480;
document.body.appendChild(canvas);

// Background image
var bgReady = false;
var bgImage = new Image();
bgImage.onload = function () {
bgReady = true;
};
bgImage.src = "images/background.png";

// Hero image
var heroReady = false;
var heroImage = new Image();
heroImage.onload = function () {
heroReady = true;
};
heroImage.src = "images/hero.png";

var keydown = true;
addEventListener("keydown", function (e) {
//e.code
// alert(e.code);
if (e.code == "ArrowLeft" || e.code == "ArrowUp" || e.code == "ArrowRight" || e.code == "ArrowDown") {
console.log("good");
if (keydown == true) {
socket.emit('sendactiondown', { ecode: e.code});
keydown = false;
}
}

// 
}, false);

addEventListener("keyup", function (e) {
if (e.code == "ArrowLeft" || e.code == "ArrowUp" || e.code == "ArrowRight" || e.code == "ArrowDown") {
keydown = true;
socket.emit('sendactionup', { ecode: e.code});
}
}, false);

// Game objects
var hero = {
speed: 256 // movement in pixels per second
};



socket.on('poscallback', function(data){
// Set starting position
hero.x = data.xpos;
hero.y = data.ypos;
render(data.xpos, data.ypos);
}); 



// Update game objects
// var update = function (modifier) {
// hero.y -= hero.speed * modifier;
// hero.x -= hero.speed * modifier;
// };

// Draw everything
var render = function (x, y) {
if (bgReady) {
ctx.drawImage(bgImage, 0, 0);
}

if (heroReady) {
ctx.drawImage(heroImage, x, y);
// console.log(hero.x+" "+hero.y);
}
};

// The main game loop
var main = function () {
var now = Date.now();
var delta = now - then;
update(delta / 1000);
then = now;
// Request to do this again ASAP
requestAnimationFrame(main);
};

// Update game objects
var update = function (modifier) {
if (38 in keysDown) { // Player holding up
hero.y -= hero.speed * modifier;
}
if (40 in keysDown) { // Player holding down
hero.y += hero.speed * modifier;
}
if (37 in keysDown) { // Player holding left
hero.x -= hero.speed * modifier;
}
if (39 in keysDown) { // Player holding right
hero.x += hero.speed * modifier;
}
};



// Cross-browser support for requestAnimationFrame
var w = window;
requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;

// Let's play this game!
var then = Date.now();
main();

My server code:


var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
var players = [];
// array.push(item.id);

server.listen(process.env.PORT || 8654);
console.log('Server is running.');

io.on('connection', function (socket) {
var playerx = Math.floor(Math.random() * 500) + 1; 
var playery = Math.floor(Math.random() * 500) + 1;
socket.on('username', function(data){
players.push(data.name);
io.sockets.emit('poscallback', { xpos: playerx, ypos: playery});
console.log(data.name+"has joined!");
})


socket.on('sendpos', function(data){
console.log(": X:"+data.x+" Y:"+data.y);
io.sockets.emit('recvupdate', { x: data.x, y: data.y});
});
//for handeling character movement
socket.on('sendactiondown', function(data){
                                                                               //HERE IS WHERE I TRIED THE WHILE LOOP TO INCREMENT THE X AND Y.
console.log(data.ecode+" down");
// io.sockets.emit('recvupdate', { x: data.x, y: data.y});
});

socket.on('sendactionup', function(data){
console.log(data.ecode+" up"); 
                                                                               //SET CONDITIONAL HERE TO BREAK OUT OF LOOP
// io.sockets.emit('recvupdate', { x: data.x, y: data.y});
});


// socket.on('disconnect', function() {
// console.log('user dissconnected');
// });
});
Exuse my extra code, I am testing some things

EDIT (1/21/17):

I have taken all of your guy's information and started working on having the server move the character, rather than the client. What I basically mean is instead of incrementing the x and y on the client I am starting to do it on the server. I am runing into a problem though. The way that the client did it was by means of the event listener. It would fire off multiple times as you held it down, then they incremented the x (and or) y by having it go through the functions each time it is fired. So on the server it only gets triggered when the key press is down and up (because I made it that way, thought it would be better than sending the server "arrow up" each time it is fired). How do I "move" the character on the server? I tried a while loop that incremented the x and y accordingly with a conditional that would knock it out of the loop once the keypressed UP command was sent to it. I did not have any luck so could anyone recommend what I should do?... :wacko:

I have updated the code above.

You don't need to send a "I'm a new character" message, because the server gets the notification of a new connection.

If you want to support saving and resuming later, though, then you need to set the initially connected client into a mode of "don't know" where you don't accept commands other than "make new character" or "resume old character" (where the resume contains a user email and password from a previous save.)

Separately, it's usually better to send "move up" than to send "set position to x,y" because it's easier to cheat when you send absolute positions.
enum Bool { True, False, FileNotFound };
Advertisement

The main issue I see with the code is that most of the logic is done on the client. Usually we don't like this because of security reasons, but another good reason to avoid it is because it's harder to coordinate things. In this example, because the client says "Hero is now at (x, y)" and the server broadcasts it out, there's no way of knowing which hero is at that location, or even any way for clients to tell each other apart. The server should be the one arbitrating in such matters by ensuring that each client connected to it is associated with the relevant data on the server; that way, it has sufficient information to tell other clients exactly what is happening.

The simplest next few steps you can take is something like this:

  • the server already has a list of connected sockets - whenever a new connection is made, it needs to associate that with a new character, on the server. It should probably assign a unique number to it, and tell the owning client what number it's been given.
  • your broadcast after a 'sendpos' needs to know which character number it is handling. It should work this out by looking up which character is associated with this socket, not by allowing the socket to report the number.
  • your broadcast can add that extra character number into the data it sends out.
  • your clients need a similar structure, mapping character numbers to hero locations. This way they know which location to change when a recvupdate comes in. They may need to be able to add new characters if a previously unrecognised character number comes in.

Than you guys for all of your help! It's helped me get a better grasp on how this all should be done. I have updated the OP with a newly found problem and will probably require me to revise the design of what I currently have.

Upddating the original post instead of posting a follow-up question makes it impossible for anyone who comes to the thread now to understand what's going on. Also, it assumes that everyone wants to read your post and try to figure out what is different now from before, which is a lot of work. I highly recommend only editing original posts if you find spelling errors or if you find that the post didn't actually express the meaning you were originally trying to convey.

If you have a new meaning to convey, new question, new comment, etc, it's better to post that as a follow-up message!

enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement