Have a very small snippet of an asteroids-like game I'm working on using only the DOM without Canvas. I have the "ship" moving pretty smoothly when arrow keys are pressed but how would I go about making the ship accelerate ( in speed and rotation ) when an arrow key is held down for a longer length of time?
window.onkeyup = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left = false;
else if ( kc === 38 ) Keys.up = false;
else if ( kc === 39 ) Keys.right = false;
else if ( kc === 40 ) Keys.down = false;
};
function update() {
if ( Keys.up ) {
document.querySelector( 'div' ).style.transform += 'translateY( -1px )';
}
else if ( Keys.down ) {
document.querySelector( 'div' ).style.transform += 'translateY( 1px )';
}
if ( Keys.left ) {
document.querySelector( 'div' ).style.transform += 'rotate( -1deg )';
}
else if ( Keys.right ) {
document.querySelector( 'div' ).style.transform += 'rotate( 1deg )';
}
requestAnimationFrame( update );
}
requestAnimationFrame( update );
#import url( "https://fonts.googleapis.com/css?family=Nunito" );
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: "Nunito", sans-serif;
font-size: 2rem;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
b {
display: block;
transform: rotate( 180deg );
}
<div>
<b>v</b>
</div>
<script>
var Keys = {
up: false,
down: false,
left: false,
right: false
}
window.onkeydown = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left = true;
else if ( kc === 38 ) Keys.up = true;
else if ( kc === 39 ) Keys.right = true;
else if ( kc === 40 ) Keys.down = true;
};
</script>
Use the arrow keys to control snippet.
Updated. As another similar question had a answer based on this previous version answer I have changed the answer to the better answer.
Transformations, Acceleration, Drag, and Rocket Ships.
There are so many ways to apply movement to a asteroids type game. This answer shows the most basic method and then gives an example showing variations on the basic methods to produce different feels. This answer also give a brief overview of how to set the CSS transformation using matrix (2D)
The basics
At the most basic you have a number that represents some component of the position or rotation. To move you add a constant x += 1; move in x one unit at a time when you let go of the control you don`t add and you stop.
But things don't move like that, they accelerate. So you create a second value that holds the speed (formerly the one from x += 1) and call it dx (delta X). When you get input you increase the speed by some small amount dx += 0.01 so that in time the speed increases gradually.
But the problem is the longer you hold the controls the faster you go, and when you let go of the controls the ship keeps going (which is all normal for space but a pain in a game) so you need to limit the speed and bring it gradually back down to zero. You can do that by applying a scale to the delta X value each frame. dx *= 0.99;
Thus you have the basic acceleration, drag, and speed limited value
x += dx;
dx *= 0.99;
if(input){ dx += 0.01);
Do that for both the x, y and angle. Where input is directional you need to use vectors for x, y as follows.
x += dx;
y += dy;
angle += dAngle;
dx *= 0.99;
dy *= 0.99;
dAngle *= 0.99;
if(turnLeft){
dAngle += 0.01;
}
if(turnRight){
dAngle -= 0.01;
}
if(inputMove){
dx += Math.cos(angle) * 0.01;
dy += Math.sin(angle) * 0.01;
}
That is the most basic space game movement.
Setting the CSS transform.
Setting the CSS transform is easiest apply via the matrix command. eg setting default transform element.style.transform = "matrix(1,0,0,1,0,0)";
The six values often named a,b,c,d,e 'matrix(a,b,c,d,e,f)' or m11, m12, m21, m22, m31, m32 or
horizontal scaling, horizontal skewing, vertical skewing, vertical scaling, horizontal moving, vertical moving and represent a shortened version of the 3 by 3 2D matrix.
I find that the majority of confusion about how this matrix works and why it is not often used is in part due to the naming of the variables. I prefer the description as x axis x, x axis y, y axis x, y axis y, origin x, origin y and simply describe the direction and scale of the x and y axis and the position of the origin all in CSS pixel coordinates.
The following image illustrates the matrix. The red box is a element that has been rotated 45 deg (Math.PI / 4 radians) and its origin moved to CSS pixel coordinated 16,16.
Image Grid shows CSS pixels. The right grid shows a zoomed view of the matrix showing the X Axis vector (a,b) = (cos(45), sin(45)), Y Axis vector (c,d) = (cos(45 + 90), sin(45 + 90)) and the Origin (e,f) = (16, 16)
Thus is I have the values for an element in terms of angle, position (x,y), scale (x,y). We then create the matrix as follows
var scale = { x : 1, y : 1 };
var pos = {x : 16, y : 16 };
var angle = Math.PI / 4; // 45 deg
var a,b,c,d,e,f; // the matrix arguments
// the x axis and x scale
a = Math.cos(angle) * scale.x;
b = Math.sin(angle) * scale.x;
// the y axis which is at 90 degree clockwise of the x axis
// and the y scale
c = -Math.sin(angle) * scale.y;
d = Math.cos(angle) * scale.y;
// and the origin
e = pos.x;
f = pos.y;
element.style.transform = "matrix("+[a,b,c,d,e,f].join(",")+")";
As most of the time we dont skew the transform and use a uniform scale we can shorten the code. I prefer to use a predefined array to help keep GC hits low.
const preDefinedMatrix = [1,0,0,1,0,0]; // define at start
// element is the element to set the CSS transform on.
// x,y the position of the elements local origin
// scale the scale of the element
// angle the angle in radians
function setElementTransform (element, x, y, scale, angle) {
var m = preDefinedMatrix;
m[3] = m[0] = Math.cos(angle) * scale;
m[2] = -(m[1] = Math.sin(angle) * scale);
m[4] = x;
m[5] = y;
element.style.transform = "matrix("+m.join(",")+")";
}
I use a slightly different function in the demo. ship.updatePos and uses the ship.pos and ship.displayAngle to set the transformation relative to the containing elements origin (top,left)
Note that the 3D matrix though a little more complex (includes the projection) is very similar to the 2D matrix, it describes the x,y, and z axis as 3 vectors each with 3 scalars (x,y,z) with the y axis at 90 deg to the x axis and the z axis at 90 deg to both the x and y axis and can be found with the cross product of the x dot y axis. The length of each axis is the scale and the origin is a point coordinate (x,y,z).
Demo:
The demo shows 4 5 variants. Use the keyboard 1,2,3,4,5 to select a ship (it will turn red) and use the arrow keys to fly. Basic up arrow you go, left right you turn.
The maths for each ship is in the object ship.controls
requestAnimationFrame(mainLoop);
const keys = {
ArrowUp : false,
ArrowLeft : false,
ArrowRight : false,
Digit1 : false,
Digit2 : false,
Digit3 : false,
Digit4 : false,
Digit5 : false,
event(e){
if(keys[e.code] !== undefined){
keys[e.code] = event.type === "keydown" ;
e.preventDefault();
}
},
}
addEventListener("keyup",keys.event);
addEventListener("keydown",keys.event);
focus();
const ships = {
items : [],
controling : 0,
add(ship){ this.items.push(ship) },
update(){
var i;
for(i = 0; i < this.items.length; i++){
if(keys["Digit" + (i+1)]){
if(this.controling !== -1){
this.items[this.controling].element.style.color = "green";
this.items[this.controling].hasControl = false;
}
this.controling = i;
this.items[i].element.style.color = "red";
this.items[i].hasControl = true;
}
this.items[i].updateUserIO();
this.items[i].updatePos();
}
}
}
const ship = {
element : null,
hasControl : false,
speed : 0,
speedC : 0, // chase value for speed limit mode
speedR : 0, // real value (real as in actual speed)
angle : 0,
angleC : 0, // as above
angleR : 0,
engSpeed : 0,
engSpeedC : 0,
engSpeedR : 0,
displayAngle : 0, // the display angle
deltaAngle : 0,
matrix : null, // matrix to create when instantiated
pos : null, // position of ship to create when instantiated
delta : null, // movement of ship to create when instantiated
checkInView(){
var bounds = this.element.getBoundingClientRect();
if(Math.max(bounds.right,bounds.left) < 0 && this.delta.x < 0){
this.pos.x = innerWidth;
}else if(Math.min(bounds.right,bounds.left) > innerWidth && this.delta.x > 0){
this.pos.x = 0;
}
if(Math.max(bounds.top,bounds.bottom) < 0 && this.delta.y < 0){
this.pos.y = innerHeight;
}else if( Math.min(bounds.top,bounds.bottom) > innerHeight && this.delta.y > 0){
this.pos.y = 0;
}
},
controls : {
oldSchool(){
if(this.hasControl){
if(keys.ArrowUp){
this.delta.x += Math.cos(this.angle) * 0.1;
this.delta.y += Math.sin(this.angle) * 0.1;
}
if(keys.ArrowLeft){
this.deltaAngle -= 0.001;
}
if(keys.ArrowRight){
this.deltaAngle += 0.001;
}
}
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.angle += this.deltaAngle;
this.displayAngle = this.angle;
this.delta.x *= 0.995;
this.delta.y *= 0.995;
this.deltaAngle *= 0.995;
},
oldSchoolDrag(){
if(this.hasControl){
if(keys.ArrowUp){
this.delta.x += Math.cos(this.angle) * 0.5;
this.delta.y += Math.sin(this.angle) * 0.5;
}
if(keys.ArrowLeft){
this.deltaAngle -= 0.01;
}
if(keys.ArrowRight){
this.deltaAngle += 0.01;
}
}
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.angle += this.deltaAngle;
this.delta.x *= 0.95;
this.delta.y *= 0.95;
this.deltaAngle *= 0.9;
this.displayAngle = this.angle;
},
speedster(){
if(this.hasControl){
if(keys.ArrowUp){
this.speed += 0.02;
}
if(keys.ArrowLeft){
this.deltaAngle -= 0.01;
}
if(keys.ArrowRight){
this.deltaAngle += 0.01;
}
}
this.speed *= 0.99;
this.deltaAngle *= 0.9;
this.angle += this.deltaAngle;
this.delta.x += Math.cos(this.angle) * this.speed;
this.delta.y += Math.sin(this.angle) * this.speed;
this.delta.x *= 0.95;
this.delta.y *= 0.95;
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.displayAngle = this.angle;
},
engineRev(){ // this one has a 3 control. Engine speed then affects acceleration.
if(this.hasControl){
if(keys.ArrowUp){
this.engSpeed = 3
}else{
this.engSpeed *= 0.9;
}
if(keys.ArrowLeft){
this.angle -= 0.1;
}
if(keys.ArrowRight){
this.angle += 0.1;
}
}else{
this.engSpeed *= 0.9;
}
this.engSpeedC += (this.engSpeed- this.engSpeedR) * 0.05;
this.engSpeedC *= 0.1;
this.engSpeedR += this.engSpeedC;
this.speedC += (this.engSpeedR - this.speedR) * 0.1;
this.speedC *= 0.4;
this.speedR += this.speedC;
this.angleC += (this.angle - this.angleR) * 0.1;
this.angleC *= 0.4;
this.angleR += this.angleC;
this.delta.x += Math.cos(this.angleR) * this.speedR * 0.1; // 0.1 reducing this as easier to manage speeds when values near pixel size and not 0.00umpteen0001
this.delta.y += Math.sin(this.angleR) * this.speedR * 0.1;
this.delta.x *= 0.99;
this.delta.y *= 0.99;
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.displayAngle = this.angleR;
},
speedLimiter(){
if(this.hasControl){
if(keys.ArrowUp){
this.speed = 15;
}else{
this.speed = 0;
}
if(keys.ArrowLeft){
this.angle -= 0.1;
}
if(keys.ArrowRight){
this.angle += 0.1;
}
}else{
this.speed = 0;
}
this.speedC += (this.speed - this.speedR) * 0.1;
this.speedC *= 0.4;
this.speedR += this.speedC;
this.angleC += (this.angle - this.angleR) * 0.1;
this.angleC *= 0.4;
this.angleR += this.angleC;
this.delta.x = Math.cos(this.angleR) * this.speedR;
this.delta.y = Math.sin(this.angleR) * this.speedR;
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.displayAngle = this.angleR;
}
},
updateUserIO(){
},
updatePos(){
this.checkInView();
var m = this.matrix;
m[3] = m[0] = Math.cos(this.displayAngle);
m[2] = -(m[1] = Math.sin(this.displayAngle));
m[4] = this.pos.x;
m[5] = this.pos.y;
this.element.style.transform = `matrix(${m.join(",")})`;
},
create(shape,container,xOff,yourRide){ // shape is a string
this.element = document.createElement("div")
this.element.style.position = "absolute";
this.element.style.top = this.element.style.left = "0px";
this.element.style.fontSize = "24px";
this.element.textContent = shape;
this.element.style.color = "green";
this.element.style.zIndex = 100;
container.appendChild(this.element);
this.matrix = [1,0,0,1,0,0];
this.pos = { x : innerWidth / 2 + innerWidth * xOff, y : innerHeight / 2 };
this.delta = { x : 0, y : 0};
this.updateUserIO = this.controls[yourRide];
return this;
}
}
var contain = document.createElement("div");
contain.style.position = "absolute";
contain.style.top = contain.style.left = "0px";
contain.style.width = contain.style.height = "100%";
contain.style.overflow = "hidden";
document.body.appendChild(contain);
window.focus();
ships.add(Object.assign({},ship).create("=Scl>",contain,-0.4,"oldSchool"));
ships.add(Object.assign({},ship).create("=Drg>",contain,-0.25,"oldSchoolDrag"));
ships.add(Object.assign({},ship).create("=Fast>",contain,-0.1,"speedster"));
ships.add(Object.assign({},ship).create("=Nimble>",contain,0.05,"speedLimiter"));
ships.add(Object.assign({},ship).create("=Rev>",contain,0.2,"engineRev"));
function mainLoop(){
ships.update();
requestAnimationFrame(mainLoop);
}
body {
font-family : verdana;
background : black;
color : #0F0;
}
Click to focus then keys 1, 2, 3, 4, 5 selects a ship. Arrow keys to fly. Best full page.
A zillion variants
There are many other variants and ways. I like the using a second derivative (first derivative dx/dt (dt is time) from x += dx, second de/dt for engine power) that simulates an engine ramping up power and winding down which can give a very nice feel. Basicly its
x += dx;
dx += de;
dx *= 0.999;
de *= 0.99;
if(input){ de += 0.01 }
What is suited for your game is up to you, you don't have to follow the rules so try out different values and methods till you are happy.
window.onkeydown = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left++;
else if ( kc === 38 ) Keys.up++;
else if ( kc === 39 ) Keys.right++;
else if ( kc === 40 ) Keys.down++;
};
window.onkeyup = function(e)
{
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) {Keys.left = 0;}
else if ( kc === 38 ) Keys.up = 0;
else if ( kc === 39 ) Keys.right = 0;
else if ( kc === 40 ) Keys.down = 0;
}
function update() {
if ( Keys.up ) {
document.querySelector( 'div' ).style.transform += 'translateY( -'+Keys.up+'px )';
}
else if ( Keys.down ) {
document.querySelector( 'div' ).style.transform += 'translateY( '+Keys.down+'px )';
}
if ( Keys.left ) {
document.querySelector( 'div' ).style.transform += 'rotate( -'+Keys.left+'deg )';
}
else if ( Keys.right ) {
document.querySelector( 'div' ).style.transform += 'rotate( '+Keys.right+'deg )';
}
requestAnimationFrame( update );
}
requestAnimationFrame( update );
Very basic idea how to implement acceleration:
Create speed variable and multiply them as you want.
Example of acceleration only for "UP" key
var speed = 1.0;
window.onkeyup = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left = false;
else if ( kc === 38 ) Keys.up = false;
else if ( kc === 39 ) Keys.right = false;
else if ( kc === 40 ) Keys.down = false;
};
function update() {
if ( Keys.up ) {
speed = speed *1.01;
document.querySelector( 'div' ).style.transform += 'translateY( -'+Math.round(speed )+'px )';
}
else if ( Keys.down ) {
if (speed>1)
{ speed = speed *0.9;
document.querySelector( 'div' ).style.transform += 'translateY( -'+Math.round(speed )+'px )';
}
else {
speed = 1;
document.querySelector( 'div' ).style.transform += 'translateY( 1px )';
}
}
if ( Keys.left ) {
document.querySelector( 'div' ).style.transform += 'rotate( -1deg )';
}
else if ( Keys.right ) {
document.querySelector( 'div' ).style.transform += 'rotate( 1deg )';
}
requestAnimationFrame( update );
}
requestAnimationFrame( update );
#import url( "https://fonts.googleapis.com/css?family=Nunito" );
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: "Nunito", sans-serif;
font-size: 2rem;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
b {
display: block;
transform: rotate( 180deg );
}
<div>
<b>v</b>
</div>
<script>
var Keys = {
up: false,
down: false,
left: false,
right: false
}
window.onkeydown = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left = true;
else if ( kc === 38 ) Keys.up = true;
else if ( kc === 39 ) Keys.right = true;
else if ( kc === 40 ) Keys.down = true;
};
</script>
Related
I'm working on a project where I simulate physics with balls.
Here is the link to the p5 editor of the project.
My problem is the following, when I add a lot of ball (like 200), balls are stacking but some of them will eventually collapse and I don't know why.
Can somebody explain why it does this and how to solve the problem ?
Thanks.
Here is the code of the sketch.
document.oncontextmenu = function () {
return false;
}
let isFlushing = false;
let isBallDiameterRandom = false;
let displayInfos = true;
let displayWeight = false;
let clickOnce = false;
let FRAME_RATE = 60;
let SPEED_FLUSH = 3;
let Y_GROUND;
let lastFR;
let balls = [];
function setup() {
frameRate(FRAME_RATE);
createCanvas(window.innerWidth, window.innerHeight);
Y_GROUND = height / 20 * 19;
lastFR = FRAME_RATE;
}
function draw() {
background(255);
if (isFlushing) {
for (let i = 0; i < SPEED_FLUSH; i++) {
balls.pop();
}
if (balls.length === 0) {
isFlushing = false;
}
}
balls.forEach(ball => {
ball.collide();
ball.move();
ball.display(displayWeight);
ball.checkCollisions();
});
if (mouseIsPressed) {
let ballDiameter;
if (isBallDiameterRandom) {
ballDiameter = random(15, 101);
} else {
ballDiameter = 25;
}
if (canAddBall(mouseX, mouseY, ballDiameter)) {
isFlushing = false;
let newBall = new Ball(mouseX, mouseY, ballDiameter, balls);
if (mouseButton === LEFT && !clickOnce) {
balls.push(newBall);
clickOnce = true;
}
if (mouseButton === RIGHT) {
balls.push(newBall);
}
}
}
drawGround();
if (displayInfos) {
displayShortcuts();
displayFrameRate();
displayBallCount();
}
}
function mouseReleased() {
if (mouseButton === LEFT) {
clickOnce = false;
}
}
function keyPressed() {
if (keyCode === 32) {//SPACE
displayInfos = !displayInfos;
}
if (keyCode === 70) {//F
isFlushing = true;
}
if (keyCode === 71) {//G
isBallDiameterRandom = !isBallDiameterRandom;
}
if (keyCode === 72) {//H
displayWeight = !displayWeight;
}
}
function canAddBall(x, y, d) {
let isInScreen =
y + d / 2 < Y_GROUND &&
y - d / 2 > 0 &&
x + d / 2 < width &&
x - d / 2 > 0;
let isInAnotherBall = false;
for (let i = 0; i < balls.length; i++) {
let d = dist(x, y, balls[i].position.x, balls[i].position.y);
if (d < balls[i].w) {
isInAnotherBall = true;
break;
}
}
return isInScreen && !isInAnotherBall;
}
function drawGround() {
strokeWeight(0);
fill('rgba(200,200,200, 0.25)');
rect(0, height / 10 * 9, width, height / 10);
}
function displayFrameRate() {
if (frameCount % 30 === 0) {
lastFR = round(frameRate());
}
textSize(50);
fill(255, 0, 0);
let lastFRWidth = textWidth(lastFR);
text(lastFR, width - lastFRWidth - 25, 50);
textSize(10);
text('fps', width - 20, 50);
}
function displayBallCount() {
textSize(50);
fill(255, 0, 0);
text(balls.length, 10, 50);
let twBalls = textWidth(balls.length);
textSize(10);
text('balls', 15 + twBalls, 50);
}
function displayShortcuts() {
let hStart = 30;
let steps = 15;
let maxTW = 0;
let controlTexts = [
'LEFT CLICK : add 1 ball',
'RIGHT CLICK : add 1 ball continuously',
'SPACE : display infos',
'F : flush balls',
'G : set random ball diameter (' + isBallDiameterRandom + ')',
'H : display weight of balls (' + displayWeight + ')'
];
textSize(11);
fill(0);
for (let i = 0; i < controlTexts.length; i++) {
let currentTW = textWidth(controlTexts[i]);
if (currentTW > maxTW) {
maxTW = currentTW;
}
}
for (let i = 0; i < controlTexts.length; i++) {
text(controlTexts[i], width / 2 - maxTW / 2 + 5, hStart);
hStart += steps;
}
fill(200, 200, 200, 100);
rect(width / 2 - maxTW / 2,
hStart - (controlTexts.length + 1) * steps,
maxTW + steps,
(controlTexts.length + 1) * steps - steps / 2
);
}
Here is the code of the Ball class.
class Ball {
constructor(x, y, w, e) {
this.id = e.length;
this.w = w;
this.e = e;
this.progressiveWidth = 0;
this.rgb = [
floor(random(0, 256)),
floor(random(0, 256)),
floor(random(0, 256))
];
this.mass = w;
this.position = createVector(x + random(-1, 1), y);
this.velocity = createVector(0, 0);
this.acceleration = createVector(0, 0);
this.gravity = 0.2;
this.friction = 0.5;
}
collide() {
for (let i = this.id + 1; i < this.e.length; i++) {
let dx = this.e[i].position.x - this.position.x;
let dy = this.e[i].position.y - this.position.y;
let distance = sqrt(dx * dx + dy * dy);
let minDist = this.e[i].w / 2 + this.w / 2;
if (distance < minDist) {
let angle = atan2(dy, dx);
let targetX = this.position.x + cos(angle) * minDist;
let targetY = this.position.y + sin(angle) * minDist;
this.acceleration.set(
targetX - this.e[i].position.x,
targetY - this.e[i].position.y
);
this.velocity.sub(this.acceleration);
this.e[i].velocity.add(this.acceleration);
//TODO : Effets bizarre quand on empile les boules (chevauchement)
this.velocity.mult(this.friction);
}
}
}
move() {
this.velocity.add(createVector(0, this.gravity));
this.position.add(this.velocity);
}
display(displayMass) {
if (this.progressiveWidth < this.w) {
this.progressiveWidth += this.w / 10;
}
stroke(0);
strokeWeight(2);
fill(this.rgb[0], this.rgb[1], this.rgb[2], 100);
ellipse(this.position.x, this.position.y, this.progressiveWidth);
if (displayMass) {
strokeWeight(1);
textSize(10);
let tempTW = textWidth(int(this.w));
text(int(this.w), this.position.x - tempTW / 2, this.position.y + 4);
}
}
checkCollisions() {
if (this.position.x > width - this.w / 2) {
this.velocity.x *= -this.friction;
this.position.x = width - this.w / 2;
} else if (this.position.x < this.w / 2) {
this.velocity.x *= -this.friction;
this.position.x = this.w / 2;
}
if (this.position.y > Y_GROUND - this.w / 2) {
this.velocity.x -= this.velocity.x / 100;
this.velocity.y *= -this.friction;
this.position.y = Y_GROUND - this.w / 2;
} else if (this.position.y < this.w / 2) {
this.velocity.y *= -this.friction;
this.position.y = this.w / 2;
}
}
}
I see this overlapping happen when the sum of ball masses gets bigger than the elasticity of the balls. At least it seems so. I made a copy with a smaller pool so it doesn't take so much time to reproduce the problem.
In the following example, with 6 balls (a mass of 150 units) pressing on the base row, we see that the 13 balls in the base row overlap. The base row has a width of ca. 300 pixels, which is only enough space for 12 balls of diameter 25. I think this is showing the limitation of the model: the balls are displayed circular but indeed have an amount of elasticity that they should display deformed instead. It's hard to say how this can be fixed without implementing drawing complicated shapes. Maybe less friction?
BTW: great physics engine you built there :-)
Meanwhile I was able to make another screenshot with even fewer balls. The weight of three of them (eq. 75 units) is sufficient to create overlapping in the base row.
I doubled the size of the balls and changed the pool dimensions as to detedt that there is a more serious error in the engine. I see that the balls are pressed so heavily under pressure that they have not enough space for their "volume" (area). Either they have to implode or it's elastic counter force must have greater impact of the whole scene. If you pay close attention to the pendulum movements made by the balls at the bottom, which have the least space, you will see that they are very violent, but apparently have no chance of reaching the outside.
Could it be that your evaluation order
balls.forEach(ball => {
ball.collide();
ball.move();
ball.display(displayWeight);
ball.checkCollisions();
});
is not able to propagate the collisions in a realistic way?
I've been trying to recreate this Project in canvas and Javascript. I wasn't able to decipher the original code so I did it from scratch. The difference is that my projects starts to lag at about 2500 particles while the project above works with 30 000.
I'll paste my entire code below but these are the relevant parts:
var particleContainer = []
var distance = 10
for(let i = 0; i< square.height/distance; i++){
for(let j = 0; j< square.height/distance; j++){
particleContainer.push( new Particle(square.x + i*distance,square.y + j*distance) )
}
}
if( c < 90 ){
i.xVelocity = a/c * -20
i.yVelocity = b/c * -20
}else if(90 < c && c < 95){
i.xVelocity = a/c * -1
i.yVelocity = b/c * -1
}else if(c2 !== 0){
i.xVelocity =( a2/c2 )
i.yVelocity = (b2/c2 )
}
(c -> distance between mouse and particle)
I'm creating a new Particle every 'distance' pixels of my square and pushing all of them into an array. when My mouse is to close to one of them the particle will start moving away from the mouse until it is 90-95px away from the Mouse.
30 000 pixels seems to work in a similar fashion judging from this line
for ( i = 0; i < NUM_PARTICLES; i++ ) {
p = Object.create( particle );
p.x = p.ox = MARGIN + SPACING * ( i % COLS );
p.y = p.oy = MARGIN + SPACING * Math.floor( i / COLS );
list[i] = p;
}
but that project doesn't run into the same case of performance Issues as I.
my full code for reference, (html is just a canvas):
var canvas = document.querySelector("canvas")
var c = canvas.getContext('2d')
function getMousePos(canvas, evt) {
// var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX,
y: evt.clientY
};
}
document.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
mouse.x= mousePos.x;
mouse.y= mousePos.y;
}, false);
var mouse = {
x:0,
y:0
}
function Particle(x,y){
this.x = x;
this.y = y;
this.xFixed = x;
this.yFixed = y;
this.radius = 1
this.xVelocity = 0
this.yVelocity = 0
this.color = 'white'
}
Particle.prototype.draw = function(){
c.save()
c.beginPath()
c.arc(this.x, this.y, this.radius,0,Math.PI*2,false)
c.fillStyle = this.color
c.fill()
}
Particle.prototype.update = function(){
this.draw()
this.x += this.xVelocity
this.y += this.yVelocity
}
var square = {
x: 500,
y: 150,
height: 500,
width: 500,
color: 'white'
}
var particleContainer = []
var distance = 10
for(let i = 0; i< square.height/distance; i++){
for(let j = 0; j< square.height/distance; j++){
particleContainer.push( new Particle(square.x + i*distance,square.y + j*distance) )
}
}
function animate(){
requestAnimationFrame(animate);
c.clearRect(0,0,window.innerWidth,window.innerHeight)
canvas.width = window.innerWidth
canvas.height = window.innerHeight
for(i of particleContainer){
let a = mouse.x - i.x
let b = mouse.y - i.y
let c = Math.sqrt(Math.pow(b,2) + Math.pow(a,2))
let a2 = i.xFixed - i.x
let b2 = i.yFixed - i.y
let c2 = Math.sqrt(Math.pow(b2,2) + Math.pow(a2,2))
if( c < 90 ){
i.xVelocity = a/c * -20
i.yVelocity = b/c * -20
}else if(90 < c && c < 95){
i.xVelocity = a/c * -1
i.yVelocity = b/c * -1
}else if(c2 !== 0){
i.xVelocity =( a2/c2 )
i.yVelocity = (b2/c2 )
}
}
for(i of particleContainer){
i.update()
}
}
animate()
To get better rendering you need to add render objects to the same path. Once the path is created then you can draw them in one call to ctx.fill
Try to limit accessing innerWidth and innerHeight as they are very slow DOM objects that may cause reflows just by accessing them.
Further improvements can be made by using object pools and pre-allocation but that is beyond the scope of a single answer.
Make the following changes to your animate function.
var W = 1, H = 1;
function animate() {
requestAnimationFrame(animate);
c.clearRect(0 ,0, W, H)
if (H !== innerHeight || W !== innerWidth) {
W = canvas.width = innerWidth;
H = canvas.height = innerHeight;
}
c.beginPath(); // start a new path
c.fillStyle = "white";
for (i of particleContainer) { // update and draw all particles in one pass
const a = mouse.x - i.x, b = mouse.y - i.y
const dist = (b * b + a * a) ** 0.5;
const a2 = i.xFixed - i.x, b2 = i.yFixed - i.y
const dist2 = (b2 * b2 + a2 * a2) ** 0.5;
if (dist < 90 ){
i.x += a / dist * -20
i.y += b / dist * -20
} else if (90 < dist && dist < 95){
i.x += a / dist * -1
i.y += b / dist * -1
} else if (dist2 !== 0){
i.x += (a2 / dist2 )
i.y += (b2 / dist2 )
}
c.rect(i.x, i.y, 1, 1);
}
c.fill(); // path complete render it.
//for(i of particleContainer){ // no longer needed
// i.update()
//}
}
Learn to use the Performance tab in dev tools and you can see which functions are taking the most time. In this case I think you’ll see that it’s ctx.fill. The example you posted is writing pixels into an ImageData buffer which will be much faster than drawing and filling arcs. There are a lot of other small optimisations in the example but that’s going to be the most important one, drawing is usually much slower than updating.
hi i am trying to gat an arc to resize as it moves across the screen.
I cant seem to asign the value of increment to the radius of the arc for it to get bigger and then smaller.
please see below for the code block in question and then the entire code.
resize(){
this.up = true;
this.r = 0;
this.increment = 10;
this.ceiling = 100;
function PerformCalc() {
if (this.up == true && this.r <= this.ceiling) {
this.r += increment
if (this.r == ceiling) {
this.up = false;
}
} else {
this.up = false
this.r -= increment;
if (this.r == 0) {
this.up = true;
}
}
console.log(this.r);
}
setInterval(PerformCalc, 1000);
}
When i log out the radius to the console it gives nan for some reason.
Any help would be greatly appreciated.
<!DOCTYPE html>
<html>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<style type="text/css">
canvas {
border: 1px solid grey;
}
</style>
</head>
<body>
<canvas id="canvas-for-ball"></canvas>
<script type="text/javascript">
// Gets a handle to the element with id canvasOne.
var canvas = document.getElementById("canvas-for-ball");
// Get a 2D context for the canvas.
var ctx = canvas.getContext("2d");
function init(){
canvas.width = 500;
canvas.height = 500;
}
init();
//angle defining spin and sections of ball
var theta = 0;
//for the sections of the ball
var theta2 = 0;
//fort he amount of sections needed
var seventh = (Math.PI*2)/7
//to control the amount of spin the ball has
var thetaInc = 0.0029;
//ball object
class Ball {
constructor(x,y,r,xvel,yvel,mass){
this.x =x;
this.y = y;
this.r =r;
this.xvel = xvel;
this.yvel = yvel;
this.mass = mass;
}
draw(){
// Update the y location.
this.x = this.x + this.xvel;
this.y = this.y + this.yvel;
//draw circle
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
ctx.stroke();
//fill the circle
ctx.fillStyle = "orange";
ctx.fill();
//draw inner circle of ball
ctx.beginPath();
ctx.arc(this.x,this.y,this.r*.9,0,Math.PI*2,false);
ctx.stroke();
//spin control
theta += thetaInc;
//loop for adding sections to pie
for( var n = 0; n < 7; ++n) { // add loop to draw radii
theta2 = theta + n * seventh;
ctx.moveTo( this.x, this.y);
ctx.lineTo( this.x + this.r*Math.cos(theta2), this.y + this.r*Math.sin(theta2));
}
ctx.lineWidth = "2";
ctx.lineCap = "round";
ctx.strokeStyle = "black";
ctx.stroke();
}
move(){
//condition take into account the this.r of the ball so
//it bounces at the edge of the canvas instead
//of going off of the screen to its center point.
if(this.y > canvas.height - this.r || this.y - this.r <0){
this.yvel = -1*this.yvel;
//to reverse the direction of the ball when hitting walls
if((this.xvel<0 && this.yvel >0) && thetaInc <0){
thetaInc = -1*thetaInc;
}
else if((this.xvel <0 && this.yvel>0) && thetaInc >0){
thetaInc = -1*thetaInc
}
else if((this.xvel >0 && this.yvel >0) && thetaInc >0){
thetaInc = -1 * thetaInc;
}
else if((this.xvel > 0 && this.yvel < 0)&& thetaInc <0){
thetaInc = -1 * thetaInc;
}
}
if(this.x > canvas.width - this.r || this.x - this.r < 0){
this.xvel = -1*this.xvel;
}
}
resize(){
this.up = true;
this.r = 0;
this.increment = 10;
this.ceiling = 100;
function PerformCalc() {
if (this.up == true && this.r <= this.ceiling) {
this.r += increment
if (this.r == ceiling) {
this.up = false;
}
} else {
this.up = false
this.r -= increment;
if (this.r == 0) {
this.up = true;
}
}
console.log(this.r);
}
setInterval(PerformCalc, 1000);
}
colour(){
}
}
//Intersect function takes a ball as a perameter
//ball will be the the object used to test if the two are touching.
function intersect(ball,ball1) {
//the x and y cordinates of the first ball are subtracted from the test ball and stored
//in productX and productY
var productX = ball1.x - ball.x;
var productY = ball1.y - ball.y;
//pythagoras theorem is used to get the distance between both center points of each circle.
var distance = Math.sqrt(productX * productX + productY * productY);
//A condition is used to check if the distance between both bencer point of each circle
//is less than or equal to the sum of both radii the circles are touching.
//the result is p[rinted out to the console
if (distance <= (ball1.r + ball.r)) {
dx = ball.x-ball1.x;
dy = ball.y-ball1.y;
collision_angle = Math.atan2(dy,dx);
magnitude_1 = Math.sqrt(ball.xvel*ball.xvel+ball.yvel*ball.yvel);
magnitude_2 = Math.sqrt(ball1.xvel*ball1.xvel+ball1.yvel*ball1.yvel);
direction_1 = Math.atan2(ball.yvel, ball.xvel);
direction_2 = Math.atan2(ball1.yvel, ball1.xvel);
new_xvel_1 = magnitude_1 * Math.cos(direction_1-collision_angle);
new_yvel_1 = magnitude_1 * Math.sin(direction_1-collision_angle);
new_xvel_2 = magnitude_2 * Math.cos(direction_2-collision_angle);
new_yvel_2 = magnitude_1 * Math.sin(direction_2-collision_angle);
final_xvel_1 = ((ball.mass-ball1.mass)*new_xvel_1+(ball1.mass+ball1.mass)*new_xvel_2)/(ball.mass+ball1.mass);
final_xvel_2 = ((ball.mass+ball.mass)*new_xvel_1+(ball1.mass-ball.mass)*new_xvel_2)/(ball.mass+ball1.mass);
final_yvel_1 = new_yvel_1;
final_yvel_2 = new_yvel_2;
ball.xvel = Math.cos(collision_angle)*final_xvel_1+Math.cos(collision_angle+Math.PI/2)*final_yvel_1;
ball.yvel = Math.sin(collision_angle)*final_xvel_1+Math.sin(collision_angle+Math.PI/2)*final_yvel_1;
ball1.xvel = Math.cos(collision_angle)*final_xvel_2+Math.cos(collision_angle+Math.PI/2)*final_yvel_2;
ball1.yvel = Math.sin(collision_angle)*final_xvel_2+Math.sin(collision_angle+Math.PI/2)*final_yvel_2;
}
}
canvas.addEventListener("click", function(event) {
var clickX = event.clientX - canvas.offsetLeft;
var clickY = event.clientY- canvas.offsetTop;
b1.x = clickX;
b1.y = clickY;
});
// Add a Javascript event listener to the keypress event.
window.addEventListener("keypress", function(event) {
// Just log the event to the console.
console.log(event);
});
//keypresses with jQuery
$(document.body).on('keydown', function(e) {
console.log(e.which);
switch (e.which) {
// key code for left arrow
case 37:
console.log('left arrow key pressed!');
b1.xvel --;
break;
//keycode for up
case 38:
console.log('up key pressed');
b1.yvel++;
break;
//key code for right
case 39:
console.log('right arrow key pressed!');
b1.xvel++;
break;
//key code for down
case 40:
console.log('down arrow key pressed!');
b1.yvel--;
break;
//key code for + key to increase spin
case 107:
console.log('down arrow key pressed!');
thetaInc +=.001;
break;
//key code for - key to decrease spin
case 109:
console.log('down arrow key pressed!');
thetaInc -=.001;
break;
}
});
b1 = new Ball(200,200,40,1,1,50);
b2 = new Ball(100,100,40,2,2,5);
b1.resize();
// A function to repeat every time the animation loops.
function repeatme() {
//clear canvas for each frame of the animation.
ctx.clearRect(0,0,500,500);
// Draw the ball (stroked, not filled).
b1.draw();
b2.draw();
b1.move();
b2.move();
intersect(b1,b2);
//put repeatme function into the animation frame and store it in animate
animate = window.requestAnimationFrame(repeatme);
}
// Get the animation going.
repeatme();
</script>
</body>
</html>
There is a reference error for increment, e.g. you didn't use this.increment, same for ceiling, should be this.ceiling.
this is used by the setInterval so you save this as that so you can use them. this removes the NaN.
<!DOCTYPE html>
<html>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<style type="text/css">
canvas {
border: 1px solid grey;
}
</style>
</head>
<body>
<canvas id="canvas-for-ball"></canvas>
<script type="text/javascript">
// Gets a handle to the element with id canvasOne.
var canvas = document.getElementById("canvas-for-ball");
// Get a 2D context for the canvas.
var ctx = canvas.getContext("2d");
function init(){
canvas.width = 500;
canvas.height = 500;
}
init();
//angle defining spin and sections of ball
var theta = 0;
//for the sections of the ball
var theta2 = 0;
//fort he amount of sections needed
var seventh = (Math.PI*2)/7
//to control the amount of spin the ball has
var thetaInc = 0.0029;
//ball object
class Ball {
constructor(x,y,r,xvel,yvel,mass){
this.x =x;
this.y = y;
this.r =r;
this.xvel = xvel;
this.yvel = yvel;
this.mass = mass;
}
draw(){
// Update the y location.
this.x = this.x + this.xvel;
this.y = this.y + this.yvel;
//draw circle
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
ctx.stroke();
//fill the circle
ctx.fillStyle = "orange";
ctx.fill();
//draw inner circle of ball
ctx.beginPath();
ctx.arc(this.x,this.y,this.r*.9,0,Math.PI*2,false);
ctx.stroke();
//spin control
theta += thetaInc;
//loop for adding sections to pie
for( var n = 0; n < 7; ++n) { // add loop to draw radii
theta2 = theta + n * seventh;
ctx.moveTo( this.x, this.y);
ctx.lineTo( this.x + this.r*Math.cos(theta2), this.y + this.r*Math.sin(theta2));
}
ctx.lineWidth = "2";
ctx.lineCap = "round";
ctx.strokeStyle = "black";
ctx.stroke();
}
move(){
//condition take into account the this.r of the ball so
//it bounces at the edge of the canvas instead
//of going off of the screen to its center point.
if(this.y > canvas.height - this.r || this.y - this.r <0){
this.yvel = -1*this.yvel;
//to reverse the direction of the ball when hitting walls
if((this.xvel<0 && this.yvel >0) && thetaInc <0){
thetaInc = -1*thetaInc;
}
else if((this.xvel <0 && this.yvel>0) && thetaInc >0){
thetaInc = -1*thetaInc
}
else if((this.xvel >0 && this.yvel >0) && thetaInc >0){
thetaInc = -1 * thetaInc;
}
else if((this.xvel > 0 && this.yvel < 0)&& thetaInc <0){
thetaInc = -1 * thetaInc;
}
}
if(this.x > canvas.width - this.r || this.x - this.r < 0){
this.xvel = -1*this.xvel;
}
}
resize(){
var that = this;
that.up = true;
that.r = 0;
that.increment = 10;
that.ceiling = 100;
function PerformCalc() {
if (that.up == true && that.r <= that.ceiling) {
that.r += that.increment
if (that.r == that.ceiling) {
that.up = false;
}
} else {
that.up = false
that.r -= that.increment;
if (that.r == 0) {
that.up = true;
}
}
console.log(that.r);
}
setInterval(PerformCalc, 1000);
}
colour(){
}
}
//Intersect function takes a ball as a perameter
//ball will be the the object used to test if the two are touching.
function intersect(ball,ball1) {
//the x and y cordinates of the first ball are subtracted from the test ball and stored
//in productX and productY
var productX = ball1.x - ball.x;
var productY = ball1.y - ball.y;
//pythagoras theorem is used to get the distance between both center points of each circle.
var distance = Math.sqrt(productX * productX + productY * productY);
//A condition is used to check if the distance between both bencer point of each circle
//is less than or equal to the sum of both radii the circles are touching.
//the result is p[rinted out to the console
if (distance <= (ball1.r + ball.r)) {
dx = ball.x-ball1.x;
dy = ball.y-ball1.y;
collision_angle = Math.atan2(dy,dx);
magnitude_1 = Math.sqrt(ball.xvel*ball.xvel+ball.yvel*ball.yvel);
magnitude_2 = Math.sqrt(ball1.xvel*ball1.xvel+ball1.yvel*ball1.yvel);
direction_1 = Math.atan2(ball.yvel, ball.xvel);
direction_2 = Math.atan2(ball1.yvel, ball1.xvel);
new_xvel_1 = magnitude_1 * Math.cos(direction_1-collision_angle);
new_yvel_1 = magnitude_1 * Math.sin(direction_1-collision_angle);
new_xvel_2 = magnitude_2 * Math.cos(direction_2-collision_angle);
new_yvel_2 = magnitude_1 * Math.sin(direction_2-collision_angle);
final_xvel_1 = ((ball.mass-ball1.mass)*new_xvel_1+(ball1.mass+ball1.mass)*new_xvel_2)/(ball.mass+ball1.mass);
final_xvel_2 = ((ball.mass+ball.mass)*new_xvel_1+(ball1.mass-ball.mass)*new_xvel_2)/(ball.mass+ball1.mass);
final_yvel_1 = new_yvel_1;
final_yvel_2 = new_yvel_2;
ball.xvel = Math.cos(collision_angle)*final_xvel_1+Math.cos(collision_angle+Math.PI/2)*final_yvel_1;
ball.yvel = Math.sin(collision_angle)*final_xvel_1+Math.sin(collision_angle+Math.PI/2)*final_yvel_1;
ball1.xvel = Math.cos(collision_angle)*final_xvel_2+Math.cos(collision_angle+Math.PI/2)*final_yvel_2;
ball1.yvel = Math.sin(collision_angle)*final_xvel_2+Math.sin(collision_angle+Math.PI/2)*final_yvel_2;
}
}
canvas.addEventListener("click", function(event) {
var clickX = event.clientX - canvas.offsetLeft;
var clickY = event.clientY- canvas.offsetTop;
b1.x = clickX;
b1.y = clickY;
});
// Add a Javascript event listener to the keypress event.
window.addEventListener("keypress", function(event) {
// Just log the event to the console.
console.log(event);
});
//keypresses with jQuery
$(document.body).on('keydown', function(e) {
console.log(e.which);
switch (e.which) {
// key code for left arrow
case 37:
console.log('left arrow key pressed!');
b1.xvel --;
break;
//keycode for up
case 38:
console.log('up key pressed');
b1.yvel++;
break;
//key code for right
case 39:
console.log('right arrow key pressed!');
b1.xvel++;
break;
//key code for down
case 40:
console.log('down arrow key pressed!');
b1.yvel--;
break;
//key code for + key to increase spin
case 107:
console.log('down arrow key pressed!');
thetaInc +=.001;
break;
//key code for - key to decrease spin
case 109:
console.log('down arrow key pressed!');
thetaInc -=.001;
break;
}
});
b1 = new Ball(200,200,40,1,1,50);
b2 = new Ball(100,100,40,2,2,5);
b1.resize();
// A function to repeat every time the animation loops.
function repeatme() {
//clear canvas for each frame of the animation.
ctx.clearRect(0,0,500,500);
// Draw the ball (stroked, not filled).
b1.draw();
b2.draw();
b1.move();
b2.move();
intersect(b1,b2);
//put repeatme function into the animation frame and store it in animate
animate = window.requestAnimationFrame(repeatme);
}
// Get the animation going.
repeatme();
</script>
</body>
</html>
I would like to accelerate the ball speed in my game.
Here is the code of my pong:
Ball.prototype = {
Draw : function () {
this.context.fillStyle = this.color;
this.context.fillRect( this.posX, this.posY, this.diameter, this.diameter );
},
GetBallDirection : function () {
if ( this.pitchX > 0 ) {
return "right";
} else if ( this.pitchX < 0 ) {
return "left";
}
return "none";
},
Update : function () {
this.posX += this.pitchX;
if ( this.posX > this.courtWidth )
return 1;
if ( this.posX + this.diameter <= 0 )
return 2
this.posY += this.pitchY;
if ( this.posY > this.courtHeight || this.posY <= 0 ) {
this.pitchY = - this.pitchY;
}
return 0;
},
Center : function () {
this.posX = this.courtWidth / 2 - this.diameter / 2;
this.posY = this.courtHeight / 2 - this.diameter / 2;
}
}
At the moment, you update your ball position with this code:
Update : function () {
this.posX += this.pitchX;
//(...)
this.posY += this.pitchY;
//(...)
},
Literally: "move the ball on the x-axis with this.pitchX, and on the y-axis with this.pitchY"
To change the speed of the ball, the best thing to do is create a "speed"-property, and use it. Like this:
this.speed = 1; // Speed = normal (100%)
Now we can adapt our Update-function:
Update : function () {
this.posX += (this.pitchX * this.speed);
//(...)
this.posY += (this.pitchY * this.speed);
//(...)
},
Now, if you want to speed up, or slow down the ball, you just change this.speed to something else.
this.speed = 0.5; //Ball will go half as fast
this.speed = 2; //Ball will go twice as fast.
this.speed += 0.01; //Ball will speed up with 1% each update.
To accelerate the speed of the ball you can change this in your update function :
this.posY += (this.pitchY*2);
this.posX += (this.pitchX*2);
So the ball will be twice as fast.
I am having an issue trying to make the main character of our class' platformer (for now it is only an arrow) jump in an arch. I've tried to make an if function inside the switch so that if both the right arrow key and if the jump button are pressed, the player will jump to the right in an arch. However, it only jumps up in a straight line. Any help? Here is my code:
var left = 37;
var up = 38;
var right = 39;
var down = 40;
var rightpress = false;
var jumppress = false;
var leftpress = false;
var CanHeight = 400;
var CanWidth = 800;
var BackgroundX = 00;
var BackgroundY = 00;
var ArrowMove = 0;
PreGame();
function preLoadImage( url )
{
image = new Image();
image.src = url;
image.onload = function( )
{
return; // return image - image was empty made global
};
}
function PreGame( )
{
preLoadImage ( "pics/arrowright.png" );
ArrowRightImg = image;
preLoadImage ( "pics/Background1.png" );
FirstBackgroundImg = image;
}
function InitGame( )
{
SetCanvas( 'classified' );
DrawScene();
//canvas.addEventListener("mousedown", doMouseDown, false);
//window.addEventListener("rightarrow", onrightarrow, false);
// Use the code below for perhaps a custom mouse cursor
//canvas.addEventListener("mouseover", doMouseOver, false);
Loop();
}
function SetCanvas( id )
{
canvas = document.createElement( 'canvas' );
var div = document.getElementById( id );
canvas.width = CanWidth;
canvas.height = CanHeight;
canvas.style.position = "absolute";
canvas.style.border = "#222222 5px solid";
div.appendChild(canvas);
Context = canvas.getContext("2d");
}
function DrawScene()
{
Context.fillStyle = 'green';
if ( ArrowMove == 0 )
{
Context.drawImage( FirstBackgroundImg, BackgroundX, BackgroundY, 1600, 800 );
}
Context.drawImage( ArrowRightImg, 400, 325 );
}
/*function doMouseDown(event)
{
canvas_x = event.pageX;
canvas_y = event.pageY;
}*/
var PlayerJump = 0;
var counter = 0;
var PJ = false;
function jump()
{
--counter;
console.log("You Jump: " + PlayerJump);
if ( PJ == true )
{
++PlayerJump;
if( PlayerJump <= 12 )
{
OldBackgroundY = BackgroundY;
BackgroundY = BackgroundY + 5;
Context.drawImage( FirstBackgroundImg, BackgroundX, BackgroundY, 1600, 800 );
}
if ( PlayerJump >= 13 && PlayerJump <= 24 )
{
BackgroundY = BackgroundY - 5;
Context.drawImage( FirstBackgroundImg, BackgroundX, BackgroundY, 1600, 800 );
}
Context.drawImage( ArrowRightImg, 400, 325 );// left
if ( PlayerJump >= 25 )
{
PlayerJump = 0;
PJ = false;
}
DrawScene();
}
}
document.onkeydown = function(e)
{
e = e || window.event;
switch(e.which || e.keyCode)
{
case 37:
Context.fillStyle = 'green';
console.log("You move left");
OldBackgroundX = BackgroundX;
BackgroundX = BackgroundX + 20;
Context.drawImage( FirstBackgroundImg, BackgroundX, BackgroundY, 1600, 800 );
Context.drawImage( ArrowRightImg, 400, 325 );// left
Context.fillText( "You move left.", 200, 100 );
break;
document.onkeypress = function(event)
{
event = event || window.event;
switch(event.which || event.keyCode)
{
case 38:
jumppress = true;
if ( jumppress == true )
{
PJ = true;
}
if ( PlayerJump >= 1 && PlayerJump < 24 )
{
rightpress = false;
}
// up/jump
break;
case 39:
rightpress = true;
if ( rightpress == true )
{
console.log("You move right");
Context.fillStyle = 'green';
OldBackgroundX = BackgroundX;
BackgroundX = BackgroundX - 20;
Context.drawImage( FirstBackgroundImg, BackgroundX, BackgroundY, 1600, 800 );
Context.drawImage( ArrowRightImg, 400, 325 );// right
Context.fillText( "You move right.", 200, 100 );
rightpress = false;
}
break;
}
if ( rightpress == true && jumppress == true )
{
//case 39:
console.log("You jump right");
Context.fillStyle = 'green';
OldBackgroundX = BackgroundX;
BackgroundX = BackgroundX - 20;
PJ = true;
Context.drawImage( FirstBackgroundImg, BackgroundX, BackgroundY, 1600, 800 );
Context.drawImage( ArrowRightImg, 400, 200 );// right
//Context.fillText( "You move right.", 200, 100 );
//break;
//case 38:
if ( PlayerJump <= 24 )
{
PJ = false;
jumppress = false;
rightpress = false;
}
}
}
function UpDate()
{
if ( PJ == true )
{
jump();
}
//--counter;
//console.log("Updated");
//DrawScene();*/
}
var lastTime = 0;
var ticks = 0;
function Loop()
{
var now = Date.now();
dt = ( now - lastTime ) / 1000.0;
//console.log("fired rocket");
UpDate(); // UPDATE ALL THE GAME MOVES
//EnemyUpDate();
lastTime = now;
requestAnimFrame( Loop ); // LOOP CALLBACK
};
var requestAnimFrame =
(
function( )
{
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback )
{
window.setTimeout( callback, 1000 / 60 );
};
}
)();
Just use a simple pseudo gravity factor together with a delta value to get the classic jump behavior.
Live demo
For example, define a few variables for the movement (only for y axis, values for x included in demo):
var floor = h - img.height + 16, // fixed floor position (here based on image)
y = floor, // set initial y position = floor
dy = 0, // current delta (=0 no movement on y axis)
jump = -5, // jump strength
g = 0.5; // "gravity" strength
When a jump is initiated we set delta y (dy) equal to the jump strength. You can variate strength as you need. This will accumulate on the y value, but since the gravity (g) is polling on the delta value it will eventually reverse the value and result will be a jump.
We check that we are on the floor and when we returned to ground so-to-speak just set y to floor and clear the delta value:
// we got a jump or are in a jump
if (dy !== 0 || y !== floor) {
y += dy; // add delta
dy += g; // affect delta with gravity
if (y > floor) { // on ground?
y = floor; // reset y position
dy = 0; // and clear delta
}
}
This is a very efficient way of emulating a jump with non-expensive math operations, which can be a critical factor in a game.
You can also click (or hit a key) several times while in a jump to extend it as was very typical in the old games. You can of course prevent this simply by checking the delta value when clicking and only reset it if dy === 0
Adjust jump strength with a more negative value for jump and if you want the character faster down simply increase the gravity value g.