I have an animation for a game that I am working on that won't seem to repeat. When the space bar is pressed, it triggers an event that "shoots" a circle to the top of a canvas. The problem is that when the key is pressed again it will not initiate the animation. Here is the sample code that I wrote:
var canvas, ctx,
spaceKey = false,
upKey = false,
downKey = false,
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
shotX = 150, shotY = 280;
function loop() {
if (spaceKey) shoot();
}
function keyDown(e) {
if (e.keyCode == 32) spaceKey = true;
}
function keyUp(e) {
if (e.keyCode == 32) spaceKey = false;
}
function shoot() {
setTimeout(function() { if (shotY > 0) {
ctx.clearRect(shotX-5,shotY-5,600,20);
ctx.beginPath();
ctx.arc(shotX, shotY,4,0,Math.PI * 2, false);
ctx.fillStyle = "yellow";
ctx.fill();
ctx.closePath();
shotY = shotY - 1;
shoot();
} else
ctx.clearRect(shotX-5,shotY-5,600,20);
}, 100);
}
(function init() {
setInterval(loop, 10);
document.addEventListener('keydown', keyDown, false);
document.addEventListener('keyup', keyUp, false);
})();
//init();
The reason I use keyUp and keyDown is because I have other functions that use different keys. This code was modified to show only this feature. To make it easier to see what I'm talking about, I created a JSFiddle. The other features I have are similarly structured and work, the only difference being that its duration isn't directly controlled by a key press.
I don't find your code much re-usable. You should consider using objects to represents the entities on the canvas. I've created simple code which does your job, but it is also not much re-usable (though you can re-use it to create a simple entity like the ball you created). Check this jsFiddle.
The problem with your code is, when that yellow ball reaches the to of the screen, the shotY is 0, then you erase the ball from the screen, but don't reset the shotY and don't redraw the ball at its home (original position).
I've created a simple Ball object to get this done-
function Ball() {
this.x = 150;
this.y = 295;
}
Ball.prototype.draw = function(newx, newy) {
ctx.clearRect(this.x-5,this.y-5,600,20);
newx = newx || this.x;
newy = newy || this.y;
this.x = newx; this.y = newy;
var ball = this;
ctx.beginPath( );
ctx.arc(ball.x, ball.y,4,0,Math.PI * 2, false);
ctx.fillStyle = "yellow";
ctx.fill();
ctx.closePath();
}
Ball.prototype.shootUp = function() {
var ball = this;
inter = setInterval(function () {
if(ball.x <= 0) clearInterval(inter);
ball.draw(ball.x, ball.y-20);
}, 100);
}
and modified your shoot function -
function shoot() {
var currentBall = new Ball();
currentBall.draw();
currentBall.shootUp();
}
And once again, this was just a simple example. You can make a lot of improvements in it. First of all, you should have an input engine to manage all the inputs. Then you should have a generic entity class to represent all the objects you have on the canvas. Like the Ball. And finally you should have a game engine to bind all those things.
You only have one "bullet".
Your bullets should be separate objects that are instantiated on shoot().
The way that I usually handle "particles" is to create a singleton object to store each instance and update all of them in the main loop.
Here's a simple Bullet object:
function Bullet(){
this.x = 150;
this.y = 280;
this.velX = 0;
this.velY = -1;
this.update = function(){
this.y += this.velY;
};
this.draw = function(){
ctx.beginPath();
ctx.arc(this.x, this.y,4,0,Math.PI * 2, false);
ctx.fillStyle = "yellow";
ctx.fill();
ctx.closePath();
};
};
And here is a BulletRenderer singleton that handles the bullets:
BulletRenderer = function(){
var bullets = [];
this.push = function(bullet){
bullets.push(bullet);
};
this.render = function(){
for (var i in bullets){
if (bullets[i].active){
bullets[i].update();
bullets[i].draw();
} else {
delete bullets[i];
}
}
};
return this;
}();
In the example I'm just clearing the canvas each frame.
The shooting mechanism acts like a laser right now. Personally I would change this behavior.
http://jsfiddle.net/NAJus/18/
Related
I have been struggling with this for a very, very long time (6 months plus). There have been some partial answers to my question but I have been unable to put the available asnswer-bits together to be able to do anything useful with it. This code will be an amazing tool for all new aspiring simple canvas game developers and will greatly benefit the community for sure.
-I need to create a very large scrollable canvas (scrolled by holding right mouse button), similar to this: https://www.desmos.com/calculator , say 50k px by 50k px and this (size)should be amendable in code.
-When we scroll, the background moves and all items, of course, will need to move with this scroll.
-There have to be some measure of scroll rate in code, which is should be amendable.
-Rendering structure need to remain as in my codepen - simple constructor objects generated on screen then rendered using request animation frame. https://codepen.io/alexhermanuk/pen/bGGXLZG
var squares = [];
var Square = function(x,y,w,h,color){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
Square.prototype.update = function(){
this.draw();
}
Square.prototype.draw = function(){
context.save();
context.beginPath();
context.fillStyle = this.color;
context.fillRect(this.x,this.y,this.w,this.h)
context.closePath();
context.restore();
}
for(var i=0; i<10; i++){
squares.push(new Square(width*Math.random(),height*Math.random(),100,100,"red"));
}
function animateEverything(){
context.clearRect(0,0,width,height);
context.save()
squares.forEach(Square => {Square.update(squares)})
requestAnimationFrame(animateEverything);
}
animateEverything();
}
-Canvas / scrollable area need to be full screen without overflows
-Canvas background will consist of 4 virtually identical square images (2 main images and 2 mirror images) and these images are rendered/ replicated as a background as we scroll left right up or down and will create an unlimited background image (in our case it is just a wavy ocean). So, these have to be logically rendered like a tile map of some sort... There is no easy way to provide sample images, so please, bring yours along.
I appreciate YOUR effort and thank all in advance.
Here you go. It scrolls with 'right click' drag.
I wasn't sure what you meant by scroll rate. Do you want the scroll rate displayed, or controlled? Anyways, I did both to be safe. scrollRate controls have fast it scrolls, and it defaults to 1. scrollMeter is a number that loosely represents how fast the canvas is being scrolled. It is printed in the top left corner of the canvas.
And about tiling, I made a BackgoundTile class. You can create 4 different tiles with different x, y, and x_gap, y_gap to have what you want. But if you care about performance, instead of making 4 tile objects, use just one with a combined image. Use an image editor to combine the 4 squares to make one big square if possible. Tiling is almost always a performance heavy job, and a bit of optimization goes a long way.
I also added a window/canvas resize handler, so the aspect ratio doesn't go haywire. This also prevents the canvas from going low-res when you enlarge the window/canvas.
I left animateEverything as is, but I'd recommend adding more control to rendering instead of recursively calling it indefinitely.
I hope you like this demo.
CodePen
paste below on in this link https://codepen.io/alexhermanuk/pen/bGGXLZG.
I have added vertical scroll only
you can implement horizontal in same way.
window.onload = function(){
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
maxWidth = 15000,
maxHeight = 15000;
var origin = { x: 0, y: 0},
scrollY = 0,
isMouseDownOn = '',
scrollBarWidth = 25,
scrollThumbHeight = 45,
navButtonSize = 25,
totalScrollHeight = height-navButtonSize*2-scrollThumbHeight;
canvas.removeEventListener('mousedown', onMouseDown);
canvas.removeEventListener('mousemove', onMouseMove);
canvas.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('mouseup', onMouseUp);
canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mousemove', onMouseMove);
canvas.addEventListener('mouseup', onMouseUp);
window.addEventListener('mouseup', onMouseUp);
var squares = [];
var Square = function(x,y,w,h,color){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
Square.prototype.update = function(){
this.draw();
}
Square.prototype.draw = function(){
var newY = this.y - origin.y;
context.save();
context.beginPath();
context.fillStyle = this.color;
context.fillRect(this.x,newY,this.w,this.h)
context.closePath();
context.restore();
}
for(var i=0; i<1000; i++){
squares.push(new Square(width*Math.random(),maxHeight*Math.random(),100,100,"red"));
}
function animateEverything(){
context.clearRect(0,0,width,height);
context.save()
console.log(' m here')
squares.forEach(Square => {
Square.update(squares)
})
drawScrollbar();
drawOrigin ();
//requestAnimationFrame(animateEverything);
}
animateEverything();
function drawScrollbar() {
var currentScroll = scrollY+navButtonSize;
context.save();
context.beginPath();
context.fillStyle = 'blue';
context.fillRect((width-navButtonSize ),0,navButtonSize,navButtonSize);
context.closePath();
context.beginPath();
context.fillStyle = 'green';
context.fillRect((width-navButtonSize ),(navButtonSize),scrollBarWidth,(height-navButtonSize*2));
context.closePath();
context.beginPath();
context.fillStyle = 'blue';
context.fillRect((width-navButtonSize ),(height-navButtonSize),navButtonSize,navButtonSize);
context.closePath();
context.beginPath();
context.fillStyle = 'yellow';
context.fillRect((width-navButtonSize),currentScroll,scrollBarWidth,scrollThumbHeight);
context.closePath();
context.restore();
}
function onMouseDown(e) {
//console.log('onMouseDown ' , e);
detectElement(e);
}
function onMouseMove(e) {
if(isMouseDownOn === 'scrollY') {
var posY = e.pageY - canvas.offsetTop;
scrollY = Math.min( (height-navButtonSize*2-scrollThumbHeight), Math.max(posY, 0) ) ;
origin.y = (scrollY/(height-navButtonSize*2-scrollThumbHeight))*maxHeight;
animateEverything();
}
}
function onMouseUp(e) {
//console.log('onMouseUp ' , e);
isMouseDownOn = '';
}
function drawOrigin () {
context.save();
context.beginPath();
context.font = "18px Arial";
context.fillText("Origin X:"+origin.x, 10, 30);
context.fillText("Origin Y:"+origin.y, 10, 50);
context.closePath();
context.restore();
}
function detectElement(e) {
isMouseDownOn = '';
var posX = e.pageX- canvas.offsetLeft;
var posY = e.pageY- canvas.offsetTop;
var currentThumbPos = scrollY+navButtonSize;
if(posX >= width-scrollBarWidth && (posY >= currentThumbPos && posY <= currentThumbPos+scrollThumbHeight) ) {
isMouseDownOn = 'scrollY';
}
}
}
I've read many posts and gone through several tutorials on HTML5 and the canvas specifically, but so far I have been unable to replicate my exact problem. If there is an answer for it already out there, please point me in the right direction.
My ultimate goal is to make a simple Pong game. I've drawn the basic objects using JavaScript and now I am trying to get the player (left) paddle to move. The problem I am running into with my current code is that instead of moving the paddle, it fills in the area it travels to. Through various trials and error of adapting and trying different methods I don't think the paddle is being elongated (adding pixels to the height), but it seems like a new paddle object is being created rather than the one being moved.
I've looked it over and over again (you guys aren't a first-ditch effort), but can't seem to figure out what's happening. Any help would be much appreciated.
// Requests a callback 60 times per second from browser
var animate = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) { window.setTimeout(callback, 1000/60) };
// Get canvas and set context
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.fillStyle = "white";
// Settle variables for canvas width and height
var canvas_width = 500;
var canvas_height = 400;
// Set varaibles for paddle width and height
var paddle_width = 15;
var paddle_height = 75;
// Initializes variables
var playerScore = 0;
var computerScore = 0;
var player = new Player();
var computer = new Computer();
var ball = new Ball((canvas_width/2),(canvas_height/2));
// Renders the pong table
var render = function() {
player.render();
computer.render();
ball.render();
};
var update = function() {
player.update();
};
// Callback for animate function
var step = function() {
update();
render();
animate(step);
};
// Creates paddle object to build player and computer objects
function Paddle(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.x_speed = 0;
this.y_speed = 0;
};
function Player() {
this.paddle = new Paddle(1, ((canvas_height/2) - (paddle_height/2)), paddle_width, paddle_height);
};
function Computer() {
this.paddle = new Paddle((canvas_width - paddle_width - 1), ((canvas_height/2) - (paddle_height/2)), paddle_width, paddle_height);
};
// Creates ball object
function Ball(x, y) {
this.x = x;
this.y = y;
this.radius = 10;
};
// Adds render functions to objects allowing them to be drawn on canvas
Ball.prototype.render = function() {
context.beginPath();
context.arc(this.x, this.y, this.radius, Math.PI * 2, false);
context.fillStyle = "white";
context.fill();
context.closePath();
};
Paddle.prototype.render = function() {
context.fillStyle = "white";
context.fillRect(this.x, this.y, this.width, this.height);
};
Player.prototype.render = function() {
this.paddle.render();
};
// Appends a move method to Paddle prototype
Paddle.prototype.move = function(x, y) {
this.y += y;
this.y_speed = y;
};
// Updates the location of the player paddle
Player.prototype.update = function() {
for(var key in keysDown) {
var value = Number(key);
if(value == 38) {
this.paddle.move(0, -4);
} else if (value == 40) {
this.paddle.move(0, 4);
} else {
this.paddle.move(0, 0);
}
}
};
Computer.prototype.render = function() {
this.paddle.render();
};
// Draws center diving line
context.strokeStyle = "white";
context.setLineDash([5, 3]);
context.beginPath();
context.moveTo((canvas_width/2), 0);
context.lineTo((canvas_width/2), canvas_height);
context.stroke();
context.closePath();
// Draws score on canvas
context.font = "40px Arial";
context.fillText('0', (canvas_width * .23), 50);
context.fillText('0', (canvas_width * .73), 50);
window.onload = function() {
animate(step);
};
var keysDown = {};
window.addEventListener("keydown", function(event) {
keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function(event) {
delete keysDown[event.keyCode];
});
My apologies: I cut the html/css code and meant to paste it, but forgot.
pong.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pong</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="canvas" width="500" height="400"></canvas>
<script src="main.js"></script>
</body>
</html>
style.css:
#canvas {
background-color: black;
}
The canvas itself has no "objects", it's just a bitmap, and anything you draw on it just changes the colours of certain pixels, making it look like it's drawing "on top" of what's already there but it doesn't even do that. It just flips pixel colours.
I don't see any code that "resets" the canvas for your next frames, so you're literally just drawing the same paddle at a different height value by colouring different pixels with the paddle's colours without recolouring the original pixels using the background colour.
The easiest solution here is to add a context.clearRect(0, 0, canvas.width, canvas.height); at the start of render():
var render = function() {
// clear the canvas so we can draw a new frame
context.clearRect(0,0,canvas.width,canvas.height);
// draw the frame content to the bitmap
player.render();
computer.render();
ball.render();
};
Note that this reveals you also need to draw your scores and center line every frame. Either that, or you need to make sure your paddles (and your ball) first restore the background colour on their old position, before drawing themselves on their new position, which would require considerably more code.
Right now my code works like I want it to but when I created my timer to redraw the triangle it overlayed a new triangle every time it was called, so to stop this I put clearRect in, but now it clears the entire canvas negating my fill of black that I have. How can I either add a new timer that still provides the same effect of moving triangles but that doesn't require clearRect or how can I fix what I have to have the background of the canvas stay black but not overlay new triangles every time? Any help is appreciated!
Edit: I also tried two different timers together instead of a timer for handleClick, but got weird results with the speed of the triangles, does anyone know why that is?:
timer = setInterval(init, 30);
timer = setInterval(Triangle, 30);
Code:
<html>
<head>
<script>
var canvas;
var context;
var triangles = [];
function init() {
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
resizeCanvas();
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('orientationchange', resizeCanvas, false);
canvas.onclick = function(event) {
handleClick(event.clientX, event.clientY);
};
timer = setInterval(handleClick, 30);
}
function Triangle(x,y,color) {
this.x = x;
this.y = y;
this.color = color;
this.vx = Math.random() * 10 - 5; //5-3
this.vy = Math.random() * 10 - 5;
for (var i=0; i < triangles.length; i++) {
var t = triangles[i];
t.x += t.vx;
t.y += t.vy;
if (t.x + t.vx > canvas.width || t.x + t.vx < 0)
t.vx = -t.vx;
if (t.y + t.vy > canvas.height || t.y + t.vy < 0)
t.vy = -t.vy;
}
}
function handleClick(x,y) {
context.clearRect(0,0, canvas.width,canvas.height);
var colors = ['red','green','purple','blue','yellow'];
var color = colors[Math.floor(Math.random()*colors.length)];
triangles.push(new Triangle(x, y, color));
for(i=0; i < triangles.length; i++) {
drawTriangle(triangles[i]);
}
}
function drawTriangle(triangle) {
context.beginPath();
context.moveTo(triangle.x,triangle.y);
context.lineTo(triangle.x + 50,triangle.y + 50);
context.lineTo(triangle.x + 50,triangle.y - 50);
context.fillStyle = triangle.color;
context.fill();
}
function resizeCanvas() {
canvas.width = window.innerWidth-10;
canvas.height = window.innerHeight-10;
fillBackgroundColor();
for(i=0; i < triangles.length; i++) {
drawTriangle(triangles[i]);
}
}
function fillBackgroundColor() {
context.fillStyle = 'black';
context.fillRect(0,0,canvas.width,canvas.height);
}
window.onload = init;
</script>
</head>
<body>
<canvas id="canvas" width="500" height="500">
</body>
</html>
I spent a few minutes reading threw your code, and found one thing that stood out to me. To begin, when you call the timer = setInterval(handleClick, 30); the commands are run from top to bottom in the handleClick function. So when we look at that function, the first thing you draw is the black background. Then, you push out a brand new triangle. Following this, you draw all the triangles in the array using a loop. This is where the issue is. What you are doing is drawing all the past triangles, ontop of the refreshed black rectangle.
Try setting your code to something like this.
function handleClick(x,y) {
context.clearRect(0,0, canvas.width,canvas.height);
var colors = ['red','green','purple','blue','yellow'];
var color = colors[Math.floor(Math.random()*colors.length)];
triangles.push(new Triangle(x, y, color));
triangles.shift(); //This removes the first point in the array, and then shifts all other data points down one index.
for(i=0; i < triangles.length; i++) {
drawTriangle(triangles[i]);
}
}
Pleaase let me know if I miss interpreted any of your code, or if you have a follow up question.
i'm creating a web page where you dynamically draw multiple rectangles. I can draw the single object, but once I tried to draw another one, the previous one is gone away. I tried to save the state using save() and restore(), and it seems that I can't put it here. Isn't it logical that save method is put in the mouseup and restore method is called in the mousedown event? Any help or advice will be appreciated.
const canvas = document.getElementById("circle"),
ctx = canvas.getContext("2d"),
circle = {},
drag = false,
circleMade = false,
mouseMoved = false;
function draw() {
ctx.beginPath();
ctx.arc(circle.X, circle.Y, circle.radius, 0, 2.0 * Math.PI);
ctx.stroke();
}
function mouseDown(e) {
ctx.restore();
circle.startX = e.pageX - this.offsetLeft;
circle.startY = e.pageY - this.offsetTop;
circle.X = circle.startX;
circle.Y = circle.startY;
if (!circleMade) {
circle.radius = 0;
}
drag = true;
}
function mouseUp() {
drag = false;
circleMade = true;
if (!mouseMoved) {
circle = {};
circleMade = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
mouseMoved = false;
ctx.save();
}
function mouseMove(e) {
if (drag) {
mouseMoved = true;
circle.X = e.pageX - this.offsetLeft;
circle.Y = e.pageY - this.offsetTop;
if (!circleMade) {
circle.radius = Math.sqrt(Math.pow((circle.X - circle.startX), 2) + Math.pow((circle.Y - circle.startY), 2));
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw();
}
}
function init() {
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
init();
you need to save the information about what you are drawing in a separate object, every time you make a draw to the canvas you will wipe and redraw the new object.
so when you clearRect and then draw you are clearing and then drawing a fresh one but the old ones are being left behind. A good example:
var SavedCircles = [];
var circleInfo = function()
{
this.x = 0;
this.y = 0;
this.startX = 0;
this.startY = 0;
this.radius = 0;
}
circle = {};
function draw()
{
for(var x=0;x<SavedCircles.length;x++)
{
ctx.beginPath();
ctx.arc(SavedCircles[x].X, SavedCircles[x].Y, SavedCircles[x].radius, 0, 2.0 * Math.PI);
ctx.stroke();
}
}
function mouseDown()
{
circle = new circleInfo();
}
function mouseUp()
{
SavedCircles.push(circle);
}
function mouseMove()
{
draw();
}
so you can get rid of save and restore, also its much faster to clear a canvas simply by:
canvas.width = canvas.width;
this should help you keep all circles ever drawn. fill in the rest with your code.
I tried to draw a simple rectangle and move it by keyboard. But the problem is that I think added everything I need in my code...Well i want to use arrows on keyboard. But before i tried JUST to get an alert....But it does not work.....Help me please... Any help will appreciated.
var canvas = document.getElementById("screen");
context = canvas.getContext("2d");
function Player() {
this.x=0, this.y = 0, this.w = 50, this.h = 50;
this.render = function (){
context.fillStyle = "orange";
context.fillRect(this.x, this.y, this.w, this.h);
}
}
var player = new Player();
player.x=100;
player.y= 460;
setInterval( function() {
context.fillStyle="black";
context.fillRect(0,0,canvas.width, canvas.height);
/*context.fillStyle = "white";
context.fillRect(100, 460, 30 , 30);*/
player.render();
//move all aliens & draw all aliens
for(var i = 0; i < 9; i++) {
aliens[i].move(),
aliens[i].draw(context);
}
}, 20);
document.addEventListener('keydown', function(event)){
var key_press = String.fromCharCode(event.keyCode);
alert(event.keyCode + " | " + key_press);
});
}
document.addEventListener('keydown', function(event)){
----------------------------------------------------^
You did put extra parentheses on this, remove it, the actual code would be
document.addEventListener('keydown', function(event){