Hi everyone I started to write a little game with ball and bricks and have some problem with collision detection. Here is my code http://jsbin.com/ibufux/9 . I know that detection works though array, but I can't figure how I can apply it to my code.
Here is what i have tried:
bricksCollision: function() {
for (var i = 0; i < $bricks.length; i++) {
if ($ball.t == $bricks[i].offset().top) {
$bricks[i].splice(i, 1);
}
}
Every bricks in game are generated by for loop and then goes to $bricks array. Every brick after generating receive top and left position and have position absolute. I have tried to check if $ball.t (it's properties of my ball object which detects ball top position) reach the bricks and than remove bricks.
Thanks for any help. I'm only start to learn JS that's why my code is
knotty.
First of all, let'stalk about some code errors
$ball.t should be probably $ball.top
you do not need to have $ as a prefix, for your code, it's simply a variable and you are calling $ball instead of ball witch results in assumption errors!
for those assumption errors here is what you are doing wrong:
$ball is a dom element, not a jQuery element
the same as $bricks
$ball is an array
with those concluded from some console.log() let's try to fix the code:
the $ball should be called, as there's only one, by it's array element, as $ball[0] and because you have variables pointing to DOM elements and not jQuery elements, you need to wrap it in Jquery as:
if ( $($ball[0]).top === $($bricks[i]).offset().top ) { ...
a good idea not to get confused is only use $ in jQuery elements, prefixing it in a variable, does not make them a jQuery Element.
And everytime you see that you have an error such as "element x has no method y" always assume that you're calling a method from a DOM element, and not a jQuery element.
Now, that #balexandre has nicely explained some points about your code, lets examine how we can compute the collision.
Imagine 2 Ranges overlapping each other (Range a partly overlaps Range b)
[100 .|.. 300]
[200 ..|. 400]
The part overlapping is from | to | -> 200 to 300, so the size of the overlap is 100
If you look at the numbers, you notice, that the overlap could be seen like,
Take the smaller number of the right side -> 300
Take the greate number of the left side -> 200
Subtract them from each other -> 300 - 200 = 100.
Lets take a look at 2 other situations. (Range b completely in Range a)
[50 ... 150]
[75...125]
So the values we have are: Math.min (150,125) //125 for the end value and Math.max (50,75) // 75 for the start value, resulting in a value of 125 - 75 = 50 for the overlap
Let's take a look the last example (Range a not in Range b)
[50 ... 150]
[200 ... 300]
Using the above formula, yields the result Math.min (150 , 300 ) - Math.max (50,200) // -50 which absolutes' value is the gap between the 2 Ranges, 50
Now we can add a last condition, as you want to compute the collision, only values > 0 are of interest for us. Given this we can put it into one condition.
Math.min ((Brick["Right"],Ball["Right"]) - Math.max (Brick["Left"], Ball["Left"]) > 0)
Which will yield true if the elements' overlap and false if they don't.
Applying this to your code, we could compute the collision the following way
bricksCollision: function () {
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l]; //brick left pos
brickBounds[1] = brickBounds[0] + 40 //bricks right pos -> left pos + .bricks.width;
var ballBounds = [ball.l]; //balls left pos
ballBounds[1] = ballBounds[0] + 20 //balls right pos -> left pos + #ball.width;
if (ball.t <= (offset.top + 20) && (Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0])) > 0) {
$bricks[i].style.opacity = 0; //Make the brick opaque so it is not visible anymore
$bricks.splice(i, 1) //remove the brick from the array -> splice on the array, not the element
return true;
}
}
}
With this we could return true to the move function, when the Ball collides with a Brick.
But hey, we want it to Bounce off in the right direction, so we will face another problem.
So rather then returning a Boolean value whether the Brick collides or not, we could return a new direction in which the Ball will should move.
To be able to easily change only the x or the y part of the direction, we should use something like a vector.
To do so, we could use 2 Bits of an Integer, where the bit b0 stays for the x direction and the bit b1 for the y direction. Such that.
Dec Bin Direction
0 -> 00 -> Down Left
^ -> Left
^ -> Down
1 -> 01 -> Down Right
^ -> Right
^ -> Down
2 -> 10 -> Up Left
^ -> Left
^ -> Up
3 -> 11 -> Up Right
^ -> Right
^ -> Up
But to be able to change only a part of the direction, we need to pass the old direction to the collision function, and use bitwise & and | respectively to turn them off or on
Also we have to compute from which side the ball collides.
Fortunatly we have overlap calculation from before, which already uses all values we need, to compute the direction of collision.
If it comes frome the
right
Brick ["Right"] - Ball["Left"] has to be the same value as the overlap.
left
Ball ["Right"] - Brick["Left"] has to be the same value as the overlap.
If none of them are true, it has to either come from the
bottom
if Ball["Top"] is more than ( Brick["Top"] plus half of the Brick["height"] )
or else from the top.
To reduce the range where the condition, for the collision from the side, evaluates to true we can add another condition that the overlap has to be less than e.g ... && overlap < 2
So if it collides with the edge it doesn't always bounce of to the side.
So enough the talking, in code this could look like something like this.
bricksCollision: function (direction) {
var newDirection = direction
var ballBounds = [ball.l]; //balls left pos
ballBounds[1] = ballBounds[0] + 20 //balls right pos -> left pos + #ball.width;
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l]; //brick left pos
brickBounds[1] = brickBounds[0] + 40 //bricks right pos -> left pos + .bricks.width;
var overlap = Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0]);
if (ball.t <= ((offset.top - field.t) + 20) && overlap > 0) {
$bricks[i].style.opacity = 0; //Make the brick opaque so it is not visible anymore
$bricks.splice(i, 1) //remove the brick from the array -> splice on the array, not the element
if (ballBounds[1] - brickBounds[0] == overlap && overlap < 2) { //ball comes from the left side
newDirection &= ~(1); //Turn the right bit off -> set x direction to left
} else if (brickBounds[1] - ballBounds[0] == overlap && overlap < 2) { //ball comes from the right side
newDirection |= 1; // Turn the right bit on -> set x direction to right;
} else {
if (ball.t > (offset.top + (20 / 2))) //Ball comes from downwards
newDirection &= ~(2) // Turn the left bit off -> set y direction to down;
else //Ball comes from upwards
newDirection |= 2; // Turn the left bit on -> set y direction to up;
}
//console.log("Coming from: %s Going to: %s", field.directionsLkp[direction], field.directionsLkp[newDirection], direction)
return newDirection;
}
}
return direction;
}
To get that to work, we should also change the moveXX functions, to use the new direction, returned.
But if we are going to get the new direction from the collision function anyway, we could move the complete collision detection to the function, to simplify our move functions.
But before that, we should have a look at the move functions and, add a lookup object to field which holds the numbers for the direction, to maintain readability.
var field = {
directions: {
uR : 3, // 11
dR : 1, // 01
dL : 0, // 00
uL : 2 // 10
},
directionsLkp: [
"dL","dR","uL","uR"
],
...
}
Now the move functions could then look like this,
ballCondact: function () {
var moves = [moveDl,moveDr,moveUl,moveUr]
var timeout = 5;
function moveUr() {
var timer = setInterval(function () {
$ball.css({
top: (ball.t--) + "px",
left: (ball.l++) + "px"
})
var newDirection = game.bricksCollision(field.directions.uR) //get the new direction from the collision function
if (newDirection !== field.directions.uR) {
clearInterval(timer);
moves[newDirection](); //move in the new direction
}
}, timeout);
}
...
}
Like this, the move function simply changes the direction if the collision function returns a direction which differs from the current one.
Now we can start moving the wall collisions to the collision function, to do this we could add another check at the beginning.
bricksCollision: function (direction) {
...
if (ball.t <= field.t)
newDirection &= ~(2); //Ball is at top, move down
else if (ball.l <= 0) //Ball is at the left, move right
newDirection |= 1;
else if (ball.t >= field.b - ball.height) //Ball is at the bottom, move up
newDirection |= 2;
else if (ball.l > field.width - ball.width) //Ball is at the right, move left
newDirection &= ~(1);
if (direction !== newDirection)
return newDirection
...
}
Note, i left out the collision check for the platform, as the idea should be clear =)
Here is a Fiddle
Related
I'm creating a simulator with the Pixijs engine.
I have a function that is is to be used to draw a wall using the mouse. But I just can't seem to get it right. This is probably more of a math issue than programming.
Anyway, it should work like this:
User clicks on a square tile (start position is set)
Tink library for Pixi returns the (x,y) position just clicked on
relative to the canvas
User clicks on second square (in same row or column) and the
destination point is set
Please take a look at this Fiddle.
https://jsfiddle.net/ensf32e0/18/
I can get it to draw from left to right and from top to bottom. But right to left and bottom to top fail me.
I'm using an object with booleans to keep track of whether the user is putting down a start position or an end position. I'm not sure this isa good implementation.
let wallsObj={
start:{
x:0,
y:0,
done:false
},
end:{
x:1,
y:1,
done:false
}
};
drawTile draws a single tile and drawWallLine is the function with the problem. It takes the start and end positions and draws a tiled line between them:
function drawWallLine (obj,size) {
// Determine whether line is to be drawn horizontally or vertically
// if abs(x2-x1) is larger than abs(y2-y1) then horizontal else vertical
// assign len the the actual length of line
let len = Math.abs(obj.end.x - obj.start.x) > Math.abs(obj.end.y - obj.start.y)
? obj.end.x - obj.start.x
: obj.end.y - obj.start.y;
console.log('drawWallLine', len);
// same as above. If direction is horizontal, mx = 1 and my = 0 and vice versa
// this to be used to determine the polarity of size
let mx = Math.abs(obj.end.x - obj.start.x) > Math.abs(obj.end.y - obj.start.y) ? 1 : 0;
let my = Math.abs(obj.end.x - obj.start.x) < Math.abs(obj.end.y - obj.start.y) ? 1 : 0;
console.log("mx, my", mx, my);
// Get polarity of size. +size is going down or right while -size is going up or left
if (mx === 1) {
size = obj.end.x - obj.start.x >= 0 ? size : size * -1;
}
if (my === 1) {
size = obj.end.y- obj.start.y >= 0 ? size : size * -1;
}
console.log('size', size);
// If going down or right then
if (size >=0 ) {
for (let i = 0; i < Math.abs(len); i+=size) {
drawTile({
len: rs,
x: obj.start.x - obj.start.x%rs - .5 + i * mx,
y: obj.start.y - obj.start.y%rs - .5 + i * my,
line:{
width:1,
color:0xC2C2C2,
alpha:1
},
fill:{
color:0xFFFFFF,
alpha:1
}
});
}
} else { // if going up or left
for (let i = Math.abs(len); i > 0; i+=size) {
drawTile({
len: rs,
x: obj.start.x - obj.start.x%rs - .5 + i * mx,
y: obj.start.y - obj.start.y%rs - .5 + i * my,
line:{
width:1,
color:0xC2C2C2,
alpha:1
},
fill:{
color:0xFFFFFF,
alpha:1
}
});
}
}
}
This is my first time doing something like this so please bear with me. I feel like there's an obvious solution but i'm failing to see it.
Well, a simple fix to your problem is just to make sure that the start x/y is always the lower then the end x/y value. So I added the following code to the beginning of the drawWallLine function:
function drawWallLine (obj, size) {
if(obj.start.x > obj.end.x){
var temp = obj.start.x;
obj.start.x = obj.end.x;
obj.end.x = temp;
}
if(obj.start.y > obj.end.y){
var temp = obj.start.y;
obj.start.y = obj.end.y;
obj.end.y = temp;
}
This basically makes sure that the start value is always the lower value by swapping between start and end if start is bigger than end.
Here is the updated working fiddle: https://jsfiddle.net/ensf32e0/24/
So, I am currently reinventing the wheel (and learning a lot) by trying my hand at making a simple physics engine for my game engine. I have been searching the internet, trying (and failing) to fix my current problem. There are a lot of resources out there on the subject, but none of those that I have found seem to apply to my case.
THE PROBLEM IN SHORT: The collision resolution does not work as intended on some of the corners when two rectangles are colliding. How it fails varies based on the dimensions of the rectangles. What I am looking for is a "shortest overlap" kind of resolution for the collision or another fairly simple solution (I am open for suggestions!). (Scroll down for a better explaination and illustrations).
WARNING: The following code is probably not very efficient...
First of all, here is my physics loop. It simply loops through all of the game entities and checks if they collide with any other game entities. It is not efficient (n^2 and all of that), but it works for now.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target);
break;
}
}
}
}
}
}
}
},
Here is the code for the actual collision detection. This also seems to work alright.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is where the problems start to pop up. I want the entity to simply be "pushed" out of the target entity and have the velocity set to 0. This works fine as long as both the entity and the target are perfect squares. If let's say the entity (the player figure in the gif) is a rectangle, then the collision will "slipp" when colliding the longest sides (the X axis) with the target (the square). If I swap the player dimensions so that it is short and wide, then the same problem appears for the Y axis instead.
resolveTestTest: function(entity, target) {
var normalizedX = (target.getMidX() - entity.getMidX());
var normalizedY = (target.getMidY() - entity.getMidY());
var absoluteNormalizedX = Math.abs(normalizedX);
var absoluteNormalizedY = Math.abs(normalizedY);
console.log(absoluteNormalizedX, absoluteNormalizedY);
// The collision is comming from the left or right
if (absoluteNormalizedX > absoluteNormalizedY) {
if (normalizedX < 0) {
entity.pos.x = target.getRight();
} else {
entity.pos.x = target.getLeft() - entity.dim.width;
}
// Set velocity to 0
entity.vel.x = 0;
// The collision is comming from the top or bottom
} else {
if (normalizedY < 0) {
entity.pos.y = target.getBottom();
} else {
entity.pos.y = target.getTop() - entity.dim.height;
}
// Set velocity to 0
entity.vel.y = 0;
}
},
Collision on the Y axis works with these shapes
Collision on the X axis slips with these shapes
What can I do to fix this slipping problem? I have been bashing my head against this for the last 5 days, so I would be immensely grateful if some one could help push me in the right direction!
Thank you :)
-- EDIT: --
The slipping also happens if only moving in one direction along the left or right side.
-- EDIT 2 WORKING CODE: --
See my answer below for an example of the working code!
The important logical error you have made is this line:
if (absoluteNormalizedX > absoluteNormalizedY) {
This only works if both entities are square.
Consider a near-extremal case for your X-slipping example: if they almost touch at the corner:
Although the diagram is a little exaggerated, you can see that absoluteNormalizedX < absoluteNormalizedY in this case - your implementation would move on to resolve a vertical collision instead of the expected horizontal one.
Another error is that you always set the corresponding velocity component to zero regardless of which side the collision is on: you must only zero the component if is it in the opposite direction to the collision normal, or you won't be able to move away from the surface.
A good way to overcome this is to also record the collided face(s) when you do collision detection:
collideEntity: function(entity, target) {
// adjust this parameter to your liking
var eps = 1e-3;
// no collision
var coll_X = entity.getRight() > target.getLeft() && entity.getLeft() < target.getRight();
var coll_Y = entity.getBottom() > target.getTop() && entity.getTop() < target.getBottom();
if (!(coll_X && coll_Y)) return 0;
// calculate bias flag in each direction
var bias_X = entity.targetX() < target.getMidX();
var bias_Y = entity.targetY() < target.getMidY();
// calculate penetration depths in each direction
var pen_X = bias_X ? (entity.getRight() - target.getLeft())
: (target.getRight() - entity.getLeft());
var pen_Y = bias_Y ? (entity.getBottom() - target.getUp())
: (target.getBottom() - entity.getUp());
var diff = pen_X - pen_Y;
// X penetration greater
if (diff > eps)
return (1 << (bias_Y ? 0 : 1));
// Y pentration greater
else if (diff < -eps)
return (1 << (bias_X ? 2 : 3));
// both penetrations are approximately equal -> treat as corner collision
else
return (1 << (bias_Y ? 0 : 1)) | (1 << (bias_X ? 2 : 3));
},
updatePhysics: function(step) {
// ...
// pass collision flag to resolver function
var result = Vroom.collideEntity(entity, target);
if (result > 0) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target, result);
break;
}
}
// ...
}
Using a bit flag instead of a boolean array for efficiency. The resolver function can then be re-written as:
resolveTestTest: function(entity, target, flags) {
if (!!(flags & (1 << 0))) { // collision with upper surface
entity.pos.y = target.getTop() - entity.dim.height;
if (entity.vel.y > 0) // travelling downwards
entity.vel.y = 0;
}
else
if (!!(flags & (1 << 1))) { // collision with lower surface
entity.pos.y = target.getBottom();
if (entity.vel.y < 0) // travelling upwards
entity.vel.y = 0;
}
if (!!(flags & (1 << 2))) { // collision with left surface
entity.pos.x = target.getLeft() - entity.dim.width;
if (entity.vel.x > 0) // travelling rightwards
entity.vel.x = 0;
}
else
if (!!(flags & (1 << 3))) { // collision with right surface
entity.pos.x = target.getRight();
if (entity.vel.x < 0) // travelling leftwards
entity.vel.x = 0;
}
},
Note that unlike your original code, the above also allows corners to collide - i.e. for velocities and positions to be resolved along both axes.
MY WORKING CODE
So with some help and guidance from the amazing #meowgoesthedog I finally got on the right track and found what I was looking for. The problem (as #meowgoesthedog pointed out) was that my code was really only going to work with squares. The solution was to check the intersection of the colliding bodies and solve based on the shortest intersection. Note: this will probably not be a suitable solution if you need accurate physics with small and fast moving objects. The code for finding the intersection depth is based on this: https://github.com/kg/PlatformerStarterKit/blob/0e2fafb8dbc845279fe4116c37b6f2cdd3e636d6/RectangleExtensions.cs which is related to this project: https://msdn.microsoft.com/en-us/library/dd254916(v=xnagamestudio.31).aspx.
Here is my working code:
My physics loop has not been changed much, except for some better names for some functions.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveDisplace(entity, target);
break;
}
}
}
}
}
}
}
},
The collision detection remains the same as well.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is the code that basically fixes the problem. The comments in the code should explain what it does pretty well.
getIntersectionDepth: function(entity, target) {
// Calculate current and minimum-non-intersecting distances between centers.
var distanceX = entity.getMidX() - target.getMidX();
var distanceY = entity.getMidY() - target.getMidY();
var minDistanceX = entity.halfDim.width + target.halfDim.width;
var minDistanceY = entity.halfDim.height + target.halfDim.height;
// If we are not intersecting at all, return 0.
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY) {
return {
x: 0,
y: 0,
};
}
// Calculate and return intersection depths.
var depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
var depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return {
x: depthX,
y: depthY,
};
},
Here is the updated resolving function. It now takes intersection depth in to account when determining axis of collision and then uses the sign of the intersection depth for the colliding axis when determining the direction to resolve.
resolveDisplace: function(entity, target) {
var intersection = Vroom.getIntersectionDepth(entity, target);
if (intersection.x !== 0 && intersection.y !== 0) {
if (Math.abs(intersection.x) < Math.abs(intersection.y)) {
// Collision on the X axis
if (Math.sign(intersection.x) < 0) {
// Collision on entity right
entity.pos.x = target.getLeft() - entity.dim.width;
} else {
// Collision on entity left
entity.pos.x = target.getRight();
}
entity.vel.x = 0;
} else if (Math.abs(intersection.x) > Math.abs(intersection.y)) {
// Collision on the Y axis
if (Math.sign(intersection.y) < 0) {
// Collision on entity bottom
entity.pos.y = target.getTop() - entity.dim.height;
} else {
// Collision on entity top
entity.pos.y = target.getBottom();
}
entity.vel.y = 0;
}
}
},
Thank you all for your help!
The problem may be that you're correcting both X and Y collision based on the same position:
Player is at a certain position. Let's check collision.
Player's bottom right corner overlaps top left corner of object.
X position is corrected: Player is moved to the left.
Player's bottom right corner overlaps top left corner of object.
Y position is corrected: Player is moved up.
End result: The player is moved up and to the left.
You probably need to "get" the player's position again, between checks.
I have a 2D voxel map for a game, which is a 2D array where 1 means ground and 0 means sky.
Example: all 1's in the array (ground) are green boxes
The algorithm starts at the leftmost ground voxel that touches the sky (red box in picture).
It will explore 8 neighbours of the current position to check if one of them is a ground voxel and also touches a sky voxel. This means it should be added to the groundline.
Example of the algorithm working (it's able to go in 'caves' too)
On this map it figured it out and returned a line across the ground.
In some situations it suddenly stops though, like on this map:
After about 10 loops it stopped creating the line.
Here's the code, with some explanatory comments in there:
voxelToLine() {
let voxels = this.voxels.length,//this.voxels is the 2d array
lineGround = [],
checkedVoxels = [],
nowChecking,
toCheck = [],
otherPaths = [],
done = false;
for (let y = 1; y < voxels - 1; y++)//sets first coordinate for line
if (this.voxels[0][y] && (!this.voxels[0][y - 1] || !this.voxels[1][y] || !this.voxels[0][y + 1])) {
lineGround[0] = [0, y / voxels];
nowChecking = [1, y];//search starts from this point
}
let looped = 0;
while (!done) {//continues search untill right side is located, or it got stuk (max 10*voxelmap width loops)
toCheck = nowChecking.neighbours(8, (n) => n[0] > 0 && n[0] < voxels - 1);//gets 8 neighbour points around current point, neighbours between 1 and (voxelwidth -1) get returned
let foundNew = false;
for (let i = 0; i < toCheck.length; i++) {//check every neighbour
let x = toCheck[i][0],
y = toCheck[i][1],
index = y * voxels + x;
if (!checkedVoxels.includes(index)) {
if (this.voxels[x][y] && (!this.voxels[x][y - 1] || !this.voxels[x + 1][y] || !this.voxels[x - 1][y] || !this.voxels[x][y + 1])) {
//if the neighbour is a floor voxel, and touches a skyvoxel this neighbour is added to the line
checkedVoxels.push(index);
if (foundNew) {//if a valid neighbour is already found, this means there are 2 possible paths from the current point
otherPaths.push([x, y]);
} else {
lineGround.push([x / voxels, y / voxels]);
nowChecking = [x, y];
//valid point gets added to the line and currently explored point get updated
foundNew = true;
}
if (x >= voxels) done = true;
}
} else if (i == toCheck.length - 1 && !foundNew) {
if (otherPaths.length > 0) {
nowChecking = otherPaths.pop();
//if none of the neighbours are correct an alternative path gets explored
foundNew = true;
}
}
}
if (!foundNew || looped++ > voxels * 10) {
//if it never found a valid neighbour, or it's looped too often break from the whileloop
console.log('loops: ', looped);
break;
}
}
if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]);
if (lineGround[lineGround.length - 1][0] !== 1) lineGround.push([1, lineGround[lineGround.length - 1][1]]);
//x=0 and x=1 have to exist, so if they don't exist yet, add them
return lineGround;
}
You can also test it here: game. If you click you remove (set to 0) a few voxels within a radius of where you clicked. Also the line gets recalculated.
I'm stuck on this, because I have no idea why the line stops in some situations.
All code is here. The relevant file is js/Level.js
There are more problems than the one you raised. I played a bit on your site and there are many patterns where things go wrong.
I tried to follow the logic of your code, but got lost in details. So I rewrote most of the code. The main idea is that you should keep record of which direction (slope) you are travelling along the ground in order to know in which order you should look among the neighbours for one that is part of the ground.
Let's say the neighbours are numbered as follows, from 0 to 7:
+---+---+---+
| 7 | 0 | 1 |
+---+---+---+
| 6 | * | 2 |
+---+---+---+
| 5 | 4 | 3 |
+---+---+---+
The cell marked with * is the last cell you found to be on ground level. Now let's say the previous one found was at 6, then the search among the neighbours should start at 7, then 0, 1, 2, ... 5. The first one that is found to be solid, should be the next cell added to ground level.
Another example: if the previous one found was at 4 (we're going upward), then the neighbours should be searched starting at 5, then 6, 7, 0, 1, 2 and 3.
The first neighbour that is found to be solid (ground) is the one you want to add to your ground line. This way you will follow every curve, into "caves", upward or downward, left or right.
Of course, things can still go weird if you start on an island. But I did not attempt to solve that particular case.
I've implemented the above idea in the following version of your method:
voxelToLine() {
let voxels = this.voxels.length, x, y, i;
// neighbors' relative coordinates listed in clockwise order
const neighbor = [ [0,-1], [1,-1], [1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1] ];
for (y = 0; y < voxels; y++) //sets first coordinate for line.
if (this.voxels[0][y]) break; // found ground, don't look further down
let lineGround = [[0, y / voxels]];
let [curX, curY] = [0, y]; //search starts here
let direction = 0; // upward
let looped = 0;
do {// Continues search until right side is located,
// or it got stuk (max 10*voxelmap width loops)
for (i = 0; i < 8; i++) {//check every neighbour, starting at `direction`
[x, y] = [curX + neighbor[direction][0], curY + neighbor[direction][1]];
// if we found ground, then pick that cell as the next one on the line
if (x>=0 && x<voxels && y>=0 && y<voxels && this.voxels[x][y]) break;
direction = (direction + 1) % 8; // turn clockwise to get next neighbour
}
//if it never found a valid neighbour
if (i === 8) break;
lineGround.push([x / voxels, y / voxels]);
// prepare for next round
[curX, curY] = [x, y];
direction = (direction + 5) % 8;
} while (looped++ <= voxels*10 && curX < voxels - 1);
//x=0 and x=1 have to exist, so if they don't exist yet, add them
if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]);
if (lineGround[lineGround.length - 1][0] !== 1)
lineGround.push([1, lineGround[lineGround.length - 1][1]]);
return lineGround;
}
Looks like it's skipping over the voxel right below the last legitimate ground voxel because it's already been "checked" (added to the checkedVoxels array).
Interestingly, this would prevent your ground path to ever turn 90 degrees (you'll notice your example picture doesn't have such a voxel pattern).
I don't know a lot about JS, but i have to do assignments with it. right now i have a ball that bounces from one side of the screen to the other. with every bounce the colour of the screen and the ball change. but i'd like a slight increase of speed with every bounce as well(or a random speed every time it bounces if that's easier). this is the code I have for moving, the bouncing and the colour changing now:
fill(r,g,b);
ellipse(circleX, circleY, circleSize, circleSize);
circleX += moveX;
if (circleX > width - circleSize / 2 || circleX < circleSize / 2) {
moveX = -moveX;
r = random(255);
g = random(255);
b = random(255);
}
moveX is always 5 now and changes to -5 when turning back. but i'd like it if it turned into -6 and then +7 when going forward again. or something like that at least.
I thank you guys in advance for helping me and please explain it like you're explaining it to a child.
First, lets make a function which takes a number and returns +1 for non-negative numbers (positive or 0) and -1 for negative numbers, i.e. it's sign
function sign(x) {
if (x < 0) return -1;
return 1;
}
A full implementation of sign would have a special case for 0, and is available natively in ES6
Next, when it becomes time to change moveX separate it's magnitude (absolute value) and sign, increment it's magnitude and put the two pieces back together again before flipping the sign over
moveX = -sign(moveX) * (Math.abs(moveX) + 1);
You'll want to add another test inside your collision detection code to increase the speed. If the velocity is positive, then you want to add 1. If the velocity is negative, you want to subtract 1. Your code would look something like this...
...
moveX = -moveX
if (moveX < 0) {
--moveX;
} else {
++moveX;
}
...
Keep track of how many times the circle has "bounced" and add it to the speed.
var base_speed = 5;
var bounces = 0;
var direction = 1; //positive for moving right, negative for moving left
var moveX = base_speed + bounces * direction;
circleX += moveX;
if (circleX > width - circleSize / 2 || circleX < circleSize / 2) {
direction = -direction;
bounces++;
r = random(255);
g = random(255);
b = random(255);
}
I want to style the second elment in this array by adding a CSS Property
here is a global variable to define the array
<canvas id="canvas"></canvas>
<script>
var paddles = [2], // Array containing two paddles
function update() {
// Update scores
updateScore();
// Move the paddles on mouse move
// Here we will add another condition to move the upper paddle in Y-Axis
if(mouse.x && mouse.y) {
for(var i = 1; i < paddles.length; i++) {
p = paddles[i];
// the botoom paddle
if (i ==1){
p.x = mouse.x - p.w/2;
}else{
// the top paddle
//paddles[2].x = mouse.x - p.w/2;
debugger
paddles[2].style.backgroundColor="red";
}
}
}
and here what the style I want
paddles[2].style.backgroundColor="red";
when I use the debugger I face this problem
TypeError: paddles[2].style is undefined
UPDATE:
Since it looks like you are creating some kind of "pong" or "breakout" game, I decided to take my first stab at HTML canvas and do it myself for fun. Here is a simple version that shows how to:
draw two paddles on a canvas (original author)
keep track of the "boxes" for the paddles in an array (original author)
use a loop via setInterval to redraw the canvas as it gets updated (original author)
use the keyboard to move shapes around on HTML canvas (my code)
See the fiddle for working demo and full code: http://jsfiddle.net/z4ckpcLc/1/
I will not post the full code because I didn't write most of it.. I used the example from this site for the code for drawing the boxes and for keeping track of them in an array: http://simonsarris.com/project/canvasdemo/demo1.html
The function I added to this example is the arrowKeyMove() handler, wired up to the onkeydown event of document.body via this line: document.body.onkeydown = arrowKeyMove;
function arrowKeyMove(e) {
var direction = 0; // -1 means left, 1 means right, 0 means no change
var moveFactor = 10; // higher factor = more movement when arrow keys pressed
var arrowKeyUsed = false; // to indicate which 'paddle' we are moving
switch (e.which) {
case 37:
// left arrow (upper paddle)
direction = -1;
arrowKeyUsed = true;
break;
case 39:
// right arrow (upper paddle)
direction = 1;
arrowKeyUsed = true;
break;
case 65:
// "a" key for left strafe (lower paddle)
direction = -1;
break;
case 68:
// "d" key for right strafe (lower paddle)
direction = 1;
break;
}
var boxIndex = 1; // box index defaults to lower paddle
if (arrowKeyUsed) { // if using arrow keys, we are moving upper paddle
boxIndex = 0;
}
var maxX = 240; // constrain movement to within 10px of box borders (240 == canvas width minus paddle width minus padding)
var minX = 20;
var box = boxes[boxIndex]; // grab the box; we will update position and redraw canvas
if((direction < 0 && box.x >= minX) || (direction > 0 && box.x <= maxX))
{
// move the box in the desired direction multiplied by moveFactor
box.x = box.x + (direction * moveFactor);
invalidate(); // invalidate canvas since graphic elements changed
}
}
ORIGINAL ANSWER:
Array items use zero-based indexing.
If you only have two paddles like you said, you must use index 1, not 2. And if you want to access the first paddle, use 0, not 1. You probably want your for loop to use var i=0 instead, and basically change places you are checking 1 to 0.
For example:
paddles[0].style.backgroundColor="red"; // paddle 1
paddles[1].style.backgroundColor="red"; // paddle 2
Also, var array = [2] does not create a two-array element. It creates a one-array element with an integer value of 2
For DOM elements you may want something like this:
<div id='paddle1'></div>
<div id='paddle2'></div>
<script type='text/javascript'>
var paddles = [];
paddles[0] = document.getElementById('paddle1');
paddles[1] = document.getElementById('paddle2');
paddles[0].style.backgroundColor="red"; // paddle 1 is red
paddles[1].style.backgroundColor="orange"; // paddle 2 is orange
</script>
I'm not sure, but maybe you can use something like this:
paddles[2].css('background-color','red');
edit: now I see you don't use jQuery, so my solution wouldn't work