Welcome, Guest. Please Login or Register.  • Help
SMF Underground
+ SHMUP-DEV » SHMUP DEV FORUMS » Assistance » Tutorials
|-+ Control Schemes [part 1] : Progear Turrets

Pages: [1]   Go Down
0 Members and 1 Guest are viewing this topic. Topic Tools  
Read August 13, 2008, 04:23:59 PM #0
motorherp

Control Schemes [part 1] : Progear Turrets

Hello again, I hope you all enjoyed my previous tutorial 'The art of collision [part 1]'. So you're probably expecting part 2 now but my mind keeps getting drawn towards control schemes so I've decided to run these two series in parallel. So this is the first tutorial in the control schemes series in which I'll be discussing how to implement gun turrets like those seen in Cave's Progear. In case you've never played this game, the enemy tanks and planes all tend to have these wonderfully animated gun turrets which rotate around to face the player and gives the game a very lively feeling. Progear isn't the only game to do this, in fact this technique can be seen in most of Cave's games as well as some games by other developers such as DoDonPachi (woops, turns out this is a Cave game after all.  For some reason I thought Treasure did that one.  Nevermind), but in my opinion Progear is the one that pulls this off the best. This tutorial is going to be fairly simple, but by the end of it we should have a good understanding of polar coordinates and have some pretty funky turret code you can whack into your games to make them look really proffesional. We'll also be re-using some of the ideas covered here in my next tutorial in this series which is going to be on spline motion.

Before I start to delve into this I recommend watching this -> www.youtube.com/watch on youtube which shows the game being played so that you have a fresh image in your mind of the kind of effect we're going to try and acheive. Pay careful attention to how the turrets rotate around and track the player. Beautiful isn't it. So how do they do that? Well first of all we're going to need some understanding of what polar coordinates are, and how to convert to them from cartesian (screen) coordinates. As the name suggests polar coordinates are defined by the parameters which make up circles, so the position of a target in these coords is described by a radius from the origin and an angle around the origin measured from a fixed reference direction. Hopefully this little diagram will make this clearer:



Our target in this diagram is represented by the green blob, and let use say for the sake of argument that each red circle represents one meter distance from the origin. So the target shown is therefore at coords (2.5, 140) since its 2.5 meters distance away from the origin, and the angle the line joining the target and origin makes with the cartesian x-axis is 140 degrees, the x-axis being the standard fixed reference direction from which angles are measured in an anti-clockwise direction. So why is this useful to us? Well a turret is an object whose only degree of freedom is to rotate and hence polar coords are a natural system to use for this, especialy if we center the coordinate system at our turret position. Hence to rotate a turret becomes a simple matter of adding to or subtracting from the angle coordinate, something that would become awkward to manage in a cartesian system. There is an extra significance to using polar coords in Progear however and that is since the turrets are drawn from a slight perspective rather than a strictly top down view. Here's a turret sprite sheet ripped from the game to show you what I mean:



Thus when the turrets rotate we cant just rotate the sprite quad but need to swap the texture altogether to maintain the feeling of perspective, otherwise our game will begin to resemble an Escher picture. In order to manage this we asign different textures with evenly distributed angle ranges around the turret where each texture depicts the turret with a corresponding rotation. Note that we could try and use a cartesian system which asigned textures depending on the difference in y value between target and turret for example, but such systems would give us uneven distributions of textures as the turret rotates as well as suffering other problems. Here's a picture to demonstrate assuming we have a turret sprite map with only eight unique sprites to keep things simple:





On top we see how we want to distribute our textures referred to by unique id's depending on which way the turret is facing which can be easily done if we represent the system using polar coords where each region is determined by adding the angle 'a' onto the previous region. As you can see, the regions are evenly distributed which is what we want. Underneath of that shows the same thing if we attempted to do it in a cartesian system were we're using the difference in y value between turret pos and target shown by the grey lines. As you can see the greater our angle becomes the smaller our regions become. In fact as the turret angle approaches 90 degrees, the area of the regions diverges to zero and we'd need an infinite number of textures. Clearly this isn't suitable and hence why we use polar coords.

So given the player and turret positions in cartesian coordinates how do we work out the angle and hence find out which texture to use to make the turret appear to point at the player? The problem boils down to representing the player's position in a polar coordinate system whose origin is at turret's position. First off I'll give you the code for working out the angle (note we're not interested in the radius coordinate) and then I'll give a discussion on why it works:

Code:
struct Obj
{
    float x, y; // position in cartesian coordinate
}


float CalculateAngle(Obj player, Obj turret)
{
    // center system around turret
    float x = player.x - turret.x;
    float y = player.y - turret.y;

    // calculate and return angle
    float angle = atan2(-x,y);
    return angle;
}

Simple eh? Well deceptively so, there's actualy more going on in there than you might at first realise. The first part is simply centering the system around the turret position by taking this away from the objects involved. Its the second atan2 part which is the clever bit. If you've never seen this function before, atan2 is included in almost every language I know and does the job of atan but takes care of quadrant considerations and also deals with nasty undefined cases automagicaly. If that just went over your heads, don't worry I'm going to explain in more detail now, but I'm going to assume you remember your high school trigonometry.



