Angle to Target

Calculating the angle from one point to another (as in the case where one sprite is targeting another) is extremely useful (if not crucial) in most games. Imagine you are working on a real-time strategy game. You must program the game so that the player can select units with the mouse and right-click a target location where the unit must move to. Even a simple process like that requires a calculation—between the unit’s location and the selected target location in the game. In the space shooter genre, in order to fire at the player’s ship, enemies must be able to face the player to fire in the correct direction. I could provide you with many more examples, but I suspect you get the point. The key to this important need is a calculation that I like to call angle to target.

The calculation is very simple—about as simple as calculating angular velocity, which is much simpler than the Distance function. We need to use another trigonometry function this time: atan2(). This is a standard C math library function that calculates the arctangent of two deltas—first the Y delta, then the X delta. A delta is the difference between two values. For our purposes here, we need to get the delta of both X and Y for two points. For instance, if Point A is located at X1,Y1, and Point B is located at X2,Y2, then we can calculate the delta of the two points like so:

deltaX = X2 - X1
deltaY = Y2 - Y1

The atan2() function requires the deltaY first, then the deltaX parameter. Here is the AngleToTarget method as it appears in the Math class:

float Math::AngleToTarget(float x1,float y1,float x2,float y2)
{
    float deltaX = (x2-x1);
    float deltaY = (y2-y1);
    return atan2(deltaY,deltaX);
}

I have coded an overloaded version of this function so you can pass Vector3 values:

float Math::AngleToTarget(Vector3& src,Vector3& tgt)
{
    return AngleToTarget(src.getX(),src.getY(),tgt.getX(),tgt.getY());
}

See, it’s like I said, fairly simple. But, wow, is this unassuming function useful! I would be remiss by not providing a demo program that shows off this newfound tool. The TargetingDemo program (shown in Figure 10.3) is one of my favorite demos! It’s the reverse of what you usually find in a video game. In this demo, you (the player) are in control of the asteroids, and the computer has to shoot them! The program first needs to figure out which asteroid is closest. Then it must calculate the angle to that target asteroid. Finally, it can fire a bullet at the target. It’s really quite fun watching the computer frantically shoot down asteroids—and get confused when there is a large cluster of asteroids close by!

Figure 10.3. The TargetingDemo program demonstrates the utility of angle to target.


#include "..EngineAdvanced2D.h"
using namespace Advanced2D;

#define SCREENW 1024
#define SCREENH 768
#define BULLET_VEL 3.0
#define ASTEROID_VEL 3.0

#define OBJECT_BACKGROUND 1
#define OBJECT_SHIP 10
#define OBJECT_BULLET 20
#define OBJECT_ASTEROID 30
#define OBJECT_EXPLOSION 40
Font *font;
Console *console;
Texture *bullet_image;
Texture *asteroid_image;
Texture *explosion_image;
Vector3 ship_position;
Vector3 nearest_asteroid;
Vector3 target_lead;
float ship_angle = 90;
float nearest_distance;
Timer fireTimer;

bool game_preload()
{
    g_engine->setAppTitle("TARGETING DEMO");
    g_engine->setFullscreen(false);
    g_engine->setScreenWidth(SCREENW);
    g_engine->setScreenHeight(SCREENH);
    g_engine->setColorDepth(32);
    return 1;
}


