I was starting to teach myself how to work with HTML5 Canvas and decided to learn by making a short game/demo.
I wanted to make a simple blocks bounce around the screen, bounce off the walls, and bounce off each other.
I'm stuck on getting them to bounce off each other. It seems like code that makes it bounce away is making it bounce back immediately after. I see where the code fails but I don't know how to fix it :( Can anyone help?
(Side question: I know I'm not working as clean/efficiently/professionally as possible in this example but if I wanted to improve with feedback and opinions about the 'best' method for this type of example, like a code review or something, is it ok to ask a question on stackoverflow?)
jsfiddle:
http://jsfiddle.net/vdcSv/
HTML:
<canvas id="canvas" Width="400" Height="300"></canvas>
Javscript:
function CheckBallCollision(BallsArray, index) {
for (var i = 0; i < BallsArray.length; i++) {
if (index != i) {
if (BallsArray[index].Xdir == 1) {
if ((BallsArray[index].Xmax >= BallsArray[i].Xmin)) {
if ((BallsArray[index].Ymin <= BallsArray[i].Ymin) && (BallsArray[index].Ymax >= BallsArray[i].Ymin) ||
((BallsArray[index].Ymax >= BallsArray[i].Ymax) && (BallsArray[index].Ymin <= BallsArray[i].Ymax))) {
BallsArray[index].Xdir = -BallsArray[index].Xdir;
}
}
} else if (BallsArray[index].Xdir == -1) {
if ((BallsArray[index].Xmin <= BallsArray[i].Xmax)) {
if ((BallsArray[index].Ymin <= BallsArray[i].Ymin) && (BallsArray[index].Ymax >= BallsArray[i].Ymin) ||
((BallsArray[index].Ymax >= BallsArray[i].Ymax) && (BallsArray[index].Ymin <= BallsArray[i].Ymax))) {
BallsArray[index].Xdir = -BallsArray[index].Xdir;
}
}
}
}
}
}
Ball Object:
function Ball() {
this.Xmin = 0;//top left X coord
this.Ymin = 0;//top left y coord
this.Height = 25;
this.Width = 25;
this.Xmax = this.Xmin + this.Width;
this.Ymax = this.Ymin + this.Height;
this.Xdir = 0; // 0 not moving, 1 moving right, -1 moving left
this.Ydir = 0;
this.Red = 0;
this.Green = 0;
this.Blue = 200;
this.Opacity = 1;
this.Speed = 1;
}
Got it working by changing the <= to ==
It's messy and things often miss the necessary bounce off a block :( I'm sure part of the reason is falling back on the == instead of <=. If anyone has a better solution - I'm all ears :)
http://jsfiddle.net/vdcSv/1/
function CheckBallCollision(BallsArray, index) {
for (var i = 0; i < BallsArray.length; i++) {
if (index != i) {
if (BallsArray[index].Xdir == 1) {
if ((BallsArray[index].Xmax == BallsArray[i].Xmin)) {
if ((BallsArray[index].Ymin <= BallsArray[i].Ymin) && (BallsArray[index].Ymax >= BallsArray[i].Ymin) ||
((BallsArray[index].Ymax >= BallsArray[i].Ymax) && (BallsArray[index].Ymin <= BallsArray[i].Ymax))) {
BallsArray[index].Xdir = -BallsArray[index].Xdir;
}
}
} else if (BallsArray[index].Xdir == -1) {
if ((BallsArray[index].Xmin == BallsArray[i].Xmax)) {
if ((BallsArray[index].Ymin <= BallsArray[i].Ymin) && (BallsArray[index].Ymax >= BallsArray[i].Ymin) ||
((BallsArray[index].Ymax >= BallsArray[i].Ymax) && (BallsArray[index].Ymin <= BallsArray[i].Ymax))) {
BallsArray[index].Xdir = -BallsArray[index].Xdir;
}
}
}
}
}
}
Here are a couple hit detection snippets you might want to look into:
ball.hitTestCircle = function(obj) {
var dx = this.x - obj.x;
var dy = this.y - obj.y;
var distance = (dx * dx) + (dy * dy);
var area = (this.radius + obj.radius)*(this.radius + obj.radius);
return (area / distance);
};
if the call returns 1 or greater your colliding, and you can even use that info to fix the difference.
Here is basic rect hit detections script:
ball.hitTestRect = function(b) {
var difference = {};
difference.x = this.x - b.x - b.width;
difference.y = this.y - b.y - b.height;
difference.height = this.height + b.height;
difference.width = this.width + b.width;
if (difference.x < 0 && difference.y <= 0 && difference.height + difference.y >= 0 && difference.width + difference.x >= 0) return true;
return false;
};
I would call either of these with something like :
for(var i=0;i!=balls.length;i++){
for(var j=0;j!=balls.length;j++){
if(j!=i){
if(balls[i].hitTestRect(balls[j])){
// all your reversing motion code
}
}
}
}
It looks like you forgot to check if
BallsArray[index].Xmin <= BallsArray[i].Xmax)
If you add this in it works. It's also worth noting that you don't need different code for the two different X directions as this behaviour is symmetrical. Regardless of which way it is travelling to begin with you have it reversing direction. It's also symmetrical in the Y direction so if you just add:
BallsArray[index].Ydir = -BallsArray[index].Ydir;
to the 'then' part of the if you'll only need one if to take care of all four kinds of collisions.
You may also want to add a break statement so that if a ball happens to collide with two other balls at the same time it will only reverse direction once.
For a more realistic simulation you can multiply by a negative number in the (0, 1) interval, however if you don't do something else your system will slowly settle into a steady state until the rounding errors kick in and it freaks out.
Related
[ETA] for better clarification:
I'm currently learning to code a HTML canvas game (items catching and dodging) using Javascript for a class project and I'm having problem with getting the objects falling down from top of the canvas.
The problem I'm having currently is occasionally (one might need to reload the game several times to notice this phenomenon) an object 'jumps' out of a random point inside the canvas where it should be thrown back up to the negative y coordinate area (aka. out of sight) and fall down again from there after collision detection.
So for example, when a giftbox is caught or disappears off the bottom edge, it should be thrown back up to the top of the canvas. However, sometimes it pops out of a random point in the middle, or near the bottom edge of the canvas instead, which is not at all what I intended.
I figure there must be a logical error with the code I write, but I have been trying several fixes and looking up answers for the past few days without any success.
I guess the problem must lie in the part of the code where I do collision detection:
function fallAgain() {
for (i = 0; i < 3; i++) {
let r = Math.floor(Math.random() * objectArray.length);
if (objectArray[i].y > canvas.height) {
[objectArray[i], objectArray[r]] = [objectArray[r], objectArray[i]];
objectArray[r].y = randomY();
objectArray[r].x = randomX();
// this loop is to check if two randomly generated items overlap
for (let idx = 1; idx < 3; idx++) {
if (Math.abs(objectArray[idx].x - objectArray[idx-1].x) <= 10
&& Math.abs(objectArray[idx].y - objectArray[idx-1].y) <=10) {
objectArray[idx].y += 30;
objectArray[idx].x += 20;
}
}
if (objectArray[r].status == 1
|| objectArray[r].status == 3
|| objectArray[r].status == 4) {
lives--;
}
}
}
}
function hitOrMissed() {
for (i = 0; i < 3; i++) {
let r = Math.floor(Math.random() * objectArray.length);
if (objectArray[i].x + objectArray[i].w > kid.x + 3
&& objectArray[i].x + 3 < kid.x + kid.w
&& objectArray[i].y + objectArray[i].h > kid.y
&& objectArray[i].y < kid.y + kid.h - 3) {
[objectArray[i], objectArray[r]] = [objectArray[r], objectArray[i]];
objectArray[r].y = randomY();
objectArray[r].x = randomX();
for (let idx = 1; idx < 3; idx++) {
if (Math.abs(objectArray[idx].x - objectArray[idx-1].x) <= 10
&& Math.abs(objectArray[idx].y - objectArray[idx-1].y) <=10) {
objectArray[idx].y += 30;
objectArray[idx].x += 20;
}
}
if (objectArray[r].status == 1) {
score++;
} else if (objectArray[r].status == 0) {
lives--;
} else if (objectArray[r].status == 4) {
if (lives < 2) {
lives++;
} else {
lives += 0;
score += 2;
}
} else if (objectArray[r].status == 3) {
score += 5;
}
}
}
}
So far I've tried merging the fallAgain and hitOrMissed functions, but the problem persists
And here's my Codepen with the full code:
Codepen demo
If someone could please help me identify where I've made it wrong and a pointer or guideline as to what I should do to fix the problem I'd really appreciate it. I'm not sure if this piece of information is of any help, but I'm just a beginner at Javascript with no prior coding experience and I've only learned the basics (I haven't touched constructor and Object-oriented Programming yet).
Thank you so much in advance for your help!
I am making a 2d platformer and am working on gravity and collision but when I actually get the collision working my gravity became very stuttery, is there a way to fix this? here is code:
gravity(){
if(this.y <= 400 - this.scale){
let gc = get(this.x, this.y + 20);
print(gc);
if(gc[0] == 0 && gc[1] == 0 && gc[2] == 255 && gc[3] == 255){
return;
}
else{
this.y += 2;
}
}
}
edit: I have heard this should be working so I will provide more code to see if it helps. here is the function that runs everything:
var groundArray = [];
groundArray[0] = [0];
groundArray[1] = [0];
groundArray[2] = [0];
groundArray[3] = [0];
groundArray[4] = [0];
groundArray[5] = [0];
groundArray[6] = [0];
groundArray[7] = [0];
groundArray[8] = [1];
groundArray[9] = [1, 1];
function setup() {
noStroke();
createCanvas(400, 400);
for(let y = 0; y < groundArray.length; y++){
for(let x = 0; x < groundArray[y].length; x++){
if(groundArray[y][x] != 0){
groundArray[y][x] = new ground(x * 40, y * 40);
}
}
}
}
var play = new player(35, 0, 20);
function draw() {
background(255);
for(let y = 0; y < groundArray.length; y++){
for(let x = 0; x < groundArray[y].length; x++){
if(groundArray[y][x] != 0){
groundArray[y][x].draw();
}
}
}
play.draw();
play.gravity();
}
Normally a function named draw() is called every frame, is that what's happening here? It looks like it. In which case, do you really want to be calling new ground(x * 40, y * 40) every frame, wouldn't this object creation be better placed in setup()?
I suspect it's unnecessary and possibly doing expensive initialization work every frame, but without seeing the full source code one can't be sure.
I'm learning some canvas stuff and am making a personal project with HTML/JS so pardon my ugly inelegant code.
In the code below, when only one of the a/b/c/d functions is called, everything works fine; but if more than one is called, then the canvas starts to freak out with weird flickering colours. No error messages could be found in the console. The problem seems to subside when I decrease the number of tiles to be drawn but not the canvas size itself. I did call the canvas draw function a lot in other sections of the code so could these functions be the straw that broke the memory limit or something?
Help would be much appreciated! Screenshot
for (let y = 0; y < yCount; y++) {
for (let x = 0; x < xCount; x++) {
if (tileList[y][x] == 1) {
a(x, y);
b(x, y);
c(x, y);
d(x, y);
}
}
}
function a(x, y) {
if (x != 0 && y != 0) { //! TOP-LEFT
if (tileList[y][x - 1] != 1 && tileList[y - 1][x] != 1) {
subfunctionTopLeft(x, y, palette[0]);
}
}
}
function b(x, y) {
if (x != xCount - 1 && y != 0) { //! TOP-RIGHT
if (tileList[y][x + 1] != 1 && tileList[y - 1][x] != 1) {
subfunctionTopRight(x, y, palette[0]);
}
}
}
function c(x, y) {
if (x != 0 && y != yCount - 1) { //! BOTTOM-LEFT
if (tileList[y][x - 1] != 1 && tileList[y + 1][x] != 1) {
subfunctionBottomLeft(x, y, palette[0]);
}
}
}
function d(x, y) {
if (x != xCount - 1 && y != yCount - 1) { //! BOTTOM-RIGHT
if (tileList[y + 1][x] != 1 && tileList[y][x + 1] != 1) {
subfunctionBottomRight(x, y, palette[0]);
}
}
}
(The subfunctions are some custom draw functions.)
The problem with this code is when I'm executing the if condition. The condition only works if i am using if (pixel.getx() <=100) but does not work for a var x = pixel.getX() & if (x <= 100). Can someone tell me why?
var image = new SimpleImage (200,200);
print (image);
for (var pixel of image.values())
var x = pixel.getX();
var y = pixel.getY()
if (x <= 100 && y <= 100)
{
pixel.setRed(255);
pixel.setBlue(0);
pixel.setGreen(0);
}
else if (x > 100)
{
pixel.setBlue(255);
pixel.setGreen(0);
pixel.setRed(0);
}
print (image);
your for loop is missing {}.
all it does the way you have it in your example is
executing var x = pixel.getX(); as many times as there are image.values()
if you need to repeat a multi line block of code within a for loop it needs to be inside {}
if you are repeating one statement - you don't need {} - that's why it worked when you had if (pixel.getX() <= 100) {...}
Your for loop is missing the braces { } and that's why its not working.
Modified code,
var image = new SimpleImage (200,200);
print (image);
for (var pixel of image.values()) {
var x = pixel.getX();
var y = pixel.getY()
if (x <= 100 && y <= 100) {
pixel.setRed(255);
pixel.setBlue(0);
pixel.setGreen(0);
} else if (x > 100) {
pixel.setBlue(255);
pixel.setGreen(0);
pixel.setRed(0);
}
print (image);
}
I have a kind of "bouncing balls" project, where I draw 150 particles at a canvas and, at every redraw, it recalc particles position, and verify if any particles is at a corner, to invert its iterator.
But thing is that this project has a factor that not all "bouncing balls" projects have. The balls need to bounce within the bounds of a map.
So, when I create the canvas, I also use a SVG to iterate over the pixels and create a array of every bound in the x axis, left and right, so my particles will know exactly where they need to rebounce.
So good, so well, it is working nice, but my canvas is 500px tall, so it need to iterate 500 times, with a lot of conditionals to prevent weird behavior, this multiplied by 150 particles, and in every redraw.
It has became very performance greedy and I need to improve performance, so, here is my collision system code
const colisionSystem = state => {
for (var b=0, hs=state.bounds.length; b<hs; b++) {
if(
state.bounds[b][0]
&& state.x - state.radius < state.bounds[b][0].x
&& state.y + state.radius > state.bounds[b][0].y
&& state.y - state.radius < state.bounds[b][0].y
) {
if (
state.bounds[b][0].x > 0
&& state.bounds[b][0].x < (state.widgetSize.width * 0.33)
&& state.bounds[b][0].y > (state.widgetSize.height * 0.33)
&& state.bounds[b][0].y < (state.widgetSize.width * 0.45)
) {
// middle left bottom corner at acre
state.x = state.radius + state.bounds[b][0].x;
state.vy *= -1;
} else if (
state.bounds[b][0].x > 0
&& state.bounds[b][0].x < (state.widgetSize.width * 0.098)
&& state.bounds[b][0].y > (state.widgetSize.height * 0.167)
&& state.bounds[b][0].y < (state.widgetSize.width * 0.206)
) {
// middle left top corner at acre
state.y = state.radius + state.bounds[b][0].y + 1;
state.vx *= -1;
state.vy *= -1;
} else {
state.x = state.radius + state.bounds[b][0].x;
state.vx *= -1;
}
if(state.oldAxis === state.x) {
state.y = state.y - 1;
} else {
state.oldAxis = state.x;
}
state.antiRebounce = false;
}
if(
state.bounds[b][1]
&& state.x + state.radius > state.bounds[b][1].x
&& state.y + state.radius > state.bounds[b][1].y
&& state.y - state.radius < state.bounds[b][1].y
) {
if (
state.bounds[b][1].x > (state.widgetSize.width * 0.555)
&& state.bounds[b][1].x < (state.widgetSize.width * 0.983)
&& state.bounds[b][1].y > 0
&& state.bounds[b][1].y < (state.widgetSize.width * 0.2098)
) {
// Top right corner
if(state.antiRebounce) {
state.vy *= -1;
state.antiRebounce = false;
} else {
state.antiRebounce = true;
}
state.y = state.bounds[b][1].y + state.radius + 1;
state.vy *= -1;
}
if (
state.bounds[b][1].x > (state.widgetSize.width * 0.604)
&& state.bounds[b][1].x < (state.widgetSize.width * 0.827)
&& state.bounds[b][1].y > (state.widgetSize.width * 0.665)
&& state.bounds[b][1].y < (state.widgetSize.width * 0.778)
) {
// bottom right corner
state.vy *= -1;
} else {
state.vx *= -1;
state.x = state.bounds[b][1].x - state.radius;
}
if(state.oldAxis === state.x) {
state.y = state.y - 1;
} else {
state.oldAxis = state.x;
}
}
}
if (state.y + state.radius > state.widgetSize.height) {
state.vy *= -1;
state.y = state.widgetSize.height - state.radius;
}
if (state.y - state.radius < 0) {
state.vy *= -1;
state.y = state.radius;
}
return state;
}
export default colisionSystem;
So, question is, is there any practical advice to improve this code itself?
You have 500 * 150 particles (750000) that is a lot and frankly too much for a JS app to handle. (BUT you say 150 particles so I am somewhat confused as to what you are doing)
To increase the performance of the function provided you can use a few simple rules of thumb.
An array lookup is slower than a direct referance.
ie
// a compound referance
state.bounds[b][1].x // find state, then bounds, then index b, then 1,then x
// if you have the common parts used frequently
var b1 = state.bounds[b][1]; // does all the lookups
b1.x; // but now only needs one lookup to get x
Same is true for item properties. Each . means an additional lookup. You can get a lot of extra performance by creating temporary variables to hold the result of all the lookups.
Applying this to your code and you get
const colisionSystem = state => {
var w = state.widgetSize.width; // used many times in the loop
var h = state.widgetSize.height;
var x = state.x;
var y = state.y;
var r = state.radius;
for (var b = 0, hs = state.bounds.length; b < hs; b++) {
var bounds = state.bounds[b];
if (bounds[0]){
var b0 = bounds[0];
if( x - r < b0.x && y + r > b0.y && y - r < b0.y) {
if ( b0.x > 0 && b0.x < (w * 0.33) && b0.y > (h * 0.33) && b0.y < (w * 0.45)) {
x = r + b0.x; // middle left bottom corner at acre
state.vy *= -1;
} else if ( b0.x > 0 && b0.x < (w * 0.098) && b0.y > (h * 0.167) && b0.y < (w * 0.206)) {
y = r + b0.y + 1; // middle left top corner at acre
state.vx *= -1;
state.vy *= -1;
} else {
x = r + b0.x;
state.vx *= -1;
}
if (state.oldAxis === x) {
y -= 1;
} else {
state.oldAxis = x;
}
state.antiRebounce = false;
}
}
if (bounds[1]){
var b1 = bounds[1];
if( x + r > b1.x && y + r > b1.y && y - r < b1.y) {
if ( b1.x > (w * 0.555) && b1.x < (w * 0.983) && b1.y > 0 && b1.y < (w * 0.2098)) {
if (state.antiRebounce) { // Top right corner
state.vy *= -1;
state.antiRebounce = false;
} else {
state.antiRebounce = true;
}
y = b1.y + r + 1;
state.vy *= -1;
}
if (b1.x > (w * 0.604) && b1.x < (w * 0.827) && b1.y > (w * 0.665) && b1.y < (w * 0.778)) {
state.vy *= -1; // bottom right corner
} else {
state.vx *= -1;
x = b1.x - r;
}
if (state.oldAxis === x) {
y = y - 1;
} else {
state.oldAxis = x;
}
}
}
}
if (y + r > h) {
state.vy *= -1;
y = h - r;
} else if (y - r < 0) { // added else. Cant both happen at the same time?????
state.vy *= -1;
y = r;
}
state.y = y; // set state x,y to reflect any changes.
state.x = x;
return state;
}
By replacing the many compound references with direct references you can free up a lot of CPU time, but this will depend on the data. If the code above spends more time rejecting the conditional statements early you will not get much of a benefit, and if the bounds[1],bounds[0] conditions are only passed once a call to colisionSystem you will get no benefit, and maybe a slight decrease in performance. Same holds true for the number of items in the loop, if the loop is iterating many items you will see an improvement, if the loop is only 1 or 2 items you will see no benefit and for one item you will see a decrease in performance.
Note, I was not careful refactoring, there may be some typos in above code and is only an example.
You say you use SVG to iterate???
I also use a SVG to iterate over the pixels
Rule of thumb #2. The DOM is slow, SVG is part of the DOM and in my book SVG is really VSG (Very Slow Graphics) and I would say that the main part of the slow down is in whatever you do with the SVG.
Recommendations:
This function is doing way too much, consider breaking this up so that expressions like this
state.bounds[b][1].x > (state.widgetSize.width * 0.604)
&& state.bounds[b][1].x < (state.widgetSize.width * 0.827)
&& state.bounds[b][1].y > (state.widgetSize.width * 0.665)
&& state.bounds[b][1].y < (state.widgetSize.width * 0.778)
Are their own function
Consider using _.map instead of the for loop, you are return state anyway, so _.map would be more semantic.
Consider breaking up the nesting of ifs with functions. Every time you use a code comment, consider making it a function. For example
// Top right corner
if(state.antiRebounce) {
state.vy *= -1;
state.antiRebounce = false;
} else {
state.antiRebounce = true;
}
state.y = state.bounds[b][1].y + state.radius + 1;
state.vy *= -1;
could be
const topRightCorner = (state) => {
if(state.antiRebounce) {
state.vy *= -1;
state.antiRebounce = false;
} else {
state.antiRebounce = true;
}
state.y = state.bounds[b][1].y + state.radius + 1;
state.vy *= -1;
return state;
}
Once you broken this giant function into many smaller easier to understand functions. You can use chrome profiling tools to find bottlenecks for performance.
https://developers.google.com/web/tools/chrome-devtools/rendering-tools/
With the code broken up, it will be easy to see what part of the script is suffering from performance problems, at that point you can see about how to fix, without doing pre-mature optimization.
AS far as I know (but I don't know so much) your situation doesn't give us so much space of manovre because you need to "see" always in the screen all that bouncing objects. The bound system seems good for me checking for the bounds before a more accurate point collision detection.
How many fps are you trying to render? Maybe something could be tuned I guess.
Bye