I just started learning javascript, and i already faced some problems. I have a very simple "game" where you move red box with arrow keys and there is black background. I saw some topics about the same question and some of the guys says that make a buffer and other half says that don't do buffer because your browser does it for you. But anyway, I just thought that there is something wrong in my code because it's flickering but it's just so simple that i think it should not.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;
var mySprite = {
x: 200,
y: 200,
width: 50,
height: 50,
color: '#c00'
};
var yvel = 0;
var xvel = 0 ;
var moved = false;
var mspeed = 15;
var keysDown = {};
window.addEventListener('keydown', function(e) {
keysDown[e.keyCode] = true;
});
window.addEventListener('keyup', function(e) {
delete keysDown[e.keyCode];
});
function update() {
if (37 in keysDown) {
moved = true;
if(xvel > -mspeed){
xvel -= 0.5;
}
}
if (38 in keysDown) {
moved = true;
if(yvel > -mspeed){
yvel -= 0.5
}
}
if (39 in keysDown) {
moved = true;
if(xvel < mspeed){
xvel += 0.5
}
}
if (40 in keysDown) {
moved = true;
if(yvel < mspeed){
yvel += 0.5
}
}
mySprite.x += xvel;
mySprite.y += yvel;
if(moved == false){
xvel = xvel * 0.95;
yvel = yvel * 0.95;
}
moved = false;
}
function render() {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = mySprite.color;
ctx.fillRect(mySprite.x, mySprite.y, mySprite.width, mySprite.height);
}
function run() {
update();
render();
}
setInterval(run, 1000/60);
You can try it here:w3school
Just copy my code and put it inside tags and change the "MyCanvas" name to "canvas"
The double buffering is in fact allready activated on major browsers.
What you need to do is to get in sync with the screen, that's what's
provide requestAnimationFrame :
https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame
here's the polyfill for requestAnimationFrame :
requestAnimationFrame (rAF) is not standard yet : every 'vendor' i.e. browser (Chrome, FF, Safari, ...) has its own naming.
A polyfill is a piece of code that will 'install' a function in your environement whatever the vendor. Here you will be able to access requestAnimationFrame using window.requestAnimationFrame and you will not care any more about the vendor.
The || operator acts as a 'scan' : it will stop on the first 'truthy' ( == true) expression and return it without evaluating the remaining ones. So the last function definition, for instance, won't get evaluated if msRequestAnimationFrame is defined.
I have to add that the fallback in case requestAnimationFrame is not found is awfull : setTimeout has a very poor accuracy, but thankfully rAF will be found : it is allready there on Chrome(without prefix), Safari (webkit), IE >9 (ms), Firefox (moz) and Opera (o). )
// requestAnimationFrame polyfill
var w=window, foundRequestAnimationFrame = w.requestAnimationFrame ||
w.webkitRequestAnimationFrame || w.msRequestAnimationFrame ||
w.mozRequestAnimationFrame || w.oRequestAnimationFrame ||
function(cb) { setTimeout(cb,1000/60); } ;
window.requestAnimationFrame = foundRequestAnimationFrame ;
and after exchanging update and render (way better), your run loop will be :
function run() {
render();
update();
window.requestAnimationFrame(run);
}
run();
in any case you are interested, i discussed the javascript game loop here :
http://gamealchemist.wordpress.com/2013/03/16/thoughts-on-the-javascript-game-loop/
Why would you need a timer ? the code above will call run again and again, since run ends by a defered call to run : run will be called next time display is available (typically 60 times per seconds).
And you can know about the current time with Date.now().
For more insights about the game loop, rAF, and some time handling issues, see my link above.
Here is your code after some refactoring :
(fiddle is here : http://jsbin.com/uvACEVA/2/edit )
var canvasWidth = 800, canvasHeight = 600;
var canvas = document.getElementById('canvas');
canvas.width = canvasWidth; canvas.height = canvasHeight;
var ctx = canvas.getContext('2d');
// player definition
var mySprite = {
x: 200,
y: 200,
xvel : 0,
yvel : 0,
velIncr : 0.5,
velAttenuation : 0.95,
maxVel : 15,
width: 50,
height: 50,
color: '#c00',
moved : false
};
mySprite.update = function (dt) {
if (keysDown[37]) {
this.moved = true;
if(this.xvel > -this.maxVel){
this.xvel -= this.velIncr;
}
}
if (keysDown[38]) {
this.moved = true;
if(this.yvel > -this.maxVel){
this.yvel -= this.velIncr;
}
}
if (keysDown[39]) {
this.moved = true;
if(this.xvel < this.maxVel){
this.xvel += this.velIncr;
}
}
if (keysDown[40]) {
this.moved = true;
if(this.yvel < this.maxVel){
this.yvel += this.velIncr;
}
}
// to have a frame-rate independant game,
// compute the real dt in run() and ...
this.x += this.xvel; // ... use this.x += this.xvel * dt;
this.y += this.yvel; // ... this.y += this.yvel * dt;
if(this.moved == false){
this.xvel *= this.velAttenuation;
this.yvel *= this.velAttenuation;
}
this.moved = false;
};
mySprite.draw = function(ctx) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
};
// Keyboard handling
var keysDown = [];
window.addEventListener('keydown', function(e) {
keysDown[e.keyCode] = true;
e.preventDefault();
e.stopPropagation();
});
window.addEventListener('keyup', function(e) {
keysDown[e.keyCode] = false;
e.preventDefault();
e.stopPropagation();
});
// requestAnimationFrame polyfill
var w=window, foundRequestAnimationFrame = w.requestAnimationFrame ||
w.webkitRequestAnimationFrame || w.msRequestAnimationFrame ||
w.mozRequestAnimationFrame || w.oRequestAnimationFrame ||
function(cb) { setTimeout(cb,1000/60); } ;
window.requestAnimationFrame = foundRequestAnimationFrame ;
// main animation loop
function update(dt) {
mySprite.update(dt); // only one for the moment.
}
function render(ctx) {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
mySprite.draw(ctx);
}
var dt = 16; // milliseconds elapsed since last call.
// ideally you should compute it in run(), i didn't for simplicity
function run() {
render(ctx);
update(dt);
window.requestAnimationFrame(run);
}
run();
Related
I am creating this game where you move a block with your mouse and avoid obstacles that are being created from right side of the screen to the left, my cursor used to work fine, so did the obstacle creation, but when i combined them it doesn't seem to work anymore and i can't figure out why, here is the game code,
var myGamePiece;
var myObstacles = [];
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGameArea.start();
}
var myGameArea = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 600;
this.canvas.height = 600;
this.canvas.style.cursor = "none";
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('mousemove', function(e) {
myGameArea.x = e.pageX;
myGameArea.y = e.pageY;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
function everyinterval(n) {
if ((myGameArea.frameNo / n) % 1 == 0) {
return true;
}
return false;
}
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) ||
(mytop > otherbottom) ||
(myright < otherleft) ||
(myleft > otherright)) {
crash = false;
}
return crash;
}
}
function updateGameArea() {
var x, y;
for (i = 0; i < myObstacles.length; i += 1) {
if (myGamePiece.crashWith(myObstacles[i])) {
myGameArea.stop();
return;
myGameArea.clear();
myObstacle.x += -1;
myObstacle.update();
if (myGameArea.x && myGameArea.y) {
myGamePiece.x = myGameArea.x;
myGamePiece.y = myGameArea.y;
}
myGamePiece.update();
}
}
}
myGameArea.clear();
myGameArea.frameNo += 1;
if (myGameArea.frameNo == 1 || everyinterval(150)) {
x = myGameArea.canvas.width;
y = myGameArea.canvas.height - 200;
myObstacles.push(new component(10, 20, "green", x, y));
}
for (i = 0; i < myObstacles.length; i += 1) {
myObstacles[i].x += -1;
myObstacles[i].update();
}
startGame();
canvas {
border: 1px solid #d3d3d3;
background-color: #f1f1f1;
}
<body>
<p>move the cursor to move the blocky boii!</p>
</body>
if you guys figure out what's wrong with it and possibly add why and what am i doing wrong in the code in general (structure, position etc) i would be very grateful, feel free to hate and criticize the code, I am self learner and don't wont to get the wrong habits that will be difficult to get rid of in the future.
thank you for reply
I respect anyone who is open to improving, so please let me know if this helps. For further code improvements, tips, etc, I'd like to point you towards Code Review Stack Exchange. I'll put my notes and working code here, but it's bound to get rather long. First, though, your immediate errors are fixed and commented in the snippet below. (Mostly comes down to "fix your curly braces")
Errors Fixed
var myGamePiece;
var myObstacles = [];
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGameArea.start();
}
var myGameArea = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 600;
this.canvas.height = 600;
this.canvas.style.cursor = "none";
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('mousemove', function(e) {
myGameArea.x = e.pageX;
myGameArea.y = e.pageY;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
function everyinterval(n) {
if ((myGameArea.frameNo / n) % 1 == 0) {
return true;
}
return false;
}
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) ||
(mytop > otherbottom) ||
(myright < otherleft) ||
(myleft > otherright)) {
crash = false;
}
return crash;
}
}
function updateGameArea() {
var x, y;
for (i = 0; i < myObstacles.length; i += 1) {
var myObstacle = myObstacles[i] // problem 3: this was missing
if (myGamePiece.crashWith(myObstacle)) {
myGameArea.stop();
return;
} // problem 4: needed to be closed
myGameArea.clear();
myObstacle.x += -1;
myObstacle.update();
if (myGameArea.x && myGameArea.y) {
myGamePiece.x = myGameArea.x;
myGamePiece.y = myGameArea.y;
}
myGamePiece.update();
}
// problem 1, from here down needed moved into the updateGameArea() function
// problem 2: myGameArea.clear(); needed removed from here
myGameArea.frameNo += 1;
if (myGameArea.frameNo == 1 || everyinterval(150)) {
x = myGameArea.canvas.width;
y = myGameArea.canvas.height - 200;
myObstacles.push(new component(10, 20, "green", x, y));
}
for (i = 0; i < myObstacles.length; i += 1) {
myObstacles[i].x += -1;
myObstacles[i].update();
}
}
startGame();
canvas {
border: 1px solid #d3d3d3;
background-color: #f1f1f1;
}
<p>move the cursor to move the blocky boii!</p>
Further Notes/Tips
General Development
Using some type of tool (like Atom, VSCode, etc) may help you identify where blocks start and end. You can often hover over an opening { and the matching } will get highlighted
You should delete obstacles so you don't infinitely spawn new ones
Try to keep "circular dependencies" out of your code
Time-based, rather than frame-based logic helps keep things moving smoothly. For the most basic approach, you can animate/move entities using the time since the last frame.
JavaScript
Knowing how prototypes work in JS can help keep your memory usage down and your game running faster. Your code currently adds new functions to every "component" instance when they get created
In the JS community, we have a couple standard style guides and conventions. For example, we usually capitalize constructor functions (and classes from ES2015). Check out some of the more complete guides like Standard and/or AirBnB to know what is considered common. (helps us speak the same language)
If you don't already know it, ES2015+ provides a number of improvements (often maintenance related) over older code. For example using const and let is now often more "normal" than using var, as is using arrow functions instead of regular functions. (these decisions are mostly highlighted in the style guides I provided above)
Try to avoid polluting the global namespace by creating variables like myGamePiece and myObstacles. Contain them using scoping somehow. In the code below, I used an IIFE to keep variables local to that code. The fewer implicit interactions we have between scripts, the better.
Also, avoid global functions, when you can. For example, most events you might need are best handled via .addEventListener(). Your pre-edit question included <body onload="startGame()">, which requires a global function. This can be replaced with a simple startGame() call within a script tag right above </body> OR you can use something like document.addEventListener('DOMContentLoaded', startGame)
You can create a better game loop using window.requestAnimationFrame() (see this documentation for more info)
Ideally, each of these classes and functions could be split into separate modules/files as they grow, too. It can be incredibly helpful to see the structure/architecture of an application without needing to read through the full code to understand...which leads me to the next section:
Architecture
Game developers often split up the parts of their games according to an architecture called ECS. I'd recommend learning how this works before going too deep. It's not considered a perfect architecture at its most basic form, but the goals behind it hold merit.
Also check out Functional Programming (FP) and what it considers to be "state". ECS is basically just FP within the context of a game loop.
Aligning yourself with the common terminology of each part will help you search for the details you don't yet know. I changed a few names around in my additional snippet below like "component" -> "Entity" and "crash" to "collision/collide" in an effort to align with what these parts are commonly called.
Putting the notes together...
The snippet below still isn't perfect, but I hope it demonstrates most of the points I provided above. I started with your code and slowly went through refactoring steps until it became what you see below:
;(() => {
class CanvasCamera2D {
constructor(canvas) {
this.canvas = canvas
this.context = canvas.getContext('2d')
}
get width() { return this.canvas.width }
get height() { return this.canvas.height }
clear() {
this.context.clearRect(0, 0, this.width, this.height)
}
render(fn) {
fn(this.context, this.canvas)
}
}
class Entity {
constructor(type, width, height, x = 0, y = 0) {
this.type = type
this.width = width
this.height = height
this.x = x
this.y = y
}
get left() { return this.x }
get right() { return this.x + this.width }
get top() { return this.y }
get bottom() { return this.y + this.height }
collidedWith(otherobj) {
return !(
(this.bottom < otherobj.top) ||
(this.top > otherobj.bottom) ||
(this.right < otherobj.left) ||
(this.left > otherobj.right)
)
}
}
const update = (
{ // state
gamePiece,
obstacles,
mouse,
deltaTime,
timestamp,
lastSpawnTime
},
{ // actions
stopGame,
setLastSpawnTime,
filterObstacles,
setGamePiecePosition,
},
camera
) => {
// Add an obstacle every so many milliseconds
if ((timestamp - lastSpawnTime) >= 800) {
obstacles.push(
new Entity('obstacle', 10, 20, camera.width, camera.height - 200)
)
setLastSpawnTime(timestamp)
}
// Go through each obstacle and check for collisions
filterObstacles(obstacle => {
if (obstacle.collidedWith(gamePiece)) {
stopGame()
}
// Move obstacles until they hit 0 (then remove from the list)
obstacle.x -= deltaTime / 4
return !(obstacle.x < 0)
})
// Move gamePiece with mouse
setGamePiecePosition(mouse.x, mouse.y)
}
const render = ({ gamePiece, obstacles }, camera) => {
camera.clear()
const entities = [gamePiece].concat(obstacles)
entities.forEach(entity => camera.render(ctx => {
ctx.fillStyle = entity.type === 'gamePiece' ? 'green' : 'red'
ctx.fillRect(entity.x, entity.y, entity.width, entity.height)
}))
}
const startGame = (update, render, inState, maxFrameRate) => {
const state = Object.assign({
deltaTime: 0,
timestamp: 0,
lastSpawnTime: 0,
gameRunning: true,
}, inState)
// Created in an effort to avoid direct modification of state
const actions = {
stopGame() { state.gameRunning = false },
setLastSpawnTime(time) { state.lastSpawnTime = time },
filterObstacles(fn) { state.obstacles = state.obstacles.filter(fn) },
setGamePiecePosition(x, y) { Object.assign(state.gamePiece, { x, y }) },
setMousePosition(x, y) { Object.assign(state.mouse, { x, y }) },
}
// Set up camera
const canvas = Object.assign(document.createElement('canvas'), {
width: 300,
height: 300,
})
document.body.insertBefore(canvas, document.body.childNodes[0])
const camera = new CanvasCamera2D(canvas)
// Update state when mouse moves (scaled in case canvas changes in size)
window.addEventListener('mousemove', e => {
actions.setMousePosition(
e.pageX * (canvas.width / canvas.clientWidth),
e.pageY * (canvas.height / canvas.clientHeight),
)
})
// Start game loop
let lastTimestamp = performance.now()
const loop = (timestamp) => {
const delta = timestamp - lastTimestamp
if (delta > MAX_FRAME_RATE) {
state.timestamp = timestamp
state.deltaTime = delta
update(state, actions, camera)
render(state, camera)
lastTimestamp = timestamp
if (!state.gameRunning) return
}
requestAnimationFrame(loop)
}
loop(performance.now())
}
const MAX_FRAME_RATE = 60 / 1000
startGame(update, render, {
mouse: { x: 0, y: 0 },
gamePiece: new Entity('gamePiece', 30, 30, 10, 120),
obstacles: [],
}, MAX_FRAME_RATE)
})()
canvas {
border: 1px solid #d3d3d3;
background-color: #f1f1f1;
max-width: 100%;
cursor: none;
}
<p>move the cursor to move the blocky boii!</p>
I'm currently working on a small game, and I'm trying to implement a "game over" function when I hit the obstacle. But every time I add all of the code to implement that feature it destroys everything. I would gladly appreciate if someone could help me. I'm not sure what the problem is.
Here is the entire code (only the js part):
var myGamePiece;
var myObstacle;
function startGame() {
myGamePiece = new component(30, 30, "red", 225, 225);
myObstacle = new component(40, 40, "green", 300, 120);
myGameArea.start();
}
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 1000;
this.canvas.height = 890;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
e.preventDefault();
myGameArea.keys = (myGameArea.keys || []);
myGameArea.keys[e.keyCode] = (e.type == "keydown");
})
window.addEventListener('keyup', function (e) {
myGameArea.keys[e.keyCode] = (e.type == "keydown");
})
},
stop : function() {
clearInterval(this.interval);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speed = 0;
this.angle = 0;
this.moveAngle = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.fillStyle = color;
ctx.fillRect(this.width / -2, this.height / -2, this.width, this.height);
ctx.restore();
}
this.newPos = function() {
this.angle += this.moveAngle * Math.PI / 180;
this.x += this.speed * Math.sin(this.angle);
this.y -= this.speed * Math.cos(this.angle);
}
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.moveAngle = 0;
myGamePiece.speed = 0;
myObstacle.update();
if (myGameArea.keys && myGameArea.keys[37]) {myGamePiece.moveAngle = -2; }
if (myGameArea.keys && myGameArea.keys[39]) {myGamePiece.moveAngle = 2; }
if (myGameArea.keys && myGameArea.keys[38]) {myGamePiece.speed= 2; }
if (myGameArea.keys && myGameArea.keys[40]) {myGamePiece.speed= -2; }
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
Try fixing this:
You have missing semicolons on line 14 and 17
Let's see...
What you're asking for is called 2D Collision detection. If you're going to use squares, it's rather simple. Basically, compare the two squares with the following:
if (rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y) {
// collision detected!
}
Taken from: https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
This code only works with squares without any rotations. For rotation, you would have then have to break out the trigonometry functions of sin/cos/tan based on the angle of rotation.
There are multiple solutions to 2D collision. One method, based on having personally used this for geofencing, is to take every point of Object 1 and compare it against every side of Object 2. If there are an odd number of sides in one direction of a point (for example, to the right) then that makes it's inside the object. You would iterate this for every point and return true if any return odd. You should then repeat process inverse process with Object 2 points.
This is called a Point-in-Polygon approach:
https://en.wikipedia.org/wiki/Point_in_polygon
This works with polygon shapes, but once you use curves, it gets harder. But, generally, gaming uses "hitboxes" because of the simplistic nature.
Edit: To note, this Point-in-Polygon works if the hitboxes are the same size, but if you can overlap two rectangles against each other, then you would have to compare the intersection points of each side of Object1 with sides in Object2.
The code will render the animation with one of the "boxes" , but acts very strangely when two of the "boxes" are drawn within the setInterval method. I suspect this may have something to do with ctx.clearRect.
JS Fiddle
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
var raf;
var switchDirection = [true, true];
function alien() {
if (canvas.getContext) {
function Spaceships(x) {
this.x = x;
this.y = 100;
this.color = 'rgb(192, 192, 192)';
this.draw = function() {
ctx.beginPath();
ctx.rect(this.x, this.y, 100, 100);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
};
};
var alienOne = new Spaceships(100);
var alienTwo = new Spaceships(500);
alienOne.draw();
alienTwo.draw();
setInterval(function redraw() {
if (alienOne.x == 200) {
switchDirection[0] = false;
} else if (alienOne.x == 100) {
switchDirection[0] = true;
}
if (switchDirection[0] == true) {
ctx.clearRect(0, 0, 0, 0);
alienOne.draw();
alienOne.x += 10;
} else if (switchDirection[0] == false) {
ctx.clearRect(0, 0, 0, 0);
alienOne.draw();
alienOne.x -= 10;
}
if (alienTwo.x == 600) {
switchDirection[1] = false;
} else if (alienTwo.x == 500) {
switchDirection[1] = true;
}
if (switchDirection[1] == true) {
ctx.beginPath();
ctx.clearRect(0, 0, 0, 0);
alienTwo.draw();
alienTwo.x += 10;
} else if (switchDirection[1] == false) {
ctx.beginPath();
ctx.clearRect(0, 0, 0, 0);
alienTwo.draw();
alienTwo.x -= 10;
}
}, 250);
} else {
alert('you need a better browser to play this game')
}
};
alien();
I have tried placing the second box in its own .setInterval. The animation renders incorrectly by not properly adjusting its width.
Any help would be greatly appreciated.
I fixed your problem, and refactored some of your code:
The main problem was the clearing of the canvas with the clearRect method. You didn't specify the right parameter values.
Some of the code is very repetitive. For now this isn't really a problem since you're only using 2 aliens, however this could become a problem once you have more than two. Imagine writing almost identical code for 40 aliens. This is solved with arrays and for-loops. Also, you use a boolean variable to determine when to add/substract from the alienX. This too can become quite a hassle. Now, we check when the spaceships exceed/go below the tolerated x-value, and change the sign of your velocity accordingly.
As mentioned by #Kaiido in the comments above, you can use requestAnimationFrame for your animations, which is better for this project than setInterval. To maintain the choppy animation effect, I used a really basic counter, with the % operator to only execute the code every 4x in a second (usually, reqAnimFrame callbacks 60x per second, so 60/15 = 4, equal to 250ms in your example) (see below).
The code:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var aliens = [];
var counter = 0;
function alien() {
if (canvas.getContext) {
function Spaceships(x) {
this.baseX = x; // added a baseX to remember where the spaceship started off
this.x = x;
this.y = 100;
this.velocityX = 10;
this.color = "rgb(192, 192, 192)";
this.draw = function() {
ctx.beginPath();
ctx.rect(this.x, this.y, 100, 100);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
}
}
aliens.push(new Spaceships(100), new Spaceships(500));
drawAllAliens();
function redraw() {
requestAnimationFrame(redraw);
var maxXDiff = 100; //choose how far or near your aliens/squares can go
counter ++;
// remainder/modulo operator:
// reduce animation to every 4x per sec for that "choppy" animation
if(counter%15 === 0){
for (var i = 0; i < aliens.length; i ++) {
if ( ( (aliens[i].baseX + maxXDiff) < aliens[i].x ) || (aliens[i].baseX > aliens[i].x) ) {
aliens[i].velocityX = -aliens[i].velocityX; // switches the sign
}
aliens[i].x += aliens[i].velocityX;
}
}
drawAllAliens();
}
redraw();
function drawAllAliens() {
ctx.clearRect(0,0,canvas.width,canvas.height);
for (var i = 0; i < aliens.length; i++) {
aliens[i].draw();
}
}
} else {
alert("you need a better browser to play this game");
}
}
alien();
In canvas, there is a image, which moves on events of arrow keys.
But it flickers in firefox and works fine on chrome and ie.
I don't want a solution like to clear only the portion of canvas, because I have other things added in canvas too.
Below is my code:
var
velY = 0,
velX = 0,
speed = 2,
friction = 0.90,
keys = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
playerDirection="img/player/player_back.png";
function update() {
if (keys[38]) {
if (velY > -speed) {
velY--;
playerDirection="img/player/player_back.png";
}
}
if (keys[40]) {
if (velY < speed) {
velY++;
playerDirection="img/player/player_front.png";
}
}
if (keys[39]) {
if (velX < speed) {
velX++;
playerDirection="img/player/player_right.png";
}
}
if (keys[37]) {
if (velX > -speed) {
velX--;
playerDirection="img/player/player_left.png";
}
}
velY *= friction;
y += velY;
velX *= friction;
x += velX;
if (x >= canvas.width - player.width) {
x = canvas.width - player.width;
} else if (x <= 5) {
x = 5;
}
if (y > canvas.height - player.height) {
y = canvas.height - player.height;
} else if (y <= 5) {
y = 5;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
playerObj = new Image();
playerObj.onload = function()
{
ctx.drawImage(playerObj, x, y);
};
playerObj.src = playerDirection;
}
update();
function handlerKeyDown(e)
{
keys[e.keyCode] = true;
}
function handlerKeyUp(e) {
keys[e.keyCode] = false;
}
Thanks in adavance.
This is what I meant with "caching the images":
var images = {}; // This will contain the Image objects
// Other definitions...
function update() {
if (keys[38]) {
if (!images.back) {
// The image object hasn't been defined yet.
images.back = new Image();
images.back.src = "img/player/player_back.png";
}
playerObj = images.back;
}
// Other checks on the pressed keys
// ...
// Computations on the position...
if (!playerObj.naturalWidth) {
// If naturalWidth/Height is 0, then the image hasn't been loaded yet.
// We update the onload listener to draw in the right position.
playerObj.onload = function() {
ctx.drawImage(playerObj, x, y);
};
// The image has already been loaded, so we just draw it
} else ctx.drawImage(playerObj, x, y);
}
(As an warning on your code, it seems that you want to handle multiple pressed keys, but the last one in the sequence up-down-right-left always "wins" over the others. Is that really what you want?)
Hey guys not entirely sure what I'm doing wrong. I've played a few HTML5 games and they've seem to suffer from a different issue. The drawing lags behind the movement and it looks weird. This doesn't seem to be the case here.
In my game the drawing seems fine, but it lags like every second as he moves.(movement is arrow keys). It does it without arrow keys also, if I set him up to move automatically, so I don't think it's a key detection issue.
It's almost as if the garbage collector is running every second. I don't think I'm spewing out that many objects though.
I'm using Chrome 21 (MacOSX) and also Firefox 14.
http://tempdrew.dreamhosters.com/spine/
Here is the js fiddle with relevant code.
http://jsfiddle.net/ju3ag/
This is fine on chrome canary. I don't know if it's just because the javascript is so much faster in canary then standard chrome. It's terrible in latest Firefox. I'm not sure what I'm doing wrong. I'm updating movement based on time. If I take that out though it's still bad.
I'm just wondering if anything will stand out to anyone. Thanks for any help.
sg = Class.extend({
});
sg.entities = [];
sg.buttonStates = [];
sg.createEntity = function (entity) {
this.entities.push(entity);
};
window.requestAnimFrame = (function () {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function (callback, element) {
window.setTimeout(callback, 1000 / 60);
};
})();
(function defineUtil() {
sg.util = {};
sg.util.getRandomInt = function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
sg.util.getRandomNumber = function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
};
})();
/*************************/
(function createEntity() {
var Entity = Class.extend({
init: function (x, y) {
this.name = 'Entity';
this.health = 100;
this.pos = {
x: x,
y: y
};
this.vel = {
x: 0,
y: 0
};
this.accel = {
x: 0,
y: 0
}
console.log(this.name + ' created ' + x + ' ' + y);
},
update: function (elapsed) {
},
draw: function (ctx) {
}
});
sg.Entity = Entity;
})();
/************************/
// -- player.js
(function createPlayer() {
var Player = sg.Entity.extend({
x: 0,
y: 0,
moveLeft: false,
moveRight: false,
speed : 5,
init: function (x, y) {
this.x = x;
this.y = y;
this.name = 'Player';
},
draw: function (ctx) {
var x = this.x,
y = this.y;
ctx.beginPath();
ctx.rect(x, y, 40, 50);
ctx.fillStyle = 'white';
ctx.fill();
ctx.lineWidth = .5;
ctx.strokeStyle = 'rgba(0,0,0,.3)';
ctx.stroke();
ctx.fillStyle = 'rgba(0,0,0,.5)';
ctx.fillRect(x + 25, y + 15, 5, 5);
},
update: function (elapsed) {
var distance = (60 / 1000) * elapsed;
if (this.moveLeft) {
this.x += this.speed * distance;
} else if (this.moveRight) {
this.x -= this.speed * distance;
}
},
keyDown: function (e) {
if (e.keyCode === 39) {
this.moveLeft = true;
} else if (e.keyCode === 37) {
this.moveRight = true;
} else {
this.moveLeft = false;
this.moveRight = false;
}
},
keyUp: function (e) {
if (e.keyCode === 39) {
this.moveLeft = false;
} else if (e.keyCode === 37) {
this.moveRight = false;
}
}
});
sg.Player = Player;
})();
/**********************************/
(function createGame() {
var Game = Class.extend({
canvas: null,
context: null,
width: null,
height: null,
init: function (width, height) {
this.canvas = document.getElementById('canvas');
this.context = this.canvas.getContext('2d');
this.width = width || 800;
this.height = height || 600;
this.canvas.width = this.width;
this.canvas.height = this.height;
},
clear: function () {
this.context.clearRect(0, 0, this.width, this.height);
},
draw: function () {
this.clear();
for (var i = 0; i < sg.entities.length; i++) {
sg.entities[i].draw(this.context);
}
},
update: function (elapsed) {
for (var i = 0; i < sg.entities.length; i++) {
sg.entities[i].update(elapsed);
}
},
keyDown: function (e) {
for (var i = 0; i < sg.entities.length; i++) {
if (typeof sg.entities[i].keyDown === 'function') {
sg.entities[i].keyDown(e);
}
}
},
keyUp: function (e) {
for (var i = 0; i < sg.entities.length; i++) {
if (typeof sg.entities[i].keyUp === 'function') {
sg.entities[i].keyUp(e);
}
}
}
});
sg.Game = Game;
var game = sg.currentGame = new sg.Game(800, 600);
var player = new sg.Player(200, 459);
sg.createEntity(player);
function update(elapsed) {
game.update(elapsed);
}
var lastUpdate = Date.now();
function draw() {
var now = Date.now();
var elapsed = (now - lastUpdate);
lastUpdate = now;
game.draw();
update(elapsed);
requestAnimFrame(draw);
}
window.addEventListener('keydown', sg.currentGame.keyDown, false);
window.addEventListener('keyup', sg.currentGame.keyUp, false);
draw();
})();
The keydown/keyup events are waiting a little bit of time between one and another press.
Here is how I solved on my case (not sure if that's exactly your problem); adding a setInterval that every 20ms checks if there are key downs (more than one for diagonal movements).
var keys = {};
$(document).bind('keyup', function(e){
delete keys[e.which];
});
$(document).bind('keydown', function(e){
keys[e.which] = true;
});
setInterval(keyEvent, 20);
function keyEvent(){
for(var idKeyPressed in keys){
switch(idKeyPressed){
case "87": // "W" key; Move to the top
// Do something
break;
}
}
}
Hope that helps :)