Handle movement after collision is detected - javascript

I'm making a small platformer in js and I'm having trouble with the collisions. Unfortunately it seems that 90% of the info online is detecting the collisions, and not what comes after. I can easily detect collisions as everything in my game is an axis aligned 2d rectangle, but handlining those collisions is the hard part.
I've tried moving the player up to the nearest floor when a collision is detected, but it also happens when you collide with a wall. So I tried calculating the closest face and snapping the player there, but it leads to all kinds of weirdness. Here is the code I have so far (the current code is just for floor collisions now, but the same principal can be applied to the rest of the directions)
if (collided) {
let ld = {'a': 'l', 'b': Math.abs(player.left.x - col.left.x)}
let rd = {'a': 'r', 'b': Math.abs(player.right.x - col.right.x)}
let td = {'a': 't', 'b': Math.abs(player.top.y - col.top.y)}
let bd = {'a': 'b', 'b': Math.abs(player.bottom.y - col.bottom.y)}
let dirs = [ld, rd, td, bd]
let nearestFace = dirs.hasMin('b').a
if (nearestFace == 'b') {
player.grounded = true
player.yvel = 0
player.pos.y = col.top.y + player.size.y/2
} else {
player.grounded = false
}
}

Your code seems to not check where the nearest face collides just that it is the nearest face after collision. Which could cause this kind of problem:
If your game is in 2d, everything is a rectangle, and nothing has an angle, then you have a collision problem known as AABB (Axis-Aligned Bounding Boxes) collision, it's a great keyword to search for in your case and an amazing starting point.
First, I advise you to predict collisions instead of dealing with them after they happen because of potential problems like this:
Moreover, if you wish an advanced tutorial on this, the above image comes from this tutorial: Swept AABB Collision Detection and Response. It probably has everything you need.
In short, the input for your collision handling logic should be your objects old position, current size, its velocity, and all other collidable objects (their size and positions). The response should be the expected behavior of your object (i.e. the new velocity, corrected for 'avoiding' collision). That way you can easily test scenarios and your implementation with ease, consider this:
const player = {x: 0, y: 0, width: 10, height: 10, xvel: 12, yvel: 0};
// player.right = {x: player.x + player.width, y: player.y} // Your engine does this, right?
const collidableList = [{x: 15, y: 0, width: 5, height: 10}];
const newVelocity = handleCollision(player, collidableList);
I imagine that the player position is aligned at top left, so in this case you can predict that your new velocity ought to be xvel = 5, yvel = 0. Which means you just have to create a handleCollision that works for this test case, and then you could run multiple tests to make sure that the collision is behaving nicely on edge cases such as when nothing collides and when two things collides and one is closer than the other.
The main idea behind this collision is to find the velocity that is closest to zero as necessary to AVOID a collision in the next frame.
For example, imagine a scenario where the player is moving to the right. Let's disregard the Y axis because it should only be used to detect if the objects will collide in the future and will not influence the calculations of the horizontal velocity itself. If the player is moving to the right, it must check the distance between the left side of every object to the right side of the player, if this space is smaller than the velocity, it will obviously cause a collision, like this:
let targetVelocityX = player.xvel;
for (const collidable of ...) {
// ...
if (targetVelocityX > 0) {
// We are moving to the right so:
// Figure out how much space do we have between the objects
const leftOverSpace = collidable.left.x - player.right.x;
// Is our velocity larger than the space we have available?
if (targetVelocityX > leftOverSpace) {
// We must restrict the velocity because the player will collide otherwise
// We can only move as much as we have space available
targetVelocityX = leftOverSpace;
}
} else if (targetVelocityX < 0) {
// Moving to the left...
}
Using the test case I made before, we should get leftOverSpace = 5, which is smaller than our targetVelocityX = 12, so the new velocity will be 5, which will make it touch the collidable object, and in the next frame our player position will be x = 5 and xvel = 5, if we run it again the collision logic will tell us that the left over space is zero so the horizontal velocity will be set to zero which means we cannot move to the right anymore because we are touching the object.
I should remind you that this is different than the tutorial I linked above, the tutorial tries to find the time where the collision happened as a floating point number which is useful if you want to conserve the velocity to deflect a pong ball, or slide the object along, which are some of his examples.

Related

JavaScript "pixel"-perfect collision detection for rotating sprites using math (probably linear algebra)

I'm making a 2D game in JavaScript. For it, I need to be able to "perfectly" check collision between two sprites which have x/y positions (corresponding to their centre), a rotation in radians, and of course known width/height.
After spending many weeks of work (yeah, I'm not even exaggerating), I finally came up with a working solution, which unfortunately turned out to be about 10,000x too slow and impossible to optimize in any meaningful manner. I have entirely abandoned the idea of actually drawing and reading pixels from a canvas. That's just not going to cut it, but please don't make me explain in detail why. This needs to be done with math and an "imaginated" 2D world/grid, and from talking to numerous people, the basic idea became obvious. However, the practical implementation is not. Here's what I do and want to do:
What I already have done
In the beginning of the program, each sprite is pixel-looked through in its default upright position and a 1-dimensional array is filled up with data corresponding to the alpha channel of the image: solid pixels get represented by a 1, and transparent ones by 0. See figure 3.
The idea behind that is that those 1s and 0s no longer represent "pixels", but "little math orbs positioned in perfect distances to each other", which can be rotated without "losing" or "adding" data, as happens with pixels if you rotate images in anything but 90 degrees at a time.
I naturally do the quick "bounding box" check first to see if I should bother calculating accurately. This is done. The problem is the fine/"for-sure" check...
What I cannot figure out
Now that I need to figure out whether the sprites collide for sure, I need to construct a math expression of some sort using "linear algebra" (which I do not know) to determine if these "rectangles of data points", positioned and rotated correctly, both have a "1" in an overlapping position.
Although the theory is very simple, the practical code needed to accomplish this is simply beyond my capabilities. I've stared at the code for many hours, asking numerous people (and had massive problems explaining my problem clearly) and really put in an effort. Now I finally want to give up. I would very, very much appreciate getting this done with. I can't even give up and "cheat" by using a library, because nothing I find even comes close to solving this problem from what I can tell. They are all impossible for me to understand, and seem to have entirely different assumptions/requirements in mind. Whatever I'm doing always seems to be some special case. It's annoying.
This is the pseudo code for the relevant part of the program:
function doThisAtTheStartOfTheProgram()
{
makeQuickVectorFromImageAlpha(sprite1);
makeQuickVectorFromImageAlpha(sprite2);
}
function detectCollision(sprite1, sprite2)
{
// This easy, outer check works. Please ignore it as it is unrelated to the problem.
if (bounding_box_match)
{
/*
This part is the entire problem.
I must do a math-based check to see if they really collide.
These are the relevant variables as I have named them:
sprite1.x
sprite1.y
sprite1.rotation // in radians
sprite1.width
sprite1.height
sprite1.diagonal // might not be needed, but is provided
sprite2.x
sprite2.y
sprite2.rotation // in radians
sprite2.width
sprite2.height
sprite2.diagonal // might not be needed, but is provided
sprite1.vectorForCollisionDetection
sprite2.vectorForCollisionDetection
Can you please help me construct the math expression, or the series of math expressions, needed to do this check?
To clarify, using the variables above, I need to check if the two sprites (which can rotate around their centre, have any position and any dimensions) are colliding. A collision happens when at least one "unit" (an imagined sphere) of BOTH sprites are on the same unit in our imaginated 2D world (starting from 0,0 in the top-left).
*/
if (accurate_check_goes_here)
return true;
}
return false;
}
In other words, "accurate_check_goes_here" is what I wonder what it should be. It doesn't need to be a single expression, of course, and I would very much prefer seeing it done in "steps" (with comments!) so that I have a chance of understanding it, but please don't see this as "spoon feeding". I fully admit I suck at math and this is beyond my capabilities. It's just a fact. I want to move on and work on the stuff I can actually solve on my own.
To clarify: the 1D arrays are 1D and not 2D due to performance. As it turns out, speed matters very much in JS World.
Although this is a non-profit project, entirely made for private satisfaction, I just don't have the time and energy to order and sit down with some math book and learn about that from the ground up. I take no pride in lacking the math skills which would help me a lot, but at this point, I need to get this game done or I'll go crazy. This particular problem has prevented me from getting any other work done for far too long.
I hope I have explained the problem well. However, one of the most frustrating feelings is when people send well-meaning replies that unfortunately show that the person helping has not read the question. I'm not pre-insulting you all -- I just wish that won't happen this time! Sorry if my description is poor. I really tried my best to be perfectly clear.
Okay, so I need "reputation" to be able to post the illustrations I spent time to create to illustrate my problem. So instead I link to them:
Illustrations
(censored by Stackoverflow)
(censored by Stackoverflow)
OK. This site won't let me even link to the images. Only one. Then I'll pick the most important one, but it would've helped a lot if I could link to the others...
First you need to understand that detecting such collisions cannot be done with a single/simple equation. Because the shapes of the sprites matter and these are described by an array of Width x Height = Area bits. So the worst-case complexity of the algorithm must be at least O(Area).
Here is how I would do it:
Represent the sprites in two ways:
1) a bitmap indicating where pixels are opaque,
2) a list of the coordinates of the opaque pixels. [Optional, for speedup, in case of hollow sprites.]
Choose the sprite with the shortest pixel list. Find the rigid transform (translation + rotation) that transforms the local coordinates of this sprite into the local coordinates of the other sprite (this is where linear algebra comes into play - the rotation is the difference of the angles, the translation is the vector between upper-left corners - see http://planning.cs.uiuc.edu/node99.html).
Now scan the opaque pixel list, transforming the local coordinates of the pixels to the local coordinates of the other sprite. Check if you fall on an opaque pixel by looking up the bitmap representation.
This takes at worst O(Opaque Area) coordinate transforms + pixel tests, which is optimal.
If you sprites are zoomed-in (big pixels), as a first approximation you can ignore the zooming. If you need more accuracy, you can think of sampling a few points per pixel. Exact computation will involve a square/square collision intersection algorithm (with rotation), more complex and costly. See http://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm.
Here is an exact solution that will work regardless the size of the pixels (zoomed or not).
Use both a bitmap representation (1 opacity bit per pixel) and a decomposition into squares or rectangles (rectangles are optional, just an optimization; single pixels are ok).
Process all rectangles of the (source) sprite in turn. By means of rotation/translation, map the rectangles to the coordinate space of the other sprite (target). You will obtain a rotated rectangle overlaid on a grid of pixels.
Now you will perform a filling of this rectangle with a scanline algorithm: first split the rectangle in three (two triangles and one parallelogram), using horizontal lines through the rectangle vertexes. For the three shapes independently, find all horizontal between-pixel lines that cross them (this is simply done by looking at the ranges of Y values). For every such horizontal line, compute the two intersections points. Then find all pixel corners that fall between the two intersections (range of X values). For any pixel having a corner inside the rectangle, lookup the corresponding bit in the (target) sprite bitmap.
No too difficult to program, no complicated data structure. The computational effort is roughly proportional to the number of target pixels covered by every source rectangle.
Although you have already stated that you don't feel rendering to the canvas and checking that data is a viable solution, I'd like to present an idea which may or may not have already occurred to you and which ought to be reasonably efficient.
This solution relies on the fact that rendering any pixel to the canvas with half-opacity twice will result in a pixel of full opacity. The steps follow:
Size the test canvas so that both sprites will fit on it (this will also clear the canvas, so you don't have to create a new element each time you need to test for collision).
Transform the sprite data such that any pixel that has any opacity or color is set to be black at 50% opacity.
Render the sprites at the appropriate distance and relative position to one another.
Loop through the resulting canvas data. If any pixels have an opacity of 100%, then a collision has been detected. Return true.
Else, return false.
Wash, rinse, repeat.
This method should run reasonably fast. Now, for optimization--the bottleneck here will likely be the final opacity check (although rendering the images to the canvas could be slow, as might be clearing/resizing it):
reduce the resolution of the opacity detection in the final step, by changing the increment in your loop through the pixels of the final data.
Loop from middle up and down, rather than from the top to bottom (and return as soon as you find any single collision). This way you have a higher chance of encountering any collisions earlier in the loop, thus reducing its length.
I don't know what your limitations are and why you can't render to canvas, since you have declined to comment on that, but hopefully this method will be of some use to you. If it isn't, perhaps it might come in handy to future users.
Please see if the following idea works for you. Here I create a linear array of points corresponding to pixels set in each of the two sprites. I then rotate/translate these points, to give me two sets of coordinates for individual pixels. Finally, I check the pixels against each other to see if any pair are within a distance of 1 - which is "collision".
You can obviously add some segmentation of your sprite (only test "boundary pixels"), test for bounding boxes, and do other things to speed this up - but it's actually pretty fast (once you take all the console.log() statements out that are just there to confirm things are behaving…). Note that I test for dx - if that is too large, there is no need to compute the entire distance. Also, I don't need the square root for knowing whether the distance is less than 1.
I am not sure whether the use of new array() inside the pixLocs function will cause a problem with memory leaks. Something to look at if you run this function 30 times per second...
<html>
<script type="text/javascript">
var s1 = {
'pix': new Array(0,0,1,1,0,0,1,0,0,1,1,0),
'x': 1,
'y': 2,
'width': 4,
'height': 3,
'rotation': 45};
var s2 = {
'pix': new Array(1,0,1,0,1,0,1,0,1,0,1,0),
'x': 0,
'y': 1,
'width': 4,
'height': 3,
'rotation': 90};
pixLocs(s1);
console.log("now rotating the second sprite...");
pixLocs(s2);
console.log("collision detector says " + collision(s1, s2));
function pixLocs(s) {
var i;
var x, y;
var l1, l2;
var ca, sa;
var pi;
s.locx = new Array();
s.locy = new Array();
pi = Math.acos(0.0) * 2;
var l = new Array();
ca = Math.cos(s.rotation * pi / 180.0);
sa = Math.sin(s.rotation * pi / 180.0);
i = 0;
for(x = 0; x < s.width; ++x) {
for(y = 0; y < s.height; ++y) {
// offset to center of sprite
if(s.pix[i++]==1) {
l1 = x - (s.width - 1) * 0.5;
l2 = y - (s.height - 1) * 0.5;
// rotate:
r1 = ca * l1 - sa * l2;
r2 = sa * l1 + ca * l2;
// add position:
p1 = r1 + s.x;
p2 = r2 + s.y;
console.log("rotated pixel [ " + x + "," + y + " ] is at ( " + p1 + "," + p2 + " ) " );
s.locx.push(p1);
s.locy.push(p2);
}
else console.log("no pixel at [" + x + "," + y + "]");
}
}
}
function collision(s1, s2) {
var i, j;
var dx, dy;
for (i = 0; i < s1.locx.length; i++) {
for (j = 0; j < s2.locx.length; j++) {
dx = Math.abs(s1.locx[i] - s2.locx[j]);
if(dx < 1) {
dy = Math.abs(s1.locy[i] - s2.locy[j]);
if (dx*dx + dy+dy < 1) return 1;
}
}
}
return 0;
}
</script>
</html>

Snake game algorithm that doesn't use a grid and the segments can move slower than their dimensions

I'm trying to do exactly what this guy is doing but I think he may have less requirements than me because the answers in that post don't seem like they'd work for my game. Let me inline his algorithm so my question is easier to understand:
The easiest solution is to loop from tail to head and set the position of the current to the next segment's position, ie: segment[i].position = segment[i - 1].position
I think the key difference between our requirements is I want to move each piece less than its own width/height on every tick. For example:
Pretend these squares are aligned horizontally (I drew it unaligned because I think it makes it easier to see what's going on). The red H is where the head currently is. The red T is where the tail currently is. The black H' is where the head should be next tick. So the snake is moving right to left. If I use the algorithm described above, won't the segments overlap or start driving apart? Let me play it out step by step:
Create H'.position
T.position = H.position
H.position = H'.position
The result of this would become:
What algorithm can I use to make sure that all the pieces move at the same speed and stay the same distance apart from each other?
I'll give my idea, but I'm skeptical of it because my research doesn't show anyone else using this:
Each segment will store it's coordinates and its direction. EG: [[50, 50, LEFT], [100, 50, LEFT]]. The head is index 0, the tail is index 1. The speed at which the snake moves is 10, even though the segments are 50x50.
Each tick I'll do this:
If the mouse was pressed, overwrite the nextDirection variable with the direction that was pressed. Otherwise, nextDirection is whatever it was last tick. In this example, lets assume someone pressed UP.
Iterate the array and apply the direction to each tick. EG: [[40, 50, LEFT], [90, 50, LEFT]]
Shift the directions from head to tail and set the head's new direction to nextDireciton. EG: [[40, 50, UP], [90, 50, LEFT]]
Repeat every tick.
Does this algorithm seem like it would work? Part of the reason I'm asking is because it's more complicated than the other guy's algorithm so I feel like I'm doing something wrong. And also, the more I think about his algorithm, the less it seems like it can work.
Rewording My Problem
Pretend each segment of the snake is a 20x20 pixel rectangle. Each tick, I want the head to move 5 pixels in some direction. How do I make sure all the segments stay touching each other?
#Rafe's description below in a comment is:
Consider the ordered set of locations occupied by the snake at step t, with the leftmost being the tail and the rightmost being the head: {A, B, C, D, E}. At step t+1, the ordered set of locations occupied by the snake is {B, C, D, E, F} where the tail has moved from A to B and the head has moved from E to F.
And I don't think this algorithm works because:
Yes but if the width of A = B = C = D = E = F = 20px. Shifting {A, B, C, D, E} so that it becomes {B, C, D, E, F} just added 20px to the right side and removed 20px from the left side. Doesn't this mean he moved 20px, not 5px? I want to move 5px, not 20px If you're saying that's accomplished with your suggestion, please explain how. I don't see it.
I know you're ignoring the grid but what about making the distance the snake moves forward some number that factors your big block size evenly?
Let's say that you're moving forward only half of the width/height of your block.
Now you can optimize a bit.
Start a counter, n, that runs throughout the game.
Display the blocks that comprise the snake (n = 0). Let's call this snake_even.
Next move (n = 1), create the blocks that comprise the next snake by moving them all forward 1/2 unit. Let's call this snake_odd.
For all subequent moves, you display either snake-even or snake_odd, but you can create snake_even(n+2) from snake_even(n), or snake_odd(n+2) from snake_odd(n) just by changing the head to the new position and writing over the tail.
Whenever the snake eats something, add the length to whichever snake_xxxx you're on, and then add the length to the other one.
If you want to move forward only 1/5th of the height, you'd do the same thing, but you'd have five arrays to keep track of instead of two.
Additional info based on your added example (20x20 px segments moving 5 px each step):
Take a 4-segment snake moving to the right. Segment sizes are 20x20 px, and they move 5 px per move. I'll define the snakes as a list of coordinates (x, y). I'll mark the head with 'H' and the snake will cycle by moving right in the list, cycling back to the beginning if needed.
// n = 0
snake_0 => H(60, 0), (40, 0), (20, 0), (0, 0)
// Snake is moving to the right.
// n = 1 -- construct snake_1 from snake_0 and display that one (n % 4 = 1)
snake_1 => H(65, 0), (45, 0), (25, 0), (5, 0)
// n = 2 -- construct snake_2 from snake_1 and display that one (n % 4 = 2)
snake_2 => H(70, 0), (50, 0), (30, 0), (10, 0)
// n = 3 -- construct snake_3 from snake_2 and display that one (n % 4 = 3)
snake_3 => H(75, 0), (55, 0), (35, 0), (15, 0)
// n = 4 -- Now just move the head, and re-use all but the tail of snake_0
snake_0 => (60, 0), (40, 0), (20, 0), H(80, 0)
// n = 5
snake_1 => (65, 0), (45, 0), (25, 0), H(85, 0)
// n = 6
snake_2 => (70, 0), (50, 0), (30, 0), H(90, 0)
// etc.
Now one thing I forgot to take into account was the direction each segment needs to move. That could be stored right alongside the coordinate. But I think that part you probably understand already.
Here's how I implemented this algorithm:
Basically, I created a Segment class. Each one has a list of pivots. Whenever the head changes direction, I populate the list of pivots in each of the segments. Whenever a segment collides with a pivot, it changes to the direction of the segment in front of it. I then remove that pivot from it's list.
The one downside is you've got to make it so the snake moves by a factor of its own size, otherwise it won't collide with the pivot.
so, the snake's head moves freeform, and You check for self-collisions?
The easiest way I see is have a list of positions of the snake head in subsequent frames, then draw the rest of segments at every k-th position from previous head positions. Then at the next frame, You append another head position to the list, and again draw the remaining segments at every k-th position.
If the snake speed is variable, You will have to also vary k, to make the snake not stretch (also possibly interpolate between the head positions). This is simple math.
In his situation, each segment just moved to where its predecessor was one tick back.
Think of the segments having a solid bar between their centers. If that isn't correct the snake will stretch and bunch up as it moves.
The difference between you and him is that he had longer solid bars so there wasn't overlap.
But if the bar is the length of a movement, each segment will just go where the previous segment was one tick ago. The head is the exception and will move the direction specified by the last mouse click.
Now ... what if the length of the bar is longer than or shorter than the length of a movement.
I think, in that case, you would have to move the head first and then move each segment so it is still the same distance from the previous segment (the one closer to the head).
The hard part is caused by the movement amount not being equal to the distance. When the head changes direction, the 2nd segment has to change both X and Y or it will not be the same distance from the head as it was.
I suggest you:
Calculate where the H' is. This is the position of the head after the tick.
Calculate the direction from T to H' and
move T along that vector until it is the right distance from H.
Useful part ends here ...
This Part (below here) Isn't Right
I'll leave it here because what is wrong is that it was moving the tail the same distance the head moved. What it should do is move the tail to where it is the same distance from the head as it was before. ('oneTick' isn't relevant for the tail. We should set the distance between segments at the start and use that length.)
Assume 'oneTick' is the length to move per tick. And 'direction' is UP, DOWN, LEFT or RIGHT
if (direction == UP) H'.x = H.x - oneTick;
else if (direction == LEFT) H'.y = H.y - oneTick;
... more elses for the other directions ...
ratio = (H'.x - T.x) / (H'.y - T.y);
dy2 = oneTick * oneTick / (ratio * ratio + 1);
dy = Math.sqrt(dy2);
dx = ratio * dy;
T'.x = T.x + dx; // or minus depending on direction
T'.y = T.y + dy; // or minus ...
The direction has to do with the sign of H'.y - T.y (for y) and similarly for x.
From the specification, if you consider the set of locations occupied by the snake, only the head and tail change at each step. Therefore you could use a circular buffer to represent the snake and update its position in O(1) time rather than O(n).

adding a margin to a fillRect with JavaScript canvas

I am making a snake game for fun, to practice JavaScript/canvas, and what I have so far is a while loop that says while i is < 100, then add a square and the square is added by using fillRect(10, 10, xCoord, yCoord) and in the while loop I have xCoord = xCoord + 11; so it moves the square to the right with a space in between... here is a link:
http://brycemckenney.com/snake_canvas
And the relevant code:
while(i < 100) {
// fillRect(x, y, width, height)... x is horizontal and y is vertical...
context.fillRect(xCoord, yCoord, sW, sH);
// We want each squere to have one pixel in between them, so we increment by 11, not 10.
xCoord = xCoord + 11;
// When i (0) gets to numOfSquares (3), then make the rest white so that you can't see them...
if (i >= numOfSquares) {
context.fillStyle = "#FFFFFF";
}
//Increment by 1 every loop, so that the squares keep going
i++;
}
I am trying to get the snake to animate, and I have tried many different options. Now I am trying to just add a margin-right the the 4 squares, so it looks like it's moving... is it possible to add margin to those? Here is a snapshot of what I have:
Well, since you're making the game of snake, we know what you want to do and I think it would be more useful if we tried to get you on the right track instead of modifying your existing code.
Lets suppose our logical game grid is 60x60.
So snake-pieces can be anywhere in this grid, having X and Y values between 0 and 59. This means:
A piece of snake in the top-left corner is at [0, 0]
A piece of snake in the top-right corner is at [59, 0]
Let us further suppose that a snake is made up of a number of segments. How about 4 segments to start. This means that we need to keep an array of the 4 positions:
[position1, position2, position3, position4]
We have to pick a front, so lets say that the end of the array is the "front" of the snake. If we pick the top left 4 positions the snake would be:
var mySnake = [[0, 0], [1,0], [2,0], [3,0]]
Which on the board looks like this:
++++OOOOOOOO
OOOOOOOOOOOO
OOOOOOOOOOOO
That means its 4 snake-pieces going from left to right, just like you have so far. The thing is, by saving these locations we've gone and added some persistent state to our program.
Now snake is a funny game, because "moving" the snake really means two things:
Taking the last (tail) piece away
Adding a new piece to the snake
So we should make a function for moving the snake that does both of these. We'll make one based on direction:
// modifies a snake's array
function moveSnake(snake, direction) {
// First we must remove a piece of the snake's tail, which is at the start of the array
// There's a built in command in JavaScript for this called shift()
snake.shift();
// Now we must add the new piece!
// To tell where we need to go we must look at the last location:
var lastLoc = snake[snake.length - 1];
// Just to be a little more clear:
var lastX = lastLoc[0];
var lastY = lastLoc[1];
switch (direction) {
case 'up':
snake.push([lastX, lastY-1]);
break;
case 'down':
snake.push([lastX, lastY+1]);
break;
case 'left':
snake.push([lastX-1, lastY]);
break;
case 'right':
snake.push([lastX+1, lastY]);
break;
}
// redraw after every move!
drawSnake(ctx, mySnake);
}
With this method we could do:
var mySnake = [[0, 0], [1,0], [2,0], [3,0]];
moveSnake(mySnake, 'down');
// mySnake is now [[1,0], [2,0], [3,0], [3,1]];
The snake now looks like this:
O+++OOOOOOOO
OOO+OOOOOOOO
OOOOOOOOOOOO
Now this method is pretty dumb, and in a real game of snake we'd need to add some conditions. Typically:
If the new piece of snake is on top of a piece of food, the tail piece is NOT removed, instead the snake is +1 block longer
If the new piece is outside of out 60x60 grid, the user loses the game
If the new piece is at a location that any of the other snake pieces already exist, the user loses the game
Alas those are not done here
We still need to draw it all, but since we are keeping track of the snake's location that's easy peasy. We can just draw one square for each piece of the snake:
function drawSnake(context, snake) {
// Remember to clear the board!
ctx.clearRect(0, 0, 600, 600);
var length = snake.length;
for (var i = 0; i < length; i++) {
context.fillStyle = 'teal';
// position is its own array and looks like: [x, y]
var position = snake[i];
// our logical snake board is 60x60, but the real canvas is 600x600,
// so we multiply x and y by 10.
// We also use "9" for the width and height so theres a tiny gap
context.fillRect(position[0]*10, position[1]*10, 9, 9);
}
}
See the completed demo here.
http://jsfiddle.net/FsqXE/
Note that it allows arrow keys to control the snake to show off the moveSnake method, but the regular snake game has the movement controlled by a timer, and the user can usually only change the next-possible-direction.
The canvas element is a 2D drawing surface, so it doesn't support (higher level) things like CSS margins.
To do animation, you'll need to continually clear the canvas and redraw the scene, each time slightly adjusting the position of moving objects (so that they appear to move). In your case, this means redrawing the squares, each time with a starting position 4 pixels away from the last time you redrew the frame.
You may want to have a look at the Basic animations page at MDN (part of their canvas tutorial).

Vectors, calculate movement forces with max speed

Im building a small space shooter game. I have how ever stubbed on a math problem when it come to the space physics.
Describing this with words is following:
There is a max speed.
So if you give full full speed you ship will move with this over the screen over and over again like in the old asteroids games.
If then release the rocket boost you ship should be keep moving with that speed over the screen.
Then the tricky part where Im stuck right now.
If you rotate the ship ANY angle and gives boost again the ship should try to get to this direction and NEVER surpas the max speed when it comes to how fast it is moving. so my question is. anyone have a good idea formula for this issue? feels like it has been done before if you know what to look for. :)
Ill add this small image to illustrate what is tried to be done with some vector calculations.
Red ring: Max speed
Green line: current ship direction.
Black line: direction(s) and how fast the ship is moveing in x and y.
Black ring: origin of movement.
Can illustrate it but hard to find a good math solution for this. :)
EDIT
This is the code Im using right now in every frame. It gives movement to the ship but does not give the force of movement the user has to counter-react with its rocket boosters to get the ship to stop or slow down. With this it stops the instant you release the accelerating speed for the ship.
//Calculates ship movement rules
var shipVelocityVec = GetVectorPosByAngle(shipMoveSpeed, shipRotationAngle);
var shipUnitVec =$V([Math.cos(shipRotationAngle),Math.sin(shipRotationAngle),0]);
var rawAccel = shipAccelSpeed / shipMass;
var scale = (shipUnitVec.dot(shipVelocityVec))/(shipTopSpeed * shipTopSpeed);
var v1 = shipUnitVec.subtract(shipVelocityVec.multiply(scale));
var finalAccelVec = v1.multiply(rawAccel);
console.log(finalAccelVec);
//move ship according to rules
var shipPosVector = $V([shipxPos, shipyPos, 0]);
var movementVector = shipPosVector.add(finalAccelVec);
shipxPos = movementVector.elements[0];
shipyPos = movementVector.elements[1];
To give the acceleration speed the user has to keep the button pressed. the instance the user releases the button the acceleration is set to zero and have to boost over again to give maximum acceleration throttle.
Solution found! Posted it here how it was done.
You seem to be confusing something - there is no issue capping the velocity to a maximum speed, even when the acceleration is at a different angle, if you are using vectors correctly.
Your setup should look something like this:
Your ship should have a position, a velocity, and an acceleration. Each of these can be represented as a 2D vector (with separate x and y components).
Every frame, add the velocity to the position, and the acceleration to the velocity.
Every frame, check that the speed does not exceed some maximum. If it does, cap the speed by normalizing the velocity vector and multiplying it by the max speed.
That's it! There are no special cases to consider - that's the magic of vector algebra!
#BlueRaja's solution should work, although you will get an abrupt change in behavior when you hit the max speed.
If you want a solution with no "seams", I believe you can do what you're looking for by adding the right kind of adjustment to the acceleration, as follows:
ship_unit_vec = [cos(ship_angle), sin(ship_angle)]
raw_accel = (engine_thrust / ship_mass)
scale = dot_product(ship_unit_vec, ship_velocity_vec) / max_speed^2
final_accel_vec = raw_accel * (ship_unit_vec - scale * ship_velocity_vec)
Notes:
If |ship_velocity_vec|<<max_speed, the scale * ship_velocity_vec component is negligable.
If |ship_velocity_vec|==max_speed, the scale * ship_velocity_vec component cancels all additional acceleration in the "wrong" direction, and aids acceleration in the "right" direction.
I've never tried this out, so I don't know how it will feel to the player...
More generally, if there are more sources of acceleration than just the ship thrusters, you can add them all together (say, as raw_accel_vec), and perform the above operation all at once:
scale_forall = dot_product(raw_accel_vec, ship_velocity_vec) / max_speed^2
final_accel_vec = raw_accel_vec - scale_forall * ship_velocity_vec
Rather than just imposing an ad hoc maximum speed, you could use some actual physics and impose a drag force. This would be an extra force acting on the spaceship, directed opposite to the velocity vector. For the magnitude of the drag force, it's simplest to just take it proportional to the velocity vector.
The overall effect is that the drag force increases as the spaceship moves faster, making it harder to accelerate in the direction of motion when the ship moves faster. It also makes acceleration easier when it is opposed to the direction of motion.
One point where this diverges from your description is that the spaceship won't continue at maximum speed forever, it will slow down. It won't, however, come to a halt, since the drag force drops as the ship slows down. That matches my memory of asteroids better than the ship continuing forever at constant velocity, but it has been quite a while since I've played.
I did it! Thank you for your help.
Finally found the solution. the problem was I was trying to modify the ships current movement when it comes to speed and then of this calculate the "drag" forces that would be the product of this movement when the user tried to go another direction. The solution was like #BlueRaja and #Comingstorm mentioned. All forces should be added together when it comes to the movement. This should be what then alter the ships position. Should not be added to the ships current movement. You might be able to effect a current movement to but then you have to do this differently. So I thought I share my solution for this how it looks.
This function is run each time the user accelerates the ship.
function CalcShipMovement() {
//Calculates ship movement rules
shipPosVector = $V([shipxPos, shipyPos, 0]);
var shipVelocityVec = GetVectorPosByAngle(shipAccelSpeed, shipRotationAngle);
var shipUnitVec = $V([Math.cos(shipRotationAngle), Math.sin(shipRotationAngle), 0]);
if(currentShipMoveVector != null && Get2DVectorLength(currentShipMoveVector) > 0) {
var nextMove = currentShipMoveVector.add(shipVelocityVec);
var nextSpeed = Get2DVectorLength(nextMove);
//check if topspeed of movement should be changed
if(nextSpeed > shipTopSpeed) {
var scale = nextSpeed / shipTopSpeed;
currentShipMoveVector = DevideVector(nextSpeed, scale);
} else {
currentShipMoveVector = currentShipMoveVector.add(shipVelocityVec);
}
}
if(currentShipMoveVector != null && Get2DVectorLength(currentShipMoveVector) == 0) {
currentShipMoveVector = currentShipMoveVector.add(shipVelocityVec);
}}
This code is run in every frame the graphics for the ship is generated to alter its position.
function SetShipMovement() {
if(currentShipMoveVector != null && Get2DVectorLength(currentShipMoveVector) > 0) {
shipMoveSpeed = Get2DVectorLength(currentShipMoveVector);
shipPosVector = shipPosVector.add(currentShipMoveVector);
shipxPos = shipPosVector.elements[0];
shipyPos = shipPosVector.elements[1];
//Makes the ship slow down if no acceleration is done for the ship
if(shipAccelSpeed == 0) {
currentShipMoveVector = currentShipMoveVector.subtract(DevideVector(currentShipMoveVector, 50));
}
} else {
currentShipMoveVector = $V([0, 0, 0]);
}}

