mla Posted January 7, 2018 Share Posted January 7, 2018 Hi, I have been struggling with a minor issue, that I'm hoping someone can help me out with. I have a multiplayer game that uses Express and Socket io. When a new player logs into the game, the player walks to a certain location on screen. In order to make the player walk, I am drawing the player to canvas using a spirit map. In order to make the player walk per frame I am using the ctx.clearRect() to clear the previous frame, however doing this naturally clears the full canvas and removes the already logged player. I'm not sure how to work around this. Any insight is greatly appreciated. Thanks, Laura server.js Player.js JackFalcon 1 Quote Link to comment Share on other sites More sharing options...
mattstyles Posted January 8, 2018 Share Posted January 8, 2018 This has nothing to do with multiplayer (express/socket.io) and, to be honest, you're not ready to tackle a multiplayer game. This is just a problem with how you're representing your game data. We can simplify things down easy enough. When the game starts create an array called `entities`. Now enter the main game loop. The game loop is simple and consists of: * Clear the current screen * Render the new screen * Gather inputs * Handle logic And that is it, this repeats until some exit condition completes (or crashes) your game. The bit you mention in your post happens in the `gather inputs` section so lets consider it, the `gather inputs` section is actually pretty dumb, it literally just gathers inputs, the next section deals with handling those inputs. Usually stuff in gathering input would be gathering mouse and keyboard interactions from the player. You're doing multiplayer so things shoot up in complexity as you must also handle network events, for now, lets just assume that we don't care about things like latency and packet drops and that all network traffic is a stream of events, this means they're basically synchronous, i.e. you can just handle them as they come in. You need to handle 2 network events, players joining and players leaving. You listen for these events in the gather input phase and handle them in the `handle logic` phase, although these two steps could intermingle (I'm not suggesting that, but it often happens, it isn't great but, depending on how complex you want to go might be the easier route). When a player joins give them an id and pop them on to the entity stack. When a player leaves inspect their id, find them in the entity stack and remove them. Each `render` phase loops over the entity stack and renders them. This is independent from the network and player events you're handling, shared only by read-only access to the entity stack. mla 1 Quote Link to comment Share on other sites More sharing options...
mla Posted January 8, 2018 Author Share Posted January 8, 2018 Thanks Mattstyles, I didn't past the entire game logic, but will add the game.js file, that shows the handing of when a player joins and when a player leaves and of course I push the player.id to an array among other variables needed. The game is actually quite simple. Players access the game, each player walks to a specific location on screen, depending on total number of players. Once all players are in and the game begins, each player sees their assigned card of dots. Dots are dragged among players, till each player has completed his/her card. The players don't walk throughout the game. They only need to walk to the screen at a specific x,y location once they enter the game. I'll eventually append the player.name to the avatar. Once a player walks into the game, I will use canvas to display each players card which is the objective of the game. Right now I am building the part of simply noting a player has accessed the game, pushing their x and y specific coordinates and seeing the avatar walk on screen. It works but when a new player walks in, my first player is removed and only shows the second player. When the third player walks in, the second palyer avatar is removed and only shows the third player's avatar, and so on. The correct image avatars show (as each player has a different image), and the correct coordinates show as well. The only issue I'm having is not deleting previous players when a new player joins. Thanks, game.js Quote Link to comment Share on other sites More sharing options...
Gio Posted January 9, 2018 Share Posted January 9, 2018 I think in a nutsheel, what mattstyles was suggesting is that you need to re-draw all the players every time. There are better / more efficient alternatives but that is simple and would solve your problem. Quote Link to comment Share on other sites More sharing options...
mattstyles Posted January 9, 2018 Share Posted January 9, 2018 Yeah, Gios hit it straight on the head, I take the approach that the absolute simplest conceptual module is to redraw everything every frame, of course, this isn't efficient in the slightest and only the most trivial of games/apps can get away with it, but, many libraries should abstract the complexity away so you only deal with the simplest case of redrawing everything every frame i.e. Pixi has a scene graph, you just call render and it works out what work it needs to do, in the general web dev world React works in exactly this way as well (and many other frameworks are taking this approach too, its nothing new). This shifts the complexity of working out what has changed away from you as the application developer and gets it handled by your framework/module/library of choice. Depending on how you're doing things this might be easy or hard. Try to separate what is logic in your game and what is 'cruft', i.e. players are either in or out of your game, the movement that is visualised when they become in is 'cruft', it isn't core logic, its a bit of visual candy that makes your game a little more interesting for the user. It still sounds like you have a single player entity somewhere in your code, when what you need is to manage an array of entities, so that when a 2nd player joins you (conceptually) render the first one and render the animation as the 2nd one trots on to the screen (I say conceptually as you may not actually re-render the first one, but, its easier to think about it as if you were re-rendering that first one each frame even though it hasn't changed and doesn't technically require a re-render), whereas at the moment it almost sounds like you're rendering the character and then when a new one joins that new one become THE character that you're rendering. Does that make sense? Or is helpful? Quote Link to comment Share on other sites More sharing options...
mla Posted January 10, 2018 Author Share Posted January 10, 2018 Thanks mattstyles and Gio! mattstyles it makes perfect sense, and I would prefer doing this the more efficient way. I wasn't using any specific frameworks but developing my own code to make this work. If you think it's best to use a framework, please let me know what you would recommend. The game is a very simple game. Up to 10 players can join. There is a login screen that requests you select your role and a name. When you enter the game, you are automatically assigned an avatar who walks to a specific location and has a display card with a belt holding 3 or 4 dots. The object of the game is to drag dots among the logged players to complete your card. As each card is complete, the players will be removed from the game. I currently have the following set up. Does this seem like I'm in the right direction? Meanwhile, I'm trying to locate where I have the single player entity issue. server.js - game server logic. Here I set up a players = [] and store the player spirit image, x and y coordinates. When client connects to the application, emit createPlayer. Loop through players[], and emit createPlayer to draw all other players game.js - client game logic create canvas and initialize remotePlayers array, remotePlayers = [] on CreatePlayer, pass data and save in remotePlayers array Call the draw() function that clears the canvas and draws each remotePlayers players.js - client, players logic Here is where the actual draw function exists chat.js - client chat logic This is all the logic for the chat screen - works well xyLocation.js - client x and y coordinates This is all the logic for obtaining the x and y coordinates, based on what player number you are. The first player has a different image then the second palyer as well as different coordinates. I have not yet implemented latency and lag yet. You hit the nail on the head! I'm working through the code now to find where I'm rendering the character and then when the new one joins that user becomes the charter that I'm rendering. Quote Link to comment Share on other sites More sharing options...
mattstyles Posted January 10, 2018 Share Posted January 10, 2018 Sounds like you're doing really rather well and this is just a small kink in the road. I can recommend Pixi for canvas (webgl) rendering or pick a general web development technology (like React, Preact, Inferno, Vue etc) if you have any experience of that and don't need super fast rendering—either solution sounds like it could be overkill, especially considering you already have a rendering thing set up that works and don't envisage the rendering becoming vastly more complex than you're already handling. The only thing that possibly stands out for me from your structure is that it almost looks like you have some player data in `players.js` and maybe some more in `xyLocation.js`? I'd have thought you'd have something like player data, which would contain a reference to the image and <x, y> location data (as well as other stuff), and possibly some utilities to help with rendering i.e. do you store screen location in player data? or do you just work out screen location based from array position? i.e. char 1 is at 100, 600, char 2 is at 200, 600 char 3 is at x * 100, 600, for example. Your game then holds the array of active players. Some people prefer to have rendering functions separate from your entity data (i.e. in game.js or maybe a new render.js), some would prefer to have entities responsible for their own rendering code and just call something like `entities.forEach(entity => entity.render())` for example. Pros and cons to both approaches. Next question I guess could be how you're stuffing entities into the array i.e. is array index lookup robust enough for you to be able to manage your list of players? What happens to the array when you have 5 players and number 3 leaves? Do you leave a sparse array or splice number 3 out so you maintain a sequential array, but this time with 4 entries? Do you need id's associated with players? If so are you happy with searching the array to find one or would an object keyed against ids serve you better? For such a small array it likely doesn't matter much, only in terms of how you write the rest of your utility functions. At any rate, I'm expecting you to have an array (list) of players (looks like its in game.js) and during your rendering loop you iterate over the list and render each entity. Sounds like you have entities responsible for rendering themselves so you must have code something like: // render remotePlayers.forEach(player => { player.render() }) and likely also have something for updating players (I'm thinking you have them animating onto the screen so need to update positions): // render remotePlayers.forEach(player => { player.update(delta) }) and inside each player update you likely have some logic like: // Note you might be using this to reference a player as sounds like you're // using instance methods for this functionality and I'd expect your actual logic // to be a bit more involved than this small example player.update = function update (delta) { if (player.state === 'entering') { player.x -= MOVE_AMOUNT * delta } if (player.x <= player.desiredX) { player.state = 'entered' } } This sort of setup means that none of your rendering code really cares about entering and entered players, as you're iterating over an array of players you just need to make sure you instantiate a new player correctly (probably with a proper desiredLocation variable in this example) and just push it onto your players stack. Then just iterate and render/update right? Quote Link to comment Share on other sites More sharing options...
mla Posted January 10, 2018 Author Share Posted January 10, 2018 mattstyles, I want to first say thank you. You really are making my brain work. Your insight is greatly appreciated. So to answer your questions. xyLocations.js, has only one job, to find the x, y coordinates and initiate the avatar image. The first thing that happens is the host logs in, using his/her name, and enters how many players will be allowed to join the game. No player can enter until the host has entered the game. When each player is allowed to enter the game, (they only need to enter their name), the zyLocation will check to see which player number you are and assign the x, y position and the avatar. Nothing else happens in xyLocations. I don't have any array set up, but it will return the values back to the game.js file and save those items in the remotePlayers array. To answer the question, I save screen location, id, x and y to the array. // Initialize the new player newPlayer = new Player(data.x, data.y); newPlayer.id = data.id; newPlayer.x = data.x; newPlayer.y = data.y; newPlayer.imgAvatar = data.imgFile; // Add new player to the remote players array remotePlayers.push(newPlayer); //Update the array list of players When I have 5 players and 1 leaves, I splice the player. I would prefer to have id's saved as part of the player array. I always find it easiest to reference things with id's and use this as a way to remove the player avatar from canvas. Unless you can advise a better way Once the players walk once to the screen, I don't need them to ever walk again. I thought once the x,y coordinates are saved in the array I can use those when I render? I never pass the delta in the update function. //render function draw() { // Wipe the canvas clean context.clearRect(0, 0, canvas.width, canvas.height); // Draw the local player newPlayer.draw(context); // Draw the remote players var i; for (i = 0; i < remotePlayers.length; i++) { console.log("Remote Players draw: "+ remotePlayers.x); remotePlayers.draw(context); }; }; Quote Link to comment Share on other sites More sharing options...
mla Posted January 10, 2018 Author Share Posted January 10, 2018 So I manged to get the players to appear as a static image in their set locations and update correctly. Now that I updated the code to work for the static image, I will retry the walking Avatar. JackFalcon 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.