Sign in to follow this  
medevilenemy

Unity Missile Tracking

Recommended Posts

I've been adding guided missiles to the game I'm working on, but have run into something of a snag. I'm basing the method on this thread: http://www.gamedev.net/community/forums/topic.asp?topic_id=419681 The code I'm using follows:
// This is the fun part -- track target
SF3dVector heretotarget = target->GetPos() - Position;
heretotarget = Normalize3dVector(heretotarget);

SF3dVector facingDifference = heretotarget - ViewDir;
float yawdirection = DotProduct(facingDifference, RightVector);
float pitchdirection = DotProduct(facingDifference, UpVector);

RotateX((yawdirection * 90)/fps);
RotateY((pitchdirection * 90)/fps);

The function DotProduct is defined as follows
float DotProduct(SF3dVector u, SF3dVector v)
{
    return (u.x * v.x) + (u.y * v.y) + (u.z * v.z);
}

Normalize3dVector is defined as follows
SF3dVector Normalize3dVector( SF3dVector v)
{
	SF3dVector res;
	float l = GetF3dVectorLength(&v);
	if (l == 0.0f) return NULL_VECTOR;
	res.x = v.x / l;
	res.y = v.y / l;
	res.z = v.z / l;
	return res;
}

When I spawn a missile object sending it off towards its target, it seems to spiral in an attempt at course correction, but ends up spiraling off into the distance at some funny angle instead. Does anyone see a problem in my code anywhere?

Share this post


Link to post
Share on other sites
At first glance, it appears as though you've mistakenly swapped rotating on the X axis for Yaw (should be Pitch) and Y for Pitch (should be Yaw).

Also, make sure you normalize your vectors before performing a dot product (to get a proper cosine of the angle between them).

Share this post


Link to post
Share on other sites
Ok, fixed the swapped x and y as well as normalized the Up and Right vectors. This seems to have helped somewhat, but not completely...

I can't quite tell whats going on... it seems that after I change targets (the first being destroyed), the missiles start acting wonky again. Perhaps they are trying to track the old destroyed enemy... but I don't see why that should be happening... relevant code below:

Constructor

Rotatable *target;
Guided_Missile_Launcher(float vel, float rt, float dmg, Rotatable *par, float dispx, float dispy, float dispz, float rx, float ry, int *a, Rotatable *targ)
{
speed = vel;
damage = dmg;
dx = dispx;
dy = dispy;
dz = dispz;
pitch = rx;
yaw = ry;

reloadtime = rt;
timelastfired = 0;

parent = par;
Position = parent->GetPos();
UpVector = parent->GetUp();
RightVector = parent->GetRight();
ViewDir = parent->GetView();


MoveForward(-dz);
StrafeRight(dx);
MoveUpward(dy);
RotateX(pitch);
RotateY(yaw);

ammo = a;

target = targ;
}



spawning the missile object

allprojectiles.push_back(new Guided_Missile(speed, damage, this, target));



Creating the weapon instance

Weapons.push_back(new Guided_Missile_Launcher(-0.95, 0.5, 10, this, 0.05, 0.05, 0.1, -1.0, 1.0, &pammo, playertarget));



where playertarget is a pointer to a rotatable object (Rotatable *playertarget)

Share this post


Link to post
Share on other sites
oops, just did. all that did was tighten up the turning for the case where it works... but it still misbehaves as before. Watching it carefully, on new targets it turns towards it but then continues turning in a wide arc and misses

Share this post


Link to post
Share on other sites
It's spiraling because when it's near the target it can't turn fast enough to move towards it. You can fix it with a combination of any of the following:

1) Make the missile turn faster.
2) Make it start far away from the target, giving it time to lock on (by the time it reaches the target it should be going straight at it).
3) (And this one's really important) Make sure the collision area is not too tiny, because unless you're implementing a proper missile control system (like the ones used in real life [grin] give me a beep if you wanna implement one of those) it will never hit the target with 100% accuracy.

4) Another work-around (harder to implement) is to have a finer simulation step (ie, more FPS for the physics).

Share this post


Link to post
Share on other sites
Just tried messing with turn speed... it is definitely not the turn speed at this point (though increasing the turn speed gives the missile a much cooler looking path). I am 100% sure that the missile launcher's target is not updating properly for some reason.

Share this post


Link to post
Share on other sites
Ran through the code with a friend last night. The problem stems from the way I try to pass the target pointer to the Guided_Missile_Launcher object. The way I want it written is so that I don't have to manually update it when the target changes. I had thought that passing the pointer (roughly: Rotatable *playertarget = *iterator) like as a pointer argument (..., Rotatable *targ) should work, but it does not update when the original pointer does. What I'm looking for is the proper way to pass the pointer.