How can a large canvas have an animated 'viewable area'

The question title may be vague. Basically, imagine a racing game built in canvas. The track takes up 10,000 x 10,000 pixels of screen space. However the browser window is 500 x 500 pixels. The car should stay centered in the browser and the 'viewable' area of the 10,000 x 10,000 canvas will change. Otherwise the car would just drive off the edge at disappear.
Does this technique have a name?
What are the basic principles to make this happen?
If the car should stay at the same position (relative to the canvas' position), then you should not move the car. Instead, move the background picture/track/map to the other side.
Causing your eyes to think the car moves right can be done by either moving the car to the right, or by moving the map to the left. The second option seems to be what you want, since the car won't move whereas the viewable area (i.e. the map) will.
This is a quick demo from scratch: http://jsfiddle.net/vXsqM/.
It comes down to altering the map's position the other way round:
$("body").on("keydown", function(e) {
if(e.which === 37) pos.x += speed; // left key, so move map to the right
if(e.which === 38) pos.y += speed;
if(e.which === 39) pos.x -= speed;
if(e.which === 40) pos.y -= speed;
// make sure you can't move the map too far.
// clamp does: if x < -250 return -250
// if x > 0 return 0
// else it's allowed, so just return x
pos.x = clamp(pos.x, -250, 0);
pos.y = clamp(pos.y, -250, 0);
draw();
});
You can then draw the map with the position saved:
ctx.drawImage(img, pos.x, pos.y);
If you're looking for a way to actually move the car when the map cannot be moved any further (because you're driving the car close to a side of the map), then you'd have to extend the clamping and also keep track of when the car should be moved and how far: http://jsfiddle.net/vXsqM/1/.
// for x coordinate:
function clamp2(x, y, a, b) { // x = car x, y = map x, a = min map x, b = max map x
return y > b ? -y : y < a ? a - y : x;
}
The position clamping then becomes a little more complex:
// calculate how much car should be moved
posCar.x = clamp2(posCar.x, posMap.x, -250, 0);
posCar.y = clamp2(posCar.y, posMap.y, -250, 0);
// also don't allow the car to be moved off the map
posCar.x = clamp(posCar.x, -100, 100);
posCar.y = clamp(posCar.y, -100, 100);
// calculate where the map should be drawn
posMapReal.x = clamp(posMap.x, -250, 0);
posMapReal.y = clamp(posMap.y, -250, 0);
// keep track of where the map virtually is, to calculate car position
posMap.x = clamp(posMap.x, -250 - 100, 0 + 100);
posMap.y = clamp(posMap.y, -250 - 100, 0 + 100);
// the 100 is because the car (circle in demo) has a radius of 25 and can
// be moved max 100 pixels to the left and right (it then hits the side)
Two things:
Canvas transformation methods
First, the canvas transformation methods (along with context.save() and context.restore() are your friends and will greatly simplify the math needed to view a portion of a large 'world`. If you use this approach, you can get the desired behavior just by specifying the portion of the world that is visible and the world-coordinates of everything you want to draw.
This is not the only way of doing things* but the transformation methods are meant for exactly this kind of problem. You can use them or you can reinvent them by manually keeping track of where your background should be drawn, etc., etc.
Here's an example of how to use them, adapted from a project of mine:
function(outer, inner, ctx, drawFunction) {
//Save state so we can return to a clean transform matrix.
ctx.save();
//Clip so that we cannot draw outside of rectangle defined by `outer`
ctx.beginPath();
ctx.moveTo(outer.left, outer.top);
ctx.lineTo(outer.right, outer.top);
ctx.lineTo(outer.right, outer.bottom);
ctx.lineTo(outer.left, outer.bottom);
ctx.closePath();
//draw a border before clipping so we can see our viewport
ctx.stroke();
ctx.clip();
//transform the canvas so that the rectangle defined by `inner` fills the
//rectangle defined by `outer`.
var ratioWidth = (outer.right - outer.left) / (inner.right - inner.left);
var ratioHeight = (outer.bottom - outer.top) / (inner.bottom - inner.top);
ctx.translate(outer.left, outer.top);
ctx.scale(ratioWidth, ratioHeight);
ctx.translate(-inner.left, -inner.top);
//here I assume that your drawing code is a function that takes the context
//and draws your world into it. For performance reasons, you should
//probably pass `inner` as an argument too; if your draw function knows what
//portion of the world it is drawing, it can ignore things outside of that
//region.
drawFunction(ctx);
//go back to the previous canvas state.
ctx.restore();
};
If you are clever, you can use this to create multiple viewports, picture-in-pictures, etc. of different sizes and zoom in and out on stuff.
Performance
Second, as I commented in the code, you should make sure your drawing code knows what portion of your larger 'world' will be visible so that you don't do a lot of work trying to draw things that will not be visible.
The canvas transformation methods are meant for solving exactly this kind of problem. Use 'em!
*You will likely have problems if your world is so large that its coordinates cannot fit in an appropriate integer. You'll hit that problem roughly when your world exceeds billion (10^9) or a long trillion (10^18) pixels in any dimension, depending on whether the integers are 32- or 64-bit. If your world isn't measured in pixels but in 'world units', you'll run into problems when your world's total size and smallest feature scale lead to floating point inaccuracies. In that case, you will need to do extra work to keep track of things... but you'll probably still want to use the canvas transformation methods!
My very first game was a racing game where I moved the background instead of the car and although I want to think now that I had my reasons to make it so... I just didn't know better.
There are a few techniques that you need to know to achieve this well.
Tiled background. You need to make your track out of smaller pieces that tiled. To To draw 10,000 x 10,000 pixels is 100MPix image usually such image will have 32bit depth (4 bytes) this will end up being 400MB in memory. Compressions like PNG, JPEG won't help you since these are made to store and transfer images. They cant be rendered to a canvas without decompressing.
Move the car along your track. There is nothing worst then moving the BG under the car. If you need to add more features to your game like AI cars... now they will have to move along the map and to implement car collisions you need to make some not hard but strange spacial transformations.
Add camera entity. The camera needs to have position and viewport size (this is the size of your canvas). The camera will make or break your game. This is the entity that will give you the sense of speed in the game... You can have a camera shake for collisions, if you have drifts if your game the camera can slide pass the desired position and center back to the car, etc. Of course the most important thing will be tracking the car. Some simple suggestions I can give you are to not put the car in dead center of the camera. put the car a little behind so you can see a bit more what's in front of your. The faster the car moves the more you should offset the camera. Also you can't just compute the position of the camera instead compute desired position and slowly per frame move the current camera position to the desired position.
Now when you have camera and a large tiled map, when you draw the tiles you have to subtrack the camera position. You can also compute which tiles are not visible and skip them. This technique will allow you do extend your game with even larger maps or you can stream your map where you don't have all the tiles loaded and load in advance on background (AJAX) what will be visible soon.

Categories