0

Take a look at the movement function of my game:

https://jsfiddle.net/qk7ayx7n/92/

var canvas = document.getElementById("canvas");
var ctx=canvas.getContext("2d");
height = window.innerHeight;
canvas.width = window.innerWidth; 
canvas.height = height; 

playerY = height / 2;
playerX = 50;
var circle = new Image();
circle.src = "https://s21.postimg.org/4zigxdh7r/circ.png";

ctx.drawImage(circle, playerX, playerY);

move();

function move(){

velocity = height * 0.00050; // the velocity should be proptional to screen height.
startPos = playerY,
endPos = 0,
distanceY = endPos - playerY,  
startTime = Date.now(),
duration = Math.abs(distanceY) / velocity;
requestAnimationFrame(startMoving);

function startMoving(){
    var elapsedTime = Math.min(Date.now() - startTime, duration),
    cbEnd = false;
    if (elapsedTime < duration){ //time's up
        setTimeout(function() {
        requestAnimationFrame(startMoving);
        }, 1000 / 60);
    }
    else cbEnd = true;
    playerY = startPos - (elapsedTime * velocity);
    console.log(playerY);
    ctx.clearRect(0, 0, window.innerWidth, height);
    ctx.drawImage(circle, playerX, playerY); //drawing circle
    if(cbEnd) console.log("over");
    }
}

Now, I will explain: My actual game is a game where the velocity of the player changes as the game progresses. The speed is proportional to the window's height, because the player's goal is to move up. I'm using this technique in order to better control how many player-draws I perform. We will get to the problem with that later.

I'm also using this technique(see the answer)to measure the timings in case there was a lag in the last frame, it will just cover the distance it was supposed to cover, it also means that the target Y will always be reached on time on all devices.

The FPS average is the biggest issue I am running into. Different devices have different specs. in order to create a good game for all devices I must master this function:

setTimeout(function() {
var fps = 60;
requestAnimationFrame(startMoving);

}, 1000 / fps);