bool game_init(HWND)
{
    //create the background
    Sprite *background = new Sprite();
    background->setObjectType(OBJECT_BACKGROUND);
    if (!background->loadImage("craters.tga")) {
        g_engine->message("Error loading craters.tga");
        return false;
    }
    g_engine->addEntity( background );

    //create the console
    console = new Console();
    if (!console->init()) {
        g_engine->message("Error initializing console");
        return false;
    }


    //create ship sprite
    Sprite *ship = new Sprite();
ship->setObjectType(OBJECT_SHIP);
if (!ship->loadImage("spaceship80.tga")) {
    g_engine->message("Error loading spaceship.tga");
    return false;
}
ship->setRotation( g_engine->math->toRadians(90) );
ship->setPosition( 10, SCREENH/2-32 );
g_engine->addEntity(ship);

//load bullet image
bullet_image = new Texture();
if (!bullet_image->Load("plasma.tga")) {
    g_engine->message("Error loading plasma.tga");
    return false;
}

//load asteroid image
asteroid_image = new Texture();
if (!asteroid_image->Load("asteroid.tga")) {
    g_engine->message("Error loading asteroid.tga");
    return false;
}

//load the explosion image
explosion_image = new Texture();
if (!explosion_image->Load("explosion_30_128.tga")) {
    g_engine->message("Error loading explosion");
    return false;
}

//load the Verdana10 font
font = new Font();
if (!font->loadImage("verdana10.tga")) {
    g_engine->message("Error loading verdana10.tga");
    return false;
}
if (!font->loadWidthData("verdana10.dat")) {
    g_engine->message("Error loading verdana10.dat");
    return false;
}
    font->setColumns(16);
    font->setCharSize(20,16);

    //load sound effects
    if (!g_engine->audio->Load("fire.wav","fire")) {
        g_engine->message("Error loading fire.wav");
        return false;
    }

    if (!g_engine->audio->Load("boom.wav","boom")) {
        g_engine->message("Error loading boom.wav");
        return false;
    }

    //maximize processor
    g_engine->setMaximizeProcessor( !g_engine->getMaximizeProcessor() );

    return true;
}

void updateConsole()
{
    std::ostringstream ostr;
    int y = 0;
    console->print(g_engine->getVersionText(), y++);
    ostr.str("");
    ostr << "REFRESH : " << (float)(1000.0f/g_engine->getFrameRate_core())
         << " ms (" << g_engine->getFrameRate_core() << " fps)";
    console->print(ostr.str(), y++);

    ostr.str("");
    ostr << "Entities: " << g_engine->getEntityCount();
    console->print(ostr.str(), y++);

    ostr.str("");
    ostr << "Nearest asteroid: " << nearest_asteroid.getX() << "," << nearest_
asteroid.getY();
    console->print(ostr.str(), y++);

    ostr.str("");
    ostr << "Nearest distance: " << nearest_distance;
    console->print(ostr.str(), y++);

    ostr.str("");
    ostr << "Leading target: " << target_lead.getX() << "," << target_lead.getY();
    console->print(ostr.str(), y++);

    ostr.str("");
    ostr << "Angle to target: " << ship_angle;
    console->print(ostr.str(), y++);
}

void addAsteroid()
{
    //add an asteroid
    Sprite *asteroid = new Sprite();
    asteroid->setObjectType(OBJECT_ASTEROID);
    asteroid->setVelocity(-ASTEROID_VEL, 0);
    asteroid->setPosition(SCREENW,50+rand()%(SCREENH-150));
    asteroid->setImage(asteroid_image);
    asteroid->setTotalFrames(64);
    asteroid->setColumns(8);
    asteroid->setSize(60,60);
    asteroid->setFrameTimer( rand() % 100 );
    asteroid->setCurrentFrame( rand() % 64 );
    if (rand()%2= =0) asteroid->setAnimationDirection(-1);
    g_engine->addEntity( asteroid );
}

void firebullet()
{
    //get the ship from the entity manager
    Sprite *ship = (Sprite*)g_engine->findEntity(OBJECT_SHIP);
    if (!ship)
    {
        g_engine->message("Error locating ship in entity manager!","ERROR");
        g_engine->Close();
    }

    //create bullet sprite
    Sprite *bullet = new Sprite();
    bullet->setObjectType(OBJECT_BULLET);
    bullet->setImage(bullet_image);
    bullet->setMoveTimer(1);
    bullet->setLifetime(5000);
    //set bullet equal to ship's rotation angle
    float angle = g_engine->math->toRadians(ship_angle);
    bullet->setRotation( angle );

    //set bullet's starting position
    float x = ship->getX() + ship->getWidth()/2;
    float y = ship->getY() + ship->getHeight()/2-8;
    bullet->setPosition(x,y);

    //set bullet's velocity
    float vx = g_engine->math->LinearVelocityX(ship_angle) * BULLET_VEL;
    float vy = g_engine->math->LinearVelocityY(ship_angle) * BULLET_VEL;
    bullet->setVelocity(vx, vy);

    //fire bullet
    g_engine->addEntity(bullet);
    g_engine->audio->Play("fire");
}


