Reputation: 63
I feel this is a difficult question to articulate, so I have illustrated on this graph (I am using SDL in C++). Each square represents a pixel on the screen, I want the red pixel to move at the same speed regardless of direction.
If the speed is 8 pixels/sec then after 1 second:
In both cases the pixel has been displaced by 8 pixels, however.. The euclidean distance between red and blue = 8.00 and red and green = 11.31. I want the pixel to arrive at yellow instead.
So I have tried to correct this by declaring a constant speed, then I divide this by the actual displacement, giving me a number I use to multiple the X and Y coordinates and travel back along the trajectory, limiting my speed.
The code looks sorta like this (I have commented the area of interest):
float velX = 0, velY = 0, currentX, currentY;
int time = 0, speed = 300;
//Events
void handleInput(){
if( event.type == SDL_KEYDOWN ){
switch( event.key.keysym.sym ){
case SDLK_UP: {velY -= speed;} break;
case SDLK_DOWN: {velY += speed;} break;
case SDLK_LEFT: {velX -= speed;} break;
case SDLK_RIGHT: {velX += speed;} break;
}
}
else if( event.type == SDL_KEYUP ){
//do the opposite
}
}
//Logic
void move(){
//float dist = sqrt( (velX*velX) + (velY*velY) );
//
//if(dist > 0){
// velX *= speed / dist;
// velY *= speed / dist;
//}
currentX += velX * (get_delta_ticks(&time) / 1000.f);
currentY += velY * (get_delta_ticks(&time) / 1000.f);
set_delta_ticks(&time);
}
//Render
void Player::render(){
apply_surface(currentX, currentY, spriteSheet, screen, ¤tClip);
}
So here is my question, I am new to programming games and I'm unsure if this is the CORRECT way to be doing movement.. It seems a bit inefficient in ways, should I be trying to deduce the position based on an angle and the length of the hypotenuse instead? I don't know very much about trigonometry but of course I am keen to learn.
Upvotes: 3
Views: 1854
Reputation: 26429
Well, it looks like most people forgot about analog input...
Anyway, It should work like this:
velX
, velY
are floats within [-1.0..1.0] range.
In case of digital input (keyboard, dpad), pressing "left" sets velX to -1, pressing "right" sets velX to 1, etc.
However, if you use analog stick, you put floating point values, where velX == 1.0
corresponds to rightmost position of analog stick, velX == -1.0
corresponds to leftmost position, and so on.
maxSpeed
is maximum game movement speed, also float.
With all this in mind, you could calculate next position of object like this:
void move(){
float curVelX = velX, curVelY = velY;
float moveSquared = (curVelX*curVelX + curVelY*curVelY);
if (moveSquared > 1.0f){
float d = sqrtf(moveSquared);
curVelX /= d;
curVelY /= d;
}
currentX += curVelX * maxSpeed * (get_delta_ticks(&time) / 1000.f);
currentY += curVelY * maxSpeed * (get_delta_ticks(&time) / 1000.f);
set_delta_ticks(&time);
}
It seems a bit inefficient in ways,
Look, you have ONE object. When you'll have few hundreds of thousands of them, then you can start worrying about efficiency.
should I be trying to deduce the position based on an angle and the length of the hypotenuse instead?
If your object is torpedo-like and can slowly turn left/right and accelerate/decelerate (you can steer it a bit and make it go faster/slower), then you probably need to store movement direction and linear movement speed.
If your object is some kind of flying orb or rolling ball that can go in any direction it wants, then you should use method similar to the one I described. Have separate velocity for x/y and limit maximum linear velocity using sqrtf.
Upvotes: 0
Reputation: 67772
Separate the logical position from the display position.
The logical position will probably need to use floating-point coordinates, and you'll round them to integer pixel coordinates for the display position. You can even do anti-aliasing with this if you want to smooth the movement.
So:
(x,y)=(1.0,0.0)
(x,y)=(0.0,-1.0)
(x,y)=(1/sqrt(2),-1/sqrt(2))
every 1/8th of a second, you add the unit vector to your current logical location, and select which pixel to draw. Obviously you can choose different units and update frequencies, but this will give the numbers you asked for.
Upvotes: 1
Reputation: 70536
I would start with different user controls: namely absolute speed and direction.
Given speed velAbs and the angle theta, you have
velX = velAbs * cos(theta);
velY = velAbs * sin(theta);
When updating the position, it is typically most convenient to decompose the absolute speed in its X and Y components, update the X and Y positions for the given time interval dt
currentX = velX * dt;
currentY = velY * dt;
whereas for collision impact computations the absolute speed is more relevant.
This will avoid your yellow/green problem because maximum throttle in both the X and Y directions will get you to green. Just let the user set the throttle from 0 to 8 and also set a direction, then you will get to yellow or blue.
Upvotes: 0
Reputation: 1276
The point is: You counted 8-x and 8-y key press events, which lead to a shortest distance from the origin of v=sqrt(8*8+8*8)=11.31, exactly as you observed.
You should be aware, that, within the time you are measuring, either 8 (only x OR y) or 16 (x plus y) key press events might be sampled, resulting in different "speeds", where speed=number_of_key_events/period_of_time
If you want to travel to the "yellow" spot, there should be only 6 X key press events plus 6 Y key press events in the same period of time in which you sampled the 8 key presses in one of the basic directions.
So there is nothing wrong with your code, and, as the other posters pointed out, your euclidian speed can be calculated using the euclidian distance divided by the sampling period, resulting in v=8 or v=11.31, respectively.
Upvotes: 0
Reputation: 403
You need to get the speed in a 2D Space. To get it you have to do a sqrt with both speeds.
curSpeed = sqrt( ( velX * velX ) + (velY * velY ) );
Upvotes: 0