Share this post


Link to post
Share on other sites
So you're caching the pointer to the target? If you do that, and the original pointer changes, you need to update the cached copy. You need some kind of UpdateTarget(Rotatable* target) call that resets the cached copy.

I'm not sure why you want to cache the target in the launcher itself. The missiles should have a pointer to it's target, but the launcher only needs to know about the target when it fires a missile, so it can pass it to the missile. Caching it is just asking for trouble later, if the target is deallocated/invalidated.

Share this post


Link to post
Share on other sites
the launcher's pointer to the target should be equal to the playertarget pointer (that is to say they should ultimately point to the same place. that way, when I change targets elswhere, the change should propagate on its own). That way it wouldn't require updates (which I don't give it). So I'm definitely passing the pointer incorrectly. I just haven't a clue how to properly write it to do what I want (ugh, I can get everything else working [grin]). I can post the relevant code in its entirely this evening if you think it will help.

Share this post


Link to post
Share on other sites
I think if you post the relevant code, we can help you with that.

This is just a guess, but I think you're doing the pointer thing wrong:

MissileLauncher:
-keeps a pointer to the target: eg Rotatable *m_pTarget

Rocket:
-keeps a pointer to the target: eg Rotatable *m_pTarget

If I understood you correctly, you do 'change the target elsewhere', say:

MissileLauncher::setTarget( Rotatable *pTarget ) { m_pTarget = pTarget; }



Now this is what I think you're doing wrong (if it's not the case, spare my life please):
Changing the literal value of the pointer (eg. making it point to another address) anywhere else won't change the the literal value of the pointer of your rocket. The rocket's target will still point towards the first target, but it can react on changes of the first target's position for example (because the object m_pTarget points to changes its position). You will need to update the target for the rocket as well, in order to react on changing targets.

Share this post


Link to post
Share on other sites
I wouldn't recommend that approach, as you are increasing coupling between two objects. This will only give you nightmares in the future. Not to mention, you have no way of knowing when the data the pointer is pointing to gets destroyed. There are certainly other ways of having the propagation happen "automatically", using a more secure design. However, theoretically, you could pass a pointer to a pointer, and cache that instead.


Rotatable **mTarget = ⌖

Then, to get the active target, you just dereference it like so:

Rotatabe *curTarget = *mTarget;

Still, I seriously don't recommend this approach. You should re-design your objects to communicate through interfaces, instead of fondling each other's data in inappropriate ways ;)

Share this post


Link to post
Share on other sites
I will post the code this evening (when I am on my dev machine). For now, I will try to clarify what I am doing.

I have a pointer *playertarget which points (at any given time) to the player's attack target. The player can change targets at will by pressing 't' or if the current dies. In either of these cases the object *playertarget points two changes.

The Guided_Missile_Launcher object's *target pointer needs to point to the same object as *playertarget. Once it does, as *playertarget changes (totally outside the scope of the weapons subsystem) so should *target. When a new missile object is spawned, it should point at the current target (so, it should point to what *target and *playertarget point to). If the current target dies before the missile hits it, no matter, the missiles are not designed to redirect to new targets once fired. the big thing is that all new missiles should fire towards whatever *playertarget is pointing towards at any given time.

Here's a bit of scope information

MAIN GAME (GLOBAL)
-- SHIPS SUBSYSTEM contains *playertarget
-- WEAPONS SUBSYSTEM contains Guided_Missile_Launcher and Guided_Missile, etc
-- launcher gets passed *playertarget in construction, should point *target through it.
-- Etc etc etc.

The game's core can see the public parts of these subsystems and issue commands that way, but the subsystems have no means of communication with eachother. So, ideally, updates to *playertarget should be totally transparently propagate through *target as if *target actually was *playertarget. The individual missiles don't need to care :-p

Share this post


Link to post
Share on other sites
I hope this is no missunderstanding, but I still think you're doing the wrong thing.


float *foo = NULL;
float *bar = NULL;

float pi = 3.14159f;

foo = π // foo points to the address of pi
bar = foo; // bar points to the address of pi (the literal value of foo is assigned to bar)

float e = 2.71f;

foo = &e; // foo points to the address of e
bar; // bar <b>still</b> points to the address of pi





It does matter. You jave to clarify whether you change the object, your pointer points to (eg assign the pointer another address) or just change some value inside the target class.

Share this post