void targetNearestAsteroid(Sprite *asteroid)
{
    //get asteroid's position
    Vector3 target = asteroid->getPosition();

    //calculate distance to target
    float dist = ship_position.Distance( target );
    if (dist < nearest_distance) {
        nearest_asteroid = target;
        nearest_distance = dist;

        //lead the target for better accuracy
        target_lead.setX(asteroid->getVelocity().getX() * 0.01f);
        target_lead.setY(asteroid->getVelocity().getY() * 0.01f);
        nearest_asteroid.setX(nearest_asteroid.getX() + target_lead.getX());
        nearest_asteroid.setY(nearest_asteroid.getY() + target_lead.getY());

        //calculate angle to target
        ship_angle = g_engine->math->AngleToTarget(ship_position,nearest_
asteroid);
        ship_angle = 90 + g_engine->math->toDegrees( ship_angle );

    }
    //is there a target to shoot at?
    if (nearest_distance < 1200) {
        if (fireTimer.stopwatch(100)) {
            firebullet();
        }
    }
}

void game_update()
{
    updateConsole();
}

void game_render2d()
{
    font->Print(1,SCREENH-20,"Press ~ or F12 to toggle the Console");
    font->Print(1,SCREENH-40,"Press SPACE to launch an asteroid!!!");
    if (console->isShowing()) console->draw();
    nearest_distance = 999999;
}

void game_end()
{
    delete console;
    delete font;
    delete bullet_image;
    delete asteroid_image;
    delete explosion_image;
}

void game_entityUpdate(Advanced2D::Entity* entity)
{
    float y;
    Sprite *ship, *bullet, *asteroid;
    Vector3 position;

    switch(entity->getObjectType())
    {
        case OBJECT_SHIP:
             ship = (Sprite*)entity;
             ship_position = ship->getPosition();
             ship->setRotation( g_engine->math->toRadians(ship_angle) );
            break;
        case OBJECT_BULLET:
            bullet = (Sprite*)entity;
            if (bullet->getX() > SCREENW)
                bullet->setAlive(false);
            break;
        case OBJECT_ASTEROID:
            asteroid = (Sprite*)entity;
            if (asteroid->getX() < -64) asteroid->setX(SCREENW);
            targetNearestAsteroid( asteroid );
            break;
    }
}


void game_entityCollision(Advanced2D::Entity* entity1,Advanced2D::Entity*
entity2)
{
    if (entity1->getObjectType() = = OBJECT_ASTEROID)
    {
        Sprite *asteroid = (Sprite*)entity1;

        if (entity2->getObjectType() = = OBJECT_BULLET)
        {
            //create an explosion
            Sprite *expl = new Sprite();
            expl->setObjectType(OBJECT_EXPLOSION);
            expl->setImage(explosion_image);
            expl->setColumns(6);
            expl->setCollidable(false);
            expl->setSize(128,128);
            float x = asteroid->getPosition().getX();
            float y = asteroid->getPosition().getY();
            expl->setPosition(x-32,y-32);
            expl->setTotalFrames(30);
            expl->setFrameTimer(40);
            expl->setLifetime(1000);
            g_engine->addEntity( expl );


            //remove the asteroid
            entity2->setAlive(false);
            //remove the bullet
            entity1->setAlive(false);

            //play explosion sound
            g_engine->audio->Play("boom");
        }
    }
}

void game_keyPress(int key)
{
    switch (key) {
        case DIK_SPACE:
            addAsteroid();
            break;
    }
}

void game_keyRelease(int key)
{
    switch (key) {
        case DIK_ESCAPE:
            g_engine->Close();
            break;
        case DIK_F12:
        case DIK_GRAVE:
             console->setShowing( !console->isShowing() );
             break;
    }
}

void game_render3d()
{
    g_engine->ClearScene(D3DCOLOR_XRGB(0,0,80));
}

void game_mouseButton(int button) { }
void game_mouseMotion(int x,int y) { }
void game_mouseMove(int x,int y) { }
void game_mouseWheel(int wheel) { }
void game_entityRender(Advanced2D::Entity* entity) { }

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset