Giving a direction to a moving object - math

I wish to create a tower defense game in SDL. Before starting the project, I experiment everything I will need to do when programming the game. In the test I am doing currently, there are a tower (static object), targets (moving objects) that are in its range, and shoots (moving objects) that are fired from the turret to the targets. What I fail to do is find a way to give the 'shoot' objects a direction. By shoot object, I mean the object that is fired by the tower when targets are in range. Also, whatever the direction is, the shoot shall always have the same speed, which forbids the use of the formula dirx = x2 - x1.
Shoots are structures defined as the following:
typedef struct shoot
{
SDL_Surface *img; // Visual representation of the shoot object.
SDL_Rect pos; // Position of the object, it's a structure containing
// coordinates x and y (these are of type int).
int index;
float dirx; // dirx and diry are the movement done by the shoots object in
// 1 frame, so that for each frame, the object shoot is moved dirx
// pixels on the axis x and diry pixels on the axis y
float diry; // (the program deals with the fact that the movement will be done
// with integers and not with floats, that is no problem to me)
float posx; // posx and posy are the real, precise coordinates of the shoot
float posy;
struct shoot *prev;
struct shoot *next;
} shoot;
What I need is a way to calculate the position of the object shoot in the next frame, given its position and direction in the current frame.
This is the best I could find (please note that it is a paper written formula, so the names are simplified, different from the names in the code):
dirx = d * ((p2x - p1x) / ((p2x - p1x) + (p2y - p1y)))
diry = d * ((p2y - p1y) / ((p2x - p1x) + (p2y - p1y)))
dirx and diry correspond to the movement done, in the pixel, by the shoot on the axis x and y, in one frame.
d is a multiplier and the big parenthesis (all of what is not d) is a coefficient.
p2 is the point the shoot shall aim for (the center of the target aimed for). p1 is the current position of the shoot object. x or y means that we use the coordinate x or y of the point.
The problem with this formula is that it gives me an unexact value. For example, aiming in diagonal will make the shoot slower that aiming straight north. Moreover, it doesn't go in the right direction, and I can't find why since my paper tests show me I'm right...
I would love some help here to find a formula that makes the shoot move correctly.

If p1 is the source of a shoot object, p2 is the destination, and s is the speed you want to move it at (units per frame or per second - latter is better), then the velocity of the object is given by
float dx = p2.x - p1.x, dy = p2.y - p1.y,
inv = s / sqrt(dx * dx + dy * dy);
velx = inv * dx; vely = inv * dy;
(You should probably change dir to vel as it is a more sensible variable name)
Your attempt seems to be normalizing the direction vector by Manhattan distance, which is wrong - you must normalize by the Euclidean distance, which is given by the sqrt term.

Related

Turn rate on a player's rotation doing a 360 once it hits pi

