I am hoping someone might be able to look at this javascript code I have been working on for a simple old-school Mario clone.
I have pieced together what I know about canvas from several tutorials and I am not able to get the collisions with blocks or the jumping working correctly.
the jumping seems to set Mario on an infinite loop of bouncing over and over, which looks funny but is not very conducive of playing a game!
function Player() {
this.srcX = 0;
this.srcY = 0;
this.drawX = gameWidth /2;
this.drawY = 0;
this.scaleWidth = 38;
this.scaleHeight = 50;
this.width = 48;
this.height = 60;
this.speed = 10;
this.maxJump = 50;
this.velY = 0;
this.velX = 0;
this.isJumpKey = false;
this.isRightKey = false;
this.isCrouchKey = false;
this.isLeftKey = false;
this.jumping = false;
this.grounded = false;
}
Player.prototype.draw = function(){
clearPlayer();
this.checkKeys();
ctxPlayer.drawImage(
player,
this.srcX,
this.srcY,
this.width,
this.height,
this.drawX,
this.drawY,
this.scaleWidth,
this.scaleHeight);
};
Player.prototype.checkKeys = function () {
if(this.isJumpKey){
if (!this.jumping && this.grounded ) {
this.jumping = true;
this.grounded = false;
this.velY = -this.speed * 2;
}
}
if(this.isRightKey){
if (this.velX < this.speed) {
this.velX++;
}
}
if(this.isLeftKey){
if (this.velX < this.speed) {
this.velX--;
}
}
if(this.isCrouchKey){
player1.grounded = true;
player1.jumping = false;
}
};
Here is a codepen with where I am at right now: http://codepen.io/AlexBezuska/pen/ysJcI
I really appreciate any help, I will continue to search and play around with this in the meantime, but any pointers you can give, even suggestions for formatting, prototype creation etc are really appreciated (I am quite new to both canvas and prototypes)
In your checkKeyDown() and checkKeyUp() functions, you have them checking for different 'jump' keys. From checkKeyDown():
if (keyID === 74) { //spacebar
e.preventDefault();
player1.isJumpKey = true;
}
From checkKeyUp():
if (keyID === 32) { // spacebar
player1.isJumpKey = false;
e.preventDefault();
}
So checkKeyUp() isn't properly resetting player1.isJumpKey. Set them both to the same, and it works fine for me.
As a general point, it might be worth setting up an object that holds all the parameters that have multiple instances in your code. Then write them into your code by referring to this object. That way you've only got to change them in a single place:
CONSTS = {
upKeyID: 32,
etc.
}
// then later:
if (keyID === CONSTS.upKeyID) {
player1.isJumpKey = false;
e.preventDefault();
}
I figured out the collision problem, I had the x position and y position vars in the player prototype named 'drawX' and 'drawY', but in the collision detection function, they were simply 'x' and 'y', now it works: http://codepen.io/AlexBezuska/pen/ysJcI w00t!
Related
I am writing a mobile movement script for a three JS app, in which if you click the upper part of the screen you rotate the camera, and the lower part you move the character. This works perfectly if you are using only one touch, but as soon as I use the second touch a problem appears:
I can't seem to find a way to use the "end touch" event ONLY for the second touch- I tried using ev.touches[1] or ev.touches[0] but seems like nothing happens. I really need the device to discriminate the touch that actually ended, if it was the first one pressed, or the second one.
This is the code...
var originTouch;
var secondTouch;
var newTouch;
var previousTouch;
document.body.addEventListener('touchstart', (ev) => {
originTouch = ev.touches[0];
secondTouch = ev.touches[1];
console.log(originTouch);
console.log(secondTouch);
})
document.body.addEventListener('touchmove', (ev) => {
if (ev.touches[0])
{
if (ev.touches[0].clientY > window.innerHeight / 2)
{
var deltaY = originTouch.clientY - ev.touches[0].clientY;
var deltaX = originTouch.clientX - ev.touches[0].clientX;
if (deltaY > 0) { isGoingForward = true; isGoingBackward = false;}
if (deltaY < 0) { isGoingBackward = true; isGoingForward = false;}
if (deltaX < 0) { isGoingRight = true; isGoingLeft = false;}
if (deltaX > 0) { isGoingLeft = true; isGoingRight = false;}
}
else
{
if (previousTouch)
{
var movementX = ev.touches[0].pageX - previousTouch.pageX;
var movementY = ev.touches[0].pageY - previousTouch.pageY;
camerina.rotation.y += movementX / 400;
camerina.rotation.x += movementY / 400;
}
previousTouch = ev.touches[0];
}
}
if (ev.touches[1])
{
if (ev.touches[1].clientY < window.innerHeight / 2)
{
if (newTouch)
{
var movementX = ev.touches[1].pageX - newTouch.pageX;
var movementY = ev.touches[1].pageY - newTouch.pageY;
camerina.rotation.y += movementX / 400;
camerina.rotation.x += movementY / 400;
}
newTouch = ev.touches[1];
}
}
})
document.body.addEventListener('touchend', (ev) => {
//this is, of course, ending every touch when the second touch is released.
//but if I use --if (ev.touches[0] or [1])-- nothing happens.
//It feels like I'm not getting a way to find which is the touch that actually ended.
//this has to end only if the first touch ended,
isGoingForward = false;
isGoingBackward = false;
isGoingLeft = false;
isGoingRight = false;
previousTouch = null;
originTouch = null;
//this has to end only if the second touch ended.
newTouch = null;
})
started to learn a bit javascript and I ran into a mission to get a "ball" bigger and bigger when I am cliking it( the ball starts from 100 and grow by 50 each click). so when it's getting too 400 its should shrink its size by 50 till the ball gets again to 100 and then he grow.
so thats the code:
function onBall2Click() {
var ball2 = document.querySelector('.ball2');
if(ball2Size === 400) {
ball2Size -= 50;
} else if(ball2Size) {
ball2Size += 50;
}
ball2.innerText = ball2Size;
ball2.style.width = ball2Size;
ball2.style.height = ball2Size;
}
ball2Size defined to 100.
now the ball is getting to 400 when I click again it is getting to 350 but then it getting to 400 again since 350 isnt === to 400. I am frustrated I tried to play with it like an hour and got stucked thats why I posted it here..
would like if someone could give me an useful solution.
thank you!
Add a variable that holds the direction. direction = "growing" or direction = "shrinking". Or even simpler, a boolean (true=growing, false=shrinking).
let isGrowing = true;
const ball2 = document.querySelector('.ball2');
function onBall2Click() {
if (ball2Size === 400) {
isGrowing = false
} else if (ball2Size === 100) {
isGrowing = true
}
if (isGrowing) {
ball2Size += 50;
} else {
ball2Size -= 50;
}
ball2.innerText = ball2Size;
ball2.style.width = ball2Size;
ball2.style.height = ball2Size;
}
The code behave like this because once you shrink it first time, it's not 400 anymore, so it goes into ELSE and grow it again.
To achieve that you need to have a variable in outside scope of your function that will hold information if the ball is growing or not.
Ideally that should not be defined in the Global scope (as it would pollute global scope that is anti-pattern), however I can't see the rest of your code so I don't know into what closure the isGrowing variable should be placed.
var ball2SizeStep = 50;
function onBall2Click() {
var ball2 = document.querySelector('.ball2');
if(ball2Size === 400) {
ball2SizeStep = -50;
} else if(ball2Size === 100) {
ball2SizeStep = 50;
}
ball2Size += ball2SizeStep;
ball2.innerText = ball2Size;
ball2.style.width = ball2Size;
ball2.style.height = ball2Size;
}
I have two animations which both work, but I'd like to have it so the second animation starts only after the first animation has finished, I tried this with Javascript callbacks but can't seem to get it to work. I'm sure someone can show me how to do this. There must be other ways to do this too? I'd be really interested to find out what they are actually. It's amazing how many different ways there are too do things isn't it.
document.addEventListener("DOMContentLoaded", first, false);
var obj, height, goUp;
function first() {
obj = document.getElementById("thetext");
obj.style.position = "absolute";
obj.style.bottom = "10px";
height = document.body.clientHeight;
goUp = true
second();
}
function second() {
var pos = parseInt(obj.style.bottom, 10);
(goUp) ? pos++ : pos--;
obj.style.bottom = pos + "px";
if (pos < 0) {
goUp = true;
}
if (pos > height) {
goUp = false;
}
if (pos < 0) {
return;
}
setTimeout(second, 10);
}
var objz, width, goRight;
function animatefirst() {
objz = document.getElementById("tues");
objz.style.position = "absolute";
objz.style.left = "10px";
objz.style.bottom = "10px";
width = document.body.clientWidth;
goRight = true;
animatesecond();
}
function animatesecond() {
var position = parseInt(objz.style.left, 10);
(goRight) ? position++ : position--;
objz.style.left = position + "px";
if (position > width) {
goRight = false;
}
if (position < 0) {
goRight = true;
}
if (position < 0) {
return;
}
setTimeout(animatesecond, 10);
}
<body>
<p id="thetext">ANIMATION </p>
<p id="tues"> Tuesday</p>
</body>
I fixed this issue in a simple way, I just put my call to the next function just before the return; which closes the current function. So the call to the next function is not inserted until the part in the code where the first function has finished, to avoid overlapping functions. I'm surprised no one could help with this, but it must be a very busy site :)
if (pos < 0) {
nextfunction();
return;
}
I have been trying to make a simple "smoothscroll" function using location.href that triggers on the mousewheel. The main problem is that the EventListener(wheel..) gets a bunch of inputs over the span of ca. 0,9 seconds which keeps triggering the function. "I only want the function to run once".
In the code below I have tried to remove the eventlistener as soon as the function runs, which actually kinda work, the problem is that I want it to be added again, hence the timed function at the bottom. This also kinda work but I dont want to wait a full second to be able to scroll and if I set it to anything lover the function will run multiple times.
I've also tried doing it with conditions "the commented out true or false variables" which works perfectly aslong as you are only scrolling up and down but you cant scroll twice or down twice.
window.addEventListener('wheel', scrolltest, true);
function scrolltest(event) {
window.removeEventListener('wheel', scrolltest, true);
i = event.deltaY;
console.log(i);
if (webstate == 0) {
if (i < 0 && !upexecuted) {
// upexecuted = true;
location.href = "#forside";
// downexecuted = false;
} else if (i > 0 && !downexecuted) {
// downexecuted = true;
location.href = "#underside";
// upexecuted = false;
}
}
setTimeout(function(){ window.addEventListener('wheel', scrolltest, true); }, 1000);
}
I had hoped there was a way to stop the wheel from constantly produce inputs over atleast 0.9 seconds.
"note: don't know if it can help in some way but when the browser is not clicked (the active window) the wheel will registre only one value a nice 100 for down and -100 for up"
What you're trying to do is called "debouncing" or "throttling". (Those aren't exactly the same thing, but you can look up the difference in case it's going to matter to you.) Functions for this are built into libraries like lodash, but if using a library like that is too non-vanilla for what you have in mind, you can always define your own debounce function: https://www.geeksforgeeks.org/debouncing-in-javascript/
You might also want to look into requestanimationframe.
a different approach
okey after fiddeling with this for just about 2 days i got fustrated and started over. no matter what i did the browsers integrated "glide-scroll" was messing up the event trigger. anyway i decided to animate the scrolling myself and honestly it works better than i had imagined: here is my code if anyone want to do this:
var body = document.getElementsByTagName("BODY")[0];
var p1 = document.getElementById('page1');
var p2 = document.getElementById('page2');
var p3 = document.getElementById('page3');
var p4 = document.getElementById('page4');
var p5 = document.getElementById('page5');
var whatpage = 1;
var snap = 50;
var i = 0;
// this part is really just to read what "page" you are on if you update the site. if you add more pages you should remember to add it here too.
window.onload = setcurrentpage;
function setcurrentpage() {
if (window.pageYOffset == p1.offsetTop) {
whatpage = 1;
} else if (window.pageYOffset == p2.offsetTop) {
whatpage = 2;
} else if (window.pageYOffset == p3.offsetTop) {
whatpage = 3;
} else if (window.pageYOffset == p4.offsetTop) {
whatpage = 4;
} else if (window.pageYOffset == p5.offsetTop) {
whatpage = 5;
}
}
// this code is designet to automaticly work with any "id" you have aslong as you give it a variable called p"number" fx p10 as seen above.
function smoothscroll() {
var whatpagenext = whatpage+1;
var whatpageprev = whatpage-1;
var currentpage = window['p'+whatpage];
var nextpage = window['p'+whatpagenext];
var prevpage = window['p'+whatpageprev];
console.log(currentpage);
if (window.pageYOffset > currentpage.offsetTop + snap && window.pageYOffset < nextpage.offsetTop - snap){
body.style.overflowY = "hidden";
i++
window.scrollTo(0, window.pageYOffset+i);
if (window.pageYOffset <= nextpage.offsetTop + snap && window.pageYOffset >= nextpage.offsetTop - snap) {
i=0;
window.scrollTo(0, nextpage.offsetTop);
whatpage += 1;
body.style.overflowY = "initial";
}
} else if (window.pageYOffset < currentpage.offsetTop - snap && window.pageYOffset > prevpage.offsetTop + snap){
body.style.overflowY = "hidden";
i--
window.scrollTo(0, window.pageYOffset+i);
if (window.pageYOffset >= prevpage.offsetTop - snap && window.pageYOffset <= prevpage.offsetTop + snap) {
i=0;
window.scrollTo(0, prevpage.offsetTop);
whatpage -= 1;
body.style.overflowY = "initial";
}
}
}
to remove the scrollbar completely just add this to your stylesheet:
::-webkit-scrollbar {
width: 0px;
background: transparent;
}
So when I shoot the enemies they get wiped from the screen, this works.
However what I want to happen is I want to place an explosion (4 pngs one after another)
basically where the enemy was. The code for the explosion works on its own but im stuck trying to integrate it with my code.
Here is the explosion class, as you can see I am having some trouble with the interval as I have no experience with them. I think the error or wrong logic lies in this object.
Also for some reason it wipes the other canvas layers :/
Try it here: http://www.taffatech.com/DarkOrbit.html
function Explosion()
{
this.srcX = 0;
this.srcY = 1250;
this.drawX = 0;
this.drawY = 0;
this.width = 70;
this.height = 70;
this.currentFrame =0;
this.totalFrames =5;
this.hasHit = false;
}
Explosion.prototype.draw = function() //makes it last 10 frames using total frames
{
if(this.currentFrame <= this.totalFrames)
{
this.currentFrame++;
Exploder(this.drawX,this.drawY);
}
else
{
this.hasHit = false;
currentFrame =0;
}
}
function Exploder(srcX,srcY)
{
whereX = this.srcX;
whereY = this.srcY;
intervalT = setInterval(BulletExplosionAnimate, 80);
}
var bulletExplosionStart = 0;
var whereX =0;
var whereY =0;
function BulletExplosionAnimate(intervalT)
{
var wide = 70;
var high = 70;
if (bulletExplosionStart > 308)
{
bulletExplosionStart = 0;
clearInterval(intervalT);
}
else
{
ctxExplosion.clearRect(0,0,canvasWidth,canvasHeight)
ctxExplosion.drawImage(spriteImage,bulletExplosionStart,1250,wide,high,whereX,whereY,wide,high);
bulletExplosionStart += 77;
}
}
my Bullet object:
function Bullet() //space weapon uses this
{
this.srcX = 0;
this.srcY = 1240;
this.drawX = -20;
this.drawY = 0;
this.width = 11;
this.height = 4;
this.bulletSpeed = 10;
this.bulletReset = -20;
this.explosion = new Explosion();
}
Bullet.prototype.draw = function()
{
this.drawX += this.bulletSpeed;
ctxPlayer.drawImage(spriteImage,this.srcX,this.srcY,this.width,this.height,this.drawX,this.drawY,this.width,this.height);
this.checkHitEnemy();
if (this.drawX > canvasWidth)
{
this.recycle();
}
}
Bullet.prototype.fire = function(startX, startY)
{
this.drawX = startX;
this.drawY = startY;
}
Bullet.prototype.checkHitEnemy = function()
{
for(var i = 0; i < enemies.length; i++)
{
if( this.drawX >= enemies[i].drawX && this.drawX <= enemies[i].drawX + enemies[i].enemyWidth && this.drawY >= enemies[i].drawY && this.drawY <= enemies[i].drawY + enemies[i].enemyHeight)
{
this.explosion.drawX = enemies[i].drawX - (this.explosion.width/2);
this.explosion.drawY = enemies[i].drawY;
this.explosion.hasHit = true;
this.recycle(); //bullet resets after hit enemy
enemies[i].recycleEnemy(); //change this soon to have if loop if health is down
}
}
}
Bullet.prototype.recycle = function()
{
this.drawX = this.bulletReset;
}
In my player object I have a function that checks if it has hit an enemy, it works:
Player.prototype.drawAllBullets = function()
{
for(var i = 0; i < this.bullets.length; i++)
{
if(this.bullets[i].drawX >= 0)
{
this.bullets[i].draw();
}
if(this.bullets[i].explosion.hasHit)
{
this.bullets[i].explosion.draw();
}
}
}
Currently when I shoot an enemy they disappear but not explosion happens, I know my interval is not great coding, so I need some help with it, thanks!
Playing a spritesheet in canvas
It’s becoming best practice to use requestAnimationFrame to do your animations. It does some nice event grouping and performance enhancing. Here’s a good post on requestAnimationFrame: http://creativejs.com/resources/requestanimationframe/
This is how you can use requestAnimationFrame to play a spritesheet:
In this case, it’s a 4x4 spritesheet that will play over 1 second:
var fps = 16;
function explode() {
// are we done? ... if so, we're outta here
if(spriteIndex>15){return;}
// It's good practice to use requestAnimation frame
// We wrap it in setTimeout because we want timed frames
setTimeout(function() {
// queue up the next frame
requestAnimFrame(explode);
// Draw the current frame
var x=spriteIndex%(cols-1)*width;
var y=parseInt(spriteIndex/(rows-1))*height;
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(sheet,x,y,width,height,0,0,width,height);
// increment the sprite counter
spriteIndex++;
}, 1000 / fps);
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/nSGyx/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// This is Paul Irish's great cross browser shim for requestAnimationFrame
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
// define the spritesheet
var spriteIndex=0;
var width=64;
var height=64;
var rows=4;
var cols=4;
// load the sheet image
var sheet=document.createElement("img");
sheet.onload=function(){
canvas.width=width;
canvas.height=height;
// call the animation
explode();
}
sheet.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/explodeSprite.png";
var fps = 16;
function explode() {
// are we done? ... if so, we're outta here
if(spriteIndex>15){return;}
// It's good practice to use requestAnimation frame
// We wrap it in setTimeout because we want timed frames
setTimeout(function() {
// queue up the next frame
requestAnimFrame(explode);
// Draw the current frame
var x=spriteIndex%(cols-1)*width;
var y=parseInt(spriteIndex/(rows-1))*height;
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(sheet,x,y,width,height,0,0,width,height);
// increment the sprite counter
spriteIndex++;
}, 1000 / fps);
}
$("#explode").click(function(){ spriteIndex=0; explode(); });
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=64 height=64></canvas><br>
<button id="explode">Explode</button>
</body>
</html>
.
.
.
[Edited to show more details of how animation fits into your code]
This is a recoding of your explosion functions.
Declare your explosion related variables outside the functions:
var bulletExplosionStart;
var whereX =0;
var whereY =0;
var wide = 70;
var high = 70;
Next, in Exploder(), set where the explosion will occur and reset the sprite index (bulletExplosionStart) to 0
Possible error: Check your Exploder function: you supply srcX,srcY but then do whereX=this.srcX, whereY=this.srcY. I assume you meant to use the srcX,srcY supplied as arguments to Exploder() instead of this.srcX,this.srcY.
function Exploder(srcX,srcY)
{
whereX = srcX;
whereY = srcY;
bulletExplosionStart=0;
BulletExplosionAnimate();
}
This is the recoded bulletExplosionAnimate function that plays the 4 frames of the spritesheet.
After 4 frames this animation automatically stops.
var fps = 2;
function bulletExplosionAnimate() {
// are we done? ... if so, we're outta here
if(bulletExplosionStart>308){return;}
// It's good practice to use requestAnimation frame
// We wrap it in setTimeout because we want timed frames
setTimeout(function() {
// queue up the next frame
requestAnimFrame(bulletExplosionAnimate);
// Draw the current frame
ctxExplosion.clearRect(0,0,canvasWidth,canvasHeight)
ctxExplosion.drawImage(spriteImage,
bulletExplosionStart,1250,wide,high,
whereX,whereY,wide,high);
// increment the sprite position
bulletExplosionStart += 77;
}, 1000 / fps);
}