I cannot figure out why my collision function is working for one element and not for another.It's madness ,please help.It detects the food collision but it doesn't detect when the head of the snake hits it's other elements.
window.onload= function ()
{
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var canvasWidth=window.innerWidth-20;
var canvasHeight=window.innerHeight-20;
canvas.width=canvasWidth;
canvas.height=canvasHeight;
var up=false;
var down=false;
var left=false;
var right=true;
var snake={
x:20,
y:0,
w:10,
h:10
};
var snakeBody=[];
for (i = 0; i < 20; i++) {
snakeBody.push({
x:snake.x ,
y:snake.y ,
w:snake.w,
h:snake.h
});
snake.x +=20;
}
var food={
x:Math.floor(Math.random() * (canvasWidth-50)),
y:Math.floor(Math.random() * (canvasHeight-50)),
w:10,
h:10
};
function moveUp()
{
snakeBody[0].y -=3;
}
function moveDown()
{
snakeBody[0].y +=3;
}
function moveLeft()
{
snakeBody[0].x -=3;
}
function moveRight()
{
snakeBody[0].x +=3;
}
function draw()
{
context.clearRect(0,0,canvasWidth,canvasHeight);
context.fillStyle="black";
context.beginPath();
for (var i = snakeBody.length - 1; i > 0 ; i--) {
context.rect(snakeBody[i].x,snakeBody[i].y,snakeBody[i].w,snakeBody[i].h);
snakeBody[i].x = snakeBody[i-1].x;
snakeBody[i].y = snakeBody[i-1].y;
}
context.rect(snakeBody[0].x,snakeBody[0].y,snakeBody[0].w,snakeBody[0].h);
context.rect(food.x,food.y,food.w,food.h);
context.stroke();
context.fill();
for (var i = 1; i < snakeBody.length; i++) {
if (intersects(food.x,food.y,food.w,food.h,snakeBody[i].x,snakeBody[i].y,snakeBody[i].w,snakeBody[i].h)) {
generateFood();
growSnake();
}
var head=snakeBody[0];
if (intersects(head.x,head.y,head.w,head.h,
snakeBody[i].x,snakeBody[i].y,snakeBody[i].w,snakeBody[i].h)) {
alert('game over');
}
}
directions();
collision();
update();
}
function growSnake()
{
for (i = 0; i < 5; i++) {
snakeBody.push({
x:snake.x ,
y:snake.y ,
w:snake.w,
h:snake.h
});
snake.x +=20;
}
}
function generateFood()
{
food.x=Math.floor(Math.random() * (canvasWidth-50));
food.y=Math.floor(Math.random() * (canvasHeight-50));
}
function intersects(x1, y1, w1, h1, x2, y2, w2, h2) {
w2 += x2;
w1 += x1;
if (x2 > w1 || x1 > w2) return false;
h2 += y2;
h1 += y1;
if (y2 > h1 || y1 > h2) return false;
return true;
}
function directions()
{
document.onkeydown = function(e)
{
var event = window.event ? window.event : e;
var keycode = event.keyCode;
if (keycode===37 && right===false) {
left=true;
right=false;
up=false;
down=false;
}
if (keycode===38 && down===false) {
up=true;
down=false;
left=false;
right=false;
}
if (keycode===39 && left===false) {
right=true;
left=false;
up=false;
down=false;
}
if (keycode===40 && up===false) {
down=true;
up=false;
left=false;
right=false;
}
};
}
function update()
{
if (up) {moveUp();}
if (down) {moveDown();}
if (left) {moveLeft();}
if (right) {moveRight();}
}
function gameOver()
{
alert('game over!');
}
function collision()
{
if (snakeBody[0].x >canvasWidth) {
snakeBody[0].x = 0;
}
if (snakeBody[0].x < 0) {
snakeBody[0].x=canvasWidth;
}
if (snakeBody[0].y>canvasHeight) {
snakeBody[0].y=0;
}
if (snakeBody[0].y <0) {
snakeBody[0].y=canvasHeight;
}
}
setInterval(draw,20);
};
It's a lot of code, so here's a fiddle http://jsfiddle.net/5nLQG/
Focus would seem to be function intersects:
function intersects(x1, y1, w1, h1, x2, y2, w2, h2) {
w2 += x2;
w1 += x1;
if (x2 > w1 || x1 > w2) return false;
h2 += y2;
h1 += y1;
if (y2 > h1 || y1 > h2) return false;
return true;
}
Yep. Check it out. Game over is being hit. Repeatedly. Because your blocks are all scrunched up it's difficult to do collision detection in the first place. In fact only checking after the 20th cube was I able to have a functional game. Check it out
if (i - 20 > 0&& intersects(snakeBody[0].x,snakeBody[0].y,snakeBody[0].w,snakeBody[0].h,
snakeBody[i].x,snakeBody[i].y,snakeBody[i].w,snakeBody[i].h)) {
clearInterval(pulse);
}
Where I declare pulse the timeInterval ID
http://jsfiddle.net/fC25X/
I would work on spacing your cubes and you should be golden
What to do?
I think there's a great joy in figuring out these simple games for yourself. For instance minesweeper if not done properly can give massive stackoverflows. I've written a snake game before and I will quickly explain how I managed cubes following each other. Your way seems like it could work with a bit of playing around, so only read further if you don't care about figuring out the process yourself. My way is probably not best either.
Actually pretty terrible
I remember my particular implementation of snake I kept a separate array called 'turningPoints' and on keypress it looked something like this:
document.onkeydown = function(e)
{
var event = window.event ? window.event : e;
var keycode = event.keyCode;
if (keycode===37 && right===false) {
left=true;
right=false;
up=false;
down=false;
turningPoints.push(snake[0].x,snake[0].y,[1,0]); // [1,0] matrix for right
}
}
Then when iterating over each cube (this is where it gets messier) I moved each cube in the direction it was going, and then checked to see if it had reached a turningPoint (with some margin of error), if it had then it would change direction:
if(snake[i].x == turningPoint[j].x && snake[i].y == turningPoint[j].y)
snake[i].direction = turningPoint[j].direction;
I made sure to clean up turningPoints (after all the cubes have followed the instruction there's no need to keep the point), and the whole thing worked OK
BUT
I got bored, and played around with your way a bit more and came up with this http://jsfiddle.net/LAMZt/ It's quiet a bit different, but it's more or less the same. Noticed another issue was that you snake's head was the left most piece going right, so naturally, it collided with the other pieces. So I fixed it by rendering additional pieces as the snake moved. The rest was just house keeping so I could save it as a gist (referenced this question of course).
Related
this is my first post! With that in mind, if I need to add anything more than what is below, please let me know. Thank you!
I am currently working to make a platformer game in Javascript where a player can move using arrow keys. Currently, my left and right movements, as well as my player gravity, works. However, when I jump, I am unable to provide smooth jumping movements. originally, I tried simply moving my player's y value higher.
if (up) {
this.y -= 100;
}
However, this makes the player "teleport" upwards, rather than look like an actual jump. To fix this, I reused my gravity code to overcome the gravitational force until a certain limit, making the player look like they are smoothly jumping until they reach a certain height. Below is my code.
if (up) { // This if statement is apart of another function, did not include
this.can_jump = false;
this.jumping = true; this.jump();
}
jump() { // Overcome the gravitational force, but jump force slowly lowers
this.y -= (this.gravity * this.velocity) * 3;
this.gravity -= 0.1;
this.velocity -= 0.1;
this.check();
this.check_jump(this.jumping);
}
check_jump(jumping) {
if (jumping) {
if (this.x < 500) { // While player is less than 500, keep jumping
this.jumping = false;
this.gravity = 2;
this.velocity = 2;
this.can_jump = true;
}
}
}
Additionally, here is the code regarding player collisions and gravity.
gravityEffect() {
this.y += (this.gravity * this.velocity);
this.check();
}
check() {
// Too far up
if (this.y <= 70) { this.y = 70; }
// Too far down
if (this.y >= 600) { this.y = 600; }
// Too far left, teleports to other side
if (this.x < 0) { this.x = 1200; }
// Too far right, teleports to other side
if (this.x > 1200) { this.x = 0; }
}
However, when testing this, my player not only keeps jumping upwards, but also does not do so smoothly (it looks like it is glitching). Here is a link to an mp4 file download (screen recording) showcasing the glitch: https://www.mediafire.com/file/jtqh3lca72vj8nz/%25F0%259D%2590%258C%25F0%259D%2590%25B2_%25F0%259D%2590%2586%25F0%259D%2590%25AB%25F0%259D%2590%259A%25F0%259D%2590%25AF%25F0%259D%2590%25A2%25F0%259D%2590%25AD%25F0%259D%2590%25B2_-_Google_Chrome_2021-04-28_19-59-08.mp4/file
Also, here is a copy of my current code (zipped), if running the program helps: https://www.mediafire.com/file/r5ewoxtb4n57htz/game.zip/file
Please let me know what is wrong. Also, if there is a different or more efficient method of simulating player jumping, please make me aware of it. Thank you for your time.
While trying to keep the code mostly the same I made some changes.
First and formost I changed how you had the controller written. Unless your intention was for the up/down/left/right arguments to stay true then you need a method for them to kick back to false. This controller class will do that.
// Apply classes to empty variables
console.log("Creating player...");
player = new Player();
console.log("Creating world...");
world = new World();
console.log("Creating Controller...")
controller = new Controller();
// Draw canvas with set size
console.log("Creating game screen...");
createCanvas(1000, 750);
}
class Controller {
constructor() {
this.up = false;
this.down = false;
this.right = false;
this.left = false;
let keyEvent = (e) => {
if (e.code === 'ArrowUp') { this.up = e.type === 'keydown' }
if (e.code === 'ArrowRight') { this.right = e.type === 'keydown' }
if (e.code === 'ArrowDown') { this.down = e.type === 'keydown' }
if (e.code === 'ArrowLeft') { this.left = e.type === 'keydown' }
}
window.addEventListener('keydown', keyEvent)
window.addEventListener('keyup', keyEvent)
}
}
Since we changed that we'll have to change the Player Class very slightly.
I've set the X and Y velocity to 0 and we'll increment those once a button is pressed. The additional update function will update your X and Y based on that.
Player Class
class Player {
// Setup player attributes
constructor() {
this.x = 100;
this.y = 395;
this.width = 50;
this.height = 50;
this.jumping = false;
this.color = "#ffdb15";
this.gravity = 2;
this.velocityY = 0;
this.velocityX = 0; //changed this from speed
this.points = 0;
}
move() {
// Reverse gravity to upwards, change player's color
if (controller.up && !this.jumping) { this.jumping = true; this.jump(); this.color = this.getRandomColor(true); }
// Reverse gravity to downwards, change player's color
if (controller.down) { this.color = this.getRandomColor(false); }
// Go left
if (controller.left) { this.velocityX -= 1 }
// Go right
if (controller.right) { this.velocityX += 1 }
}
jump() {
this.velocityY -= 35;
}
check() {
// Too far up
if (this.y <= 70) { this.y = 70; }
// Too far down
if (this.y >= 600) { this.y = 600; this.jumping = false } //once collision player can jump again
// Too far left, teleports to other side
if (this.x < 0) { this.x = 1200; }
// Too far right, teleports to other side
if (this.x > 1200) { this.x = 0; }
}
// Get a random player color
getRandomColor(isMoving) {
if ((this.y === 70 || this.y === 600) && isMoving) {
// Explanation: Each color has RGB values from 0 to 255, or 256 total options
// Since colors start from "000000" and go until "FFFFFF", there are ((256^3) - 1) possibilities
// (256^3) - 1 = 16777215
// Use this number, and a random number from Math.Random(), to get a random color
// Idea from: https://css-tricks.com/snippets/javascript/random-hex-color/
this.color = Math.floor(Math.random() * 16777215).toString(16);
return "#" + this.color;
} else { return this.color; }
}
show() {
// Show player
fill(this.color);
strokeWeight(0);
rect(this.x, this.y, this.width, this.height);
}
update() {
this.velocityY += this.gravity;
this.x += this.velocityX;
this.y += this.velocityY;
this.velocityY *= 0.9;
this.velocityX *= 0.9; //provides a sliding slow down once button is released.
this.move();
this.check();
}
}
The draw function is the same but replace gravity with update
function draw() {
world.generate();
player.update();
player.show();
}
This should get you where you want to go with it.
Do you have a variable for y speed? i've found the best way to create a fairly normal looking jump would be to set the y speed to a set number, EG: -4. My personal favorite method for realistic player jumping and gravity would be as follows, but can be easily modified for your uses:
//Initial y position
var y=height/2;
//This is how far the player moves each frame
var ys=0;
//This is how intense the gravity is.
var yss=0.1;
function draw(){
y+=ys;
//yss can be replaced with some arbitrary number if you don't plan on changing it(EG: for collisions)
ys+=yss;
//Then to jump, something like this
if(mouseIsPressed){
ys=-4;
}
//Heck, if you wanted to flip gravity you could do this
//yss*=-1;
//and bouncing would look like this
//ys*=-0.9;
}
Let me know if there is anything I can clarify or help with!
I am trying to move an SVG to, say, X,Y by having a setTimeout call the moveSelected function recursively. The moveSelected function in turn sets the new x and y a little closer to X,Y to reproduce the animation effect.
Here is my function:
this.moveSelected = function moveSelected(moveToHex) {
//setting new cords on screen
var self=this;
var repeater;
var curX = parseInt(this.selectedEle.getAttribute('cx'));
var curY = parseInt(this.selectedEle.getAttribute('cy'));
//console.log('curX '+curX+'curY ',curY);
if(curX < moveToHex.cx) //moveRight
{
this.selectedEle.setAttribute('cx', curX + .5);
}
if(curX > moveToHex.cx) //moveLeft
{
this.selectedEle.setAttribute('cx',curX-.5);
}
if(curY < moveToHex.cy) //movedown
{
console.log('move down');
this.selectedEle.setAttribute('cy', curY + .5);
}
if(curY > moveToHex.cy) //moveup
{
console.log('move Up');
this.selectedEle.setAttribute('cy', curY - .5);
}
console.log('condition:' + curY + '<' + parseInt(moveToHex.cy));
if((Math.abs(curY - moveToHex.cy)) < 1 &&
(Math.abs(curX - moveToHex.cx) < 1)) {
clearTimeout(repeater);
unitArray[this.selectedUnit.num].hexagon = moveToHex;
console.log('cleared');
}
else if(!repeater) {
repeater = setTimeout(function() {
self.moveSelected(moveToHex)
}, 25);
}
}
Now everything works as expected but the movedown and moveRight conditions, i.e. curY + .5 and curX + .5.
I tried consoling out the values but don't understand why it is behaving like that. I assumed it is typecasting problem but typecasting as curX and curY into int didn't help either
Fiddle:
https://jsfiddle.net/Snedden27/z4xfy6n8/2/ (scroll to the two movable circles at the right)
How would one go about coding a jump like in this game? I've got some working code already (made in Adobe Edge) but the way it's programmed it won't start another jump while mid-air. This is what I've got atm:
var velocityY = 0;
var gravity = 0.5;
var onGround = false;
var fps = 60;
function StartJump()
{
if(onGround)
{
velocityY = -13.0;
onGround = false;
}
}
function EndJump()
{
if(velocityY < -6,0)
velocityY = -6,0;
}
var positionY = sym.$("ball").position().left;
Loop();
function Loop()
{
Update();
setTimeout(Loop, 1000/fps);
}
function Update()
{
velocityY += gravity;
positionY += velocityY;
if(positionY > 730)
{
positionY = 730;
velocityY = 0;
onGround = true;
}
sym.$("ball").css("top", positionY + "px");
console.log(velocityY);
}
The top value 730 is where "ball" starts on the ground. StartJump() is called on the event 'mousedown' and EndJump() on 'mouseup'. I can't figure out how to change the code so I can make it jump while mid-air (as in when it collides with sth, like how in the game the bunny hits the bell).
I'm trying to write a simple 2D platform game and I can't get my player object to double jump - or rather, I can't get him NOT to. When the up arrow or spacebar is pressed once, double jump is triggered no matter what. I am a JS newbie so I assume this has something to do with my use of logical operators, but I could be wrong. Here is the code:
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
width = 500,
height = 200,
player = {
x : width/2,
y : height - 5,
width : 5,
height : 5,
speed : 3,
velX : 0,
velY : 0,
jumping : false,
jumping_twice : false
};
keys = [],
friction = 0.8,
gravity = 0.3;
canvas.width = width;
canvas.height = height;
function update(){
if (keys[38] || keys[32]) {
// up arrow or space
if (!player.jumping) {
player.jumping = true;
player.velY = -player.speed*2;
console.log("Player is jumping");
}
else if (!player.jumping_twice) {
player.jumping_twice = true;
player.velY = -player.speed;
console.log("Player is jumping twice");
}
}
if (keys[39]) {
// right arrow
if (player.velX < player.speed) {
player.velX++;
}
}
if (keys[37]) {
// left arrow
if (player.velX > -player.speed) {
player.velX--;
}
}
player.velX *= friction;
player.velY += gravity;
player.x += player.velX;
player.y += player.velY;
if (player.x >= width-player.width) {
player.x = width-player.width;
} else if (player.x <= 0) {
player.x = 0;
}
if (player.y >= height-player.height) {
player.y = height - player.height;
player.jumping = false;
player.jumping_twice = false;
}
ctx.clearRect(0,0,width,height);
ctx.fillStyle = "red";
ctx.fillRect(player.x, player.y, player.width, player.height);
requestAnimationFrame(update);
}
window.addEventListener("load", function(){
update();
})
document.body.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
});
document.body.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
#rhino is totally right about the explanation. I have another solution though. After the jump has been processed, reset the information about the jump key being pressed:
keys[32] = keys[38] = false;
requestAnimationFrame(update);
This happens because update() runs multiple times while the jump key is held down; both conditions are met, one after the other, before you release the key. You could do the check and make the character jump in the keydown handler to work around that, but it is a better idea to keep the changes of your character in the main loop (update() in your case). To do so, you need an extra variable which determines whether the player can actually jump, based on the changes of key state - when the key gets released, the player can jump again or perform a double jump. You could give player a new property called, for example, can_jump and initially set it to true. Then you can do as follows:
if (keys[38] || keys[32]) {
// up arrow or space
if (player.can_jump) {
if (!player.jumping) {
player.jumping = true;
player.velY = -player.speed*2;
player.can_jump = false; // the player can't jump anymore until the key is released
console.log("Player is jumping");
}
else if (!player.jumping_twice) {
player.jumping_twice = true;
player.velY = -player.speed;
console.log("Player is jumping twice");
}
}
And in the keyup handler:
if (e.keyCode == 32 || e.keyCode == 38) player.can_jump = true;
Working demo: http://jsfiddle.net/5JF69/
Also, be aware that the speed of your game depends on how fast the browser performs a redraw, as you are updating your game logic using requestAnimationFrame().
EDIT: In case the state of the spacebar and up keys doesn't need to remain "down" at all times the keys are actually held down (which doesn't in your current code), go for #lordvlad's solution. It's definitely better if you don't perform any other checks on those keys.
Hah I'm actually the author of the tutorial code you're using.
All the answers are good answers, this is how I would personally do it.
if (keys[38] || keys[32]) {
// up arrow or space
if (!player.jumping) {
player.jumping = true;
player.velY = -player.speed*2;
console.log("Player is jumping");
}
else if(player.jumping && !player.jumping_twice) {
player.jumping_twice = true;
player.velY = -player.speed;
console.log("Player is jumping twice");
}
keys[38] = keys[32] = false;
}
I would just set the keys to false if they are true in the check itself.
#lordvlad makes a good point, there should be an update cycle separate from the rendering, however for the purpose of the tutorial it was really just to get the user up and running to get something on the screen. At some point I'll make a part 3 to break the logic out a bit more. Anyway hope this helps.
Live Demo
I want to achieve 'Panning' in svg while 'dragging' an element in particular direction.
Let say i select an element and start 'dragging' it upward untill it reached top of screen, now my svg should pan upwards automatically, without causing any problem with dragging. how i can achieve this.?
i have made a small mockup of this, where user can select and drag elements. it also contain two button, which cause svg to pan upward and downward. I am achiveing 'Panning' by changing 'ViewBox' of svg. ( i have to use this logic, i cannot use any other solution);
here is the fiddle link : http://jsfiddle.net/9J25r/
Complete Code:-
addEventListener('mousedown', mousedown, false);
var mx, my;
var dx, dy;
var mainsvg = document.getElementById('svg');
var selectedElement;
var eleTx, eleTy;
function getSvgCordinates(event) {
var m = mainsvg.getScreenCTM();
var p = mainsvg.createSVGPoint();
var x, y;
x = event.pageX;
y = event.pageY;
p.x = x;
p.y = y;
p = p.matrixTransform(m.inverse());
x = p.x;
y = p.y;
x = parseFloat(x.toFixed(3));
y = parseFloat(y.toFixed(3));
return {x: x, y: y};
}
function mousedown(event) {
if (event.target.id === 'arrow_t') {
panning('up');
}
else if (event.target.id === 'arrow_b') {
panning('down');
}
else if (event.target.id.split('_')[0] === 'rect') {
selectedElement = event.target;
var translatexy = selectedElement.getAttribute('transform');
translatexy = translatexy.split('(');
translatexy = translatexy[1].split(',');
eleTx = translatexy[0];
translatexy = translatexy[1].split(')');
eleTy = translatexy[0];
eleTx = parseFloat(eleTx);
eleTy = parseFloat(eleTy);
var xy = getSvgCordinates(event);
mx = xy.x;
my = xy.y;
mx = parseFloat(mx);
my = parseFloat(my);
addEventListener('mousemove', drag, false);
addEventListener('mouseup', mouseup, false);
}
}
function drag(event) {
var xy = getSvgCordinates(event);
dx = xy.x - mx;
dy = xy.y - my;
selectedElement.setAttribute('transform', 'translate(' + (eleTx + dx) + ',' + (eleTy + dy) + ')');
}
function mouseup(event) {
removeEventListener('mousemove', drag, false);
removeEventListener('mouseup', mouseup, false);
}
function panning(direction) {
var viewBox = svg.getAttribute('viewBox');
viewBox = viewBox.split(' ');
var y = parseFloat(viewBox[1]);
if (direction === 'up')
{
y+=5;
}
else if (direction === 'down')
{
y-=5;
}
viewBox=viewBox[0]+' '+y+' '+viewBox[2]+' '+viewBox[3];
svg.setAttribute('viewBox',viewBox);
}
here is the fiddle link : http://jsfiddle.net/9J25r/
EDIT:- (UPDATE)
I use the solution of Ian , it works well on the sample, but when i applied it to my original application, it did not work. check the below gif. You can see the 'gap' between mouse pointer and element. how i can remove that? .
This is one way, I've just done it with the Y/vertical for the moment...
You may want to adjust it, so that if the cursor is off the screen it adjusts the viewBox automatically as well, depends how you want it to drag (otherwise you will need to keep wiggling it to kick the drag func in).
var viewBox = svg.getAttribute('viewBox');
viewBoxSplit = viewBox.split(' ');
if( ely < viewBoxSplit[1] ) {
panning('down');
} else if( ely + +event.target.getAttribute('height')> +viewBoxSplit[1] + 300 ) {
panning('up');
}
jsfiddle here