Creating a stopwatch/timer along with canvas animation at 60fps - javascript

I am trying to create a timer alongside some canvas animations. The animation is using a function loop set at 60 fps to refresh the canvas and redraw the objects. The only way I can think of making the stopwatch is by using the same loop to take the milliseconds per frame and add it to the text object. I'm just wondering if there is a more efficient way of doing this?
var frame = 0;
canvas.setLoop(function() {
if(particle.x < 1080 && particle.x > 0){
frame++;
particle.x = 540 + (acc*frame*frame)/120;
gField.t.text = "g = 9.81ms⁻²\nMass = "+particle.mass+"kg\nF = ma\nFrame: " + frame + "\nDistance: " + (particle.x - 540).toFixed(1);
stopwatch();
}else{
canvas.timeline.stop();
}
})
var sec = 0;
var tsec = 0;
var hsec = 0;
function stopwatch(){
hsec+= (5/3);
if(hsec >= 10){
tsec++;
hsec = hsec -10;
}
if(tsec >= 10){
sec++;
tsec = tsec-10;
}
time.text = (sec)+":"+(tsec)+(hsec).toFixed(0);
}
var clicks = 0
control.button.bind("click tap", function() {
clicks++;
if(clicks == 1){
canvas.timeline.start();
}else{
clicks = 0;
canvas.timeline.stop();
}
})
P.s. this is for a dynamics simulation program. I am using the oCanvas library for the canvas animation.

Use requestAnimationFrame as this is the most accurate timer you'll get with JavaScript, and bonus is it will provide you with a high-resolution time-stamp:
var ctx = canvas.getContext('2d'),
startTime = null,
lastTime = null, // for scale
isRunning = false,
FPS = 1000/60,
x = 0,
dx = 4; // ideal frame rate
function loop(timeStamp) {
if (!startTime) startTime = timeStamp;
var timeDiff = lastTime ? timeStamp - lastTime : FPS,
timeElapsed = timeStamp - startTime,
timeScale = timeDiff / FPS; // adjust variations in frame rates
lastTime = timeStamp;
ctx.clearRect(0,0,canvas.width, canvas.height);
// do your stuff using timeScale, ie:
// pos.x += velocity.x * timeScale
x += dx * timeScale;
if (x < 0 || x > canvas.width-1) dx = -dx;
ctx.fillRect(x,0,8,8);
ctx.fillText((timeElapsed*0.001).toFixed(4), 10, 50);
ctx.fillText(timeScale.toFixed(1), 10, 90);
if (isRunning) requestAnimationFrame(loop);
}
ctx.font = "40px sans-serif";
btnToggle.addEventListener("click", function() {
if (isRunning) {
isRunning = false;
this.innerHTML = "Start";
} else {
startTime = lastTime = null;
isRunning = true;
requestAnimationFrame(loop)
this.innerHTML = "Stop";
}
}, false);
<canvas id=canvas width=360 height=100></canvas>
<br><button id="btnToggle">Start</button>
To reset start time initialize it with null (or 0). isRunning is used here just as an example on how you can stop the loop (by setting it to false).
Notice that timeScale is used to compensate for frame rate variations. If the loop is not running at 60 FPS then timeScale will compensate for this, ie. if FPS is 30 timeScale would be 2 and so, so that you can update the parameters correctly based on time.

Related

Moving rectangle to location using deltatime

