
3D Game Programming All In One (2004)
.pdf
78 Chapter 2 ■ Introduction to Programming
// ------------------------------------------------------------------------
//This function does the shape analysis and prints the result.
//PARAMETERS: %theWidth - horizontal dimension
// |
%theHeight - vertical dimension |
// |
|
// |
RETURNS: none |
// |
------------------------------------------------------------------------ |
{ |
|
|
// calculate perimeter |
|
%perimeter = 2 * (%theWidth+%theHeight); |
|
// calculate area |
|
%area = %theWidth * %theHeight; |
|
// first, set up the dimension output string |
|
%prompt = "For a " @ %theWidth @ " by " @ |
|
%theHeight @ " quadrilateral, area and perimeter of "; |
|
// analyze the shape's dimensions and select different |
|
|
// descriptors based on the shape's dimensions |
|
|
if (%theWidth == %theHeight) |
// if true, then it's a square |
|
%prompt = %prompt @ "square: "; |
|
|
else |
// otherwise it's a rectangle |
|
%prompt = %prompt @ "rectangle: "; |
|
|
// always output the analysis |
|
|
print (%prompt @ %area @ " " @ %perimeter); |
|
} |
|
|
function main() |
|
|
// |
------------------------------------------------------------------------ |
|
// |
Entry point for the program. |
|
// |
------------------------------------------------------------------------ |
|
{ |
|
|
//calculate and output the results for three
//known dimension sets
calcAndPrint(22, 26); // rectangle calcAndPrint(31, 31); // square calcAndPrint(47, 98); // rectangle
}
Team LRN

Programming Concepts |
79 |
What we've done here is analyze a shape. In addition to printing its calculated measurements, we modify our output string based upon the (simple) analysis that determines if it is a square or a rectangle. I realize that a square is a rectangle, but let's not get too picky, okay? Not yet, at least.
Nesting if Statements
You saw earlier in "The if Statement" section how an if statement can contain another if statement. These are called nested if statements. There is no real limit to how deep you can nest the statements, but try to be reasonable and only do it if it is absolutely necessary for functional reasons. It might be good to do it for performance reasons, and that's fine as well.
By the way, I had asked if you could tell which of the two examples would execute faster, remember that? The answer is that the nested version will execute faster when there is no overdraft condition. This is because only one condition is tested, resulting in less work for the computer to do. The sequential version will always perform both tests, no matter what the bank balance is.
The if and if-else statements allow a choice to be made between two possible alternatives. Well, sometimes we need to choose between more than two alternatives. For example, the following sign function returns 1 if the argument is less than 0, returns +1 if the argument is greater than 0, and returns 0 if the argument is 0.
function sign (%value)
//determines the arithmetic sign of a value
//PARAMETERS: %value – the value to be analyzed
//RETURNS: -1 - if value is negative
//0 - if value is zero
//1 - if value is positive
{
if (%value < 0) // is it negative ?
{
return -1;
}
else // nope, not negative
{
if (%value == 0) // is it zero ?
{
return 0;
}
else // nope, then it must be positive
{
Team LRN

80 Chapter 2 ■ Introduction to Programming
return 1;
}
}
}
So there you go. The function has an if-else statement in which the statement following the else is also an if-else statement. If %value is less than 0, then sign returns 1, but if it is not less than 0, the statement following the else is executed. In that case if %value is equal to 0, then sign returns 0; otherwise it returns 1. I used the compound statement form in order to make the nesting stand out more. The nesting could also be written like this:
if (%value < 0) // is it negative ?
return -1; |
|
else |
// nope, not negative |
if (%value == 0) // is it zero ? |
|
return 0; |
|
else |
// nope, then it must be positive |
return 1; |
|
This is nice and compact, but it can sometimes be hard to discern where the nesting properly happens, and it is easier to make mistakes. Using the compound form formalizes the nesting a bit more, and personally, I find it more readable.
Newbie programmers sometimes use a sequence of if statements rather than nested ifelse statements when the latter should be used. They would write the guts of the sign function like this:
if (%value < 0) %result = -1;
if (%value == 0) %result = 0; if (%value > 0) %result = 1;
return %result;
It would work and it's fairly easy to read, but it's inefficient because all three conditions are always tested.
If nesting is carried out to too deep a level and indenting is not consistent, then deeply nested if or if-else statements will be confusing to read and interpret. You should note that an else always belongs to the closest if without an else.
Team LRN

