Articles Archive
Director Forums
Director Wiki
Job Board
Search
 

Toss That Ball, Fire That Gun, Shoot That Arrow; Projectiles in 3D Games, Part 1

by Allen Partridge

In this series of articles, I'll explain the basic elements involved in shooting 3D objects of varying velocities and trajectories toward targets. I'll also demonstrate a collision system that takes advantage of Director's native 3D collision modifier. If you thirst for more you'll probably find it in my book, Real-Time Interactive 3D Games: Creating 3D Games with Macromedia Director 8.5 / Shockwave Studio. In the book I describe various implementations of projectiles and collision in greater detail.

By reading and understanding this article you should:

  • learn the process of creating model resources dynamically;
  • learn the process of creating models dynamically;
  • learn the process of creating and manipulating shaders dynamically;
  • learn how to tile textures;
  • learn how and why to subtract vectors;
  • learn how to perform math functions on #rgb color objects;
  • learn to work with the pointAtOrientation property of a model;
  • learn to work with the pointAt command;
  • learn to work with the collision modifier and collision callback objects;
  • find the basic formulas for simulating the effects of gravity, drag, and force on a 3D model.

Conversely, you will not learn anything about making the shooting aspect of your game exciting or engaging, from the technical information found here. This tutorial is meant only to provide you with the technical information related to animating projectiles. I would be hard pressed to make a game less engaging than this demo.

Sample Director 8.5 movie source is available for download in ZIP or HQX 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.

In the demo, aim the cannon at the targets and press the left mouse button down to begin charging the weapon. When you release the left mouse button the projectile will be fired out of the front of the cannon. The velocity of the projectile is determined by the amount of time that you hold down the mouse.

If the projectile collides with a target, the target and the projectile are eliminated. If a projectile collides with another projectile, the projectile is eliminated. Projectiles are also removed after their velocity descends beneath a minimum value.

The cannon glows a steadily brighter radioactive-red in order to provide the player visual feedback regarding the potential velocity of the shot. Once the mouse is released, the cannon's red glow is rapidly removed.

I've divided the information about this demo into two sections:

  • Assembly and Aiming; and
  • Tossing and Collision.

In Assembly I'll talk about the generation of the models and model resources used in the movie. Once the models have been generated, I'll explain in Aiming how the user's mouse motions are combined with information about the position of the firing mechanism to plan a trajectory, or aim, for each projectile. Then in Tossing I'll show you how the flying ball works to calculate its flight path. Finally, in Collision, I'll introduce you to the collision modifier and explain custom collision callbacks.

If you like to follow along in Director as you work, I have a word of warning. Running Director and Shockwave at the same time can cause unpredictable behavior on some systems.

Assembly and Aiming

This movie is created from six cast members (Figure 1.2). The first is an empty 3D member. That's right empty. There really wouldn't be any reason to use a 3rd party modeler for this movie, because the models used in the movie are all primitives. Most of the models are generated at startup using the initModels handler found in cast member 8, a movie script.

There are two parent scripts in the movie. The first creates a physical and virtual gun object that is used throughout, and the second is used every time the mouse is released to create an instance of a bullet.

The last two cast members are behaviors. The fireWeapon behavior handles the actual weapon firing, while the loop behavior simply loops the playback head.

Figure 1.2. The cast.

That's it. Just an empty 3D member, a movie script for the startup, two parent scripts, and a weapon fire behavior.

The Plan

There are two global variables in the movie. The first is a reference to the 3D world. This is really just a convenience. Its easier to type w than it is to type member ("world") so I prefer the former. The gun global is a pointer to the gun object created from one of the parent scripts. Bullets need to poll the gun as they are born to determine the current trajectory, so there must be a convenient reference to the gun object.

If you aren't familiar with objects and parent scripts, don't panic. I'll slow down and explain a bit as I get to them. The global variables are declared here, in the startup script, and everywhere that they are referenced. The global variable declaration is found in Listing 1.1 below.

Listing 1.1 Global variables declared

global w
-- a pointer to the 3D member
global gun
-- a pointer to the gunObject

  During the startMovie handler, four simple commands are executed. First, the global variable, w, is given a value. By setting it as a reference to the Shockwave 3D cast member, it will act as a shortcut to the 3D world. Second, the initModels custom handler is called. We'll talk about what this does in the section Something From Nothing.

The third command creates a new gun object using the parent script named gun to generate the object. The global variable gun is assigned a value, a reference to the instance of the gun parent script. Finally, the gun object is added to a special list in Director called the actorlist.

The actorlist is a special list. Any object added to the actorlist will receive stepFrame events. This means that each time the playback head moves between frames, the objects in the actorlist will receive a stepFrame event. If these objects have stepFrame handlers then they can execute code between frames.

Listing 1.2. Start Movie Script

on startMovie
  -- When the movie starts
  w = member ("world")
  -- point the w toward the world member
  initModels ()
  -- create the models and resources for the demo
  gun = new (script "gun")
  -- create the gun object from the parent script member named "gun"
  -- the resulting object will be sort of like a behavior on a sprite - you
  -- can check its properties and send it commands just like you would
  -- a behavior script attached to a sprite. The only difference is that instead
  -- of saying sprite(x).property or sprite(x).command, you say objectRef.property
  -- or objectRef.command()
  add (the actorList, gun)
  -- add the gun to the actorList so that it receives stepFrame events
