Toss That Ball, Fire That Gun, Shoot That Arrow; Projectiles in 3D Games, Part 4
May 10, 2002
by Allen Partridge
Part 1 of this series defined our objective of creating a simple Shockwave 3D shooting game, with an aimable gun and an array of targets, as well as covering the code that sets up most of the elements of the world. In Part 2, objects were defined to create the gun and its bullets. Part 3 covered aiming the gun and initialization of the bullets.
Sample Director 8.5 movie source is available for download in ZIP or SIT archive format (each file is approximately 70K). Before we begin, amuse yourself for a moment tinkering with the demo movie. (6K, Requires Shockwave 8.5)
Figure 1.1. Demo Simulating Projectiles, Gravity and Collision.
Tossing and Collision (continued)
Giving Itself Form and Feelings
In Listing 2.5 the bullet object's initialization script is completed. It uses a sphere model as its physical representative in the 3D world. Then the bullet object aligns the bullet model with the firing end of the gun, applies the proper shader, and adds a collision modifier to the bullet. Unlike the target models, the bullet will also associate a callback script with its model, using the syntax;
bullet.registerScript (#collideAny, callbackHandlerName, objectContainingScript)
The bullet object registers its own parseCollision handler with the bullet model's collision modifier. In this way, any time a bullet hits a 3D model with a collision modifier attached, the bullet object's parseCollision handler will be called.
Listing 2.5 Making a Model With Custom Collision Callbacks
bullet = w.newModel (pModelName, w.modelResource ("bulletResource"))
bullet.transform.position = gun.model.transform.position
bullet.translate (0,25,0, gun.model)
bullet.shader = w.shader ("bulletShader")
bullet.addModifier (#collision)
bullet.registerScript (#collideAny, #parseCollision, me)
return me
end
Figuring the Flight Path: Velocity and Gravity Unite
Because each bullet object is added to the actorlist, the models are aware of motion in the playback head. We could also trigger such animation with timeout events, but I didn't want to clutter the issue.
After a bit of error checking to verify the state of the 3D member, the velocity of the bullet is reduced by a few percent. This represents the decay of the bullet's velocity due to friction or drag. The velocity of the bullet will be reduced every frame, until the bullet is moving so slowly that it is removed.
A constant gravity is applied to the bullet. You could increase the effect of gravity as an object approached another.
The calculation of the variable newVector is the real point of the whole exercise. Visualize the vectors being added geometrically. First, remember that you can multiple a vector by a scalar. Because pTrajectory is a unit vector (magnitude of 1) its new magnitude when multiplied will be equal to the scalar, which in this case is pVelocity. Once you have multiplied the vector for direction, add the vector of gravity. This way the bullet will arc, since the gravity affects the trajectory more as velocity decreases. You could use a vector to represent drag, but why complicate things?
You can see the removal of slow bullets illustrated in the conditional comparing the velocity of the bullet to the pDestructVelocity in Listing 2.6. If the velocity is sufficient, the new vector of the bullet is calculated, but if the velocity is insufficient, then the model is deleted and the object is removed from the actor list.
Listing 2.6 Adjusting the Flight Path Dynamically
on stepFrame (me)
if w.state > 3 then
-- if the w3d member is fully loaded
x = w.model (pModelName)
-- make a temporary reference to the bullet model
if NOT voidP (x) then
pVelocity = pVelocity * 0.98
if pVelocity > pDestructVelocity then
newVector = (pTrajectory * pVelocity) + pGravity
w.model(pModelName).translate(newVector)
-- Finally translate the bullet.
else
-- The bullet is moving too slowly
w.deleteModel(pModelName)
-- delete the bullet model
deleteOne(the actorList, me)
-- kill this object
end if
end if
end if
end
Collision
It doesn't do much good to shoot, toss, or fire projectiles if you can't detect the things they hit. In this section I'll explain the basic principles behind Director's built-in 3D collision modifier and show you how it is used in this case to detect and respond to collisions between bullets and targets.
You Shot What?
When using the collision modifier to detect collisions you must attach the modifier to every 3D model that will detect interpenetration with other collision-modified models. In other words, the gun (which has no collision modifier) is invisible to the collision modifiers, but the bullets and targets will all detect collisions with any other bullet or target. Bullets can collide with targets, bullets can collide with bullets, and (at least in theory) targets can collide with targets.
To add the collision modifier to a given model use the syntax;
worldMemberReference.modelReference.addModifier (#collision)
This adds the collision modifier to the model, but it doesn't do a whole lot without some more work on your part. If we want to know something about the collisions that are occurring, we'll need to solicit some additional information from the modifier.
Call Me If You Care
One of the most useful features of the collision modifier is that you can ask an individual model's collision modifiers to report their collisions to individual object scripts. If you think about our bullet architecture, it makes good sense to take advantage of this feature. The registerScript syntax looks like this;
worldMember.bulletModel.registerScript (#collideAny, #parseCollision, bulletObjectScriptReference)
I have created a bullet object with a physical representative bullet model on screen. That bullet model has a collision modifier, and because it is registered to call back the bullet object associated with the model, I can rapidly determine which collision has occurred and decide if it matters, and what I should do about it. The script that parses the collision callbacks is found in Listing 2.7.
Listing 2.7 Parsing Collision Responses
on parseCollision (me, collisionDataObject)
-- a collision occured between this bullet and something else
case TRUE of
-- if the following collision is met
((collisionDataObject.modelA).name contains "target"):
-- the bullet model hit a target
w.deleteModel((collisionDataObject.modelA).name)
-- delete the target
x = w.model(pModelName)
-- make a temporary reference to the bullet model
if NOT voidP(x) then
-- if the bullet has not already been destroyed then
w.model((collisionDataObject.modelB).name).removeModifier(#collision)
-- remove the collision modifier from the bullet
w.deleteModel((collisionDataObject.modelB).name)
-- delete the bullet model
end if
otherwise:
-- Kill bullets that run into other bullets
x = w.model((collisionDataObject.modelB).name)
-- make a temporary reference to the bullet model
if NOT voidP(x) then
-- if the bullet has not already been destroyed then
w.model((collisionDataObject.modelB).name).removeModifier(#collision)
-- remove the collision modifier from the bullet
w.deleteModel((collisionDataObject.modelB).name)
-- delete the bullet model
end if
end case
end
Notice that the parseCollisions handler accepts an extra variable, that I've named collisionDataObject. As the name implies, the collisionDataObject is an object that contains information about a collision that occured.
Now it's important at this juncture to remind yourself that you are working with objects, not score-driven Lingo. That means that you always want to step up your already respectable error-checking habits. It is very possible that you'll be getting multiple collision reports and that the timing of those collisions will be inconsistent with your plans. I first check to see if the reported collision was with a target. If it was then I delete the target, check to make certain that the bullet model has not already been deleted, and if it hasn't then I remove its collision modifier and delete the model.
If the bullet hit another bullet, I do the same error checking and delete the bullet that reported the impact. I do this to avoid bullets constantly interpenetrating and reporting back a horrific chain of impacts. This would drag the playback to near standstill on Macs.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.