Jump to content

Improving performance of MMO-style canvas game


que
 Share

Recommended Posts

Hi,

I'm currently developing a Tibia-like MMO for fun on my spare time. I didn't think alot of performance as I've seen / done some stuff with babylon.js which should be much more heavy than my game with 64x64 tiles.

But the game is somewhat sluggish when moving. The networking is not optimal, but that is not the issue I'm focusing on right now.

I'm using 3 html canvas stacked on each other:

  • Tile-layer, the bottom layer which displays the 64x64 tiles
  • Player-layer, displays all charachters/NPC's
  • Effect-layer, displays effects such as arrows moving etc.

I have my drawMap() javascript function that updates the tiles on each tick. I call it like this:

            setTimeout(function () {
                requestAnimationFrame(tiles.drawMap);
            }, 1000 / drawMapFpsTarget);

 In my drawMap() I loop through the height and width of the map that is currently on the users view like this:

for (var x = 0; x < numTilesWidth; x++) {
     for (var y = 0; y < numTilesHeight; y++) {
          //draw tiles.
     }
}

I don't use a spritesheet as of now. Would that improve the rendering significantly? I mean, 64x64 can't be that heavy? Or am I missing something?

This is my drawImage() function, which caches the .png's

function drawImage(canvasX, canvasY, url, ctx) {

    let existingImage = cachedImages.get(url);
    if (existingImage != null) {
        ctx.drawImage(existingImage, Math.round(canvasX), Math.round(canvasY));

    } else {
        let img = new Image();
        img.ex = canvasX;
        img.ey = canvasY;
        img.onload = function () {
            ctx.drawImage(img, Math.round(img.ex), Math.round(img.ey));
        };
        img.src = url;
        cachedImages.set(url, img);
    }


}

Here is a quick video, but it looks more sluggish in the video as i think the fps is quite low in the video.

https://i.gyazo.com/0cba3ac12a976f5d8882365b2869c204.mp4


Here is also a link to my game:

http://tilegame.azurewebsites.net/

Works better in chrome than other browsers. You need to register with a valid email and atleast 6 letter password. Then refresh the page a few times after signing in. 

 

Thanks!

Link to comment
Share on other sites

I wasn't able to figure out how to throw a spear, as shown in your video, but I did walk around a bunch, and opened and closed a chest. I didn't register with a valid email, I just typed "qwer" and "asdf" into your two textfields.

I pasted mrdoob's stats.js into your page and got a consistent 75 fps (that's the highest I seem to be able to get in Chrome) with no noticeable frame delays. Granted, I have pretty beefy hardware, so YMMV.

I made a Zelda 2 clone 5 years ago with 2d drawImage calls, clearing the canvas every frame; I had at least double the drawImage calls I think you're using and it ran very quickly on older (at the time) hardware, including my phone. I'm not sure what it's costing you to use 3 canvases. Any reason why you're doing that instead of just compositing everything onto one canvas?

I'm also concerned about your setTimeout loop. I think it's generally preferable to have a pure requestAnimationFrame loop and accumulate elapsed time, updating your world state when the elapsed time reaches a "tick" and rendering everything every animation frame. (As a bonus, this lets you do interpolation between update steps for even smoother animations.)

I did notice a significant delay in the game responding to my keypresses. It took roughly 800ms for my character to move one tile, and when I tried queuing up several commands, some of them were dropped. There was also some very noticeable delay loading in new map chunks. I'm guessing the delays I experienced were related to your networking.

Chunk loading can be solved relatively easily by pre-loading a larger border of tiles around the player, but not rendering them yet. Input lag is much trickier to solve.

One common approach I've read about to deal with these kinds of delays is to predictively start player animations on the client side immediately, and deal with the server's response when it gets back. With the information the client has about the world, it can easily decide whether you will be able to move to the next tile or if a wall will prevent you.

This gets tricky when there's hidden information (e.g. invisible landmines or invisible walls) or random events (e.g. hits, misses, and damage rolls.) This works well for some scenarios and poorly for others; for example, if there are invisible landmines that explode when you've finished your move, you'll probably get a response back from the server before the player finishes moving to the next tile, so that should be easy to resolve; however, invisible walls may cause players to "rubber-band" back after seemingly travelling part-way through the wall (or all the way through, if their connection is lagged more.)