Link to post
Share on other sites
that is the problem I'm having. I'm not sure how to achieve what I want (as defined in my last post). I could probably shoehorn manual updates (mostly by exposing playertarget to global scope, but it would be greatly preferable not to.

Share this post


Link to post
Share on other sites
I have no need to update the rocket's targets -- they don't have the "fuel" for it. But as things stand, I have to make the launchers themselves poll to check for updated targets, and I'd prefer not to. And I can't directly pass them *playertarget because of scope (which can easily be changed) and more importantly because the weapons system is supposed to be modular. It is designed so that I can install a weapon of any type on any suitable object (any ship or whatnot. The player's host object is derived from ship). If I bound the code to directly read from *playertarget, things would get very weird when I would try to use the missile launchers on enemy ships (they would target the player's enemies... aka themselves. while that would be funny, it would not be correct).

I hope I'm making myself sufficiently clear. Apologies if I am not.

Share this post


Link to post
Share on other sites
I agree with ThrustGoblin, the "correct" way would be to use a double-pointer. The actual data that matters to missiles and the player is the -pointer- to the target; the pointer is subject to change and everyone needs to know about that change. So they need to "point to" the "pointer" to the target.

Conceptually it's a bit hard to swallow. So I'd design a small helper class, like this:



class Target {
Ship *who;
};

class Ship{...};

class Missile{
Target *target;
};

Target player_target; //the word "target" is starting to sound funny at this point...

...

//missiles 1 and 2 will be directed by the player's target
missile1.target = &player_target;
missile2.target = &player_target;

//direct them at ship 34
player_target.who = &ship34;

//now direct them at ship 123
player_target.who = &ship123;

//the missile can get the ship's position like so:
float goto_x = target->who->x;
float goto_y = target->who->y;





It's like a double pointer but the double-ness is hidden away from view.

Share this post


Link to post
Share on other sites
Here's the relevant code... hopefully this will clear up the structure of things...

This is how playertarget is defined

Rotatable *playertarget;
list<Ship *>::iterator playertargetiter; // locked to player's target iterator




Here is the entire definition of Guided_Missile_Launcher -- the thing that shoots

class Guided_Missile_Launcher : public Weapon
{
public:
Rotatable *target;
Guided_Missile_Launcher(float vel, float rt, float dmg, Rotatable *par, float dispx, float dispy, float dispz, float rx, float ry, int *a, Rotatable **targ)
{
speed = vel;
damage = dmg;
dx = dispx;
dy = dispy;
dz = dispz;
pitch = rx;
yaw = ry;

reloadtime = rt;
timelastfired = 0;

parent = par;
Position = parent->GetPos();
UpVector = parent->GetUp();
RightVector = parent->GetRight();
ViewDir = parent->GetView();


MoveForward(-dz);
StrafeRight(dx);
MoveUpward(dy);
RotateX(pitch);
RotateY(yaw);

ammo = a;

target = *(targ);
}
void Fire()
{
Update();
if(timeGetTime()-timelastfired >= (reloadtime*1000) && (*ammo > 0 || *ammo == -1))
{
allprojectiles.push_back(new Guided_Missile(speed, damage, this, target));
timelastfired = timeGetTime();
if(*ammo > 0)
{
*ammo-=1;
}
}

}
void Update() // Updates position
{
Position = parent->GetPos();
UpVector = parent->GetUp();
RightVector = parent->GetRight();
ViewDir = parent->GetView();

MoveForward(-dz);
StrafeRight(dx);
MoveUpward(dy);
RotateX(pitch);
RotateY(yaw);

/*glPushMatrix();
glTranslated(Position.x, Position.y, Position.z);
rotateTo();
glColor4d(1.0, 0.0, 0.0, 1.0);
glBegin(GL_QUADS);
glVertex3d(0, 0, 0);
glVertex3d(0, 0, 0.5);
glVertex3d(0, 0.1, 0.5);
glVertex3d(0, 0.1, 0);
glEnd();
glPopMatrix();*/



}
/*void ChangeAmmo(int amt)
{
ammo += amt;
if(ammo > ammomax)
ammo = ammomax;
if(ammo < 0)
ammo = 0;
}*/

};



Here is the entire definition of Guided_Missile -- the actual thing that gets fired

class Guided_Missile : public Projectile
{
public:
Rotatable *target;
Guided_Missile(float vel, float dmg, Rotatable *parent, Rotatable *targ)
{
velocity = vel;
damage = dmg;
Position = parent->GetPos();
ViewDir = parent->GetView();
UpVector = parent->GetUp();
RightVector = parent->GetRight();
RotatedX = parent->GetXrot();
RotatedY = parent->GetYrot();
RotatedZ = parent->GetZrot();
target = targ;
impact = false;
armed = false;
explodetime = 0.0;

travelled = 0.0;
modelnum = 1;
texnum = 1;
}
void Update()
{
MoveForward(velocity/fps); // Take car of actual forward motion
if((velocity/fps) > 0)
travelled += (velocity/fps);
if((velocity/fps) < 0)
travelled -= (velocity/fps);

// This is the fun part -- track target
SF3dVector heretotarget = target->GetPos() - Position;
heretotarget = Normalize3dVector(heretotarget);

SF3dVector normalView = Normalize3dVector(ViewDir);

SF3dVector facingDifference = heretotarget - normalView;
facingDifference = Normalize3dVector(facingDifference);

SF3dVector normalRight = Normalize3dVector(RightVector);
SF3dVector normalUp = Normalize3dVector(UpVector);

float yawdirection = -1 * DotProduct(facingDifference, normalRight);
float pitchdirection = DotProduct(facingDifference, normalUp);


RotateY(yawdirection * (60.0/fps));
RotateX(pitchdirection * (60.0/fps));

if(travelled > 0.5)
{
armed = true;
}
}
void Render()
{

glPushMatrix();
//glTranslatef(displacement_x,displacement_y,displacement_z);
glTranslated(Position.x, Position.y, Position.z);
rotateTo();
glRotated(90, 0, 1.0, 0.0); // TEMPORARY LINE
if(travelled < 0.5)
DrawObj(models[1], 1, 0.0, 0.0, 0.0);
if(travelled >= 0.5)
{
DrawObj(models[1], 10, 0.0, 0.0, 0.0);
}
glRotated(-90, 0, 1.0, 0.0); // TEMPORARY LINE
glPopMatrix();
}
};



Here is how I change targets with the 't' key

if(GetAsyncKeyState('T')&0x8000)
{
if(timeGetTime() - lasttimetarget >= 100)
{
lasttimetarget = timeGetTime(); // Update the last time player targeted a ship
if(playertargetiter != Allships.end())
{
playertarget->targeted = false;

playertargetiter++;
playertarget = *playertargetiter;
playertarget->targeted = true;
}
else
{
playertarget->targeted = false;

playertargetiter = Allships.begin();
playertarget = *playertargetiter;
playertarget->targeted = true;
}
}
}



And here is how I instantiate a Guided_Missile_Launcher object in a linked list of all weapons on the ship (Weapons) the player's ship itself being known as pShip.


Weapons.push_back(new Guided_Missile_Launcher(-0.95, 0.5, 10, this, 0.05, 0.05, 0.1, -1.0, 1.0, &pammo, &playertarget));



The weapons stuff is in a file called weapons.h, the definition of *playertarget is in ships.h, the target changing is in my core source file, and the instantiation is in a file called player.h. Sorry for the mess.

Share this post


Link to post
Share on other sites
Quote:
Original post by medevilenemy
I have no need to update the rocket's targets -- they don't have the "fuel" for it.


Well, your rockets need to be aware of the entities around them. Otherwise they cannot change targets.
Your code block shows me that you want to change the target by hitting t. If you're afraid that you will change all your rocket's targets, if you loop through them, then you really need to consider another (better) approach to do all this stuff (or simply filter enemy & player targets).

-Rocket: Contains some sort of awareness, checks if the current target is destroyed, looks for the next enemy and try to reach that one.
That approach would require some sort of state management, so that entities can be aware of entities around them.

Consider some class that contains all entities in a scenegraph or some other graph. If a rocket noticies it's target is dead, it will start a query and get another target from that class, if there is one.

Share this post


Link to post
Share on other sites
Such a structure would be a good idea as far as efficiency is concerned, but as for the designed function of the rockets now necessary. The rockets do not need to know if their target has died (at least, not directly). The program already senses when the current dies and resets targets on its own. So, the missile launcher needs to know of this change passively and maybe the missiles can then learn of the change a similar way -- but that is not absolutely necessary. The important part is that the launcher be passively aware of any change in target. The current inability to be so is a result of my own failure in passing the pointers properly. What I seek is direction as to how to properly pass the reference from *playertarget (as defined in the code I posted) through a constructor argument, to a local variable in the launcher (*target), and then maybe to the individual missile objects through a constructor argument such that at all stages the pointers all point at the same place (the target of *playertarget) and a change in one changes all of them (since they are all supposed to point to the same place).

Thanks for the suggestions, thus far, at least. I'm definitely planning on going through the code in the not-too-distant future to speed things up, but I'll get to that after more pressing concerns of functionality.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this