How can I create a running animation on an HTML5 canvas? - javascript

I am building a simple 2D game as an attempt to learn canvas. The character can run around a virtual environment, and a variable called yOffset controls his offset from the top of the screen. I also have a global variable called running which sets itself to true or false based on whether or not the character is running (not shown here). My goal is to make the character bob up and down whilst he is running, and all the below code does is spawn lots of setInterval()s. Is this the right way to make my character run, or should I do it another way? If so, how?
$(document).keydown(function(e) {
if(e.which == 97) {
running = true;
run();
} else if(e.which == 100) {
running = true;
run();
} else if(e.which == 119) {
running = true;
run();
} else if(e.which == 115) {
running = true;
run();
}
});
(yes, if the character stops running, the running variable does go to false [not shown here] - I've already made sure the running variable works well)
runTimer = 0;
function run() {
if(runTimer == 0 && running) {
runTimer = 1;
yOffset = 80;
setTimeout(function() {
yOffset = 120;
}, 150);
setTimeout(function() { if (running) { runTimer = 0;run(); } }, 300);
}
}
If you need more information, the version that I am currently working on is available here.

I think you can simplify your code, and in fact you must in the quite probable case where you'd like to add some other characters.
To allow re-use of the animation, it's better to separate what is an animation (== the different steps that your character will go through), and an animation state (== in which step your character is now).
I wrote here some elements of an animation system.
So i define what is an animation step, a whole Animation (which is so far only an array of animation step), and an Animator (which holds the state, one might see it as a 'reader' of an animation).
Once you defined the animation and animators, and started the animators, you just have to call tick(time) to have the animation move on, and offset() to read the offset, which is way simpler than fighting with a bunch of setIntervals.
http://jsfiddle.net/xWwFf/
// --------------------
function AnimationStep(duration, offset) {
this.duration = duration;
this.offset = offset;
// you might add : image index, rotation, ....
}
// --------------------
function Animation(animationSteps) {
this.steps = animationSteps; // Array of AnimationStep
}
// define a read-only length property
Object.defineProperty(Animation.prototype, 'length', {
get: function () {
return this.steps.length
}
});
// --------------------
function Animator() {
this.currentAnimation = null;
this.step = -1;
this.running = false;
this.remainingTime = 0; // remaining time in current step;
}
Animator.prototype.startAnim = function (newAnim, firstStep) {
this.currentAnimation = newAnim;
this.step = firstStep || 0;
this.remainingTime = newAnim.steps[this.step].duration;
this.running = true;
}
Animator.prototype.tick = function (dt) {
// do nothing if no animation ongoing.
if (!this.running) return;
this.remainingTime -= dt;
// 'eat' as many frames as required to have a >0 remaining time
while (this.remainingTime <= 0) {
this.step++;
if (this.step == this.currentAnimation.length) this.step = 0;
this.remainingTime += this.currentAnimation.steps[this.step].duration;
}
};
Animator.prototype.offset = function () {
return this.currentAnimation.steps[this.step].offset;
}
// ______________________________
// example
var bounceAnim = [];
bounceAnim.push(new AnimationStep(200, 10));
bounceAnim.push(new AnimationStep(180, 20));
bounceAnim.push(new AnimationStep(150, 30));
bounceAnim.push(new AnimationStep(300, 40));
bounceAnim.push(new AnimationStep(320, 45));
bounceAnim.push(new AnimationStep(200, 40));
bounceAnim.push(new AnimationStep(120, 30));
bounceAnim.push(new AnimationStep(100, 20));
var anim1 = new Animation(bounceAnim);
var animator1 = new Animator();
var animator2 = new Animator();
animator1.startAnim(anim1);
animator2.startAnim(anim1, 3);
// in action :
var ctx = document.getElementById('cv').getContext('2d');
function drawScene() {
ctx.fillStyle = 'hsl(200,60%, 65%)';
ctx.fillRect(0, 0, 600, 200);
ctx.fillStyle = 'hsl(90,60%,75%)';
ctx.fillRect(0, 200, 600, 200);
ctx.fillStyle = 'hsl(10,60%,75%)';
ctx.fillRect(200, 200 + animator1.offset(), 22, 22);
ctx.fillStyle = 'hsl(40,60%,75%)';
ctx.fillRect(400, 200 + animator2.offset(), 22, 22);
animator1.tick(20);
animator2.tick(20);
}
setInterval(drawScene, 20);

Related

Trying to provide webgl with a random color when the user clicks on a falling block

When the user clicks on the falling block it pauses, and when clicked on again it unpauses. I am trying to figure out how to make it so that once you pause the block it changes to a random color and then when unpaused it will then change again to a another random color. I am not too familiar with WebGL. Here is the source code.
Would I need to add a render function?
and then add
function checkclickPressed(e) {
if (e.keyCode == "0") {
color = [Math.random(), Math.random(), Math.random(), 1];
alert("left click pressed");
requestAnimationFrame( render );
}
}
Here's a dirty solution.
Your falling block's color is determined by:
rainingRect.color = [Math.random(), Math.random(), Math.random()];
gl.clearColor(rainingRect.color[0], rainingRect.color[1], rainingRect.color[2], 1.0);
Inside your
if (!paused) {
}
code block - where you update the score and pause the game, you could simply add the above lines to change the color. The problem is, that your drawAnimation() function doesn't get called anymore so your canvas wouldn't be updated with the new color.
Furthermore, if we simply call drawAnimation inside the if-block it would start firing every 17ms again because of the setTimeout timer inside.
So I recommend adding a paramter which controls if the timer should restart or not.
function drawAnimation(repeat) {
gl.scissor(rainingRect.position[0], rainingRect.position[1],
rainingRect.size[0], rainingRect.size[1]);
gl.clear(gl.COLOR_BUFFER_BIT);
rainingRect.position[1] -= rainingRect.velocity;
if (rainingRect.position[1] < 0) {
misses += 1;
missesDisplay.innerHTML = misses;
rainingRect = new Rectangle();
}
// We are using setTimeout for animation. So we reschedule
// the timeout to call drawAnimation again in 17ms.
// Otherwise we won't get any animation.
if (repeat) {
timer = setTimeout(drawAnimation, 17, true);
}
}
Now you can safely call drawAnimation(false); to update your canvas once. Just remember to make it true for all other calls to drawAnimation.
So after setting the color to a random new one and calling drawAnimation, you have to set a new color once more. This will be visible as soon as the game continues.
(function() {
"use strict"
window.addEventListener("load", setupAnimation, false);
var gl,
timer,
rainingRect,
scoreDisplay,
missesDisplay,
status,
paused = false;
function setupAnimation(evt) {
window.removeEventListener(evt.type, setupAnimation, false);
if (!(gl = getRenderingContext()))
return;
gl.enable(gl.SCISSOR_TEST);
rainingRect = new Rectangle();
timer = setTimeout(drawAnimation, 17, true);
document.querySelector("canvas")
.addEventListener("click", playerClick, false);
var displays = document.querySelectorAll("strong");
scoreDisplay = displays[0];
missesDisplay = displays[1];
status = displays[2];
}
var score = 0,
misses = 0;
function drawAnimation(repeat) {
gl.scissor(rainingRect.position[0], rainingRect.position[1],
rainingRect.size[0], rainingRect.size[1]);
gl.clear(gl.COLOR_BUFFER_BIT);
rainingRect.position[1] -= rainingRect.velocity;
if (rainingRect.position[1] < 0) {
misses += 1;
missesDisplay.innerHTML = misses;
rainingRect = new Rectangle();
}
// We are using setTimeout for animation. So we reschedule
// the timeout to call drawAnimation again in 17ms.
// Otherwise we won't get any animation.
if (repeat) {
timer = setTimeout(drawAnimation, 17, true);
}
}
function playerClick(evt) {
// We need to transform the position of the click event from
// window coordinates to relative position inside the canvas.
// In addition we need to remember that vertical position in
// WebGL increases from bottom to top, unlike in the browser
// window.
var position = [
evt.pageX - evt.target.offsetLeft,
gl.drawingBufferHeight - (evt.pageY - evt.target.offsetTop),
];
// if the click falls inside the rectangle, we caught it.
// Increment score and create a new rectangle.
var diffPos = [position[0] - rainingRect.position[0],
position[1] - rainingRect.position[1]
];
if (diffPos[0] >= 0 && diffPos[0] < rainingRect.size[0] &&
diffPos[1] >= 0 && diffPos[1] < rainingRect.size[1]) {
score += 1;
scoreDisplay.innerHTML = score;
// rainingRect = new Rectangle();
if (!paused) {
clearTimeout(timer)
paused = true;
rainingRect.color = [Math.random(), Math.random(), Math.random()];
gl.clearColor(rainingRect.color[0], rainingRect.color[1], rainingRect.color[2], 1.0);
drawAnimation(false);
rainingRect.color = [Math.random(), Math.random(), Math.random()];
gl.clearColor(rainingRect.color[0], rainingRect.color[1], rainingRect.color[2], 1.0);
status.innerHTML = 'Paused';
} else {
timer = setTimeout(drawAnimation, 17, true);
paused = false;
status.innerHTML = 'Playing';
}
}
}
function Rectangle() {
// Keeping a reference to the new Rectangle object, rather
// than using the confusing this keyword.
var rect = this;
// We get three random numbers and use them for new rectangle
// size and position. For each we use a different number,
// because we want horizontal size, vertical size and
// position to be determined independently.
var randNums = getRandomVector();
rect.size = [
5 + 120 * randNums[0],
5 + 120 * randNums[1]
];
rect.position = [
randNums[2] * (gl.drawingBufferWidth - rect.size[0]),
gl.drawingBufferHeight
];
rect.velocity = 1.0 + 6.0 * Math.random();
rect.color = getRandomVector();
gl.clearColor(rect.color[0], rect.color[1], rect.color[2], 1.0);
function getRandomVector() {
return [Math.random(), Math.random(), Math.random()];
}
}
function getRenderingContext() {
var canvas = document.querySelector("canvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
var gl = canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl");
if (!gl) {
var paragraph = document.querySelector("p");
paragraph.innerHTML = "Failed to get WebGL context." +
"Your browser or device may not support WebGL.";
return null;
}
gl.viewport(0, 0,
gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
return gl;
function checkKeyPressed(e) {
}
function checkclickPressed(e) {
}
}
})();
<style>body {
text-align: center;
}
canvas {
display: block;
width: 280px;
height: 210px;
margin: auto;
padding: 0;
border: none;
background-color: black;
}
button {
display: block;
font-size: inherit;
margin: auto;
padding: 0.6em;
}
</style>
<p>You caught
<strong>0</strong>. You missed
<strong>0</strong> Status
<strong>Playing</strong>.</p>
<canvas>Your browser does not seem to support
HTML5 canvas.</canvas>
Here's the full example - hit 'Run code snippet' to see it in action:

I am having a bug with the enemies and when they reach your character in a small game I made

The bug happens when you shoot and die at the same time, and in the console it pops up as "Uncaught TypeError: Cannot read property 'draw' of undefined. Line 65"
The thing is, the bug started happening when I included the function that kills the player, which starts on line 143, and the thing that breaks it is the single line
enemy = [];
Here's the enemy function
function Enemy(x, y) {
this.x = x;
this.y = y;
this.draw = function() {
noStroke();
fill(255, 0, 0);
rect(this.x, this.y, 20, 20);
}
this.move = function() {
this.x -= movement; }
this.offscreen = function() {
if(this.x < 0) {
return true;
} else {
return false;
}
}
this.contact = function() {
for(let i = 0; i < enemy.length; i++) {
var d = dist(playerx, playery, enemy[i].x, enemy[i].y);
if(d <= 20) {
this.kill();
}
}
}
this.kill = function() {
var prevScore = score;
playerx = width / 10;
playery = height / 2;
alert("You died! your score was: " + prevScore);
fire = [];
enemy = [];
score = 0
}
}
Here's where it says it's getting the type error (which worked perfectly before)
This is lines 55 - 74
for(let i = fire.length - 1; i > 0; i--) {
fire[i].draw();
fire[i].move();
fire[i].check();
if(fire[i].offscreen()) {
fire.splice(i, 1);
}
}
for(let i = enemy.length - 1; i > 0; i--) {
enemy[i].draw();
enemy[i].move();
if(enemy[i].offscreen()) {
enemy.splice(i, 1);
}
if(enemy[i].contact()) {
enemy[i].kill();
}
}
The entire code is here: https://code.sololearn.com/Wtza5vElEZ9d/?ref=app
(It's less than 200 lines so not really big)
As well as that bug, I am wanting to find out how it would be possible to make the game gradually get faster. I tried having the frameCount be divided/modulos by a variable (that I had called spawnRate) but when I altered the variable in any way, it just stopped spawning the squares all together.
It's also made with p5.js.
Your enemy.contact() function is used in if statement so you should return a boolean with that function.
this.contact = function() {
return dist(playerx, playery, this.x, this.y) <= 20;
}
It would be better if you make function death() in player object.
It will not be so confusing.

Phaser: Key Down + Collision Kill

Forewarning: semi-newbie
Basically, if the user has the left cursor down and a "token" collides with the lftRect, I want to kill the token. For some reason my kill callback function for the collision is not working (below is relevant code):  
gumballGoGo.Preloader = function(game){
this.player = null;
this.ground = null;
//this.tokens = null;
this.ready = false;
};
var cursors, lftInit, rghtInit, ground, testDrp, sprite, tokens, rect, lftRect, ctrRect, rghtRect, lftToken;
var total = 0;
function lftHit(lftRect, lftToken) {
if ( lftInit == true ){
lftToken.kill()
}
};
gumballGoGo.Preloader.prototype = {
preload: function(){
},
create: function() {
// LFT BOX
lftRect = this.add.sprite(0, this.world.height - 150, null);
this.physics.enable(lftRect, Phaser.Physics.ARCADE);
lftRect.body.setSize(100, 100, 0, 0);
// CNTR BOX
ctrRect = this.add.sprite(100, this.world.height - 150, null);
this.physics.enable(ctrRect, Phaser.Physics.ARCADE);
ctrRect.body.setSize(100, 100, 0, 0);
// RGHT BOX
rghtRect = this.add.sprite(200, this.world.height - 150, null);
this.physics.enable(rghtRect, Phaser.Physics.ARCADE);
rghtRect.body.setSize(100, 100, 0, 0);
// INIT TOKEN GROUP
tokens = this.add.group();
tokens.enableBody = true;
this.physics.arcade.enable(tokens);
testDrp = tokens.create(125, -50, 'token');
testDrp.body.gravity.y = 300;
// CONTROLS
this.cursors = this.input.keyboard.createCursorKeys();
},
update: function() {
this.ready = true;
if (this.cursors.left.isDown)
{
lftInit = true;
}
else if (this.cursors.right.isDown)
{
rghtInit = true;
}
else
{
lftInit, rghtInit = false;
}
if (total < 20)
{
tokenSpawn();
}
this.physics.arcade.collide(lftRect, lftToken, lftHit, null, this);
}
};
function tokenSpawn() {
lftToken = tokens.create(25, -(Math.random() * 800), 'token');
lftToken.body.gravity.y = 300;
total++;
}
The ultimate goal is to recreate this type of gameplay. 
One additional note: as of now I am dropping "tokens" using a random spawn loop. I'd rather use a timed patter for the token drop. If you have any advice on that please share as well. Thanks :]
Not sure, so this is a guess, but you're applying the last parameter of 'this' to the function 'lfthit' that is outside of the gumballGoGo.Preloader.prototype object. What error are you getting in the console?

Stopping more than one instance of a function running at a time

I am fairly new to JavaScript and have searched everywhere for an answer to my question and cant seem to find anything related at all. This tells me that I'm missing something with my understanding of how my program works.
I have written a small game where the player navigates through a randomly generated maze using a gameloop that checks keydown events every x milliseconds. The game has a difficulty dropdown menu and then the game is started my clicking a button that calls a function to create a canvas where the game is drawn.
My problem is that when the button is clicked again to create a new maze without reloading the page, the gameloop for the original maze is still running and so key events are registered twice. This is causing some unexpected behavior. It's as though every time the button is clicked, a new instance of the function is running. Is there some way that each time the button is clicked I can set it to stop the previous game function?
var canvas;
var div;
var mazeGenButton;
$(document).ready(function () {
canvas = null;
div = document.getElementById('canvascontainer');;
mazeGenButton = document.getElementById("mazeGenButton");
mazeGenButton.onclick = createInstance;
});
function createInstance() {
if (canvas != null) {
div.removeChild(document.getElementById("myCanvas"));
}
canvas = document.createElement('canvas');
canvas.id = "myCanvas";
canvas.width = 1000;
canvas.height = 1000;
div.appendChild(canvas);
drawMaze();
};
var drawMaze = function () {
//code here to create the game(not posted)
//here is the Key listener - not sure if it's related
var keyState = {};
window.addEventListener('keydown', function (e) {
keyState[e.keyCode || e.which] = true;
}, true);
window.addEventListener('keyup', function (e) {
keyState[e.keyCode || e.which] = false;
}, true);
function gameLoop() {
//left
if (keyState[37] || keyState[65]) {
if (isLegalMove(playerXPos - 1, playerYPos)) {
grid[playerXPos][playerYPos].removePlayerCell();
playerXPos -= 1;
grid[playerXPos][playerYPos].setPlayerCell();
}
}
//right
if (keyState[39] || keyState[68]) {
if (isLegalMove(playerXPos + 1, playerYPos)) {
grid[playerXPos][playerYPos].removePlayerCell();
playerXPos += 1;
grid[playerXPos][playerYPos].setPlayerCell();
}
}
//up
if (keyState[38] || keyState[87]) {
if (isLegalMove(playerXPos, playerYPos - 1)) {
grid[playerXPos][playerYPos].removePlayerCell();
playerYPos -= 1;
grid[playerXPos][playerYPos].setPlayerCell();
}
}
//down
if (keyState[40] || keyState[83]) {
if (isLegalMove(playerXPos, playerYPos + 1)) {
grid[playerXPos][playerYPos].removePlayerCell();
playerYPos += 1;
grid[playerXPos][playerYPos].setPlayerCell();
}
}
drawSurroundingCells();
setTimeout(gameLoop, 50);
}
}

Can't achieve jumping in Javascript Game

I am currently developing a Javascript game (almost everything is based on a tutorial yet, so I am not worried of sharing the code).
The problem is, I can't get the character to jump after pressing the Space button. Please, can someone look at the code and help me?
// EDIT: Sorry for lack of information I provided. The thing is - code is written, the game is in the state, that the character is animated (=is running) and the backgrounds are moving. Yesterday, I tried to implement some basic controls, such as jump by pressing spacebar. The thing is, the player won't jump at all, and browser console is not giving me any error statements.
Character is defined as Player on line 5. and 321. in the code provided below.
The jumping is defined in the following examples:
Pressing the Space button
var KEY_CODES = {
32: 'space'
};
var KEY_STATUS = {};
for (var code in KEY_CODES) {
if (KEY_CODES.hasOwnProperty(code)) {
KEY_STATUS[KEY_CODES[code]] = false;
}
}
document.onkeydown = function(e) {
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if (KEY_CODES[keyCode]) {
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = true;
}
};
document.onkeyup = function(e) {
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if (KEY_CODES[keyCode]) {
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = false;
}
};
Other jump information (please, read the comments in the code)
this.update = function() {
// jump, if the characted is NOT currently jumping or falling
if (KEY_STATUS.space && this.dy === 0 && !this.isJumping) {
this.isJumping = true;
this.dy = this.jumpDy;
jumpCounter = 12;
assetLoader.sounds.jump.play();
}
// longer jump if the space bar is pressed down longer
if (KEY_STATUS.space && jumpCounter) {
this.dy = this.jumpDy;
}
jumpCounter = Math.max(jumpCounter-1, 0);
this.advance();
// gravity
if (this.isFalling || this.isJumping) {
this.dy += this.gravity;
}
// change animation is-falling
if (this.dy > 0) {
this.anim = this.fallAnim;
}
// change animation is-jumping
else if (this.dy < 0) {
this.anim = this.jumpAnim;
}
else {
this.anim = this.walkAnim;
}
this.anim.update();
};
/**
* Update the Sprite's position by the player's speed
*/
this.update = function() {
this.dx = -player.speed;
this.advance();
};
/**
* Draw the current player's frame
*/
this.draw = function() {
this.anim.draw(this.x, this.y);
};
}
Player.prototype = Object.create(Vector.prototype);
Everything seems just fine to me, but the player just won't move. :(
Any help?
If you are curious about the full code, go here: http://pastebin.com/DHZKhBMT
EDIT2:
Thank you very much for your replies so far.
I have moved the RequestAnimFrame to the end of the function - will keep that in mind, thanks.
I have also implemented the simple jumping script Ashish provided above, but the character is still not jumping.
This is what it looks like now:
/** JUMP KEYS DEFINITION **/
$(document).keypress(function(e){
if(e.which==32){
$('Player.prototype').css({'top':"0px"});
}
setTimeout(function(){
$('Player.prototype').css({'top':"200px"});
},350);
});
/** DEFINING CHARACTER **/
function Player(x, y) {
this.dy = 0;
this.gravity = 1;
this.speed = 6;
this.jumpDy = -10;
this.isJumping = false;
this.width = 60;
this.height = 96;
this.sheet = new SpriteSheet('imgs/normal_walk.png', this.width, this.height);
this.walkAnim = new Animation(this.sheet, 4, 0, 11);
this.jumpAnim = new Animation(this.sheet, 4, 3, 3);
this.fallAnim = new Animation(this.sheet, 4, 3, 3);
this.anim = this.walkAnim;
Vector.call(this, x, y, 0, this.dy);
var jumpCounter = 0; // Maximalna dlzka drzania tlacidla skakania
}
Player.prototype = Object.create(Vector.prototype);
Where am I wrong?
I've tried in http://jsfiddle.net/Ykge9/1/
and you have an infinite loop in animate, the requestAnimFrame should be at the end of the function:
/**
* Loop cykly hry
*/
function animate() {
background.draw();
for (i = 0; i < ground.length; i++) {
ground[i].x -= player.speed;
ctx.drawImage(assetLoader.imgs.grass, ground[i].x, ground[i].y+250);
}
if (ground[0].x <= -platformWidth) {
ground.shift();
ground.push({'x': ground[ground.length-1].x + platformWidth, 'y': platformHeight});
}
player.anim.update();
player.anim.draw(64, 260);
requestAnimFrame( animate );
}

Categories