rich_is_bored offers part 2 in scriptting tutorials. In this tutorial we will take the mechanical arm from the last tutorial as well as some new pieces and create a machine.
Level Scripting Tutorial 3
Introduction
Welcome to the third and last of my scripting tutorials. Well, that's
probably not entirely true. Maybe I'll figure something else out and it
will be worth writing about.
Anyway, In this tutorial we will take the mechanical arm from the
last tutorial as well as some new pieces and create a machine. It's a
pretty simple machine and it isn't perfect. But I wasn't making eye
candy to begin with.
Advanced: Creating a Machine
Just like in the previous tutorials I am streamlining the process. I am providing you will everything you will need.
The first thing to do is to plan. You need to know what you're trying to accomplish and how to do it.
One thing that is necessary is an idea. So here is the point where I am going to trust you...
You see, I decided it would be easier to just show you it in action
than draw out a bunch of diagrams. This means that everything needed
including the script is included in this zip file. Of course in order
to learn anything you're going to rename, move, or delete the script
after you've seen what we are going to do.
So, this means your going to run the map and observe the machine running through its cycle. So...
Extract the zip to [Your Doom 3 Directory]\base. Fire up doom, run the map rich_scripting_tutorial_3. After it loads and starts up
you'll see these crazy robotic arms moving around and doing all
kinds of weird nonsense. As you can see, I didn't do a good job of
planning. Yeah, I'm not proud of it but it'll work for this tutorial.
As you can see, the robotic clasp picks up a canister and the
welding gun will meet up with it and fire. Next the clasp will place
the canister back and the sequence will repeat with the second
canister.
Now you should have a pretty good idea what we will be trying to
accomplish. But, before we start scripting away I need to give you a
good idea how the parts come together to form arms and such.
And here are all the parts of the clasp. From here on out we will
just refer to this as the grab arm. You should be familiar with it
already (Previous tutorial, Hint, Hint).
It is also a good idea to come up with a naming convention when we
are working with several parts. This will make it easier to tell what
is where. I am going to name these parts from the top down.
Here are the names for the grab arm components from top to bottom.
We will also be forming a child parent tree. The items at the bottom
will be parented to the items above it. Now take into consideration
that the fingers will all be parented to the finger joint a.k.a. $GrabArm_Base3.
We'll need a name for both canisters as well. We can name them from
left to right to keep things simple. You all read from left to right
don't you?
$Can1
$Can2
Nutz 'n' Bolts: Screwing around in the Map Editor
After you have planned... umm yeah, you'll fire up the editor and
begin putting parts into place. It's not over yet though; you'll still
need to run through the motions to figure out where everything goes and
what you'll need to make it work.
Go ahead and load up rich_scripting_tutorial_3.map in the editor. You'll see that the parts are laid out in the editor similar to the diagram below.
Of course in the editor you'll see several entities that are not
displayed here. All of these are speakers and particle generators
except for three. See if you can find them.
Hint: Take a look at the base of the grab arm (where it attaches to the machine) and to the left, directly above each canister.
Guess what these are for? Well, the grab arm needs to move to each
canister and then back to its original position. These entities act as
markers so the arm will know where to go. Basically they are three func_statics with the following names.
$Can_Pos1
$Can_Pos2
$Arm_Pos1
It's pretty straight forward. They're named from left to right again.
We aren't interested in the sounds and special effects yet. We need to
get this puppy moving before we think about what sounds or special
effects to use.
Playing God: Scripting it to Life
This would be the part where you delete, rename, move, or in other
words, get rid of the script. Unless you wanna steal my work thief!
So, start up your favorite text editor editpad... What?! You haven't
downloaded editpad yet?! Ah, forget it. Stick to whatever you're using.
If you haven't changed yet you never will. Although I was thinking
about working on a colored text filter specifically for doom 3
scripting. Oh well.
Alright. We are definitely going to need some comments in this code so we don't get all confused.
////////////////////////////////////////////////////
//
// Part Movement...
//
////////////////////////////////////////////////////
////////////////////////////////////////////////////
//
// MAIN
//
////////////////////////////////////////////////////
void main ()
{
setup_objects ();
sys.wait(3);
}
You should already know what's coming next. That's right. We need
to setup our binds. Let's take care of the spark arm. You should know
where this goes by now.
Now, we are ready to move some stuff. Let's setup a motion for the grab arm first since it's the more difficult of the two.
We aren't interested in covering the entire motion at first. In
fact, we will only write enough code to grasp the first canister and
move it into position. We are breaking the cycle into each component.
(Remember talking about that from the first tutorial?)
First we will give this motion a name. Hmmm?. GrabCanister sounds good. (Pretty self explanatory huh?)
So let's make a function like this.
Code:
void GrabCanister()
{
}
Now, I'll explain what needs to happen on each line and you type up the code.
Move GrabArm_Base1 to Can_Pos1.
Wait for GrabArm_Base1 to finish it's movement.
Rotate GrabArm_Finger1 once 45 degrees on the X axis.
Rotate GrabArm_Finger2 once 45 degrees on the X axis.
Rotate GrabArm_Finger3 once 45 degrees on the X axis.
Move Can1 up 30.
Wait for Can1 to finish it's movement.
Rotate GrabArm_Finger1 once -35 degrees on the X axis.
Rotate GrabArm_Finger2 once -35 degrees on the X axis.
Rotate GrabArm_Finger3 once -35 degrees on the X axis.
Wait for Finger1 to finish it's movement. All three fingers should finish at the same time.
Bind Can1 to GrabArm_Base3.
Rotate GrabArm_Base3 once 360 degrees on the Y axis.
Wait for GrabArm_Base3 to finish it's movement.
Rotate GrabArm_Base2 once 90 degrees on the Y axis.
Wait for GrabArm_Base2 to finish it's movement.
Rotate GrabArm_Swivel2 once 90 degrees on the X axis.
Wait for GrabArm_Swivel2 to finish it's movement.
Move GrabArm_Base1 to Arm_Pos1.
Rotate GrabArm_Base2 once -90 on the Y axis.
Wait for GrabArm_Base2 to finish it's movement.
Now here is the code below. Yeah you could've just cheated and copied and pasted from below. But that's really lame.
Now add this function to the main function like this.
Code:
void main ()
{
setup_objects ();
sys.wait(3);
GrabCanister();
}
Save your script as rich_scripting_tutorial_3.script and fire up the map. You've got a
mechanical arm picking up a canister.
You know what is really interesting? When it picks up the other
canister it pretty much runs through the same set of motions doesn't
it? Wouldn't it save you a lot of trouble if you could just call the
same function to pick up the other canister? But how?
This is where we bring parameters into the equation. You see, every
function has a set of parentheses appended to the end of it. You can
pass objects to the functions by placing the object's names within
these parentheses. All you have to do is change the function slightly.
Let's see what objects will change in the GrabCanister function when I am picking up the second canister? Hmmm? Just two, Can1 and Can_Pos1. Everything else remains the same.
We will need to create alias names for the objects that change. These are called reference variables. We cannot use the names Can1, Can2, Can_Pos1, or Can_Pos2
in the function in order for this to work. We need to use these
reference variables as aliases for the objects that are passed to the
function instead. In the function we will use the names Can and CanPos.
When we call this modified form of the GrabCanister function it will look like this when we pass Can1 and Can_Pos1.
Code:
GrabCanister($Can_Pos1, $Can1);
Or when we pass Can2 and Can_Pos2 it will look like this.
Code:
GrabCanister($Can_Pos2, $Can2);
And when we declare our function we will now change the line to look like this.
Code:
void GrabCanister(entity CanPos, entity Can)
{
Notice that I defined the type of variables by using the keyword
entity. This is because we are passing entity names to these reference
variables.
And to modify the code of the function we will replace every Can1 with Can and every Can_Pos1 with CanPos. The code should look like this.
And now in the main function I will change the call to GrabCanister to look like this.
Code:
void main ()
{
setup_objects ();
sys.wait(3);
GrabCanister($Can_Pos2, $Can2);
}
Save your script and fire up the map. Now we are looking at some
impressive stuff. Here you are reusing the same code to move a
completely different canister.So from here we can make a function to
move the place the canister back. We'll call it simply PlaceCanister.
Basically we are doing what we did above backwards. So here is the code.
There are two lines I do want you to take note of though. Notice in this function the unbind
command. This is because I want to detach the canister from the
mechanical arm rather than bind it. Also notice how I am not placing
anything in the parentheses. This is because you can only bind an
object to one other object at a time. So there is no need to tell the
engine what to detach the canister from. There is only one possible
object it could be detached from in the first place.
The second line to look at is this line...
Code:
$GrabArm_Base2.rotateTo('0 0 0');
The rotateTo command is
used here to ensure that GrabArm_Base2 is positioned the same as it was
when the map first loaded. Without this command the base of the arm
would start bending in the wrong direction. Go ahead and comment it out
and give it a test run if you're curious.
Now we throw a call to PlaceCanister immediately following the call to GrabCanister.
Save the script and start up the map. It will now grab the second
canister and place it back. Go ahead and add another call to both GrabCanister and PlaceCanister each pointing to the first canister instead. It should look like this.
Same deal. Save and start the map. Now it will grab the second
canister, place it back, and then do the same with the first canister.
Neat huh?
Now we just need to code a function to move the spark gun into
position. We'll call this PosPiecesToFire. Why the crazy name? Well
later we will call the spark effects function from within this
function. So basically this function will position the pieces and then fire. Make sense now?
This function is pretty simple compared to the previous one. We won't
be passing objects to it and its movement is simple. So here's your
code.
Again, save the script and start up the map. Now each arm does its own thing.
Let's change something real quick. I mean we don't want this to happen once and stop right?
We're going to create a function called MechCycle that will execute forever and a day. Basically we will cut the machine movement code from main and paste it.
Then we'll place that code in a while loop. It should look like this.
Now, we just throw a call in main so it looks like this.
Code:
void main ()
{
setup_objects ();
sys.wait(3);
MechCycle();
}
Save the script and run the map. Now the machine runs continuously.
Adding Bells, Whistles and a Light
Here is the fun part. Everything is already moving all you got to
do is make it pretty. Well, I suppose it isn't going to look too pretty
unless I went through the trouble of texturing my objects in the first
place. Ahhh, whatever. I'm still learning modeling anyway.
Technically this part is so simple it really isn't worth documenting but hey, I'm trying to cover it all right?
Now, I'm no expert at sound type stuff and I tried really hard to
find sounds that matched the action. But hey, in all honesty you should
probably be creating new sounds for your work anyway. I decided I would
need eight speakers. Two upward hydraulic pump sounds, one for each
arm. Two downward hydraulic pump sounds again, one for each arm. Two
steam type sounds for each canister being released. A spark gun sound
and finally some form of ambient mechanical hum.
The ambient sound loops and the remaining sounds are all setup to sound when triggered by setting a key called s_waitfortrigger to 1. Here are the names of the sounds that need scripting.
Now because the spark arm doesn't move a significant amount the
joint sounds don't need to be bound. Neither do the canister tube
sounds. However, since the grab arm and the spark arm move a pretty
good distance we'll bind their speakers to the moving entities.
Don't worry about the binds yet though, We'll be knocking out the sounds as well as lighting and special effects all at once.
Triggering a sound is pretty simple. Really all you have to do is perform a sys.trigger(insert speaker name here) whenever you want that particular sound to fire. There is no need to clutter your map with actual trigger entities.
Now for the spark gun. I created a func_fx, brought up the entities properties, set the fx key to fx/sparks.fx, and set the restart key to 1 so that I can trigger this effect multiple times.
Next I created a light and set the key startoff to 1. I set the shader to lights/roundfire.
Again, both the light and the func_fx are triggered using the command sys.trigger(insert light or func_fx name here).
////////////////////////////////////////////////////
//
// MAIN
//
////////////////////////////////////////////////////
void main ()
{
setup_objects ();
sys.wait(3);
MechCycle();
}
Now you save your script and run the map. Ta Da! Here is the exact
same script you started with... Ahem.. I mean look! All that hard work
paid off!
Conclusion
That pretty much sums up what I know of scripting as a whole. I'm
hoping that some of you out there will be on my level soon and we can
start tackling ideas together. If not, run over this tutorial a couple
of times. You should get it.