Amiga.org
Coffee House => Coffee House Boards => CH / Science and Technology => Topic started by: motorollin on August 27, 2007, 08:03:16 PM
-
In C++/SDL, how can I tell a sprite to move in a specific angle, in degrees? Currently I only know how to move it a certain number of pixels on the x or y axis, which doesn't really help when I want to move it at an angle of, say, 30 degrees.
Note this is moving in a straight line at that angle, not rotating the sprite.
--
moto
-
Two possible approaches:
1) Use component velocities (velocities in X and Y axes). You can work this out from taking the sine and cosine of the angle and using them to factor your sprite's overall speed into the vX and vY components (which will allow you to position the sprite at any time along the trajectory).
2) Use a bresenham style line drawing algorithm to calculate your sprite position over successive iterations. This is only really useful if your sprite moves a fixed distance every frame.
Since you know how to move the sprite in X and Y, you could use either of the above. I'd go for (1) myself.
-
Thanks Karlos! Here's what I've knocked together in the move() function:
if ( x <= 20 ) if ( xVel < 0 ) xVel = -xVel;
if ( y <= 20 ) if ( yVel < 0 ) yVel = -yVel;
if ( x >= 620 ) if ( xVel > 0 ) xVel = -xVel;
if ( y >= 440 ) if ( yVel > 0 ) yVel = -yVel;
x+=xVel*cos(angle);
y+=yVel*sin(angle);
This does work, and makes the sprite move in the angle specified by int angle, and bounce off the edges of the screen if it reaches the edge of the playfield. However, if I have lots of the same sprite (a vector of the same class) lots of the sprites seem to go off the edge of the screen instead of bouncing back in to the playfield :-?
-
Strange. You're not using threads or anything like that are you?
Incidentally, I had something like this in mind for the components:
vX = sprite->speed*cos(sprite->angle);
vY = sprite->speed*sin(sprite->angle);
ie, you only have speed and angle and calculate vX amd vY as required.
I'd probably go for a basic lookup table or something for the sin/cos too if my angle is an integer with a small enough range. Or cache the cos/sin angle values and recalculate only when angle changes. Or both :lol:
-
No threads. It just runs through the vector of sprites and moves/checks/recalculates/shows them one at a time. I'm wondering if my variables are declared as public when they should be private, or vice versa? I'm not sure I fully understand the difference, but if a variable were public might that mean the velocity for all of the sprites was being adjusted the same instead of them each having their own velocity?
--
moto
-
Looks like I had some of the < and > signs the wrong way round. Also I expanded the nested if statements out, and it seems to be working now.
if ( x <= 20 )
{
if ( xVel > 0 )
{
xVel = -xVel;
}
}
if ( y <= 20 )
{
if ( yVel < 0 )
{
yVel = -yVel;
}
}
if ( x >= 620 )
{
if ( xVel < 0 )
{
xVel = -xVel;
}
}
if ( y >= 440 )
{
if ( yVel > 0 )
{
yVel = -yVel;
}
}
x+=xVel*cos(angle);
y+=yVel*sin(angle);
Not sure how this is different to your last example Karlos, but at least it works now :-D
Cheers!
--
moto
-
Hmmm, I borked it again :lol: I found out what is causing it, but I don't know why.
When the enemy is created its initial angle is set. If I set this to, e.g., 40 degrees, then the enemies all stay within the playfield. If I set it to rand() % 350 + 10 (should be an angle betwen 10 and 350 degrees) then they start flying off the screen :-?
--
moto
-
Right. I was looking at the if statements and was pretty sure something weird must be happening, but as it worked in the single sprite case and failed in the many sprite case, simple boundary detection didn't seem the obvious cause ;-)
With respect to the calculation stuff, it's different in that you have have already separated the velocities into xVel and yVel which you then multiply by the corresponding component factor (cos(angle) / sin(angle) in the respective direction.
Or in English, you already have separate xVel / yVel which you then multiply by the cos/sin, which is quite different from having single speed which is then used to derive xVel / yVel.
In other words, I'm saying:
xVel = speed*cos(angle)
where as you have
xVel*cos(angle)
...what was xVel initialised with?
-
OIC. xVel and yVel are initialised with enemyspeed, which is in turn initialised at the top of my code, just to make it easy to tweak sprite speeds and have the changes cascade through the code. xVel and yVel end up (currently) with a value of 3.
--
moto
-
Right.
I'd suggest it's more numerically correct to stick with either totally separate 'vectorized' style xVel / yVel (ie no angle) or have speed and angle and derive xVel/yVel from the speed and angle.
In the latter case, the component velocities xVel and yVel, at any time, will always be:
xVel = speed*cos(angle);
yVel = speed*sin(angle);
which you derive just as you need them. For example, you can say, at any time:
sprite->posX += sprite->speed*cos(sprite->angle);
sprite->posY += sprite->speed*sin(sprite->angle);
to reposition the sprite. As an optimisation, if angle doesn't vary much, I'd consider storing the result of cos(angle) and sin(angle), but not xVel/yVel, eg:
sprite->scaleVelX = cos(sprite->angle);
sprite->scaleVelY = sin(sprite->angle);
...
sprite->posX += sprite->speed*sprite->scaleVelX;
sprite->posY += sprite->speed*sprite->scaleVelY;
I'm obviously not taking edge rebound into consideration here, but you get the general idea.
Having just one speed/angle pair per sprite makes things a bit easier to understand. It also allows you to alter the speed and angle (to simulate acceleration etc) with relative ease. The correct coordinates will always be found by the expressions above for any given value of speed/angle.
Also, unless your sprites always all move with the same speed, you'd want to make the speed/angle properties part of the sprite structure.
-
The reason for having two separate velocity variables (xVel and yVel) is for the edge rebound code. If the sprite hits the top or bottom then yVel is made negative, and if it hits the left or right then xVel is made negative.
If I only use one variable for the velocity then I can't do this, since changing the velocity variable would affect the direction on both the X and Y axes.
--
moto
-
True, but remember, you could alter the angle instead.
-
The problem there is that sometimes the sprite crosses a couple of pixels over the edge of the playfield boundary. This is not a problem aesthetically, but it does mean that it keeps having its angle reversed over and over again so gets stuck. At least if I check whether the velocity is going to take the sprite further out of the playfield before changing it then I can stop this from happening.
--
moto
-
Hey, all is fair in love, warfare and game physics ;-)
I remember doing trajectory stuff for objects bouncing over a polygon mesh. Not much fun at all :lol:
-
Ok this works:
if ( x <= 20 )
{
x=21;
angle=(180-angle);
}
if ( y <= 20 )
{
y=21;
angle=(180-angle)-180;
}
if ( x >= 620 )
{
x=619;
angle=(180-angle);
}
if ( y >= 440 )
{
y=439;
angle=(180-angle)-180;
}
x+=pinwheelspeed*cos(angle);
y+=pinwheelspeed*sin(angle);
But every so often the sprite hits the wall at an angle which means it can't escape for some reason. It's probably due to the fact that certain angle can't be represented precisely at such a low resolution. I think it might be better to have tables of x and y velocities to give a variety of "angles" rather than trying to calculate actual angles.
--
moto
-
As sin / cos use doubles and return doubles, that's unlikely. It's more likely that the conversion to whatever type x and y are discards fractional data for results that would otherwise be less than one, giving a zero x or y velocity.
Strange that you are using degrees as an angular measure for sin and cos. I thought it only worked in radians?
Anyway, if you are going for strict x / y velocities and only ever inverting their signs on collision with a wall, you might as well do away with angles except when firing them off initially.
That is to say, each sprite is initialised with
xVel = speed*cos(angle);
yVel = speed*sin(angle);
and then basically the values dont change for the sprite except for sign flipping on hitting a wall.
Avoid narrow angles by only allowing say a 5 degree granularity for the starting angle.
-
Karlos wrote:
Strange that you are using degrees as an angular measure for sin and cos. I thought it only worked in radians?
Ohhhhh, that could be my mistake - I was treating the result as degrees :roll: However, this seems to work better anyway:
Pinwheel::Pinwheel()
{
...
xVel=4;
yVel-2;
...
}
Pinwheel::move()
{
if ( x <= 0 )
{
x=1;
xVel = -xVel;
}
if ( y <= 0 )
{
y=1;
yVel = -yVel;
}
if ( x >= LEVEL_WIDTH-PINWHEEL_WIDTH )
{
x=(LEVEL_WIDTH-PINWHEEL_WIDTH)-1;
xVel = -xVel;
}
if ( y >= LEVEL_HEIGHT-PINWHEEL_HEIGHT )
{
y=(LEVEL_HEIGHT-PINWHEEL_HEIGHT)-1;
yVel = -yVel;
}
x+=xVel;
y+=yVel;
}
This works perfectly even with 1000 pinwheels on screen. Only problem is, when I tried changing the values of xVel and yVel to "rand() % 10 + 2" to make them a random number, the pinwheels all moved in the same direction every time. Did I get the rand() syntax wrong?
--
moto
-
My mind hurts...
-
C(++) stdlib rand() returns a pseudorandom number between 0 and RAND_MAX, which is implementation defined (from as low as 32767 or as high as (2^32)-1, depending on the implementation).
To convert the output to the range 0 ... N use ((N*rand())/RAND_MAX)
-
whabang wrote:
My mind hurts...
Wrong forum, really :lol:
-
Hmmm, it doesn't work...
int xv=(3*(rand()/RAND_MAX))+1;
int yv=(3*(rand()/RAND_MAX))+1;
int r=1*(rand()/RAND_MAX);
if ( r == 1 ) xv = -xv;
r=1*(rand()/RAND_MAX);
if ( r == 1 ) yv = -yv;
xVel=xv;
yVel=yv;
Lines 1 and 2 should (I think) generate two random numbers between 1 and 4 inclusive. Lines 3-6 then give each velocity a 50/50 chance of becoming negative (IOW the sprite has a 50/50 chance of having its direction reversed in each axis). Lines 7 and 8 obviously then assign the result to the velocities.
When I run this over and over again, the sprite always moves in the same direction. I then added "if ( xv == 1 && yv == 1 ) quit=true;", and my game quits every single time. That suggests to me that the result of the rand() functions are always returning 0 :-?
--
moto
-
As I said, you need
(N*rand())/RAND_MAX
What you are doing is this:
N*(rand()/RAND_MAX)
Since you are dealing with integer arithmetic here, you will always get zero for that except in the rare (about 1 in RAND_MAX ;-) ) chance that rand() returns RAND_MAX.
Order of evaluation is very important here. You want to multiply rand() by your scaling factor *before* dividing it, not the other way around.
-
Ok I changed my code to this:
int xv=((3*rand())/RAND_MAX)+1;
int yv=((3*rand())/RAND_MAX)+1;
int r1=((1*rand())/RAND_MAX)+1;
if ( r1 == 1 ) xv = -xv;
int r2=((1*rand())/RAND_MAX)+1;
if ( r2 == 1 ) yv = -yv;
pinwheels.xVel=xv;
pinwheels.yVel=yv;
The enemy still moves in the same direction each time the game is launched (though a different one to the one it went in before I changed the rand() lines)
-
Well, you didn't expect rand() to be actually random did you? ;-)
Unsurprisingly, rand() returns the next number in a series of pseudorandom numbers that are in fact entirely predictable if you know the algorithm in use and the seed that it started with.
Try initialising the random number generator at the start with something reasonably likely to be different each time your program runs. The current system time is a favourite random seed of old.
-
Well actually I do have "srand( time(NULL) );" at the top of my main function, before the rand() commands :-?
--
moto