And things get very, very tricky when you take other players' actions into account. Modern FPS game servers temporarily rewind time when you shoot your gun to compensate for your lag, to see if your bullet would have hit anything. This means you don't have to lead your targets to account for your network latency, but it also means you can get shot immediately *after* jumping behind cover.

P.S. Cool to see you're using Vue for UI, I think I might do the same for my current project!

 

Link to comment
Share on other sites

On 5/18/2018 at 5:16 AM, chriswa said:

I wasn't able to figure out how to throw a spear, as shown in your video, but I did walk around a bunch, and opened and closed a chest. I didn't register with a valid email, I just typed "qwer" and "asdf" into your two textfields.

I pasted mrdoob's stats.js into your page and got a consistent 75 fps (that's the highest I seem to be able to get in Chrome) with no noticeable frame delays. Granted, I have pretty beefy hardware, so YMMV.

I made a Zelda 2 clone 5 years ago with 2d drawImage calls, clearing the canvas every frame; I had at least double the drawImage calls I think you're using and it ran very quickly on older (at the time) hardware, including my phone. I'm not sure what it's costing you to use 3 canvases. Any reason why you're doing that instead of just compositing everything onto one canvas?

I'm also concerned about your setTimeout loop. I think it's generally preferable to have a pure requestAnimationFrame loop and accumulate elapsed time, updating your world state when the elapsed time reaches a "tick" and rendering everything every animation frame. (As a bonus, this lets you do interpolation between update steps for even smoother animations.)

I did notice a significant delay in the game responding to my keypresses. It took roughly 800ms for my character to move one tile, and when I tried queuing up several commands, some of them were dropped. There was also some very noticeable delay loading in new map chunks. I'm guessing the delays I experienced were related to your networking.

Chunk loading can be solved relatively easily by pre-loading a larger border of tiles around the player, but not rendering them yet. Input lag is much trickier to solve.

One common approach I've read about to deal with these kinds of delays is to predictively start player animations on the client side immediately, and deal with the server's response when it gets back. With the information the client has about the world, it can easily decide whether you will be able to move to the next tile or if a wall will prevent you.

This gets tricky when there's hidden information (e.g. invisible landmines or invisible walls) or random events (e.g. hits, misses, and damage rolls.) This works well for some scenarios and poorly for others; for example, if there are invisible landmines that explode when you've finished your move, you'll probably get a response back from the server before the player finishes moving to the next tile, so that should be easy to resolve; however, invisible walls may cause players to "rubber-band" back after seemingly travelling part-way through the wall (or all the way through, if their connection is lagged more.)

And things get very, very tricky when you take other players' actions into account. Modern FPS game servers temporarily rewind time when you shoot your gun to compensate for your lag, to see if your bullet would have hit anything. This means you don't have to lead your targets to account for your network latency, but it also means you can get shot immediately *after* jumping behind cover.

P.S. Cool to see you're using Vue for UI, I think I might do the same for my current project!

 

Hi chriswa, sorry for the late reply, haven't had internet for the whole weekend.

I'll have to check out mrdoob's stat.js. Perhaps it's not an FPS problem after all.. But I feel like it is, especially when throwing spears.. 

I read that it was good to have multiple canvases. Specially if one canvas needs quicker updates than the rest. My tile canvas could probably be optimized so that I just update one "line" of tiles every time I move. And If I stand still, I should be able to not update the tile-canvas but only update the player canvas. Although I do not utilize that as of now. But perhaps later on.

Where on earth are you? The site is hosted on free-tier azure in EU, so there could be some delay for you when moving. 

Right now I don't load any more of the map until i hit an empty tile. THis can ofc be optimized so that I load the map further away from me :) 
 

Yeah as you see, currently its all server side movements that the client reacts on. I will try to implement some sort of client checking wether or not it's OK to move.

 

Yeah I thought it was a good time to try out Vue, it's probably not up to par on vue-standards. But I really like Vue, simple, powerful and not in the way(!!!)

Thanks alot for your reply.

Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...