end

Something From Nothing

There are four visible elements within the demo movie (Figure 1.3). The gun is created from a cylinder primitive and uses a default shader that has been modified to give it a teal color.

Figure 1.2. Visible elements within the demo.

Each bullet object creates its own sphere within the 3D world. It will create these models and delete them when a collision occurs or when the velocity of the projectile slows to a given minimum. The bullets use a default shader that has been modified to give a soft yellow glow.

The target objects are all created from a primitive plane. They use the default shader. The ground object is also created from a plane primitive. It uses a more complex derivative of the default shader. The color of the shader has been modified, and the default texture is tiled across the surface of the plane.

The first part of the initModels handler creates the ground plane resource. All models in Director 3D are created from mesh resources. This allows you to share and modify resources. It is important to keep this relationship in mind because it also means that any changes you make to a model's resource will not only change the appearance of that model. They will also change the appearance of every other model that uses that resource.

The only elements of the ground plane that I adjust here are the length and width of the plane. Each is set to 700 world units, a dimension which will make the plane slightly larger than the field of view of the camera at the front of the projection plane. With the resource ready, I create the actual ground plane model. By default that plane will face the negative z axis. I need it to lay down across the x and z axes, facing up along the positive y axis. To get it there, I'll rotate it 90 degrees on it's own x axis. Next, I move it down and back a bit.

 

Listing 1.3. Creating the Ground Plane

on initModels()
  -- GROUND PLANE --------------------------------------------------------------------------
  -- The ground plane will give the player a sense of depth
  ground = w.newModelResource("ground", #plane, #front)
  -- All 3D models are build from resources. The previous line of code creates
  -- a model resource for the ground plane model.
  ground.width = 700
  ground.length = 700
  -- Next set the width and the length of the plane resource.
  gModel = w.newModel("groundPlane", ground)
  -- Now create the actual ground plane model from that resource.
  gModel.rotate(90,0,0)
  -- Rotate the ground plane around its X axis. It starts out face to the camera,
  -- we need to tilt it backward so that it becomes a floor
  gModel.translate(0,-150,-350, #world)
  -- Now move that plane down so that it is below the targets and shooter.

Once the ground plane has been created it is ready for a shader. I don't want to do anything too fancy, but I would like a large checkered green to give the player something against which to gauge distance. First I create a new standard shader, then I assign this new shader to the ground model. I switch on the useDiffuseWithTexture property so that the texture and the diffuse color of the shader will be able to blend together.

The diffuse property of a shader determines its base color. I change the model's diffuse color to a medium green and then set its blendConstant to 5. This will make the first texture in the shader's texturelist blend with the diffuse color of the model, as long as the useDiffuseWithTexture property is TRUE.

This particular shader uses the default texture, but I want it to repeat many times across the surface of the plane. I need to set a couple of properties up to accomplish this effect. First, I have to enable the textureRepeat property of the shader. If this property is set to FALSE, then the texture would appear only once at postage stamp size. Next, I set the scale of the textureTransform of the shader to a value less than than 1.0. The scale of the textureTransform determines the number of times the texture will tile.

 

Listing 1.4. Creating the Ground Shader

 groundShader = w.newShader("groundShader", #standard)
  -- The ground plane will need its own shader so that we can get that tiled
  -- green field effect. The preceding line creates the new shader.
  gModel.shader = groundShader
  -- Next assign the newly created shader to the ground plane model.
  gModel.shader.useDiffuseWithTexture = 1
  -- The useDiffuseWithTexture property of a shader determines whether or not
  -- (BOOLEAN) the shader will blend with the its texture. It is FALSE by default.
  -- Here I switch it to true, so that I can change the color of the model with
  -- adjustments to the diffuse color of the shader.
  gModel.shader.diffuse = rgb(0,125,0)
  -- The diffuse shader color is set to 50% green
  gModel.shader.blendConstant = 5
  -- The blendConstant property of a shader determines the percentage of
  -- blending between the diffuse color of the shader and the first layer of the
  -- shader's textures. If the texture were in a deeper layer, we'd have to use the
  -- blendConstantList to access other texture layers.
  gModel.shader.textureRepeat = 1
  -- I want the texture to repeat so I need to switch the value of this BOOLEAN
  -- property from its default, FALSE, to TRUE.
  gModel.shader.textureTransform.scale(0.1, 0.1, 0.1)
  -- To get the texture to tile, I just set the scale of the texture transform to a
  -- value less than 1.0. The above setting should cause the texture to repeat
  -- 10 times in each direction.

In Part 2, we'll set up the bullets and the gun.


Al Partridge is an independent game developer, host of dirGames-L, and Professor of Dramatic Media at The University of Georgia. Partridge's recent publications include Real-time Interactive 3D Games: Creating 3D Games in Macromedia Director 8.5 Shockwave Studio.

(This article has been viewed 12118 times.)