I'm trying to have a simple rectangle move from current location to the clicked location on the canvas. When I provide a constant speed, the rectangle appears and moves fine. But when I multiply it by deltatime the rectangle doesn't appear anymore. I'm using requestAnimationFrame as the draw loop.
canvas.js
window.addEventListener('DOMContentLoaded', (event) => {
let canvas = document.getElementById("gamecanvas");
let ctx = canvas.getContext('2d');
var oldframetime = 0;
var x = 0;
var y = 0;
var dstx = 0;
var dsty = 0;
var deltatime = 0;
var speed = 5;
function getmousepos(evt){
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener("mousedown",e=>{
let coord = getmousepos(e);
dstx = coord.x;
dsty = coord.y;
});
function movetowards(current,target,maxdistancedelta){
if(Math.abs(target - current)<=maxdistancedelta){
return target;
}
return current+Math.sign(target - current) * maxdistancedelta;
}
function tick(timestamp){
deltatime = (timestamp - oldframetime)/1000;
var step = deltatime*speed;
var newlocx = movetowards(x,dstx-50,5);
var newlocy = movetowards(y,dsty-50,5);
ctx.clearRect(0,0,canvas.clientWidth,canvas.clientHeight);
ctx.fillStyle = 'green';
ctx.fillRect(newlocx,newlocy,100,100);
x = newlocx;
y = newlocy;
console.log(newlocx+":"+newlocy);
oldframetime = timestamp;
requestAnimationFrame(tick);
}
requestAnimationFrame(function(){
tick();
});
});
In that example, newlocx and newlocy both print NaN:NaN
But if I choose not to use step and give it a constant speed of like 5, then it works fine.
function tick(timestamp){
deltatime = (timestamp - oldframetime)/1000;
var step = deltatime*speed;
var newlocx = movetowards(x,dstx-50,5);
var newlocy = movetowards(y,dsty-50,5);
ctx.clearRect(0,0,canvas.clientWidth,canvas.clientHeight);
ctx.fillStyle = 'green';
ctx.fillRect(newlocx,newlocy,100,100);
x = newlocx;
y = newlocy;
console.log(newlocx+":"+newlocy);
oldframetime = timestamp;
requestAnimationFrame(tick);
}
The print is also accurate now. Why does multiplyng step by deltatime prevent the rectangle from moving? Or even appearing?
Here's the HTML if anyone's interested.
index.html
<html>
<head>
<script src="canvas.js"></script>
</head>
<body>
<canvas id="gamecanvas" width="2000" height="1000"></canvas>
</body>
</html>
I have checked and found out the issue is in the first statement of tick function. When program starts then value of parameter is undefined. And thats why first statement of tick function result in NaN.
Just use some default value for parameter "timestamp". Its working now but speed is low and i hope you are aware about it.
Line to check:
function tick(timestamp=10)
function tick(timestamp=10) {
deltatime = (timestamp - oldframetime) / 1000;
var step = deltatime * speed;
var newlocx = movetowards(x, dstx - 50, 5);
var newlocy = movetowards(y, dsty - 50, 5);
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx.fillStyle = 'green';
ctx.fillRect(newlocx, newlocy, 100, 100);
x = newlocx;
y = newlocy;
console.log(newlocx + ":" + newlocy);
oldframetime = timestamp;
requestAnimationFrame(tick);
}
You should be starting the animation with requestAnimationFrame rather than indirectly as you have done.
Replace
requestAnimationFrame(function(){
tick();
});
with
requestAnimationFrame(tick)
Then in the function tick you check if oldframetime has been set, if not set deltaTime to 0, as no time has passed, and thus starts the animation at the start. If you set the value of deltaTime to any other value you end up not rendering the animation from the start.
function tick(timestamp){
if (!oldframetime) {
deltaTime = 0;
} else {
deltatime = (timestamp - oldframetime)/1000;
}
// .. animation code
oldframetime = timestamp;
requestAnimationFrame(tick);
}

Javascript game, make spaceship shoot on pressdown with interval

I am working on my gaming skills (mainly with arrays) to generate enemies and now bullets to take them down. I was able to set-up bullets while testing, but were visible only when I had a key pressed (space bar let's say) and with no interval in between, so the browser was not able to take that many at one point.
Is there any simple way to make the ship fire bullets with interval in between (not to load the browser that much) and maybe upon going to the enemy[i].x / y location to delete an enemy and the bullet can disappear ?
Here is the cleaned as much as possible code I have for now (HTML and JS file. Have some images as well and will provide URL to the game to check it if needed - http://sarahkerrigan.biz/spaceship
<!DOCTYPE html>
<html>
<head>
<title>Space Ship</title>
</head>
<body>
<h3>Space Ship</h3>
<canvas id="canvas" width="1000" height="600"></canvas>
<script src="spaceship.js"></script>
</body>
</html>
And here is the spaceship.js file:
var cvs = document.getElementById("canvas");
var ctx = cvs.getContext("2d");
//-------------------------------
// load images
var player = new Image();
var enemy = new Image();
var bullet = new Image();
player.src = "images/player.png";
enemy.src = "images/enemy.png";
bullet.src = "images/fire.png";
//-------------------------------
// vars
var score = 0;
var pause = 0;
var playerY = 300;
var playerX = 100;
var upPressed = false;
var downPressed = false;
var leftPressed = false;
var rightPressed = false;
// audio
var fire = new Audio();
var hit = new Audio();
fire.src = "sounds/fire.mp3";
hit.src = "sounds/hit.mp3";
//-------------------------------
// on key down
document.addEventListener("keydown", keyDownHandler);
function keyDownHandler(e) {
if (e.keyCode == 87) {
upPressed = true;
}
if (e.keyCode == 83) {
downPressed = true;
}
if (e.keyCode == 65) {
leftPressed = true;
}
if (e.keyCode == 68) {
rightPressed = true;
}
}
// on key up
document.addEventListener("keyup", keyUpHandler);
function keyUpHandler(e) {
if (e.keyCode == 87) {
upPressed = false;
}
if (e.keyCode == 83) {
downPressed = false;
}
if (e.keyCode == 65) {
leftPressed = false;
}
if (e.keyCode == 68) {
rightPressed = false;
}
}
//-------------------------------
function moveUp() {
if (playerY <= canvas.height - canvas.height){
}
else{
playerY -= 6;
}
}
function moveDown() {
if (playerY >= canvas.height - player.height){
}
else{
playerY += 6;
}
}
function moveLeft() {
if (playerX <= canvas.width - canvas.width){
}
else{
playerX -= 6;
}
}
function moveRight() {
if (playerX >= canvas.width - player.width){
}
else{
playerX += 6;
}
}
//-------------------------------
// Enemy coordinates
var enemies = [];
enemies[0] = {
x: cvs.width,
y: 0
};
//-------------------------------
// reload page
function reLoad() {
location.reload(); // reload the page
}
//-------------------------------
// draw images
function draw() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (upPressed === true) {
moveUp();
}
if (downPressed === true) {
moveDown();
}
if (leftPressed === true) {
moveLeft();
}
if (rightPressed === true) {
moveRight();
}
//-------------------------------
for (var i = 0; i < enemies.length; i++) {
//draw the enemy
ctx.drawImage(enemy, enemies[i].x, enemies[i].y);
// enemy movement speed
enemies[i].x -= 3;
if (enemies[i].x == 880) {
enemies.push({
x: cvs.width,
y: Math.floor(Math.random() * enemy.height) * 10 - enemy.height
});
}
// detect collision
// if enemy hits player
if (playerX + player.width >= enemies[i].x && playerX <= enemies[i].x + enemy.width && (playerY <= enemies[i].y + enemy.height && playerY + player.height >= enemies[i].y)) {
pause = 1;
}
}
//-------------------------------
//draw the player
ctx.drawImage(player, playerX, playerY);
//draw score
ctx.fillStyle = "#fff";
ctx.font = "20px Verdana";
ctx.fillText("Destroyed ships : " + score + "$", 10, cvs.height - 20);
function onPause() {
if (pause >= 1) {
hit.play();
ctx.fillStyle = "#df8a62";
ctx.fillRect(150, 150, 280, 100);
ctx.fillStyle = "#000";
ctx.font = "20px Verdana";
ctx.fillText("You died:", 165, 170);
document.addEventListener("keydown", reLoad);
} else if (pause <= 0) {
requestAnimationFrame(draw);
}
}
onPause();
}
draw();
You want to use time intervals instead of listeners.
var myVar = setInterval(timeCycle, 50);
function timeCycle() {
//all the stuff you currently have listeners for.
}
This way when the time interval happens it will execute your key presses just once. Then if you want to change your rate of fire you add something like this:
setInterval(timeCycle, 50);
rateOfFire = 5;
shootCoolDown = 0;
function timeCycle() {
if (shootPressed === true) {
if(shootCoolDown === 0){
shootCoolDown = rateOfFire;
shoot();
}
}
if (shootCoolDown > 0){
shootCoolDown --;
}
}
This way it will shoot once every 5 game cycles (or 4-rounds per second in this case).
There are more fancy things you can do to create a delta-time system to offset lag by changing your sim rate based on the time it takes your timeCycle to execute, but that tends to be much more complex and easy to mess up, so I would not suggest going down that rabbit hole for beginners.
[EDIT]
So, I've seen several questions recently about deltaTime, but don't see any good examples of how to implement it; so, here is a basic example I threw together. To implement it, just replace the GAME STUFF part with your actual code of what happens in a game cycle, and run all of your time-based values through the delta() function, and it will convert your values from to units per second to units per currentFrame.
My game us under a load of <input type="text" id="lag" value="100000000"> operations per frame.<br>
My speed is = <input type="text" id="speed" value="500"> px per second<br>
I moved <span id="adjusted"></span>px this frame.<br>
FPS: <span id="fps"></span>
<script>
function wrapDelta(lastTime){
var d = new Date();
var n = d.getSeconds()*1000 + d.getMilliseconds();
if (lastTime >= n) {
lastTime -= 60000;
}
return n - lastTime;
}
function delta(input){
return input * deltaCoeff / 1000;
}
var d = new Date();
var ed = new Date();
var endTime = d.getSeconds()*1000 + d.getMilliseconds();
var startTime = d.getSeconds()*1000 + d.getMilliseconds();
var deltaCoeffMin = 25;
var deltaCoeff = deltaCoeffMin;
setInterval(function () {
d = new Date();
startTime = d.getSeconds()*1000 + d.getMilliseconds();
// START GAME STUFF
var lag = Math.round(Math.sqrt(document.getElementById('lag').value)); //because comparing large numbers caused a wierd lag spike at from 9999999 to 10000000
var speed = document.getElementById('speed').value;
document.getElementById('adjusted').innerHTML = delta(speed);
document.getElementById('fps').innerHTML = (1000/deltaCoeff).toFixed(2);
var i; var j; var k; for (i=0; i<lag; i++){ for (j=0; j<lag; j++){ k = 1234567*1.1;}} //This is just a random math loop to simulate the lag cause by actual game stuff
// END GAME STUFF
ed = new Date();
endTime = ed.getSeconds()*1000 + ed.getMilliseconds();
deltaCoeff = endTime - startTime;
if (deltaCoeff < deltaCoeffMin){deltaCoeff = deltaCoeffMin;}
} , deltaCoeffMin);
</script>
The call to requestAnimationFrame will run the draw function only at a rate supported by the monitor and only if the computer is fast enough. If the code is running slow, it will automatically skip a call to the draw function every now and then. Therefore, the draw function should only contain the rendering code and no logic.
You should first put any code that updates the game's state into another function called update. This function will be called at a consistent-ish rate using setInterval:
function update() {
// read inputs
// move objects
// detect collisions
// etc.
// render a new frame only if the browser is done drawing the previous one
requestAnimationFrame(draw);
}
// run the update function 60 times per second
var updateInterval = setInterval(update, 1000 / 60);
It's always good to store the updateInterval so that we can stop the game from running completely with clearInterval(updateInterval), but you might never need to use it.
Now that you have a somewhat consistent game speed, you can set a cooldown on the shooting like this:
if (fireCooldown > 0) {
fireCooldown -= 1;
}
if (/* holding the fire key */ && fireCooldown === 0) {
// create a projectile in front of the player ship
fireCooldown = 30;
}
You'll need to declare that variable somewhere first with var fireCooldown = 0; somewhere, but it should get you started.
As mentioned by Jake Holzinger in the comments, setInterval isn't 100% accurate and the update function might be called a few milliseconds later than expected. You'd have to check the time between two calls yourself using Date objects or other means if you wanted to time stuff perfectly, but I doubt this is necessary for a simple shooter game.

Best way to slow down javascript canvas animation?

I have a canvas animation that I wish to slow down, it's pretty simple noise effect that moves around pixels. demo can be found here: https://codepen.io/anon/pen/eyyjqm - I want to slow down the pixels movement/jitter.
const canvas = document.querySelector('canvas'),
ctx = canvas.getContext('2d')
canvas.width = canvas.height = 128
resize();
window.onresize = resize;
function resize() {
canvas.width = window.innerWidth * window.devicePixelRatio / 1
canvas.height = window.innerHeight * window.devicePixelRatio / 1
canvas.style.width = window.innerWidth + 'px'
canvas.style.height = window.innerHeight + 'px'
}
function noise(ctx) {
const w = ctx.canvas.width,
h = ctx.canvas.height,
iData = ctx.createImageData(w, h),
buffer32 = new Uint32Array(iData.data.buffer),
len = buffer32.length
let i = 1
for(; i < len;i++)
if (Math.random() < 0.5) buffer32[i] = 0xffffffff;
ctx.putImageData(iData, 0, 0);
}
(function loop() {
noise(ctx);
requestAnimationFrame(loop);
})();
I've tried;
window.setInterval('noise(ctx)',10);
but this looks so jittery and not very smooth because im setting an interval of 10 frames. What would be a better way to slow down the animation?
Appreciate any ideas! Thanks, John
Here is an approach maybe can help you.
The requestAnimationFrame pass as parameter the currentTime which is executing, so you can get some delta currentTime - oldTime time in each call and if is very short not execute the noise function again, on the other hand if it has passed a considerable time execute it again, this deltaTime can de set:
something like this:
delta = 200;
oldTime = 0;
function loop(currentTime) {
if(oldTime === 0) {
oldTime = currentTime;
}
if((currentTime - oldTime) >= delta){
noise(ctx);
oldTime = currentTime;
}
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
here is working: https://codepen.io/anon/pen/GyyYKa
I rewrote some of the code to make it slightly more efficient and to hopefully get a similar effect you were looking for:
const w = ctx.canvas.width;
h = ctx.canvas.height;
const iData = ctx.createImageData(w, h);
buffer32 = new Uint32Array(iData.data.buffer);
len = buffer32.length;
window.setInterval('noise(ctx)',38);
function noise(ctx) {
let i = 1
for(; i < len;i += 4)
if (Math.random() < 0.4)
{
buffer32[i] = 0xffffffff;
} else {
buffer32[i] = 0x00000000;
}
ctx.putImageData(iData, 0, 0);
}
//(function loop() {
//noise(ctx);
// requestAnimationFrame(loop);
//})();
I would however recommend applying a more conventional noise algorithm such as the simplex algorithm. See example here: http://haptic-data.com/toxiclibsjs/examples/simplex-noise-canvas. It will most likely be much smoother.
I've used this method in the past to limit how frequently the animation is updated.
let frame = 0
let frameLimit = 3
draw() {
// animation code here...
}
animate() {
frame++
if (frame % frameLimit === 0) {
// make some changes then redraw animation
draw()
}
requestAnimationFrame(animate)
}
Now your animation will only update every third frame. you can then easily adjust the value to speed up or slow down your animation. I don't want to claim that this is the best approach but its an alternative that I didn't see listed.

Canvas game, where to write code for background?

The Problem
I have been creating a game, I have got to a stage where I want to see what it looks like with a mockup background I have created.
The Question
Where about in my code should I place this code as the place it currently is doesnt show the background.
I want this background on the canvas, the dimensions are correct.
The Code
var game = create_game();
game.init();
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var background_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
projectile_img.src = "projectile.png";
player_img.src = "player.png";
background_img.src = "background.png";
background_img.onload = function(){
context.drawImage(background_img, 0, 0);
}
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#410b11";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function setupProjectiles() {
var max_projectiles = level * projectiles_per_level;
while (projectiles.length < max_projectiles) {
initProjectile(projectiles.length);
}
}
function initProjectile(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
projectiles[index] = {
x: Math.round(Math.random() * (width - 2 * projectile_w)) + projectile_w,
y: -projectile_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_projectiles++;
}
function collision(projectile) {
if (projectile.y + projectile_img.height < player.y + 20) {
return false;
}
if (projectile.y > player.y + 74) {
return false;
}
if (projectile.x + projectile_img.width < player.x + 20) {
return false;
}
if (projectile.x > player.x + 177) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(player.score / 10));
setupProjectiles();
}
function tick() {
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#410b11";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
ctx.drawImage(background_img, 0, backgroundY);
}
return {
init: init
};
}
As already pointed out in a comment, here more precisely:
First of all, the background picture must be rendered first in every animation frame.
However, the picture didn't show up at all. This is due to the fact that variable was used (backgroundY), which is never declared somewhere.
This should actually printed to the console as an error "backgroundY" is not defined.
Whenever an the property src of an image object is set to a value, it takes some time until it's loaded. So in many cases, it's necessary to indicate the moment, when it's finished loading by the onload callback.
In this case, however, it's not necessary. The tick / animation loop function will just draw nothing (an empty image object) until it's loaded. After it's loaded it will continue to draw the loaded image every frame.
If the background is really important, meaning, the app should only start, when it's there, of course, one can only start the whole game / animation from within the img.onload handler.
You must draw:
the background first
the player later
level/score info last
Background < Player < UI < You Looking
The drawing order is from back to top (painters algorithm)
Also note that for performance reasons if you background never changes you could draw it in another 'static' canvas under the game canvas.
Otherwise the background will be drawn above/over the player and hide it.

Javascript Sluggish player movement

UPDATE
changed it to the following and noticed speed improvements. The issue now is that the player will just slide without animating the frames.
var animator, frames;
animator = window.setInterval(function(){
if(currentFrame == totalFrames){
clearInterval(animator);
currentFrame = 0;
update();
isMoving = 0;
return;
}
xPosition += x;
yPosition += y;
frames = window.requestAnimationFrame(animator);
currentFrame++;
update();
},frames);
Some of the issues I am currently facing are: the map edges code section is completely broken. I am just trying to make it so that the player cannot move beyond the canvas.width/canvas.height. Also, my player movement is very sluggish and unresponsive. I think it's because of the isMoving check I added. I want to be able to move much smoother. Right now the character takes so long to move that I feel as if I am lagging. Also, for some reason, sometimes it'll move more than one time. It is completely random when it happens. Any help would be appreciated
var playerSprite = new Image();
playerSprite.src = "male.png";
var playerWidth = 64;
var playerHeight = 64;
var currentFrame = 0;
var totalFrames = 8;
var moveDistance = 4; // move 4 pixels
var xPosition = 300;
var yPosition = 200;
var direction = 2; // south, options: 0 - 3
var isMoving = 0;
var canvas, context;
window.addEventListener("load", function(){
canvas = document.getElementById('map');
context = canvas.getContext('2d');
})
function draw(){
context.drawImage(playerSprite,currentFrame * playerWidth, direction* playerHeight ,playerWidth,playerHeight,xPosition,yPosition,playerWidth,playerHeight);
}
function update()
{
clearMap();
draw();
}
function move(x, y){
if(isMoving)return;
isMoving = 1;
if(x > 0) direction = 3;
else if(x < 0) direction = 1;
if(y > 0) direction = 2;
else if(y < 0) direction = 0;
//update direction no matter what, implemented
// in order for directions to update
// when changing directions in map edges
//update();
/* Broken
if(xPosition + playerWidth + x > canvas.width)return; //works
else if(xPosition - x < 0)return; // player gets stuck
if(yPosition + playerHeight + y > canvas.height)return; //works
else if(yPosition - y < 0)return; // player gets stuck
//xPosition += x;
//yPosition += y;
*/
//actual animation update
var animator;
animator = window.setInterval(function(){
if(currentFrame == totalFrames){
clearInterval(animator);
currentFrame = 0;
update();
isMoving = 0;
return;
}
xPosition += x;
yPosition += y;
currentFrame++;
update();
},1000/16);
}
function clearMap(){
context.clearRect(0, 0, canvas.width, canvas.height);
}
function keyPress(e)
{
if(currentFrame == totalFrames){
currentFrame = 0;
}
switch(e.keyCode){
case 38: move(0, -moveDistance); break;
case 40: move(0, +moveDistance); break;
case 39: move(+moveDistance, 0); break;
case 37: move(-moveDistance, 0); break;
}
}
window.addEventListener("load", update, false);
window.addEventListener("keydown",keyPress);
Main points I changed:
No use of setInterval anywhere. Instead we let the browser handle the FPS at a rate it can handle using requestAnimationFrame.
One central game loop (update()). Before, you were doing a bunch of calculations and starting new background loops every time you press a key. That's bad. If someone were to mash the arrow keys, the browser would have to process 100+ setIntervals in the background.
Instead of doing any calculation in the key events, we're just using a variable to keep track of which buttons are pressed. Then in the game loop, which happens each frame, we can move the player a few pixels if an arrow key is held.
Exercises for you:
The animation is insanely fast because the player-frame is advanced every game-frame. Slow it down!
If a faster computer runs at 60fps, the player will move 60 * 4 = 240 pixels every second. If a slower computer runs at 20fps, the player will only move 20 * 4 = 80 pixels every second. That's actually a huge difference. To make your game run consistently regardless of platform, you should move the player more or less depending on how fast the game is running. Here's a good article to get you started. Also the requestAnimationFrame documentation will be helpful.
Here's the code:
var playerSprite = new Image();
playerSprite.src = "male.png";
var playerWidth = 64;
var playerHeight = 64;
var currentFrame = 0;
var totalFrames = 8;
var direction = 2; // south, options: 0 - 3
var moveDistance = 4; // move 4 pixels
var xPosition = 300;
var yPosition = 200;
var left = 0,
right = 0,
up = 0,
down = 0;
var canvas, context;
window.addEventListener("keydown", keyPress);
window.addEventListener("keyup", keyRelease);
window.addEventListener("load", function(){
canvas = document.getElementById('map');
context = canvas.getContext('2d');
// tells the browser to call update() as soon as it's ready
// this prevents lockups, and also the browser regulates the FPS
window.requestAnimationFrame(update);
});
function update() {
// EVERYTHING game related happens in update (except listening for key events).
// This keeps everything organized, and prevents any lag/slowdown issues
// handles player movement and animation
movePlayer();
// handles all drawing
draw();
// lets the browser know we're ready to draw the next frame
window.requestAnimationFrame(update);
}
function movePlayer() {
if(left) {
xPosition -= moveDistance;
direction = 1;
}
if(right) {
xPosition += moveDistance;
direction = 3;
}
if(up) {
yPosition -= moveDistance;
direction = 0;
}
if(down) {
yPosition += moveDistance;
direction = 2;
}
// all this code happens every frame
// in english: if we're moving, advance to the next frame
if(left || right || up || down) {
currentFrame ++;
if(currentFrame == totalFrames) currentFrame = 0;
}
}
function draw() {
// clear the map
context.clearRect(0, 0, canvas.width, canvas.height);
// draw the next frame
context.drawImage(playerSprite, currentFrame * playerWidth, direction * playerHeight,
playerWidth, playerHeight,
xPosition, yPosition,
playerWidth, playerHeight);
}
// keyPress and keyRelease ensure that the variables are
// equal to 1 if pressed and 0 otherwise.
function keyPress(e)
{
switch(e.keyCode){
case 38: up = 1; break;
case 40: down = 1; break;
case 39: right = 1; break;
case 37: left = 1; break;
}
}
function keyRelease(e)
{
switch(e.keyCode){
case 38: up = 0; break;
case 40: down = 0; break;
case 39: right = 0; break;
case 37: left = 0; break;
}
}
EDIT: Forgot to mention that I opted to include delta on my player's movement instead of on my update() redraws to prevent the whole game from running at 20 fps.
Forgot to post my revised and fully functional code. Thanks #Entity for helping me out <3. As you can see, I've begun to take more liberties and experimenting as well.
var fps = 20, fpsInterval = 1000/fps, now, then = Date.now(), delta;
var moving = {38:0, 40:0, 39:0, 37:0} // north, south, east, west
function move(direction, toggle){moving[direction] = toggle}
function keyDown(e){move(e.keyCode,1)}
function keyUp(e){move(e.keyCode,0)}
function playerMovement(){
now = Date.now();
delta = now - then;
if(delta > fpsInterval){
then = now - (delta % fpsInterval);
// north = 38, south = 40, east = 39, west = 37
// stop movement stall from opposite directions
if(moving[38] && moving[40]){move(40,0)}
if(moving[39] && moving[37]){move(37,0)}
// flip order to change diagonal rendering mode
if(moving[38]) {direction = 0; yPosition -= moveDistance};
if(moving[40]) {direction = 2; yPosition += moveDistance};
if(moving[39]) {direction = 3; xPosition += moveDistance};
if(moving[37]) {direction = 1; xPosition -= moveDistance};
if(moving[38] || moving[40] || moving[39] || moving[37]) currentFrame++;
if(currentFrame == totalFrames) currentFrame = 0;
}
}

Categories

Resources