Programming Concepts |
81 |
The switch Statement
We just explored how we can choose between more than two possibilities by using nested if-else statements. There is a sleeker and more readable method available for certain kinds of multiple choice situations—the switch statement. For example, the following switch statement will set a game's weapon label based upon a numeric weapon type variable:
switch (%weaponType)
{
case 1: %weaponName = "knife"; case 2: %weaponName = "pistol"; case 3: %weaponName = "shotgun"; case 4: %weaponName = "bfg1000"; default: %weaponName = "fist";
}
Here is what that would look like using if-else:
if (%weaponType == 1) %weaponName = "knife";
else if (%weaponType == 2) %weaponName = "pistol"; else if (%weaponType == 3) %weaponName = "shotgun"; else if (%weaponType == 4) %weaponName = "bfg1000";
else
%weaponName = "fist";
It's pretty obvious from that simple example why the switch statement is so useful.
The general form of a switch statement is this:
switch ( selection-variable )
{
case label1:
statement1;
case label2:
statement2;
...
case labeln:
statementn;
default:
statementd;
}
Team LRN

82Chapter 2 ■ Introduction to Programming
The selection-variable may be a number or a string or an expression that evaluates to a
number or a string. The selection-variable is evaluated and compared with each of the case labels. The case labels all have to be different. If a match is found between the selec- tion-variable and one of the case labels, then the statements that follow the matched case until the next case statement will be executed. If the value of the selection-variable can't be matched with any of the case labels, then the statements associated with default are executed. The default is not required but should only be left out if it is certain that the selection-variable will always take the value of one of the case labels.
Here is another example, which writes out the day of the week depending on the value of the number variable %day.
switch (%day)
{
case 1 :
print("Sunday");
case 2 :
print("Monday");
case 3 :
print("Tuesday");
case 4 :
print("Wednesday");
case 5 :
print("Thursday");
case 6 :
print("Friday");
case 7 :
print("Saturday");
default :
print("Not a valid day number");
}
Debugging and Problem Solving
When you run your programs, the Torque Engine will automatically compile them and output a new .cs.dso file if it needs to. Therefore, geometry.cs (the source code) will become geometry.cs.dso (the compiled code). There is a gotcha though: If the script compiler detects an error in your code, it will abort the compilation, but will not stop the program execution—rather, it will use the existing compiled version if one exists. This is an important point to remember. If you are changing your code, yet you don't see any change in behavior, then you should check the log file in console.log and look for any compile errors.
Team LRN

Programming Concepts |
83 |
The log output is pretty verbose and should guide you to the problem area pretty quickly. It writes out a piece of code around the problem area and then inserts a pair of sharp characters ("##") on either side of the exact spot where the compiler thinks there is a problem.
Once you've fixed the first problem, don't assume you are done. Quite often, once one problem is fixed, the compiler marches on through the code and finds another one. The compiler always aborts as soon as it encounters the first problem.
Of the large number of programming errors that the compiler catches and identifies, here are a few specific ones that frequently crop up:
■Missing semicolon at the end of a statement.
■Missing a slash in double-slash comment operator.
■Missing % or $ (scope prefix) from variable names.
■Using uninitialized variables.
■Mixing global and local scope prefixes.
■Unbalanced parentheses or braces.
In a later chapter we will cover how to use the console mode in Torque. That will give us access to three built-in Torque functions—echo, warn, and error—which are quite useful for debugging.
Without using those three functions, the best tool for debugging programs you've created is the print statement. You should print out interim results throughout your code that will tell you how your program is progressing.
Tell you what—here is a different version of the TwotyFruity program. Type it in and save it as C:\3DGPAi1\book\WormyFruit.cs. I've put five bugs in this version. See if you can spot them (in addition to any you might introduce while typing).
//========================================================================
//WormyFruit.cs
//
//Buggy version of TwotyFruity. It has five known bugs in it.
//This program adds up the costs and quantities of selected fruit types
//and outputs the results to the display. This module is a variation
//of the FruitLoopy.cs module designed to demonstrate how to use
//functions.
//========================================================================
function InitializeFruit(%numFruitTypes)
//------------------------------------------------------------------------
//Set the starting values for our fruit arrays, and the type
//indices
Team LRN

