Instantaneous Projectile
by Stony Brook University (#0008) · 02/15/2008 (12:23 pm) · 3 comments
This is compatible with TGE v1.5.2
Here's some sample code that I took off of someone in the forum and slightly modified to create a projectile that travels fast enough that it seems almost instanteous.
It uses a ContainerRayCast to "find" an object located in the path of the shot. The projectile is spawned at the target and then the projectile's velocity kicks in.
The scripting is a little bit buggy. The projectile will not always hit the terrain when aimed at the ground, but it seems to always hit other objects with collision boxes (such as enemies).
It's not perfect, but it's a start. Please take it and try optimizing it. I'm a n00b.
Here's some sample code that I took off of someone in the forum and slightly modified to create a projectile that travels fast enough that it seems almost instanteous.
It uses a ContainerRayCast to "find" an object located in the path of the shot. The projectile is spawned at the target and then the projectile's velocity kicks in.
The scripting is a little bit buggy. The projectile will not always hit the terrain when aimed at the ground, but it seems to always hit other objects with collision boxes (such as enemies).
function CrossbowImage::onFire(%this, %obj, %slot)
{
%projectile = %this.projectile;
// Decrement inventory ammo. The image's ammo state is update
// automatically by the ammo inventory hooks.
%obj.decInventory(%this.ammo,1);
// Gives projectile velocity after spawning at target
// This is required for using this script to hit moving targets
%muzzleVector = %obj.getMuzzleVector(%slot);
%muzzleVelocity = VectorScale(%muzzleVector, %projectile.muzzleVelocity);
// Original author's matrix transformation to get "correct" coordinates
%transformedMuzzleVector = MatrixMulVector("0 0 0 0 0 1 0", %obj.getMuzzleVector(%slot));
// These three are required for the ray-cast function call
%muzzlePosition = %obj.getMuzzlePoint(%slot);
%masks = $TypeMasks::GameBaseObjectType | $TypeMasks::TerrainObjectType | $TypeMasks::InteriorObjectType;
%endPosition = VectorAdd(%muzzlePosition, VectorScale(%transformedMuzzleVector, 1000));
// Ray-cast function call
%hitPosition = ContainerRayCast(%muzzlePosition, %endPosition, %masks, %obj);
// Hit conditional
if (%hitPosition != "0 0 0")
{
%endPosition = getWord(%hitPosition, 1) @ " " @ getWord(%hitPosition, 2) @ " " @ getWord(%hitPosition, 3);
}
else
{
%endPosition = %hitPosition;
}
// Create the projectile object at the target
%p = new (%this.projectileType)() {
dataBlock = %projectile;
initialVelocity = %muzzleVelocity;
initialPosition = %endPosition;
sourceObject = %obj;
sourceSlot = %slot;
client = %obj.client;
};
MissionCleanup.add(%p);
return %p;
}It's not perfect, but it's a start. Please take it and try optimizing it. I'm a n00b.
#2
Probably so the correct callbacks and the explosion call still get made. The only actual problem I see with this method is that I'm not certain the projectile will always correctly hit the target.. I mean you spawned it at the actual hit location moving towards the object it hit, is that always going to work out correctly? I'll have to test it.
I also see something a little confusing going on... If hitPosition != (0,0,0), set endPosition = hitPosition[position]; else, just set them equal.. considering the way that ContainerRayCast returns data, this doesn't really make sense. It returns 7 fields, so it'll never actually be equal to "0 0 0" no matter what happens. What you want is something more like this:
I would personally just write the instant-hit code into the projectile object itself. Add an "instant" flag, link it to script. If the flag is set, have the projectile do its entire raycast (ie, from the firing position all the way to its max range) during the first tick. If the raycast finds something, place the explosion there, do the callback, delete the projectile object (basically do exactly what the code already does when it finds a hit). If it doesn't hit anything, we can assume the player fired into open space and just delete the projectile obj. Granted, this is a lot more effort for the exact same functionality, so it might not be worth it.
You can realistically get the same effect from making the projectile absurdly fast, ie making its speed per tick = the total distance you want it to travel and giving it a lifetime of, say, 50ms. In this case, it should only exist for 2 (?) ticks, and should travel as far as you need it to within that time.
You're still wasting some packets on this whole thing, since you're still ghosting an object and its explosion. The most efficient way to handle this would probably be to link it to the shapeImage state machine code.
Anyway, though, this looks like it should work, excepting any oddities which might arise regarding projectiles flying through things they spawn too close to (???).
02/17/2008 (2:13 pm)
"Rather than spawning a projectile, why not just apply damage on the hit object?"Probably so the correct callbacks and the explosion call still get made. The only actual problem I see with this method is that I'm not certain the projectile will always correctly hit the target.. I mean you spawned it at the actual hit location moving towards the object it hit, is that always going to work out correctly? I'll have to test it.
I also see something a little confusing going on... If hitPosition != (0,0,0), set endPosition = hitPosition[position]; else, just set them equal.. considering the way that ContainerRayCast returns data, this doesn't really make sense. It returns 7 fields, so it'll never actually be equal to "0 0 0" no matter what happens. What you want is something more like this:
// Hit conditional
if (%getWord(%hitPosition, 0) != 0) // raycast returns object ID 0 if it finds nothing
{
%endPosition = getWord(%hitPosition, 1) @ " " @ getWord(%hitPosition, 2) @ " " @ getWord(%hitPosition, 3);
// Create the projectile object at the target
%p = new (%this.projectileType)() {
dataBlock = %projectile;
initialVelocity = %muzzleVelocity;
initialPosition = %endPosition;
sourceObject = %obj;
sourceSlot = %slot;
client = %obj.client;
};
MissionCleanup.add(%p);
return %p;
}
else
{
// don't do anything; projectile didn't hit anything
return;
}I would personally just write the instant-hit code into the projectile object itself. Add an "instant" flag, link it to script. If the flag is set, have the projectile do its entire raycast (ie, from the firing position all the way to its max range) during the first tick. If the raycast finds something, place the explosion there, do the callback, delete the projectile object (basically do exactly what the code already does when it finds a hit). If it doesn't hit anything, we can assume the player fired into open space and just delete the projectile obj. Granted, this is a lot more effort for the exact same functionality, so it might not be worth it.
You can realistically get the same effect from making the projectile absurdly fast, ie making its speed per tick = the total distance you want it to travel and giving it a lifetime of, say, 50ms. In this case, it should only exist for 2 (?) ticks, and should travel as far as you need it to within that time.
You're still wasting some packets on this whole thing, since you're still ghosting an object and its explosion. The most efficient way to handle this would probably be to link it to the shapeImage state machine code.
Anyway, though, this looks like it should work, excepting any oddities which might arise regarding projectiles flying through things they spawn too close to (???).
#3
Henry: I thought I heard something about Torque not handling projectiles very well at extreme velocities. Would be something to look into.
A possible solution could be to spawn the projectile 1m in front of the hit location, travelling along the muzzle vector. But then you get the interesting problem of a player firing point-blank into a wall and hitting himself in the back. Could be fun...
On second thoughts, you simply need to run some safety checks. If the length between te muzzle point and the target point is > 1 unit, then spawn the projectile 1 unit away and let it fly itself. If you're closer than 1 unit, spawn the projectile half way along the vector.
03/26/2008 (10:32 am)
Nice work here. Just a point:%endPosition = getWord(%hitPosition, 1) @ " " @ getWord(%hitPosition, 2) @ " " @ getWord(%hitPosition, 3);You can write that as:
%endPosition = getWords(%hitPosition,1,3);
Henry: I thought I heard something about Torque not handling projectiles very well at extreme velocities. Would be something to look into.
A possible solution could be to spawn the projectile 1m in front of the hit location, travelling along the muzzle vector. But then you get the interesting problem of a player firing point-blank into a wall and hitting himself in the back. Could be fun...
On second thoughts, you simply need to run some safety checks. If the length between te muzzle point and the target point is > 1 unit, then spawn the projectile 1 unit away and let it fly itself. If you're closer than 1 unit, spawn the projectile half way along the vector.

Torque Owner Matt Vitelli