What we see here is a picture of our trigonometry quadrants where the turret position is at the center (0,0) and I've placed a target in each quadrant so I can demonstrate what atan2 does. Starting with quadrant 1, what we have is a right angled triangle formed between the turret, target, and cartesian axes. Remembering our high school trig we know that the angle shown in the triangle can be calculated with atan( y1 / x1 ), easy enough. Now consider our target in quadrant 2. Our trig in this case is calculating the angle from the negative x axis, but in polar coordinates we want the angle to be measured from the positive x axis. Knowing that the angle between the positive and negative x axes is pi radians (180 degrees), then our polar angle is pi - atan( y2 / x2 ). Going straight onto quadrant 4 we see that this gives us a different problem. In this case our trig is measuring the angle in the wrong direction since in polar coords the angle is always measured in an anti-clockwise direction. If this angle was measured from the x axis all the way around in an anti-clockwise direction then the answer becomes 2*pi - atan( y4 / x4) since we know there are 2*pi radians (360 degrees) in a full circle. The atan2 function responds like the atan function in this respect however in that it give us a negative angle but it turns out this works in our favour which is nice. The third quadrant is a combination of quadrants 2 and 4. I'll leave this one up to you to work out should you wish. As well as quandrants requiring different equations, there's also another case which can trip us over. What if our target has x = 0? As you can see, since the trig requires a divide by x this case becomes undefined and we'd need to test for this explicitly and set the angle to 90 degrees or 270 degrees accordingly. Luckily for us though, the atan2 function takes care of all of the above for us and is perfect for our needs, so instead of having to consider all these special cases we can just pass in atan2(y, x) and get the correct polar angle regardless of quadrant. So just to clarify, atan2 will return us an angle to the target between -pi and pi measured from the reference direction.

Have you noticed the difference yet? In the above explanation I say to use atan2(y, x) to solve the problem in the trig diagram and yet in the code example I used atan2(-x, y). Take a look at the turret sprite sheet again. Because of the perspective used the sprites range from looking directly up coming down to the right and ending with directly down, and we're going to flip the sprite horizontaly for targets to the left. Therefore it would be useful for us if our reference direction from which our polar angle is measured was the positive y axis rather than the positive x axis. What we need to do to acheive this is rotate our polar system 90 degrees anticlockwise so our reference direction is now pointing straight up, and this is what we've acheived with our little swap of x and y in the atan2 call. Stick out your first finger and thumb on your left hand so that your thumb is pointing straight up. Your first finger is now the positive x axis, and your thumb the positive y. Now rotate your hand 90 degrees anticlockwise so that now your first finger is pointing straight up. What was your x-axis has now become positive y, and what was your y axis has now become negative x (your thumb is pointing to the left rather than right hence the negative). Presuming you havn't just broken your wrist lets now consider how we decide which sprite texture to use for the turret depending on this angle.

I'm not going to go into detail but just provide an example code dump since how we do this really depends on how you store your textures and adress your texture coords (u,v). I just want to point out one thing which is that we have to flip the sprite on a positive angle rather than a negative one like you might expect since our polar angle is measure anticlockwise where as in our sprite sheet the turret is depicted rotating clockwise. So sssuming the turret sprite sheet is stored as its own texture with the top left having u=0.0, v=0.0, and the bottom right being u=1.0, v=1.0, then your code could look something like:

Code:
struct TurretSprite
{
    float u, v; // our current top left texture coord
    float du, dv; // our sprite width and height in texture coords
    bool horizontalFlip; // stores whether we should flip our sprite or not

    void Initialise();
    void SetTexture(float turretAngle);
}

void TurretSprite::Initialise()
{
    du = 1.0f / 6.0f;         // our sprite map is 6 sprites wide
    dv = 1.0f / 3.0f;         // and 3 deep
}

void TurretSprite::SetTexture(float turretAngle)
{
    if(turretAngle > 0)     { horizontalFlip = true; }
    else                             { horizontalFlip = false; }

    int texId = int(fabsf(turretAngle)*17.0f / pi) - 1;         // there's 17 sprites in total over an angle range of pi
    u = du * (float)(texId % 6);
    v = dv * floor(texId * du);
}

Ok, there's just one thing left I really have to mention to wrap this whole thing up and thats how to convert back from polar coordinates to cartesian coordinates. So for example, say we have our turret angle and the sprite is now making it look all pretty and pointing in the correct direction. We now want to spawn bullets from this turret, but rather than have them appear directly over the turrent we want them to start a little distance out so it looks like they're actualy coming out of the barrel. Well again this is simple trig so I'll not go into the details but rather just tell you how. Given the angle and the spawn distance, and keeping in mind that we rotated our polar system so that the reference direction is up, then we can calculate the bullet position using:

Code:
void SetBulletPosition(Obj turret, Obj* pBullet, float angle, float distance)
{
    pBullet->x = turret.x - distance*sin(angle);
    pBullet->y = turret.y + distance*cos(angle);
}

Well that's your lot. I hoped you enjoyed this tutorial and if you wish to ask any questions or give me any feedback feel free. My next tutorial in this series is going to be on controling motion using splines during which we'll be re-using some of these texture mapping ideas, so stay tuned.



Have fun.

Dan.
« Last Edit: August 13, 2008, 05:04:38 PM by motorherp »

Offline  
Read August 14, 2008, 07:12:25 PM #1
motorherp

Re: Control Schemes [part 1] : Progear Turrets

[reserved]


Offline  
Read August 14, 2008, 07:32:40 PM #2
motorherp

Re: Control Schemes [part 1] : Progear Turrets

You can find previous replies to this tutorial posted in the following thread:

http://www.shmup-dev.com/forum/index.php?topic=1081.0

That topic has now been locked.  Please continue any discussions or add any further questions in this thread.  Many thanks.


Offline  
Pages: [1]   Go Up
Jump to:  

Page created in 0.124 seconds with 19 queries.