84 |
Chapter 2 ■ Introduction to Programming |
|
|
// |
|
|
// |
RETURNS: number of different types of fruit |
|
// |
|
|
// |
------------------------------------------------------------------------ |
|
{ |
|
$numTypes = 5; // so we know how many types are in our arrays
$bananaIdx=0; |
// initialize the values of our index variables |
$appleIdx=1; |
|
$orangeIdx=2; |
|
$mangoIdx=3; |
|
$pearIdx=3; |
|
$names[$bananaIdx] = "bananas"; // initialize the fruit name values $names[$appleIdx] = "apples";
$names[$orangeIdx] = "oranges"; $names[$mangoIdx] = "mangos"; $names[$pearIdx] = "pears";
$cost[$bananaIdx] = 1.15; // initialize the price values $cost[$appleIdx] = 0.55;
$cost[$orangeIdx] = 0.55; $cost[$mangoIdx] = 1.90; $cost[$pearIdx] = 0.68;
$quantity[$bananaIdx] = 1; // initialize the quantity values $quantity[$appleIdx] = 3;
$quantity[$orangeIdx] = 4; $quantity[$mangoIdx] = 1; $quantity[$pearIdx] = 2;
return(%numTypes);
}
function addEmUp($numFruitTypes)
// ------------------------------------------------------------------------
//Add all prices of different fruit types to get a full total cost
//PARAMETERS: %numTypes –the number of different fruit that are tracked
//RETURNS: total cost of all fruit
//
// ------------------------------------------------------------------------
Team LRN

Programming Concepts |
85 |
{
%total = 0;
for (%index = 0; %index <= $numFruitTypes; %index++)
{
%total = %total + ($quantity[%index]*$cost[%index]);
}
return %total;
}
// ------------------------------------------------------------------------
//countEm
//Add all quantities of different fruit types to get a full total
//PARAMETERS: %numTypes –the number of different fruit that are tracked
//RETURNS: total of all fruit types
//
// ------------------------------------------------------------------------
function countEm($numFruitTypes)
{
%total = 0;
for (%index = 0; %index <= $numFruitTypes; %index++)
{
%total = %total + $quantity[%index];
}
}
function main()
// ------------------------------------------------------------------------
//Entry point for program. This program adds up the costs
//and quantities of selected fruit types and outputs the results to
//the display. This program is a variation of the program FruitLoopy
//------------------------------------------------------------------------
{
//
// ----------------- |
Initialization --------------------- |
//
$numFruitTypes=InitializeFruit(); // set up fruit arrays and variables
Team LRN

86 |
Chapter 2 ■ Introduction to Programming |
|
|
%numFruit=0 |
// always a good idea to initialize *all* variables! |
|
%totalCost=0; |
// (even if we know we are going to change them later) |
//
//----------------- Computation ---------------------
//Display the known statistics of the fruit collection for (%index = 0; %index < $numFruitTypes; %index++)
{
print("Cost of " @ $names[%index] @ ":$" @ $cost[%index]); print("Number of " @ $names[%index] @ ":" @ $quantity[%index]);
}
//count up all the pieces of fruit, and display that result %numFruit = countEm($numFruitTypes));
print("Total pieces of Fruit:" @ %numFruit);
//now calculate the total cost
%totalCost = addEmUp($numFruitTypes); print("Total Price of Fruit:$" @ %totalCost);
}
Run the program, and use the original TwotyFruity output as a specification to tell you whether or not this program is working correctly.
Best Practices
Programming is as much an art as it is anything else. There are often quite strenuous discussions between programmers about the best way to do certain things. However, there is consensus on a few practices that are considered to be good.
So take the following list as a guideline, and develop a style that is comfortable for you.
■Use module and function header comments to document your code.
■Sprinkle lots of commentary through your code, and make sure that it actually explains what is happening.
■Don't comment obvious things. Save the effort for the stuff that matters.
■Use white space (blank lines and spaces) to improve readability.
■Indent your code with readability in mind.
■Decompose large problems into small ones, and assault the small problems with functions.
Team LRN

Moving Right Along |
87 |
■Organize your code into separate modules, and make sure the module file name is appropriate for the content, and vice versa.
■Restrict the number of lines of code you put in a module. Pick a size that suits you—about 1,000 lines should be near your upper limit.
■Use descriptive and meaningful variable names.
■While keeping your variable names descriptive, don't let the names get too long.
■Never embed tabs in code—use spaces instead. When you view your code later, you may have different tab settings, and therefore find the code hard to read. Using spaces guarantees that the visual appearance is consistent. Three spaces for an indent is a good number.
■Be consistent in your programming style decisions.
■Be alert to what programming decisions you make that work well for you, and try to consistently employ those techniques.
■Keep a change log of your work so you can keep track of the evolution of your programs.
■Use revision control software to manage your program versions.
Moving Right Along
You've now bitten off a fairly big chunk o' stuff. You've learned a new tool—in fact, a new kind of tool—the programmer's editor. After getting a handle on UltraEdit-32, we looked at how software does its thing, bringing people and computer hardware together by using programming languages.
We then went off and started bullying the computer around, using one of those programming languages called Torque Script.
Coming up next, we'll delve into the world of 3D programming at a similar level, and discover the basics of 3D objects, and then how we can manipulate them with Torque Script.
Team LRN