 |
 |
 |
 |
| |
|
 |
This is the first in a series of three short
articles that focus on the development of Lingo
generated 3D mazes in Macromedia Director—and
the common game elements that you might find
in such a maze.
It isn't hard to make these mazes once you
understand the basic principles. The resulting
mazes are generated incredibly rapidly, making
them a very viable option for Shockwave 3D.
|
|
|
| In
this article we'll discuss the basic procedures involved
in setting up a box maze. All of the code is provided so
that you can build your own maze. The code examples do not
include optimization or error checking, in order to keep
the handlers as "on topic" as possible. |
| |
 |
| Before
you begin, download the sample files for this tutorial: |
|
| |
 |
|
| |
| Before
we begin, let's take a look at the map for the maze and the
finished maze, just to make sure that we are all on the same
page. You'll need the latest Shockwave
plug-in to view the example below. The movie should have
loaded by now, since the file is only a few kilobytes. |
| |
To view this demo, you need the latest version of the Macromedia Flash Player.
Download the free Macromedia Flash Player now.
|
| |
| Once
you have had a good look at the map (the colored squares with
letters on them) click to move on to the actual maze. The
switch happens so quickly that you might easily believe that
the maze was pre-built. In reality, the movie builds the maze
when you click it. The DIR file displays dynamically and downloads
quickly because: |
| |
|
· |
There are
no media elements (other than Lingo scripts) in the
movie |
|
· |
When the
maze is built, there are fewer than 1200 polygons created
|
|
· |
The application
uses the built-in 3D primitives within Director to do
all of the work |
|
| |
| Before
beginning any maze, you have to draw a plan. That's right,
draw (using a pencil and paper) the plan for the maze. When
you are developing 3D applications, it is very important to
make notes in advance. |
| |
| Click
on the finished maze again so that you can see the map in
the movie above once more. The movie just loops back and forth
between the map and the maze. The dark line in the map represent
the walls of the maze itself. The finished maze will be composed
of box primitives. In this view, the boxes are seen from above
with the front side of the box toward the top of your monitor.
The left side of the box is to your left, and the right side
is to your right. |
| |
| On
each box, a code indicates the sides of the box that will
be removed. As you begin in the upper left-hand corner, you'll
see the letters "BR" on the box. This indicates
that the box will have its back and right sides removed. There
is no black line in these positions, therefore there should
be no walls here. Each string of characters represents a shorthand
description of the walls that will be removed from that box.
An 'L' indicates that the left wall will be removed, an F
means that the front must go. |
| |
| Once
you've defined a map and identified the type of box you'll
need to draw the cell, it's time to start writing the code.
The handlers to build the maze are surprisingly simple. |
| |
I'll
start with some prep work for when the W3D
(the object file format of Director) sprite initializes. The
beginSprite event behavior
first creates some shortcut references to the sprite and its
member. Everything in W3D starts with the member, so it will
be a big time-saver to have a shortcut reference to the member
on hand. |
| |
Once
these properties have been defined, it's time to make some
model resources. In order for geometry (meshes) to exist as
models in a W3D member, they must point to a geometric resource
called a model resource. There are several primitive shapes
built into Director that may be created dynamically by calling
the newModelResource()
command and providing it with appropriate values as arguments.
This takes the form: |
| |
pMember.newModelResource("resourceName",
\
#primitiveType, #visibility) |
| |
The
beginSprite handler below
creates a new model resource for each type of box. There's
a model resource with the front removed, one with the left
side removed, and so on. This happens in two stages. First,
create an appropriately named resource. Then, remove the appropriate
sides. After this is done for each of the potential side wall
variants, remove the tops of all the boxes so that it is easier
to see the entire 3D box maze from above. The following code
example illustrates this process: |
| |
| property pMember
property pSprite
on beginSprite(me)
-- These are references to the sprite
-- and
the member
pSprite = sprite(me.spriteNum)
pMember = pSprite.member
--- Create a series of plans for boxes
---
that have one or more sides missing.
--- Start with one that has no front.
--
When creating a model resource you
-- may use 3 args. The name of the resource,
--
the geometry type, and the faces that
-- you would like to be visible.
pMember.newModelResource ("boxF", #box,
#both)
-- You may remove faces of a box at
--
the resource level. Any face may
-- be set to 0 to remove it. (top,
... -- bottom, left, right, front
or back.)
pMember.modelResource("boxF").front = FALSE
... --
Now one with no back
pMember.newModelResource ("boxB", #box,
#both)
pMember.modelResource("boxB").back = FALSE
-- no right side
pMember.newModelResource ("boxR", #box,
#both)
pMember.modelResource("boxR").right = FALSE
-- no left side
pMember.newModelResource ("boxL", #box,
#both)
pMember.modelResource("boxL").left = FALSE
-- no front and no back
pMember.newModelResource ("boxFB", #box,
#both)
pMember.modelResource("boxFB")Front =
FALSE
pMember.modelResource("boxFB")Back =
FALSE
-- no left and no right
pMember.newModelResource ("boxLR", #box,
#both)
pMember.modelResource("boxLR")Left =
FALSE
pMember.modelResource("boxLR")Right =
FALSE
-- no front and no left side
pMember.newModelResource ("boxFL", #box,
#both)
pMember.modelResource("boxFL")Front =
FALSE
pMember.modelResource("boxFL")Left =
FALSE
-- no front and no right side
pMember.newModelResource ("boxFR", #box,
#both)
pMember.modelResource("boxFR")Front =
FALSE
pMember.modelResource("boxFR")Right =
FALSE
-- no front, no left side and no right side
pMember.newModelResource ("boxFLR", #box,
#both)
pMember.modelResource("boxFLR")Front =
FALSE
pMember.modelResource("boxFLR")Left =
FALSE
pMember.modelResource("boxFLR")Right =
FALSE
-- no back and no left side
pMember.newModelResource ("boxBL", #box,
#both)
pMember.modelResource("boxBL")Back =
FALSE
pMember.modelResource("boxBL")Left =
FALSE
-- no back and no right side
pMember.newModelResource ("boxBR", #box,
#both)
pMember.modelResource("boxBR")Back =
FALSE
pMember.modelResource("boxBR")Right =
FALSE
-- no back, no left side and no right side
pMember.newModelResource ("boxBLR", #box,
#both)
pMember.modelResource("boxBLR")Back =
FALSE
pMember.modelResource("boxBLR")Left =
FALSE
pMember.modelResource("boxBLR")Right =
FALSE
-- no front, no back, no left side
-- and no right side
pMember.newModelResource ("boxFBLR", #box,
#both)
pMember.modelResource("boxFBLR")Front =
FALSE
pMember.modelResource("boxFBLR")Back =
FALSE
pMember.modelResource("boxFBLR")Left =
FALSE
pMember.modelResource("boxFBLR")Right =
FALSE
-- no front, no back, and no left side
pMember.newModelResource ("boxFBL", #box,
#both)
pMember.modelResource("boxFBL")Front =
FALSE
pMember.modelResource("boxFBL")Back =
FALSE
pMember.modelResource("boxFBL")Left =
FALSE
-- no front, no back and no right side
pMember.newModelResource ("boxFBR", #box,
#both)
pMember.modelResource("boxFBR")Front =
FALSE
pMember.modelResource("boxFBR")Back =
FALSE
pMember.modelResource("boxFBR")Right =
FALSE
-- TAKE the tops off -- so you can see the
maze.
repeat with
x = 2 to
pMember.modelResource.count
pMember.modelResource[x]Top = FALSE
end repeat
end
|
| |
The
buildMaze() handler is
called from a frame script after the user clicks the map to
continue. This handler will actually create the box models
based on the resources and then move them around the space
in order to build the maze. |
| |
| The
basic strategy of the handler is to create the appropriate
box for each position in a 9x10 grid, and then move the box
into a position that correlates to the box position on the
grid. In order for this code to work, there must be a meaningful
plan, so that the handler can figure out which variation of
the box goes where. |
| |
| To
do this, use a list stored in a field cast member. The field
contains a simple linear list that contains strings that may
be used to reference the appropriate model and model resource
for the given box. The list looks like this: |
| |
|
put value(member("plan").text)
-- ["BR", "LR", "LR", "LR",
"LR", "LR", "LR", "LR",
"BL", "FB", "BR", "LR",
"LR", "LR", "BLR", "LR",
"L", "FB", "FB", "FB",
"BR", "LR", "LR", "FLR",
"LR", "LR", "FBL", "FB",
"FB", "FB", "BR", "LR",
"BLR", "BLR", "BL", "FB",
"FB", "FB", "FB", "FB",
"B", "FB", "FB", "FB",
"FB", "FB", "F", "FB",
"FB", "FB", "FB", "FB",
"FBR", "FBL", "FBR", "LR",
"FBL", "FB", "FR", "FBL",
"FB", "FB", "FB", "FB",
"FB", "FB", "FBR", "L",
"FB", "F", "FB", "FB",
"FB", "FB", "FB", "FBR",
"LR", "FLR", "LR", "FL",
"FB", "FR", "FL", "F",
"FR", "LR", "LR", "LR",
"LR", "FL"]
|
| |
Because
the list is stored in a field text member, it is a string.
That string can be converted into a list if you plan to read
its values as list items, so the first step is to convert
this field text to a value using (oddly enough) the value()
command, and then store that list in a variable. |
| |
Though
they can seem a bit muddy at first glance, a nested repeat
loop is a good way to build objects that need to correspond
to rows and columns. In this handler, use the x repeat loop
to work with each column and the y loop to work with each
row. A little math will let us convert the nested references
into list positions within that linear list. |
| |
Once
the type of box has been retrieved from the corresponding
position in the linear list, I use the createBox()
subroutine to build a box of the appropriate type and then
return a reference to that box model so that the buildMaze()
handler can finish by moving the box into position. |
| |
| on buildMaze (me)
-- member("plan") contains a list of the
-- correct wall sets so the maze will lay
-- out correctly.
-- At this point you may be thinking, "hunh?"
-- I draw a sketch (that's right on paper)
-- of a gridded version of the maze. It
has
-- 9 columns and 10 rows. Now I create a
-- list in a text field that has
-- references to the type of box that I
need
-- in each corresponding row / column working
-- left to right, top to bottom, one row
at
-- a time. "FR" for example indicates a
box
-- where the front and right sides have
been
-- removed
-- NOTE THAT THIS CORRELATES to the "boxFR"
-- resource type that was created when the
-- sprite initialized.
tPlan = value(member("plan")Text)
-- For every column
repeat with
x = 1 to
10 -- columns
......-- For
every item in this row in each column
repeat with
y = 1 to
9 -- rows
--
This creates a pointer to the same point
--
within the list. Just math, x-1- cuz we
--
want to start with 0 to avoid the
--
multiplication by nine for items in the
--
first row / column and offset each row
--
by nine
tBoxResourceItem = (((x-1) *
9) + y)
--
match the list pos this one gets
--
the value stored in the corollary
--
position within the list.
tResourceId = (tPlan[tBoxResourceItem])
--
now set the variable tBoxRef to the
--
active model, while creating that model
--
using the createBox() routine.
--
Boxes are named dynamically with unique
--
#'s and the faces of the boxes are
--
determined by the list.
tBoxRef = createBox(me, "boxModel"&\
(y + ((x-1) * 10)),
tResourceId)
--
finally move the box into the correct
--
position by offsetting all but the
--
corner box by the width of the boxes.
tBoxRef.translate(((y - 1)
* 50), 0,
\
(((x - 1))* 50))
end
repeat
end repeat
end
|
| |
This
createBox() subroutine
just makes a box of the type requested, then returns a reference
to that box model to the handler that called it. |
| |
|
On createBox(me, boxName,
boxType)
-- make a box of the type indicated.
pMember.newModel(boxName, (pMember.modelResource\
("box" & boxType)))
-- return a reference to the new box
return pMember.model(boxName)
end
|
| |
| In
the next part of this series, we'll discuss how to do some
decorating within the box maze. We'll look at some tricks
to optimize the number of polygons used to display the maze
and learn how to control the appearance with shaders and textures
of each wall within the maze. |
| |
| |
| |
|
| About the author |
| Dr. Allen
Partridge runs Insight
Interactive, a game-development company. He is the
author of Real-Time
Interactive 3D Games; Creating 3D Games with Macromedia
Director 8.5 / Shockwave Studio. Allen is
the host of dirGames-L,
the largest online e-mail discussion community centered
on game design and creation using Macromedia Director. |
|
Submit feedback on
our tutorials, articles, and sample applications.
|
|
|
|