I have tested my game on many devices, on some the fps needs to be 40, 50, 30 or something else in order for the game to run smoothly(which means each player "move" will be equal to it's previous, otherwise they will experience lag). So I was thinking, either secretly running the game as the game "loads" for the first time and finding out the average FPS, or maintaining some kind of a learning curve in order to adjust how many times this function runs. Either way I do not really know how to achieve it perfectly, I know it may be hard but please I need some help with this.

Community
  • 1
  • 1
RunningFromShia
  • 590
  • 6
  • 20
  • The callback for requestAnimationFrame returns the time in high precision 1/1,000,000th second in ms 1/1,000 (smallest increment 0.001ms) use that for your timing data. eg `reaquestAnimatFrame(myLoop); function myLoop(timeInMS){` Appart from that I am not sure what you are asking? – Blindman67 Dec 13 '16 at 21:20
  • How to know when is the exact time to call for requestAnimationFrame based on the average time it is usually takes for it to happen(by measuring previous calls), therefore adjusting the setTimeout to fire only when it's safe to say there won't be a lag. I basically want to achieve moves which are equal to one-another, no sudden "spike". – RunningFromShia Dec 13 '16 at 22:02
  • requestAnimationFrame (rAF) is synced to the display refresh. It will fire at 60fps as long as your animation render time is under 1/60th of a second. If however you go over that 1/60th of a second then rAF will wait until the next display refresh giving a frame rate of 30fps. After that 20fps. If you want to keep a frame rate you can ignore rAF calls that are early and exit without rendering, rendering only on frames at the desired rate. – Blindman67 Dec 13 '16 at 22:11
  • My suggestion is to bind your animation to time and not FPS, then FPS won't matter (instead of movement/frame, think movement/time etc.). That way canvas becomes a "camera" and objects in your scene independent of how often canvas shows/update the scene. –  Dec 14 '16 at 08:24

2 Answers2

1

Frame Rates

To control frame rate using requestAnimationFrame (rAF) you need to monitor the time each call to the rAF callback is made. This can be done by using the first argument passed be rAF to your function.

Example gets mean frame rate for 60 frames.

function log(data){
    var div = document.createElement("div");
    div.textContent = data;
    document.body.appendChild(div);
    scroll(0,10000);
}
var samples = [];
function myLoop(time){
    if(samples.length < 60){
        samples.push(time);
        requestAnimationFrame(myLoop) 
    }else{
        log("First 60 callback times in ms.");
        samples.forEach(s=>log(s.toFixed(3)));
        log("Mean frame time : "+ ((samples[59]-samples[0])/60).toFixed(3))
        log("Mean frame rate : "+ (1000/((samples[59]-samples[0])/60)).toFixed(0))
    }
}
requestAnimationFrame(myLoop) 

This example waits for 4 seconds (approx) befor sampling frame rate.

function log(data){
    var div = document.createElement("div");
    div.textContent = data;
    document.body.appendChild(div);
    scroll(0,10000);
}
log("4 second wait for stable");
var waitFor = 240; // wait for browser to be nice.
var samples = [];
function myLoop(time){
    if(waitFor > 0){
        waitFor -= 1;
        requestAnimationFrame(myLoop) 
        return;
    }
    if(samples.length < 60){
        samples.push(time);
        requestAnimationFrame(myLoop) 
    }else{
        log("First 60 callback times in ms.");
        samples.forEach(s=>log(s.toFixed(3)));
        log("Mean frame time : "+ ((samples[59]-samples[0])/60).toFixed(3))
        log("Mean frame rate : "+ (1000/((samples[59]-samples[0])/60)).toFixed(0))
    }
}
requestAnimationFrame(myLoop) 

To control the frame rate you need to ignore early frames if they come in. There is a little kludging because the frame time is not perfect but because you know the frames will not come faster than 1/60th it is easy to deal with.

const FRAME_RATE = 30;
// set min frame time at frame rate sub 1/4 frame
const FRAME_MIN_TIME = (1000/FRAME_RATE) - (1000/(60*4));
var lastFrameTime = 0;    

function log(data){
    var div = document.createElement("div");
    div.textContent = data;
    document.body.appendChild(div);
    scroll(0,10000);
}
var samples = [];
function myLoop(time){
    // if the frame is early call the next frame and dont render
    if(time - lastFrameTime < FRAME_MIN_TIME){            
        requestAnimationFrame(myLoop);
        return;
    }
    lastFrameTime = time;

    if(samples.length < 60){
        samples.push(time);
        requestAnimationFrame(myLoop) 
    }else{
        log("First 60 callback times in ms.");
        samples.forEach(s=>log(s.toFixed(3)));
        log("Mean frame time : "+ ((samples[59]-samples[0])/60).toFixed(3))
        log("Mean frame rate : "+ (1000/((samples[59]-samples[0])/60)).toFixed(0))
    }
}
requestAnimationFrame(myLoop) 

Good numbers?

It is very hard to get a good frame rate estimate with javascript, especially at startup as there is a lot going on that can cause frames to be dropped. Depending on what is going on on the page you may need several seconds or more before you can reliably estimate the load of your game.

The best way to deal with the frame rate is to first allow some stabilization time. Monitor the frame rate and while you see many frames in a row dropped, wait. Once you have a consistent rate use the min rate as the base rate, throttling frame render as shown in the second snippet.

You can only have consistent frame rates at rate = 60/n where n is an integer >= 1. Frame rates 60, 30, 20, 15, 12, 10, 8.87... and so on.

Community
  • 1
  • 1
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • How can I learn from the numbers which frame rate(must be lowest) can I use in order to avoid fps drops? – RunningFromShia Dec 13 '16 at 22:56
  • @RunningFromShia The frame rate in the last snippet is `1000 / (time - lastFrameTime)` . This time is just an estimate so round it to the closest framerate (last paragraph of answer). Personally I run my games at 60 or 30 for all devices. If it runs slow then I have made the code to complex and need to find more ways to optimise. I write for the base (minimum) system intended to use the product, never the otherway around. – Blindman67 Dec 13 '16 at 23:03
0

canvas.getContext("2d") - is slow.

You need use webgl. For work with webgl use Pixi.js. It's pretty good library.

Loot at this DEMO ( without source )

Alex Shtorov
  • 236
  • 1
  • 7
  • It's pretty good but I don't see better results; on weaker devices, even on my laptop I get FPS drops(there is frame average on the top-left) with your example just like I get with my game. – RunningFromShia Dec 13 '16 at 21:59
  • You can set -2x resolution, and then weaker device will work better. And turn of antialiasing in demo above. Without antialising: http://57.playcode.io With resolution 0.5: http://58.playcode.io Or both method: http://59.playcode.io Yep, weaker phones not ready for web games... – Alex Shtorov Dec 13 '16 at 22:23