Making a game using Golang since it seems to work quite well for games. I made the player face the mouse always, but wanted a turn rate to make certain characters turn slower than others. Here is how it calculates the turn circle:
func (p *player) handleTurn(win pixelgl.Window, dt float64) {
mouseRad := math.Atan2(p.pos.Y-win.MousePosition().Y, win.MousePosition().X-p.pos.X) // the angle the player needs to turn to face the mouse
if mouseRad > p.rotateRad-(p.turnSpeed*dt) {
p.rotateRad += p.turnSpeed * dt
} else if mouseRad < p.rotateRad+(p.turnSpeed*dt) {
p.rotateRad -= p.turnSpeed * dt
}
}
The mouseRad being the radians for the turn to face the mouse, and I'm just adding the turn rate [in this case, 2].
What's happening is when the mouse reaches the left side and crosses the center y axis, the radian angle goes from -pi to pi or vice-versa. This causes the player to do a full 360.
What is a proper way to fix this? I've tried making the angle an absolute value and it only made it occur at pi and 0 [left and right side of the square at the center y axis].
I have attached a gif of the problem to give better visualization.
Basic summarization:
Player slowly rotates to follow mouse, but when the angle reaches pi, it changes polarity which causes the player to do a 360 [counts all the back to the opposite polarity angle].
Edit:
dt is delta time, just for proper frame-decoupled changes in movement obviously
p.rotateRad starts at 0 and is a float64.
Github repo temporarily: here
You need this library to build it! [go get it]
Note beforehand: I downloaded your example repo and applied my change on it, and it worked flawlessly. Here's a recording of it:
(for reference, GIF recorded with byzanz)
An easy and simple solution would be to not compare the angles (mouseRad and the changed p.rotateRad), but rather calculate and "normalize" the difference so it's in the range of -Pi..Pi. And then you can decide which way to turn based on the sign of the difference (negative or positive).
"Normalizing" an angle can be achieved by adding / subtracting 2*Pi until it falls in the -Pi..Pi range. Adding / subtracting 2*Pi won't change the angle, as 2*Pi is exactly a full circle.
This is a simple normalizer function:
func normalize(x float64) float64 {
for ; x < -math.Pi; x += 2 * math.Pi {
}
for ; x > math.Pi; x -= 2 * math.Pi {
}
return x
}
And use it in your handleTurn() like this:
func (p *player) handleTurn(win pixelglWindow, dt float64) {
// the angle the player needs to turn to face the mouse:
mouseRad := math.Atan2(p.pos.Y-win.MousePosition().Y,
win.MousePosition().X-p.pos.X)
if normalize(mouseRad-p.rotateRad-(p.turnSpeed*dt)) > 0 {
p.rotateRad += p.turnSpeed * dt
} else if normalize(mouseRad-p.rotateRad+(p.turnSpeed*dt)) < 0 {
p.rotateRad -= p.turnSpeed * dt
}
}
You can play with it in this working Go Playground demo.
Note that if you store your angles normalized (being in the range -Pi..Pi), the loops in the normalize() function will have at most 1 iteration, so that's gonna be really fast. Obviously you don't want to store angles like 100*Pi + 0.1 as that is identical to 0.1. normalize() would produce correct result with both of these input angles, while the loops in case of the former would have 50 iterations, in the case of the latter would have 0 iterations.
Also note that normalize() could be optimized for "big" angles by using floating operations analogue to integer division and remainder, but if you stick to normalized or "small" angles, this version is actually faster.
Preface: this answer assumes some knowledge of linear algebra, trigonometry, and rotations/transformations.
Your problem stems from the usage of rotation angles. Due to the discontinuous nature of the inverse trigonometric functions, it is quite difficult (if not outright impossible) to eliminate "jumps" in the value of the functions for relatively close inputs. Specifically, when x < 0, atan2(+0, x) = +pi (where +0 is a positive number very close to zero), but atan2(-0, x) = -pi. This is exactly why you experience the difference of 2 * pi which causes your problem.
Because of this, it is often better to work directly with vectors, rotation matrices and/or quaternions. They use angles as arguments to trigonometric functions, which are continuous and eliminate any discontinuities. In our case, spherical linear interpolation (slerp) should do the trick.
Since your code measures the angle formed by the relative position of the mouse to the absolute rotation of the object, our goal boils down to rotating the object such that the local axis (1, 0) (= (cos rotateRad, sin rotateRad) in world space) points towards the mouse. In effect, we have to rotate the object such that (cos p.rotateRad, sin p.rotateRad) equals (win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized.
How does slerp come into play here? Considering the above statement, we simply have to slerp geometrically from (cos p.rotateRad, sin p.rotateRad) (represented by current) to (win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized (represented by target) by an appropriate parameter which will be determined by the rotation speed.
Now that we have laid out the groundwork, we can move on to actually calculating the new rotation. According to the slerp formula,
slerp(p0, p1; t) = p0 * sin(A * (1-t)) / sin A + p1 * sin (A * t) / sin A
Where A is the angle between unit vectors p0 and p1, or cos A = dot(p0, p1).
In our case, p0 == current and p1 == target. The only thing that remains is the calculation of the parameter t, which can also be considered as the fraction of the angle to slerp through. Since we know that we are going to rotate by an angle p.turnSpeed * dt at every time step, t = p.turnSpeed * dt / A. After substituting the value of t, our slerp formula becomes
p0 * sin(A - p.turnSpeed * dt) / sin A + p1 * sin (p.turnSpeed * dt) / sin A
To avoid having to calculate A using acos, we can use the compound angle formula for sin to simplify this further. Note that the result of the slerp operation is stored in result.
result = p0 * (cos(p.turnSpeed * dt) - sin(p.turnSpeed * dt) * cos A / sin A) + p1 * sin(p.turnSpeed * dt) / sin A
We now have everything we need to calculate result. As noted before, cos A = dot(p0, p1). Similarly, sin A = abs(cross(p0, p1)), where cross(a, b) = a.X * b.Y - a.Y * b.X.
Now comes the problem of actually finding the rotation from result. Note that result = (cos newRotation, sin newRotation). There are two possibilities:
Directly calculate rotateRad by p.rotateRad = atan2(result.Y, result.X), or
If you have access to the 2D rotation matrix, simply replace the rotation matrix with the matrix
|result.X -result.Y|
|result.Y result.X|

How to calculate the angles of the projection in 3d for an object to step at given point?

I need to calculate the angles to through the ball in that direction for a given speed and the point where it should land after thrown.
The horizontal angle is easy(We know both start and step points).How to calculate the vertical angle of projection.There is gravy applying on object.
Time of travel will be usual as bowling time(time between ball release and occurring step) as per video.
Is there a way directly in unity3d?
Watch this video for 8 seconds for clear understating of this question.
According to the Wikipedia page Trajectory of a projectile, the "Angle of reach" (The angle you want to know) is calculated as follows:
θ = 1/2 * arcsin(gd/v²)
In this formula, g is the gravitational constant 9.81, d is the distance you want the projectile to travel, and v is the velocity at which the object is thrown.
Code to calculate this could look something like this:
float ThrowAngle(Vector3 destination, float velocity)
{
const float g = 9.81f;
float distance = Vector3.Distance(transform.position, destination);
//assuming you want degrees, otherwise just drop the Rad2Deg.
return Mathf.Rad2Deg * (0.5f * Asin((g*distance)/Mathf.Pow(velocity, 2f)));
}
This will give you the angle assuming no air resistance etc. exist in your game.
If your destination and your "throwing point" are not at the same height, you may want to set both to y=0 first, otherwise, errors may occur.
EDIT:
Considering that your launch point is higher up than the destination, this formula from the same page should work:
θ = arctan(v² (+/-) √(v^4-g(gx² + 2yv²))/gx)
Here, x is the range, or distance, and y is the altitude (relative to the launch point).
Code:
float ThrowAngle(Vector3 start, Vector3 destination, float v)
{
const float g = 9.81f;
float xzd = Mathf.Sqrt(Mathf.Pow(destination.x - start.x, 2) + Mathf.Pow(destination.z - start.z, 2));
float yd = destination.y - start.y;
//assuming you want degrees, otherwise just drop the Rad2Deg. Split into two lines for better readability.
float sqrt = (Mathf.Pow(v,4) - g * (g*Mathf.Pow(xzd,2) + 2*yd*Mathf.Pow(v,2))/g*xzd);
//you could also implement a solution which uses both values in some way, but I left that out for simplicity.
return Mathf.Atan(Mathf.Pow(v, 2) + sqrt);
}

Difference between the two quaternions

Solved
I'm making a 3D portal system in my engine (like Portal game). Each of the portals has its own orientation saved in a quaternion. To render the virtual scene in one of the portals I need to calculate the difference between the two quaternions, and the result use to rotate the virtual scene.
When creating the first portal on the left wall, and second one on the right wall, the rotation from one to another will take place in only one axis, but for example when the first portal will be created on the floor, and the second one on the right wall, the rotation from one to another could be in two axis, and that's the problem, because the rotation goes wrong.
I think the problem exists because the orientation for example X axis and Z axis are stored together in one quaternion and I need it separately to manualy multiply X * Z (or Z * X), but how to do it with only one quaternion, (the difference quaternion)? Or is there other way to correct rotate the scene?
EDIT:
Here on this picture are two portals P1 and P2, the arrows show how are they rotated. As I am looking into P1 I will see what sees P2. To find the rotation which I need to rotate the main scene to be like the virtual scene in this picture I'm doing following:
Getting difference from quaternion P2 to quaternion P1
Rotating result by 180 degrees in Y axis (portal's UP)
Using the result to rotate the virtual scene
This method above works only when the difference takes place in only one axis. When one portal will be on the floor, or on te ceiling, this will not work because the difference quaternion is build in more than one axis. As suggested I tried to multiply P1's quaternion to P2's quaternion, and inversely but this isn't working.
EDIT 2:
To find the difference from P2 to P1 I'm doing following:
Quat q1 = P1->getOrientation();
Quat q2 = P2->getOrientation();
Quat diff = Quat::diff(q2, q1); // q2 * diff = q1 //
Here's the Quat::diff function:
GE::Quat GE::Quat::diff(const Quat &a, const Quat &b)
{
Quat inv = a;
inv.inverse();
return inv * b;
}
Inverse:
void GE::Quat::inverse()
{
Quat q = (*this);
q.conjugate();
(*this) = q / Quat::dot((*this), (*this));
}
Conjugate:
void GE::Quat::conjugate()
{
Quat q;
q.x = -this->x;
q.y = -this->y;
q.z = -this->z;
q.w = this->w;
(*this) = q;
}
Dot product:
float GE::Quat::dot(const Quat &q1, const Quat &q2)
{
return q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w;
}
Operator*:
const GE::Quat GE::Quat::operator* ( const Quat &q) const
{
Quat qu;
qu.x = this->w*q.x + this->x*q.w + this->y*q.z - this->z*q.y;
qu.y = this->w*q.y + this->y*q.w + this->z*q.x - this->x*q.z;
qu.z = this->w*q.z + this->z*q.w + this->x*q.y - this->y*q.x;
qu.w = this->w*q.w - this->x*q.x - this->y*q.y - this->z*q.z;
return qu;
}
Operator/:
const GE::Quat GE::Quat::operator/ (float s) const
{
Quat q = (*this);
return Quat(q.x / s, q.y / s, q.z / s, q.w / s);
}
All this stuff works, because I have tested it with GLM library
If you want to find a quaternion diff such that diff * q1 == q2, then you need to use the multiplicative inverse:
diff * q1 = q2 ---> diff = q2 * inverse(q1)
where: inverse(q1) = conjugate(q1) / abs(q1)
and: conjugate( quaternion(re, i, j, k) ) = quaternion(re, -i, -j, -k)
If your quaternions are rotation quaternions, they should all be unit quaternions. This makes finding the inverse easy: since abs(q1) = 1, your inverse(q1) = conjugate(q1) can be found by just negating the i, j, and k components.
However, for the kind of scene-based geometric configuration you describe, you probably don't actually want to do the above, because you also need to compute the translation correctly.
The most straightforward way to do everything correctly is to convert your quaternions into 4x4 rotation matrices, and multiply them in the appropriate order with 4x4 translation matrices, as described in most introductory computer graphics texts.
It is certainly possible to compose Euclidean transformations by hand, keeping your rotations in quaternion form while applying the quaternions incrementally to a separate translation vector. However, this method tends to be technically obscure and prone to coding error: there are good reasons why the 4x4 matrix form is conventional, and one of the big ones is that it appears to be easier to get it right that way.
I solved my problem. As it turned out I don't need any difference between two rotations. Just multiply one rotation by rotation in 180 degrees, and then multiply by inverse of second rotation that way (using matrices):
Matrix m1 = p1->getOrientation().toMatrix();
Matrix m2 = p2->getOrientation().toMatrix();
Matrix model = m1 * Matrix::rotation(180, Vector3(0,1,0)) * Matrix::inverse(m2);
and translation calculating this way:
Vector3 position = -p2->getPosition();
position = model * position + p1->getPosition();
model = Matrix::translation(position) * model;
No, you have to multiply two quaternions together to get the final quaternion you desire.
Let's say that your first rotation is q1 and the second is q2. You want to apply them in that order.
The resulting quaternion will be q2 * q1, which will represent your composite rotation (recall that quaternions use left-hand multiplication, so q2 is being applied to q1 by multiplying from the left)
Reference
For a brief tutorial on computing a single quaternion, refer to my previous stack overflow answer
Edit:
To clarify, you'd face a similar problem with rotation matrices and Euler angles. You define your transformations about X, Y, and Z, and then multiply them together to get the resulting transformation matrix (wiki). You have the same issue here. Rotation matrices and Quaternions are equivalent in most ways for representing rotations. Quaternions are preferred mostly because they're a bit easier to represent (and easier for addressing gimbal lock)
Quaternions work the following way: the local frame of reference is represented as the imaginary quaternion directions i,j,k. For instance, for an observer standing in the portal door 1 and looking in the direction of the arrow, direction i may represent the direction of the arrow, j is up and k=ij points to the right of the observer. In global coordinates represented by the quaternion q1, the axes in 3D coordinates are
q1*(i,j,k)*q1^-1=q1*(i,j,k)*q1',
where q' is the conjugate, and for unit quaternions, the conjugate is the inverse.
Now the task is to find a unit quaternion q so that directions q*(i,j,k)*q' in local frame 1 expressed in global coordinates coincide with the rotated directions of frame 2 in global coordinates. From the sketch that means forwards becomes backwards and left becomes right, that is
q1*q*(i,j,k)*q'*q1'=q2*(-i,j,-k)*q2'
=q2*j*(i,j,k)*j'*q2'
which is readily achieved by equating
q1*q=q2*j or q=q1'*q2*j.
But details may be different, mainly that another axis may represent the direction "up" instead of j.
If the global system of the sketch is from the bottom, so that global-i points forward in the vertical direction, global-j up and global-k to the right, then local1-(i,j,k) is global-(-i,j,-k), giving
q1=j.
local2-(i,j,k) is global-(-k,j,i) which can be realized by
q2=sqrt(0.5)*(1+j),
since
(1+j)*i*(1-j)=i*(1-j)^2=-2*i*j=-2*k and
(1+j)*k*(1-j)=(1+j)^2*k= 2*j*k= 2*i
Comparing this to the actual values in your implementation will indicate how the assignment of axes and quaternion directions has to be changed.
Check https://www.emis.de/proceedings/Varna/vol1/GEOM09.pdf
Imagine to get dQ from Q1 to Q2, I'll explain why dQ = Q1*·Q2, instead of Q2·Q1*
This rotates the frame, instead of an object. For any vector v in R3, the rotation action of operator
L(v) = Q*·v·Q
It's not Q·v·Q*, which is object rotation action.
If you rotates Q1 and then Q1* and then Q2, you can write
(Q1·Q1*·Q2)*·v·(Q1·Q1*·Q2) = (Q1*·Q2)*·Q1*·v·Q1·(Q1*·Q2) = dQ*·Q1*·v·Q1·dQ
So dQ = Q1*·Q2

I've got my 2D/3D conversion working perfectly, how to do perspective

Although the context of this question is about making a 2d/3d game, the problem i have boils down to some math.
Although its a 2.5D world, lets pretend its just 2d for this question.
// xa: x-accent, the x coordinate of the projection
// mapP: a coordinate on a map which need to be projected
// _Dist_ values are constants for the projection, choosing them correctly will result in i.e. an isometric projection
xa = mapP.x * xDistX + mapP.y * xDistY;
ya = mapP.x * yDistX + mapP.y * yDistY;
xDistX and yDistX determine the angle of the x-axis, and xDistY and yDistY determine the angle of the y-axis on the projection (and also the size of the grid, but lets assume this is 1-pixel for simplicity).
x-axis-angle = atan(yDistX/xDistX)
y-axis-angle = atan(yDistY/yDistY)
a "normal" coordinate system like this
--------------- x
|
|
|
|
|
y
has values like this:
xDistX = 1;
yDistX = 0;
xDistY = 0;
YDistY = 1;
So every step in x direction will result on the projection to 1 pixel to the right end 0 pixels down. Every step in the y direction of the projection will result in 0 steps to the right and 1 pixel down.
When choosing the correct xDistX, yDistX, xDistY, yDistY, you can project any trimetric or dimetric system (which is why i chose this).
So far so good, when this is drawn everything turns out okay. If "my system" and mindset are clear, lets move on to perspective.
I wanted to add some perspective to this grid so i added some extra's like this:
camera = new MapPoint(60, 60);
dx = mapP.x - camera.x; // delta x
dy = mapP.y - camera.y; // delta y
dist = Math.sqrt(dx * dx + dy * dy); // dist is the distance to the camera, Pythagoras etc.. all objects must be in front of the camera
fac = 1 - dist / 100; // this formula determines the amount of perspective
xa = fac * (mapP.x * xDistX + mapP.y * xDistY) ;
ya = fac * (mapP.x * yDistX + mapP.y * yDistY );
Now the real hard part... what if you got a (xa,ya) point on the projection and want to calculate the original point (x,y).
For the first case (without perspective) i did find the inverse function, but how can this be done for the formula with the perspective. May math skills are not quite up to the challenge to solve this.
( I vaguely remember from a long time ago mathematica could create inverse function for some special cases... could it solve this problem? Could someone maybe try?)
The function you've defined doesn't have an inverse. Just as an example, as user207422 already pointed out anything that's 100 units away from the camera will get mapped to (xa,ya)=(0,0), so the inverse isn't uniquely defined.
More importantly, that's not how you calculate perspective. Generally the perspective scaling factor is defined to be viewdist/zdist where zdist is the perpendicular distance from the camera to the object and viewdist is a constant which is the distance from the camera to the hypothetical screen onto which everything is being projected. (See the diagram here, but feel free to ignore everything else on that page.) The scaling factor you're using in your example doesn't have the same behaviour.
Here's a stab at trying to convert your code into a correct perspective calculation (note I'm not simplifying to 2D; perspective is about projecting three dimensions to two, trying to simplify the problem to 2D is kind of pointless):
camera = new MapPoint(60, 60, 10);
camera_z = camera.x*zDistX + camera.y*zDistY + camera.z*zDistz;
// viewdist is the distance from the viewer's eye to the screen in
// "world units". You'll have to fiddle with this, probably.
viewdist = 10.0;
xa = mapP.x*xDistX + mapP.y*xDistY + mapP.z*xDistZ;
ya = mapP.x*yDistX + mapP.y*yDistY + mapP.z*yDistZ;
za = mapP.x*zDistX + mapP.y*zDistY + mapP.z*zDistZ;
zdist = camera_z - za;
scaling_factor = viewdist / zdist;
xa *= scaling_factor;
ya *= scaling_factor;
You're only going to return xa and ya from this function; za is just for the perspective calculation. I'm assuming the the "za-direction" points out of the screen, so if the pre-projection x-axis points towards the viewer then zDistX should be positive and vice-versa, and similarly for zDistY. For a trimetric projection you would probably have xDistZ==0, yDistZ<0, and zDistZ==0. This would make the pre-projection z-axis point straight up post-projection.
Now the bad news: this function doesn't have an inverse either. Any point (xa,ya) is the image of an infinite number of points (x,y,z). But! If you assume that z=0, then you can solve for x and y, which is possibly good enough.
To do that you'll have to do some linear algebra. Compute camera_x and camera_y similar to camera_z. That's the post-transformation coordinates of the camera. The point on the screen has post-tranformation coordinates (xa,ya,camera_z-viewdist). Draw a line through those two points, and calculate where in intersects the plane spanned by the vectors (xDistX, yDistX, zDistX) and (xDistY, yDistY, zDistY). In other words, you need to solve the equations:
x*xDistX + y*xDistY == s*camera_x + (1-s)*xa
x*yDistX + y*yDistY == s*camera_y + (1-s)*ya
x*zDistX + y*zDistY == s*camera_z + (1-s)*(camera_z - viewdist)
It's not pretty, but it will work.
I think that with your post i can solve the problem. Still, to clarify some questions:
Solving the problem in 2d is useless indeed, but this was only done to make the problem easier to grasp (for me and for the readers here). My program actually give's a perfect 3d projection (i checked it with 3d images rendered with blender). I did left something out about the inverse function though. The inverse function is only for coordinates between 0..camera.x * 0.5 and 0.. camera.y*0.5. So in my example between 0 and 30. But even then i have doubt's about my function.
In my projection the z-axis is always straight up, so to calculate the height of an object i only used the vieuwingangle. But since you cant actually fly or jumpt into the sky everything has only a 2d point. This also means that when you try to solve the x and y, the z really is 0.
I know not every funcion has an inverse, and some functions do, but only for a particular domain. My basic thought in this all was... if i can draw a grid using a function... every point on that grid maps to exactly one map-point. I can read the x and y coordinate so if i just had the correct function i would be able to calculate the inverse.
But there is no better replacement then some good solid math, and im very glad you took the time to give a very helpfull responce :).

Make character escape from shot

Hello all math masters, I got a problem for you:
I have a 2D game (top down), and I would like to make the character escape from a shot, but not just walk away from the shot (I mean, don't be pushed by the shot), I want it to have a good dodging skills.
The variables are:
shotX - shot x position
shotY - shot y position
shotSpeedX - shot x speed
shotSpeedY - shot x speed
charX - character x position
charY - character y position
keyLeft - Set to true to make the character press the to left key
keyRight - Set to true to make the character press the to right key
keyUp - Set to true to make the character press the to up key
keyDown - Set to true to make the character press the down key
I can understand the following languages:
C/C++
Java
Actionscript 2/3
Javascript
I got this code (Actionscript 3), but sometimes it doesn't work:
var escapeToLeft:Boolean = false;
var r:Number = Math.atan2(0 - shotSpeedY, 0 - shotSpeedX)
var angle:Number = Math.atan2(charY - (shotY + shotSpeedY), charX - (shotX + shotSpeedX));
var b:Number = diepix.fixRotation(r-angle); // This function make the number between -180 and 180
if(b<0) {
escapeToLeft = true;
}
r += (escapeToLeft?1:0 - 1) * Math.PI / 2;
var cx:Number = Math.cos(r);
var cy:Number = Math.sin(r);
if(cx < 0.0) {
keyLeft = true;
}else {
keyRight = true;
}
if(cy < 0.0) {
keyUp = true;
}else {
keyDown = true;
}
Some observations:
Optimal dodging probably involves moving at a 90 degree angle from the bullets direction. That way, you get out of harms way the quickest.
If you do err, you want to err in the direction of the bullet, as that buys you time.
you can calculate 90 degrees to bullet direction with the scalar product
find the closest compass direction to the calculated optimal angle (4 possible answers)
are you allowed to go up and left at the same time? Now you have 8 possible answers to a bullet
bonus points for dodging in optimal direction according to second point
The scalar product of two vectors (ax, ay) and (bx, by) is ax * bx + ay * by. This is 0 if they are orthogonal (90 degrees). So, given the bullet (ax, ay), find a direction (bx, by) to run that has a scalar product of 0:
ax * bx must equal ay * by, so (bx, by) = (-ax, -ay)
Now to find the closest point on the compass for (bx, by), the direction you would like to run to. You can probably figure out the technique from the answer to a question of mine here on SO: How to "snap" a directional (2D) vector to a compass (N, NE, E, SE, S, SW, W, NW)? (note, thow, that I was using a wonky coordinate system there...)
If you have only 4 compass directions, your easiest path is to take:
max(abs(bx), abs(by))
The bigger vector component will show you the general direction to go - for
bx positive: right
bx negative: left
by positive: up (unless (0, 0) is top left with y positive in bottom left...)
by negative: down
I guess you should be able to come up with the rest on your own - otherwise, good luck on writing your own game!
I am not following what the line
var angle:Number = Math.atan2(charY - (shotY + shotSpeedY), charX - (shotX + shotSpeedX));
is supposed to be doing. The vector ( charY - shotY, charX - shotX ) would be the radius vector pointing from the location of the shot to the location of the character. But what do you have when you subtract a speed vector from that, as you are doing in this line?
It seems to me that what you need to do is:
Calculate the radius vector (rY, rX) where rY = shotY - charY; rX = xhotX - charX
Calculate the optimal direction of jump, if the character weren't constrained to a compass point.
Start with a vector rotated 90 degrees from the shot-character radius vector. Say vJump = ( rX, -rY ). (I think Daren has this calculation slightly wrong--you are transposing the two coordinates, and reversing one of their signs.)
The character should either wants to jump in the direction of vJump or the direction of -vJump. To know which, take the scalar product of vJump with (shotSpeedY, shotSpeedX). If this is positive, then the character is jumping towards the bullet, which you don't want, obviously, so reverse the sign of both components of vJump in this case.
Jump in the permissible direction that is closest to vJump. In the code you listed, you are constrained to jump in one of the diagonal directions--you will never jump in one of the cardinal directions. This may in fact be the mathematically optimal solution, since the diagonal jumps are probably longer than the cardinal jumps by a factor of 1.414.
If your jumps are actually equal distance, however, or if you just don't like how it looks if the character always jumps diagonally, you can test each of the eight cardinal and intermediate directions by calculating the scalar product between vJump and each of the eight direction vectors (0,1), (0.7071,0.7071), (1,0), (0.7071,-0.7071), etc. Take the direction that gives you the biggest positive scalar product. Given the patterns present, with some clever programming you can do this in fewer than eight tests.
Note that this algorithm avoids any math more complicated than addition and multiplication, so will likely have much better performance than something that requires trig functions.

Resources