I have a problem with using the interp to improve my drawing function. For some reason the realX returns undefined every 3 frames. I'm using this guide to implement this functionality. The end goal is to make the collision system work so it would reflect bullets on an incomming shield (basicly how the bullets currently interact with the canvas borders. This is part of the code:
//INT
var canvas = document.getElementById('ctx'),
cw = canvas.width,
ch = canvas.height,
cx = null,
bX = canvas.getBoundingClientRect().left,
bY = canvas.getBoundingClientRect().top,
mX,
mY,
lastFrameTimeMs = 0,
maxFPS = 60,
fps = 60,
timestep = 1000/fps,
lastTime = (new Date()).getTime(),
currentTime = 0,
delta = 0,
framesThisSecond = 0,
lastFpsUpdate = 0,
running = false,
started = false,
frameID = 0;
var gametimeStart = Date.now(),
frameCount = 0,
score = 0,
player,
enemyList = {},
boostList = {},
bulletList = {},
pulseList = {},
shieldList = {};
//CREATE , create object
var Unit = function(){
this.x;
this.y;
this.realX;
this.realY;
this.type = "unit";
this.hp = 0;
this.color;
this.collision = function(arc){
return this.x + this.r + arc.r > arc.x
&& this.x < arc.x + this.r + arc.r
&& this.y + this.r + arc.r > arc.y
&& this.y < arc.y + this.r + arc.r
};
this.position = function(delta){
boundX = document.getElementById('ctx').getBoundingClientRect().left;
boundY = document.getElementById('ctx').getBoundingClientRect().top;
this.realX = this.x;
this.realX = this.y;
this.realR = this.r;
this.x += this.spdX * delta / 1000;
this.y += this.spdY * delta / 1000;
if (this.x < this.r || this.x + this.r > cw){
this.spdX = -this.spdX;
if (Math.random() < 0.5) {
this.spdY += 100;
} else {
this.spdY -= 100;
}
}
if (this.y < this.r || this.y + this.r > ch){
this.spdY = -this.spdY;
if (Math.random() < 0.5) {
this.spdX += 100;
} else {
this.spdX -= 100;
}
}
};
this.draw = function(interp){
ctx.save();
ctx.fillStyle = this.color;
ctx.beginPath();
//THIS DOESN'T WORK, the code is working perfectly without it
this.x = (this.realX + (this.x - this.realX) * interp);
this.y = (this.realY + (this.y - this.realY) * interp);
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ctx.arc(this.x,this.y,this.r,0,2*Math.PI,false);
ctx.fill();
ctx.restore();
};
}
//Player
var Player = function(){
//Coördinates
this.x = 50;
this.y = 50;
this.r = 10;
//Stats
this.spdX = 30;
this.spdY = 5;
this.atkSpd = 1;
this.hp = 100;
//Other
this.name;
this.color = "green";
this.type = "player";
this.angle = 1;
this.attack = function attack(enemy){
enemy.hp -= 10;
};
};
Player.prototype = new Unit();
//Boost
var Boost = function(){
//Coördinates
this.x = 300;
this.y = 300;
this.r = 5;
};
Boost.prototype = new Unit();
//Bullet
var Bullet = function(){
//Coördinates
this.x = player.x;
this.y = player.y;
this.r = 3;
//Stats
var angle = player.angle*90;
this.spdX = Math.cos(angle/180*Math.PI)*200;
this.spdY = Math.sin(angle/180*Math.PI)*200;
//Other
if (player.angle === 4)
player.angle = 0;
player.angle++;
};
Bullet.prototype = new Unit();
var player = new Player();
var boost = new Boost();
var bullet = new Bullet();
//UPDATE
update = function(delta){
bullet.position(delta);
if(player.collision(boost)){
alert("collide");
}
};
//DRAW
draw = function(interp){
ctx.clearRect(0,0,cw,ch);
fpsDisplay.textContent = Math.round(fps) + ' FPS';
boost.draw(interp);
bullet.draw(interp);
player.draw(interp);
};
//LOAD
if (!document.hidden) { console.log("viewed"); }
function panic() { delta = 0; }
function begin() {}
function end(fps) {
if (fps < 25) {
fpsDisplay.style.color = "red";
}
else if (fps > 30) {
fpsDisplay.style.color = "black";
}
}
function stop() {
running = false;
started = false;
cancelAnimationFrame(frameID);
}
function start() {
if (!started) {
started = true;
frameID = requestAnimationFrame(function(timestamp) {
draw(1);
running = true;
lastFrameTimeMs = timestamp;
lastFpsUpdate = timestamp;
framesThisSecond = 0;
frameID = requestAnimationFrame(mainLoop);
});
}
}
function mainLoop(timestamp) {
if (timestamp < lastFrameTimeMs + (1000 / maxFPS)) {
frameID = requestAnimationFrame(mainLoop);
return;
}
delta += timestamp - lastFrameTimeMs;
lastFrameTimeMs = timestamp;
begin(timestamp, delta);
if (timestamp > lastFpsUpdate + 1000) {
fps = 0.25 * framesThisSecond + 0.75 * fps;
lastFpsUpdate = timestamp;
framesThisSecond = 0;
}
framesThisSecond++;
var numUpdateSteps = 0;
while (delta >= timestep) {
update(timestep);
delta -= timestep;
if (++numUpdateSteps >= 240) {
panic();
break;
}
}
draw(delta / timestep);
end(fps);
frameID = requestAnimationFrame(mainLoop);
}
start();
if (typeof (canvas.getContext) !== undefined) {
ctx = canvas.getContext('2d');
ctx.font = '30px Arial';
}
//CONTROLLES
//Mouse Movement
document.onmousemove = function(mouse){
var mouseX = mouse.clientX - bX;
var mouseY = mouse.clientY - bY;
if(mouseX < player.width/2)
mouseX = player.width/2;
if(mouseX > cw-player.width/2)
mouseX = cw-player.width/2;
if(mouseY < player.height/2)
mouseY = player.height/2;
if(mouseY > ch-player.height/2)
mouseY = ch-player.height/2;
player.x = mX = mouseX;
player.y = mY = mouseY;
}
//Pauze Game
var pauze = function(){
if(window.event.target.id === "pauze"){
stop();
}
if(window.event.target.id === "ctx"){
start();
}
};
document.addEventListener("click", pauze);
html {
height: 100%;
box-sizing: border-box;
}
*,*:before,*:after {
box-sizing: inherit;
}
body {
position: relative;
margin: 0;
padding-bottom: 6rem;
min-height: 100%;
font-family: "Helvetica Neue", Arial, sans-serif;
}
main {
margin: 0 auto;
padding-top: 64px;
max-width: 640px;
width: 94%;
}
main h1 {
margin-top: 0;
}
canvas {
border: 1px solid #000;
}
<canvas id="ctx" width="500" height="500"></canvas>
<div id="fpsDisplay" style="font-size:50px; position:relative; margin: 0 auto;"></div>
<p id="status"></p>
<button id="pauze">Pauze</button>
Declare, define, use.
The problem is you are using a variables that are undefined.
You have 3 objects, Player, Boost, and Bullet each of which you assign the object Unit to their prototypes.
You declare and define unit
function Unit(){
this.x;
this.y;
this.realX;
this.realY;
// blah blah
this.draw = function(){
// blah blah
this.draw = function (interp) {
ctx.save();
ctx.fillStyle = this.color;
ctx.beginPath();
// this.realX and this.realY are undefined
this.x = (this.realX + (this.x - this.realX) * interp);
this.y = (this.realY + (this.y - this.realY) * interp);
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
ctx.fill();
ctx.restore();
};
}
Then Player
var Player = function () {
//Coördinates
this.x = 50;
this.y = 50;
this.r = 10;
//Stats
// etc....
};
Then add a Unit to the prototype.
Player.prototype = new Unit();
This gives Player the unit properties x, y, realX, realY But realX and realY are undefined. Same with the two other objects Boost, Bullet
Any existing properties with the same name stay as is and keep their values. Any properties not in Player will get them from Unit, but in unit you have only declared the properties realX, realY you have not defined them.
You need to define the properties by giving them a value before you use them. You can do this in Unit
function Unit(){
this.x = 0; // default value I am guessing
this.y = 0;
this.realX = 10;
this.realY = 10;
Then in Player
function Player(){
this.x = 50; // these will keep the value they have
this.y = 50;
// realX and realY will have the defaults when you add to the prototype
// Or you can define them here
this.realX = 20;
this.realY = 20;
Before you can use a variable you must define what it is or check if it is defined and take the appropriate action.
// in draw function.
if(this.realX === undefined){
this.realX = 100; // set the default if not defined;
}
BTW you should get rid of the alerts they are very annoying when you have them appear one after the other without a chance to navigate away from the page.
Related
I have made a post on this before however, it was not in a canvas, but when you try to apply this to a canvas It gives an error along the lines of Cant change the color of undefined so I thought it was an error of the way I defined it so I would add it to a class then run it as changing the color of all elements in the class, but when I try it the canvas just disappears.. but getting to the point is there an easy way to change the color of a HTML element in a canvas or am I just crazy
Code
var myGamePiece = el.classList.add("class1");
var myObstacles = [];
var myScore;
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGamePiece.gravity = 0.05;
myScore = new component("30px", "Consolas", "black", 280, 40, "text");
myGameArea.start();
}
var myGameArea = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.score = 0;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.gravity = 0;
this.gravitySpeed = 0;
this.update = function() {
ctx = myGameArea.context;
if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, this.x, this.y);
} else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.newPos = function() {
this.gravitySpeed += this.gravity;
this.x += this.speedX;
this.y += this.speedY + this.gravitySpeed;
this.hitBottom();
}
this.hitBottom = function() {
var rockbottom = myGameArea.canvas.height - this.height;
if (this.y > rockbottom) {
this.y = rockbottom;
this.gravitySpeed = 0;
}
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function updateGameArea() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
for (i = 0; i < myObstacles.length; i += 1) {
if (myGamePiece.crashWith(myObstacles[i])) {
return;
}
}
myGameArea.clear();
myGameArea.frameNo += 1;
if (myGameArea.frameNo == 1 || everyinterval(150)) {
x = myGameArea.canvas.width;
minHeight = 20;
maxHeight = 200;
height = Math.floor(Math.random() * (maxHeight - minHeight + 1) + minHeight);
minGap = 50;
maxGap = 200;
gap = Math.floor(Math.random() * (maxGap - minGap + 1) + minGap);
myObstacles.push(new component(10, height, "green", x, 0));
myObstacles.push(new component(10, x - height - gap, "green", x, height + gap));
}
for (i = 0; i < myObstacles.length; i += 1) {
myObstacles[i].x += -1;
myObstacles[i].update();
}
myScore.text = "SCORE: " + myGameArea.frameNo;
myScore.update();
myGamePiece.newPos();
myGamePiece.update();
}
function everyinterval(n) {
if ((myGameArea.frameNo / n) % 1 == 0) {
return true;
}
return false;
}
function accelerate(n) {
myGamePiece.gravity = n;
}
function Test() {
document.getElementsByClassName("class1").style.background = "yellow";
}
canvas {
border: 1px solid #d3d3d3;
background-color: #f1f1f1;
}
.class1 {}
<body onload="startGame()">
<button onmousedown="accelerate(-0.2)" onmouseup="accelerate(0.05)">ACCELERATE</button>
<p> Credit To W3Schools.com </p>
<button onclick="test">Click To Change Player's Color</button>
</body>
The code starts with: var myGamePiece = el.classList.add("class1");el is not defined but anyway myGamePiece needs to be a component as created in startGame, it is not an ordinary HTML element.
It is the creation of that component that the color is set. Currently the color is hardcoded as 'red'. To change the color you need to set a variable, let's call it playerColor, and use that in the component creation call rather than 'red'.
You are using function test to set up a color so let's change the test function to do that, but you need to get that color set up before startGame otherwise the canvas will have the incorrect color for the piece (or no set color).
var playersColor = "red"; //the default value if the player doesn't change it
var myGamePiece;
var myObstacles = [];
var myScore;
function startGame() {
myGamePiece = new component(30, 30, playersColor, 10, 120);//use playersColor
myGamePiece.gravity = 0.05;
myScore = new component("30px", "Consolas", "black", 280, 40, "text");
myGameArea.start();
}
var myGameArea = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.score = 0;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.gravity = 0;
this.gravitySpeed = 0;
this.update = function() {
ctx = myGameArea.context;
if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, this.x, this.y);
} else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.newPos = function() {
this.gravitySpeed += this.gravity;
this.x += this.speedX;
this.y += this.speedY + this.gravitySpeed;
this.hitBottom();
}
this.hitBottom = function() {
var rockbottom = myGameArea.canvas.height - this.height;
if (this.y > rockbottom) {
this.y = rockbottom;
this.gravitySpeed = 0;
}
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function updateGameArea() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
for (i = 0; i < myObstacles.length; i += 1) {
if (myGamePiece.crashWith(myObstacles[i])) {
return;
}
}
myGameArea.clear();
myGameArea.frameNo += 1;
if (myGameArea.frameNo == 1 || everyinterval(150)) {
x = myGameArea.canvas.width;
minHeight = 20;
maxHeight = 200;
height = Math.floor(Math.random() * (maxHeight - minHeight + 1) + minHeight);
minGap = 50;
maxGap = 200;
gap = Math.floor(Math.random() * (maxGap - minGap + 1) + minGap);
myObstacles.push(new component(10, height, "green", x, 0));
myObstacles.push(new component(10, x - height - gap, "green", x, height + gap));
}
for (i = 0; i < myObstacles.length; i += 1) {
myObstacles[i].x += -1;
myObstacles[i].update();
}
myScore.text = "SCORE: " + myGameArea.frameNo;
myScore.update();
myGamePiece.newPos();
myGamePiece.update();
}
function everyinterval(n) {
if ((myGameArea.frameNo / n) % 1 == 0) {
return true;
}
return false;
}
function accelerate(n) {
myGamePiece.gravity = n;
}
function test() {
playersColor = "yellow";
}
<body>
<button onmousedown="accelerate(-0.2)" onmouseup="accelerate(0.05)">ACCELERATE</button>
<p> Credit To W3Schools.com </p>
<button onclick="test()">Click To Change Player's Color</button>
<button onclick="startGame();">Click to start the game</button>
</body>
...............................................................................
Although the following errors are not fundamentally what is causing non-changing of the player's color, they contain some coding errors which it might be helpful to point out for the future.
<button onclick="test">Click To Change Player's Color</button>
<script>
function Test() {
document.getElementsByClassName("class1").style.background = "yellow";
</script>
The onclick="test" does nothing - you may be confusing the structure element.onclick = test - which sets which function is run on the element being clicked - with the syntax for the onclick="test();" needed in the button element attributes.
A further error is that the function is called Test, not test. Javascript is case sensitive. Also you need to select the one element with class1, not retrieve a collection of such elements, so you can set its style (if that is useful, is it?).
N-Body gravity simulation seems to be working fine at first glance, and the same is true for body collisions, but once gravitationally attracted objects start to collide, they start to spiral around each other frantically and the collection of them as a whole have very erratic motion... The code (html-javascript) will be included below, and to reproduce what I'm talking about, you can create a new body by clicking in a random location on the screen.
The math for gravitational attraction is done in the Body.prototype.gravityCalc() method of the Body object type (line 261). The math for the collision resolution is found in the dynamic collision section of the bodyHandle() function (line 337).
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// event handling
document.addEventListener('keydown', keyDown);
document.addEventListener('mousedown', mouseDown)
document.addEventListener('mouseup', mouseUp)
document.addEventListener('mousemove', mouseMove);
document.addEventListener('touchstart', touchStart);
document.addEventListener('touchmove', touchMove);
document.addEventListener('touchend', touchEnd);
window.addEventListener('resize', resize);
window.onload = function() {reset()}
mouseDown = false;
nothingGrabbed = true;
mouseX = 0;
mouseY = 0;
function keyDown(data) {
if(data.key == "r") {
clearInterval(loop);
reset();
}
else if(data.key == 'g') {
gravityOn = !gravityOn;
}
else if(data.key == 'Delete') {
for(i = 0; i < bodies.length ; i++) {
if(((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies.splice(i, 1);
}
}
}
else if(data.key == 'c') {
gravity_c *= -1;
}
else if(data.key == 'f') {
falling = !falling;
}
else if(data.key == 'a') {
acceleration *= -1;
}
}
function mouseDown(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function mouseUp(data) {
mouseDown = false;
nothingGrabbed = true;
for(i = 0; i < bodies.length; i++) {
bodies[i].grabbed = false
}
}
function mouseMove(data) {
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function touchStart(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchMove(data) {
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchEnd(data) {
mouseDown = false;
nothingGrabbed = true;
for(i=0;i<bodies.length;i++) {
bodies[i].grabbed = false;
}
}
function resize(data) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize Variables
function reset() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.color = 'rgb(70, 70, 70)';
scale = Math.min(canvas.width, canvas.height);
fps = 120;
running = true;
loop = setInterval(main, 1000/fps);
gravityOn = true // if true, objects are gravitationally attracted to each other
gravity_c = 334000 // universe's gravitational constant
boundaryCollision = true // if true, objects collide with edges of canvas
wallDampen = 0.7 // number to multiply by when an objects hit a wall
bodyCollision = true // if true, bodies will collide with each other
bodyDampen = 0.4 // number to multiply when two objects collide
falling = false // if true, objects will fall to the bottom of the screen
acceleration = 400
bodies = [] // a list of each Body object
collidingPairs = [] // a list of pairs of colliding bodies
/*
var bounds = 200;
for(i = 0; i<70; i++) { // randomly place bodies
Body.create({
x: Math.floor(Math.random()*canvas.width),
y: Math.floor(Math.random()*canvas.height),
a: Math.random()*Math.PI*2,
xV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
yV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
mass: Math.ceil(Math.random()*23)
})
} */
/*
Body.create({
x: canvas.width/2 - 50,
xV: 10,
yV: 0,
aV: 3,
y: canvas.height/2 + 0,
mass: 10
});
Body.create({
x: canvas.width/2 + 50,
xV: 0,
aV: 0,
y: canvas.height/2,
mass: 10
});
*/
Body.create({
x: canvas.width/2,
y: canvas.height/2,
mass: 24,
xV: -10.83
});
Body.create({
x: canvas.width/2,
y: canvas.height/2 + 150,
mass: 1,
xV: 260,
color: 'teal'
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Body Type Object
function Body(params) {
this.x = params.x || canvas.width/2;
this.y = params.y || canvas.height/2;
this.a = params.a || 0;
this.xV = params.xV || 0;
this.yV = params.yV || 0;
this.aV = params.aV || 0;
this.xA = params.xA || 0;
this.yA = params.yA || 0;
this.aA = params.aA || 0;
this.grabbed = false;
this.edgeBlock = params.edgeBlock || boundaryCollision;
this.gravity = params.gravityOn || gravityOn;
this.mass = params.mass || 6;
this.density = params.density || 0.008;
this.radius = params.radius || (this.mass/(Math.PI*this.density))**0.5;
this.color = params.color || 'crimson';
this.lineWidth = params.lineWidth || 2;
}
Body.create = function(params) {
bodies.push(new Body(params));
}
Body.prototype.move = function() {
this.xV += this.xA/fps;
this.yV += this.yA/fps;
this.aV += this.aA/fps;
this.x += this.xV/fps;
this.y += this.yV/fps;
this.a += this.aV/fps;
if(this.edgeBlock) {
if(this.x + this.radius > canvas.width) {
this.x = canvas.width - this.radius;
this.xV *= -wallDampen
}
else if(this.x - this.radius < 0) {
this.x = this.radius;
this.xV *= -wallDampen;
}
if(this.y + this.radius > canvas.height) {
this.y = canvas.height - this.radius;
this.yV *= -wallDampen;
}
else if(this.y - this.radius < 0) {
this.y = this.radius;
this.yV *= -wallDampen;
}
}
if(this.grabbed) {
this.xA = 0;
this.yA = 0;
this.xV = 0;
this.yV = 0;
this.x = mouseX;
this.y = mouseY;
}
}
Body.prototype.draw = function() {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.lineWidth;
ctx.fillStyle = this.color;
ctx.arc(this.x, canvas.height - this.y, this.radius, 0, Math.PI*2, true);
ctx.fill();
ctx.stroke();
ctx.closePath()
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.linewidth;
ctx.moveTo(this.x, canvas.height - this.y);
ctx.lineTo(this.x + this.radius*Math.cos(this.a), canvas.height - (this.y + this.radius*Math.sin(this.a)))
ctx.stroke();
ctx.closePath();
}
// calculates gravitational attraction to 'otherObject'
Body.prototype.gravityCalc = function(otherObject) {
var x1 = this.x;
var y1 = this.y;
var x2 = otherObject.x;
var y2 = otherObject.y;
var distSquare = ((x2-x1)**2 + (y2-y1)**2);
var val = (gravity_c*otherObject.mass)/((distSquare)**(3/2));
var xA = val * (x2 - x1);
var yA = val * (y2 - y1);
return [xA, yA]
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Physics Code
function bodyHandle() {
for(i = 0; i < bodies.length; i++) {
if(mouseDown && nothingGrabbed) {
if(Math.abs((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies[i].grabbed = true;
nothingGrabbed = false;
}
}
bodies[i].draw()
if(running) {
if(falling) {
bodies[i].yV -= acceleration/fps;
}
bodies[i].move();
}
bodies[i].xA = 0;
bodies[i].yA = 0;
collidingPairs = []
if(gravityOn || bodyCollision) {
for(b = 0; b < bodies.length; b++) {
if(i != b) {
if(bodyCollision) {
var x1 = bodies[i].x;
var y1 = bodies[i].y;
var x2 = bodies[b].x;
var y2 = bodies[b].y;
var rSum = bodies[i].radius + bodies[b].radius;
var dist = { // vector
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
if(dist.mag <= rSum) { // static collision
var overlap = rSum - dist.mag;
bodies[i].x -= overlap/2 * dist.norm.i;
bodies[i].y -= overlap/2 * dist.norm.j;
bodies[b].x += overlap/2 * dist.norm.i;
bodies[b].y += overlap/2 * dist.norm.j;
collidingPairs.push([bodies[i], bodies[b]]);
}
}
if(gravityOn) {
if(bodies[i].gravity) {
var accel = bodies[i].gravityCalc(bodies[b]);
bodies[i].xA += accel[0];
bodies[i].yA += accel[1];
}
}
}
}
}
for(c = 0; c < collidingPairs.length; c++) { // dynamic collision
var x1 = collidingPairs[c][0].x;
var y1 = collidingPairs[c][0].y;
var r1 = collidingPairs[c][0].radius;
var x2 = collidingPairs[c][1].x;
var y2 = collidingPairs[c][1].y;
var r2 = collidingPairs[c][1].radius;
var dist = { // vector from b1 to b2
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
var m1 = collidingPairs[c][0].mass;
var m2 = collidingPairs[c][1].mass;
var norm = { // vector normal along 'wall' of collision
i: -dist.j/(((dist.i)**2 + (-dist.j)**2)**0.5),
j: dist.i/(((dist.i)**2 + (-dist.j)**2)**0.5)
}
var perp = { // vector normal pointing from b1 to b2
i: dist.norm.i,
j: dist.norm.j
}
var vel1 = { // vector of b1 velocity
i: collidingPairs[c][0].xV,
j: collidingPairs[c][0].yV,
dot: function(vect) {
return collidingPairs[c][0].xV * vect.i + collidingPairs[c][0].yV * vect.j
}
}
var vel2 = { // vector of b2 velocity
i: collidingPairs[c][1].xV,
j: collidingPairs[c][1].yV,
dot: function(vect) {
return collidingPairs[c][1].xV * vect.i + collidingPairs[c][1].yV * vect.j
}
}
// new velocities along perp^ of b1 and b2
var nV1Perp = (vel1.dot(perp))*(m1-m2)/(m1+m2) + (vel2.dot(perp))*(2*m2)/(m1+m2);
var nV2Perp = (vel1.dot(perp))*(2*m1)/(m1+m2) + (vel2.dot(perp))*(m2-m1)/(m1+m2);
/* testing rotation after collision
// velocities of the points of collision on b1 and b2
var pVel1M = vel1.dot(norm) + collidingPairs[c][0].aV*r1;
var pVel2M = vel2.dot(norm) + collidingPairs[c][1].aV*r2;
// moment of inertia for b1 and b2
var I1 = 1/2 * m1 * r1**2;
var I2 = 1/2 * m2 * r2**2;
// new velocities of the points of collisions on b1 and b2
var newpVel1M = ((I1-I2)/(I1+I2))*pVel1M + ((2*I2)/(I1+I2))*pVel2M;
var newpVel2M = ((2*I1)/(I1+I2))*pVel1M + ((I2-I1)/(I1+I2))*pVel2M;
var vectToCol1 = { // vector from x1,y1 to point of collision on b1
i: r1*perp.i,
j: r1*perp.j
};
var vectToCol2 = { // vector from x2,y2 to point of collision on b2
i: r2*-perp.i,
j: r2*-perp.j
};
// sign of cross product of pVelM and vectToCol
var vCrossR1 = (pVel1M*norm.i)*(vectToCol1.j) - (pVel1M*norm.j)*(vectToCol1.i);
vCrossR1 = vCrossR1/Math.abs(vCrossR1);
var vCrossR2 = (pVel2M*norm.i)*(vectToCol2.j) - (pVel2M*norm.j)*(vectToCol2.i);
vCrossR2 = vCrossR2/Math.abs(vCrossR2);
collidingPairs[c][0].aV = vCrossR1 * (newpVel1M)/r1;
collidingPairs[c][1].aV = vCrossR2 * (newpVel2M)/r2;
/* draw collision point velocity vectors [debugging]
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(x1 + vectToCol1.i, canvas.height - (y1 + vectToCol1.j));
ctx.lineTo((x1+vectToCol1.i) + pVel1M*norm.i, (canvas.height- (y1+vectToCol1.j + pVel1M*norm.j)));
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = 'white';
ctx.moveTo(x2 + vectToCol2.i, canvas.height - (y2 + vectToCol2.j));
ctx.lineTo((x2+vectToCol2.i) + pVel2M*norm.i, (canvas.height- (y2+vectToCol2.j + pVel2M*norm.j)));
ctx.stroke();
ctx.closePath();
console.log(pVel1M, pVel2M);
clearInterval(loop);
*/
collidingPairs[c][0].xV = vel1.dot(norm)*norm.i + nV1Perp*perp.i * bodyDampen;
collidingPairs[c][0].yV = vel1.dot(norm)*norm.j + nV1Perp*perp.j * bodyDampen;
collidingPairs[c][1].xV = vel2.dot(norm)*norm.i + nV2Perp*perp.i * bodyDampen;
collidingPairs[c][1].yV = vel2.dot(norm)*norm.j + nV2Perp*perp.j * bodyDampen;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Main Loop
function main() {
// blank out canvas
ctx.fillStyle = canvas.color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
bodyHandle();
if(nothingGrabbed && mouseDown) {
bodies.push(new Body({x: mouseX,
y: mouseY,
mass: 90}));
bodies[bodies.length-1].move();
bodies[bodies.length-1].draw();
}
}
<html>
<meta name='viewport' content='width=device-width,height=device-height'>
<body>
<canvas id="canvas" width='300px' height='300px'></canvas>
<style>
body {
padding: 0;
margin: 0;
}
canvas {
padding: 0;
margin: 0;
}
</style>
</html>
I cannot tell you much about the code. Personally it seems to me that the animations could be correct.
If you want to test your code you could try to test if laws of conservation of energy and momentum are respected. You could, for example, sum the momentum of every object (mass times velocity) and see if the number are maintained constant when there are no forces from the outside (collisions with the wall). To do this I would suggest to make the free space available larger. Another quantity is the total energy (kinetic plus potential) which is a bit harder, but still easy to compute (to compute tot. pot. energy you have to sum over all pairs).
I would like to remove the balls already generated in the canvas on the click and decrease the counter on the bottom, but my function does not work. Here is my code concerning the part of the ball removal.
Is it possible to use a div to get the same result and to facilitate the removal of the balls? thank you
ball.onclick = function removeBalls(event) {
var x = event.clientX;
var y = event.clientY;
ctx.clearRect(x, y, 100, 50);
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length - 1, 10, canvas.height - 10);
}
below I enclose my complete code
// GLOBAL VARIBLES
var gravity = 4;
var forceFactor = 0.3; //0.3 0.5
var mouseDown = false;
var balls = []; //hold all the balls
var mousePos = []; //hold the positions of the mouse
var ctx = canvas.getContext('2d');
var heightBrw = canvas.height = window.innerHeight;
var widthBrw = canvas.width = window.innerWidth;
var bounciness = 1; //0.9
window.onload = function gameCore() {
function onMouseDown(event) {
mouseDown = true;
mousePos["downX"] = event.pageX;
mousePos["downY"] = event.pageY;
}
canvas.onclick = function onMouseUp(event) {
mouseDown = false;
balls.push(new ball(mousePos["downX"], mousePos["downY"], (event.pageX - mousePos["downX"]) * forceFactor,
(event.pageY - mousePos["downY"]) * forceFactor, 5 + (Math.random() * 10), bounciness, random_color()));
ball
}
function onMouseMove(event) {
mousePos['currentX'] = event.pageX;
mousePos['currentY'] = event.pageY;
}
function resizeWindow(event) {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
}
function reduceBounciness(event) {
if (bounciness == 1) {
for (var i = 0; i < balls.length; i++) {
balls[i].b = bounciness = 0.9;
document.getElementById("btn-bounciness").value = "⤽ Bounciness";
}
} else {
for (var i = 0; i < balls.length; i++) {
balls[i].b = bounciness = 1;
document.getElementById("btn-bounciness").value = " ⤼ Bounciness";
}
}
return bounciness;
}
function reduceSpeed(event) {
for (var i = 0; i < balls.length; i++) {
balls[i].vx = velocityX = 20 + c;
balls[i].vy = velocityY = 20 + c;
}
}
function speedUp(event) {
for (var i = 0; i < balls.length; i++) {
balls[i].vx = velocityX = 120 + c;
balls[i].vy = velocityY = 120 + c;
}
}
function stopGravity(event) {
if (gravity == 4) {
for (var i = 0; i < balls.length; i++) {
balls[i].g = gravity = 0;
balls[i].vx = velocityX = 0;
balls[i].vy = velocityY = 0;
document.getElementById("btn-gravity").value = "►";
}
} else {
for (var i = 0; i < balls.length; i++) {
balls[i].g = gravity = 4;
balls[i].vx = velocityX = 100;
balls[i].vy = velocityY = 100;
document.getElementById("btn-gravity").value = "◾";
}
}
}
ball.onclick = function removeBalls(event) {
var x = event.clientX;
var y = event.clientY;
ctx.clearRect(x, y, 100, 50);
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length - 1, 10, canvas.height - 10);
}
document.getElementById("btn-gravity").addEventListener("click", stopGravity);
document.getElementById("btn-speed-up").addEventListener("click", speedUp);
document.getElementById("btn-speed-down").addEventListener("click", reduceSpeed);
document.getElementById("btn-bounciness").addEventListener("click", reduceBounciness);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
window.addEventListener('resize', resizeWindow);
}
// GRAPHICS CODE
function circle(x, y, r, c) { // x position, y position, r radius, c color
//draw a circle
ctx.beginPath(); //approfondire
ctx.arc(x, y, r, 0, Math.PI * 2, true);
ctx.closePath();
//fill
ctx.fillStyle = c;
ctx.fill();
//stroke
ctx.lineWidth = r * 0.1; //border of the ball radius * 0.1
ctx.strokeStyle = "#000000"; //color of the border
ctx.stroke();
}
function random_color() {
var letter = "0123456789ABCDEF".split(""); //exadecimal value for the colors
var color = "#"; //all the exadecimal colors starts with #
for (var i = 0; i < 6; i++) {
color = color + letter[Math.round(Math.random() * 15)];
}
return color;
}
function selectDirection(fromx, fromy, tox, toy) {
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.moveTo(tox, toy);
}
//per velocità invariata rimuovere bounciness
function draw_ball() {
this.vy = this.vy + gravity * 0.1; // v = a * t
this.x = this.x + this.vx * 0.1; // s = v * t
this.y = this.y + this.vy * 0.1;
if (this.x + this.r > canvas.width) {
this.x = canvas.width - this.r;
this.vx = this.vx * -1 * this.b;
}
if (this.x - this.r < 0) {
this.x = this.r;
this.vx = this.vx * -1 * this.b;
}
if (this.y + this.r > canvas.height) {
this.y = canvas.height - this.r;
this.vy = this.vy * -1 * this.b;
}
if (this.y - this.r < 0) {
this.y = this.r;
this.vy = this.vy * 1 * this.b;
}
circle(this.x, this.y, this.r, this.c);
}
// OBJECTS
function ball(positionX, positionY, velosityX, velosityY, radius, bounciness, color, gravity) {
this.x = positionX;
this.y = positionY;
this.vx = velosityX;
this.vy = velosityY;
this.r = radius;
this.b = bounciness;
this.c = color;
this.g = gravity;
this.draw = draw_ball;
}
// GAME LOOP
function game_loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (mouseDown == true) {
selectDirection(mousePos['downX'], mousePos['downY'], mousePos['currentX'], mousePos['currentY']);
}
for (var i = 0; i < balls.length; i++) {
balls[i].draw();
}
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length, 10, canvas.height - 10);
}
setInterval(game_loop, 10);
* {
margin: 0px; padding: 0px;
}
html, body {
width: 100%; height: 100%;
}
#canvas {
display: block;
height: 95%;
border: 2px solid black;
width: 98%;
margin-left: 1%;
}
#btn-speed-up, #btn-speed-down, #btn-bounciness, #btn-gravity{
padding: 0.4%;
background-color: beige;
text-align: center;
font-size: 20px;
font-weight: 700;
float: right;
margin-right: 1%;
margin-top:0.5%;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Power Balls</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<canvas id="canvas"></canvas>
<input type="button" value="⤼ Bounciness" id="btn-bounciness"></input>
<input type="button" onclick="c+=10" value="+ Speed" id="btn-speed-up"></input>
<input type="button" value="◾" id="btn-gravity"></input>
<input type="button" onclick="c+= -10" value=" - Speed" id="btn-speed-down"></input>
<script src="js/main.js"></script>
</body>
</html>
I see two problems:
ball.onclick will not get triggered, as ball it is not a DOM object. Instead, you need to work with canvas.onclick. Check whether the user clicked on a ball or on empty space to decide whether to delete or create a ball.
To delete the ball, ctx.clearRect is not sufficient. This will clear the ball from the currently drawn frame, but the object still remains in the array balls and will therefore be drawn again in the next frame via balls[i].draw();. Instead, you need to remove the clicked ball entirely from the array, e. g. by using splice.
Otherwise, nice demo! :)
This cannot be done because the canvas is an immediate mode API. All you can do is draw over the top but this is not reliable.
https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics)
You will have to store each item in a separate data structure, then re-draw whenever a change occurs. In your case there is an array of balls, so you should remove the one that was clicked, before redrawing the entire array.
Each time you remove something, clear the canvas then re-draw each ball.
There are optimisations you can make if this is too slow.
I'm working on a game that uses projectiles and a shielding system. The player would hold down 'Space' to use the shield. My plan is to get the projectiles to bounce of of the enemies shields (I've implemented speed so I already know how to do that). The problem I am having is with the collision, since the player rotates to follow the mouse I struggled with finding the best way to create the shield but I eventually settled on an arc, I used some trigonometry to get the left, leftHalf, mid, rightHalf, and right point of the arc/shield. The Player with Shield. The issue is I can't get the collision to work from just 5, x/y coordinates (the arc is just being drawn for show I'm only sending the points to the server). This is what I have for my collision so far:
p: Player object
self: bullet object
bot: a variable based on the direction the character is facing (bottom: true or false)
shieldLeft, sheildRight, etc: an array containing x and y coordinate 0 for x, 1 for y
if (self.getDistance(p) < 32 && self.parent !== p.id)
{
if (p.isShielding == true)
{
switch(self.bot)
{
case true:
if (self.x >= p.shieldRight[0] && self.x <= p.shieldLeft[0])
{
console.log("BOT X");
if ((self.y >= p.shieldLeft[1] || self.y >= p.shieldRight[1]) && self.y <= p.shieldMid[1])
{
console.log("BOT Y");
self.spdX = -self.spdX;
self.spdY = -self.spdY;
}
}
break;
case false:
if (self.x <= p.shieldRight[0] && self.x >= p.shieldLeft[0])
{
console.log("TOP X");
if ((self.y <= p.shieldLeft[1] || self.y <= p.shieldRight[1]) && self.y >= p.shieldMid[1])
{
console.log("TOP Y");
self.spdX = -self.spdX;
self.spdY = -self.spdY;
}
}
break;
}
}
}
I would really appreciate any help, I can't continue with the game features until there actually is a game. Thank you!
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
position: absolute;
margin: auto;
left: 0;
right: 0;
border: solid 1px white;
border-radius: 10px;
cursor: crosshair;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
// Anonymous closure
(function() {
// Enforce strict rules for JS code
"use strict";
// App variables
var canvasWidth = 180;
var canvasHeight = 160;
var canvas = null;
var bounds = null;
var ctx = null;
var player = null;
var projectiles = [];
projectiles.length = 5;
// Classes
// Constructor function
function Player(x,y) {
this.x = x;
this.y = y;
this.dx = 0.0;
this.dy = 0.0;
this.rotation = 0.0;
this.targetX = 0.0;
this.targetY = 0.0;
this.isShieldUp = false;
this.isShieldRecharging = false;
this.shieldPower = this.shieldPowerMax;
this.left = false;
this.right = false;
this.up = false;
this.down = false;
window.addEventListener("keydown",this.onkeydown.bind(this));
window.addEventListener("keyup",this.onkeyup.bind(this));
window.addEventListener("mousemove",this.onmousemove.bind(this));
}
// shared properties/functions across all instances
Player.prototype = {
width: 10,
height: 10,
shieldRadius: 15.0,
shieldArcSize: 3.0, // In Radians
shieldPowerMax: 50.0,
shieldPowerCharge: 0.5,
shieldPowerDrain: 0.75,
onkeydown: function(e) {
switch(e.key) {
case " ": this.isShieldUp = true && !this.isShieldRecharging; break;
case "w": this.up = true; break;
case "s": this.down = true; break;
case "a": this.left = true; break;
case "d": this.right = true; break;
}
},
onkeyup: function(e) {
switch(e.key) {
case " ": this.isShieldUp = false; break;
case "w": this.up = false; break;
case "s": this.down = false; break;
case "a": this.left = false; break;
case "d": this.right = false; break;
}
},
onmousemove: function(e) {
this.targetX = e.clientX - bounds.left;
this.targetY = e.clientY - bounds.top;
},
tick: function() {
var x = (this.targetX - this.x);
var y = (this.targetY - this.y);
var l = Math.sqrt(x * x + y * y);
x = x / l;
y = y / l;
this.rotation = Math.acos(x) * (y < 0.0 ? -1.0 : 1.0);
if (this.isShieldUp) {
this.shieldPower = this.shieldPower - this.shieldPowerDrain;
if (this.shieldPower < 0.0) {
this.shieldPower = 0.0;
this.isShieldUp = false;
this.isShieldRecharging = true;
}
} else {
this.shieldPower = this.shieldPower + this.shieldPowerCharge;
if (this.shieldPower > this.shieldPowerMax) {
this.shieldPower = this.shieldPowerMax;
this.isShieldRecharging = false;
}
}
if (this.up) { --this.y; this.dy = -1; } else
if (this.down) { ++this.y; this.dy = 1; } else { this.dy = 0; }
if (this.left) { --this.x; this.dx = -1; } else
if (this.right) { ++this.x; this.dx = 1; } else { this.dx = 0; }
},
render: function() {
ctx.fillStyle = "darkred";
ctx.strokeStyle = "black";
ctx.translate(this.x,this.y);
ctx.rotate(this.rotation);
ctx.beginPath();
ctx.moveTo(0.5 * this.height,0.0);
ctx.lineTo(-0.5 * this.height,0.5 * this.width);
ctx.lineTo(-0.5 * this.height,-0.5 * this.width);
ctx.lineTo(0.5 * this.height,0.0);
ctx.fill();
ctx.stroke();
if (this.isShieldUp) {
ctx.strokeStyle = "cyan";
ctx.beginPath();
ctx.arc(0.0,0.0,this.shieldRadius,this.shieldArcSize * -0.5,this.shieldArcSize * 0.5,false);
ctx.stroke();
}
ctx.rotate(-this.rotation);
ctx.translate(-this.x,-this.y);
ctx.fillStyle = "black";
ctx.fillRect(canvasWidth - 80,canvasHeight - 20,75,15);
ctx.fillStyle = this.isShieldRecharging ? "red" : "cyan";
ctx.fillRect(canvasWidth - 75,canvasHeight - 15,65 * (this.shieldPower / this.shieldPowerMax),5);
}
};
function Projectile(x,y,dx,dy) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
}
Projectile.prototype = {
radius: 2.5,
tick: function(player) {
this.x = this.x + this.dx;
this.y = this.y + this.dy;
if (this.x + this.radius < 0.0) { this.x = canvasWidth + this.radius; }
if (this.x - this.radius > canvasWidth) { this.x = -this.radius; }
if (this.y + this.radius < 0.0) { this.y = canvasHeight + this.radius; }
if (this.y - this.radius > canvasHeight) { this.y = -this.radius; }
if (player.isShieldUp) {
var px = (player.x - this.x);
var py = (player.y - this.y);
var pl = Math.sqrt(px * px + py * py);
var ml = Math.sqrt(this.dx * this.dx + this.dy * this.dy);
var mx = this.dx / ml;
var my = this.dy / ml;
px = px / pl;
py = py / pl;
if (Math.acos(px * mx + py * my) < player.shieldArcSize * 0.5 && pl < player.shieldRadius) {
px = -px;
py = -py;
this.dx = this.dx - 2.0 * px * (this.dx * px + this.dy * py) + player.dx;
this.dy = this.dy - 2.0 * py * (this.dx * px + this.dy * py) + player.dy;
}
}
},
render: function() {
ctx.moveTo(this.x + this.radius,this.y);
ctx.arc(this.x,this.y,this.radius,0.0,2.0 * Math.PI,false);
}
}
// Game loop
function loop() {
// Tick
player.tick();
for (var i = 0; i < projectiles.length; ++i) {
projectiles[i].tick(player);
}
// Render
ctx.fillStyle = "#555555";
ctx.fillRect(0,0,canvasWidth,canvasHeight);
player.render();
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.beginPath();
for (var i = 0; i < projectiles.length; ++i) {
projectiles[i].render();
}
ctx.fill();
ctx.stroke();
//
requestAnimationFrame(loop); // Runs the loop at 60hz
}
// "Main Method", executes after the page loads
window.onload = function() {
canvas = document.getElementById("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
bounds = canvas.getBoundingClientRect();
ctx = canvas.getContext("2d");
player = new Player(90.0,80.0);
for (var i = 0; i < projectiles.length; ++i) {
projectiles[i] = new Projectile(
Math.random() * canvasWidth,
Math.random() * canvasHeight,
Math.random() * 2.0 - 1.0,
Math.random() * 2.0 - 1.0
);
}
loop();
}
})();
</script>
</body>
</html>
I am trying to make a pong canvas game responsive on resize, i am nor sure what i am missing and what would be the best way to have it responsive while still not being to process heavy. i tried to delete and reinject the canvas on resize what is a bad idea i think, the responsiveness works on load so far... thanks a lot for the insights, much appreciated
http://jsfiddle.net/ut0hsvd4/1/
var animate = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) {
window.setTimeout(callback, 1000 / 60)
};
var canvas = document.createElement("canvas");
var width = document.getElementById('pong').offsetWidth;
var height = document.getElementById('pong').offsetHeight;
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
var player = new Player();
var computer = new Computer();
var ball = new Ball(width / 2, height / 2);
var keysDown = {};
var render = function() {
context.fillStyle = "red";
context.fillRect(0, 0, width, height);
player.render();
computer.render();
ball.render();
};
var update = function() {
player.update();
computer.update(ball);
ball.update(player.paddle, computer.paddle);
};
var step = function() {
update();
render();
animate(step);
};
function Paddle(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.x_speed = 0;
this.y_speed = 0;
}
Paddle.prototype.render = function() {
context.fillStyle = "#0000FF";
context.fillRect(this.x, this.y, this.width, this.height);
};
Paddle.prototype.move = function(x, y) {
this.x += x;
this.y += y;
this.x_speed = x;
this.y_speed = y;
if (this.x < 0) {
this.x = 0;
this.x_speed = 0;
} else if (this.x + this.width > width) {
this.x = width - this.width;
this.x_speed = 0;
}
};
function Computer() {
this.paddle = new Paddle(width / 2 - 25, 10, 50, 10);
}
Computer.prototype.render = function() {
this.paddle.render();
};
Computer.prototype.update = function(ball) {
var x_pos = ball.x;
var diff = -((this.paddle.x + (this.paddle.width / 2)) - x_pos);
if (diff < 0 && diff < -4) {
diff = -5;
} else if (diff > 0 && diff > 4) {
diff = 5;
}
this.paddle.move(diff, 0);
if (this.paddle.x < 0) {
this.paddle.x = 0;
} else if (this.paddle.x + this.paddle.width > width) {
this.paddle.x = width - this.paddle.width;
}
};
function Player() {
this.paddle = new Paddle(width / 2 - 25, height - 20, 50, 10);
}
Player.prototype.render = function() {
this.paddle.render();
};
Player.prototype.update = function() {
for (var key in keysDown) {
var value = Number(key);
if (value == 37) {
this.paddle.move(-4, 0);
} else if (value == 39) {
this.paddle.move(4, 0);
} else {
this.paddle.move(0, 0);
}
}
};
function Ball(x, y) {
this.x = x;
this.y = y;
this.x_speed = 0;
this.y_speed = 3;
}
Ball.prototype.render = function() {
context.beginPath();
context.arc(this.x, this.y, 5, 2 * Math.PI, false);
context.fillStyle = "#000000";
context.fill();
};
Ball.prototype.update = function(paddle1, paddle2) {
this.x += this.x_speed;
this.y += this.y_speed;
var top_x = this.x - 5;
var top_y = this.y - 5;
var bottom_x = this.x + 5;
var bottom_y = this.y + 5;
if (this.x - 5 < 0) {
this.x = 5;
this.x_speed = -this.x_speed;
} else if (this.x + 5 > width) {
this.x = width - 5;
this.x_speed = -this.x_speed;
}
if (this.y < 0 || this.y > height) {
this.x_speed = 0;
this.y_speed = 3;
this.x = width / 2;
this.y = height / 2;
}
if (top_y > height / 2) {
if (top_y < (paddle1.y + paddle1.height) && bottom_y > paddle1.y && top_x < (paddle1.x + paddle1.width) && bottom_x > paddle1.x) {
this.y_speed = -3;
this.x_speed += (paddle1.x_speed / 2);
this.y += this.y_speed;
}
} else {
if (top_y < (paddle2.y + paddle2.height) && bottom_y > paddle2.y && top_x < (paddle2.x + paddle2.width) && bottom_x > paddle2.x) {
this.y_speed = 3;
this.x_speed += (paddle2.x_speed / 2);
this.y += this.y_speed;
}
}
};
document.getElementsByClassName('js-pong')[0].appendChild(canvas);
animate(step);
window.addEventListener("keydown", function(event) {
keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function(event) {
delete keysDown[event.keyCode];
});
var resizer = function() {
document.getElementsByTagName('canvas').remove();
document.getElementsByClassName('js-pong')[0].appendChild(canvas);
animate(step);
}
window.addEventListener('resize', resizer);
Dont use the Resize event for animations.
(if you use requestAnimationFrame)
Do not add a resize to the resize event. The resize event does not fire in sync with the display, and it can also fire many time quicker than the display rate. This can make the resize and game feel sluggish while dragging at the window size controls.
Smooth and efficient resizing
For the smoothest resize simply check the canvas size inside the main loop. If the canvas does not match the containing element size then resize it. This ensures you only resize when needed and always resize in sync with the display (if you use requestAnimationFrame)
Eg
// get a reference to the element so you don't query the DOM each time
const pongEl = document.getElementById("pong");
// then in the main loop
function mainLoop() {
// do first thing inside the render loop
if(canvas.width !== pongEl.offsetWidth || canvas.height !== pongEl.offsetHeight ){
canvas.width = pongEl.offsetWidth;
canvas.height = pongEl.offsetHeight;
// note the above clears the canvas.
}
// game code as normal
requestAnimationFrame(mainLoop);
}
You have to detect when the resize happens with JavaScript:
window.onresize = function(event) {
...
};
and add a css class or properties to make the canvas width 100%. Also you have to give it a min-width to make sure no body can resize it to a really uncomfortable width.
This piece of code needs to run again after a resize:
var canvas = document.createElement("canvas");
var width = document.getElementById('pong').offsetWidth;
var height = document.getElementById('pong').offsetHeight;
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');