Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

3D Game Programming All In One (2004)

.pdf
Скачиваний:
139
Добавлен:
17.08.2013
Размер:
17.91 Mб
Скачать

638 Chapter 23 The Game Client

setFirstResponder = "0"; noCursor = true;

new GuiNoMouseCtrl() {

profile = "GuiDefaultProfile"; horizSizing = "relative"; vertSizing = "bottom"; position = "0 0";

extent = "400 300"; minExtent = "8 8"; visible = "1";

new GuiBitmapCtrl(OuterChatFrame)

{

profile = "GuiDefaultProfile"; horizSizing = "width"; vertSizing = "bottom"; position = "8 32";

extent = "256 72"; minExtent = "8 8"; visible = "1"; setFirstResponder = "0"; bitmap = "./hudfill.png";

new GuiButtonCtrl(chatPageDown)

{

profile = "GuiButtonProfile"; horizSizing = "right"; vertSizing = "bottom"; position = "217 54";

extent = "36 14"; minExtent = "8 8"; visible = "0"; text = "Dwn";

};

new GuiScrollCtrl(ChatScrollFrame)

{

profile = "ChatBoxScrollProfile"; horizSizing = "width"; vertSizing = "bottom";

position = "0 0";

Team LRN

Client Interfaces

639

extent = "256 72"; minExtent = "8 8"; visible = "1"; setFirstResponder = "0"; willFirstRespond = "1"; hScrollBar = "alwaysOff"; vScrollBar = "alwaysOff"; constantThumbHeight = "0";

new GuiMessageVectorCtrl(ChatBox)

{

profile = "ChatBoxMessageProfile"; horizSizing = "width";

vertSizing = "height"; position = "4 4"; extent = "252 64"; minExtent = "8 8"; visible = "1"; setFirstResponder = "0"; lineSpacing = "0";

lineContinuedIndex = "10"; allowedMatches[0] = "http"; allowedMatches[1] = "tgeserver"; matchColor = "0 0 255 255"; maxColorIndex = 5;

};

};

};

};

};

You've probably noticed that there is a heck of lot of indenting. This shows that there are many nested objects within objects. Each nesting level is there for a reason.

The outer level, owned by MainChatBox, is a general-purpose GuiControl container that encompasses the entire screen, occupying the same extents as the Canvas that we view the 3D world through.

Inside that is a GuiNoMouseCtrl control whose role is to shield the chat boxes within it from being accessible by a mouse cursor, if you were to display one on the screen.

Inside that is the GuiBitmapCtrl control named OuterChatFrame, which has two useful functions. You can use it to provide a nice bitmap background for your chat box if you want one, and it holds two subobjects.

Team LRN

640Chapter 23 The Game Client

One of those subobjects is an icon that appears to tell you when you've scrolled the chat box up far enough to hide text off the bottom of the box. That control is a GuiButtonCtrl named chatPageDown.

The other control is a GuiScrollCtrl named ChatScrollFrame that provides scroll bars for both vertical and horizontal scrolling.

And finally, in the inner sanctum is the actual control that contains the text of the chat box when it is displayed. This GuiMessageVectorCtrl supports multiline buffers of text that will display new text at the bottom and scroll older text up. You can use commands (that we have bound to the PageUp and PageDown keys) to scroll up and down through the text buffer.

MessageBox Interface

The MessageBox interface is where we type in our messages, as shown in Figure 23.6.

It is not normally on the screen but pops up when we hit the key we bound to it. This, too, has several nested levels, though not as many as the ChatBox interface.

new GuiControl(MessageBox)

{

profile = "GuiDefaultProfile"; horizSizing = "width"; vertSizing = "height"; position = "0 0";

extent = "640 480"; minExtent = "8 8"; visible = "0"; noCursor = true;

Figure 23.6 MessageBox interface.

Team LRN

Client Interfaces

641

new GuiControl(MessageBox_Frame)

{

profile = "GuiDefaultProfile"; horizSizing = "right"; vertSizing = "bottom"; position = "120 375";

extent = "400 24"; minExtent = "8 8"; visible = "1";

new GuiTextCtrl(MessageBox_Text)

{

profile = "GuiTextProfile"; horizSizing = "right"; vertSizing = "bottom"; position = "6 5";

extent = "10 22"; minExtent = "8 8"; visible = "1";

};

new GuiTextEditCtrl(MessageBox_Edit)

{

profile = "GuiTextEditProfile"; horizSizing = "right"; vertSizing = "bottom"; position = "0 5";

extent = "10 22"; minExtent = "8 8"; visible = "1";

altCommand = "$ThisControl.eval();"; escapeCommand = "MessageBox_Edit.onEscape();"; historySize = "5";

maxLength = "120";

};

};

};

It is all familiar stuff, but take note that the outer object, MessageBox, is initially invisible. The code that pops the box up will make it visible and invisible again as needed.

There is a GuiTextCtrl named MessageBox_Text that is at the same level as the

GuiTextEditCtrl named MessageBox_Edit. The MessageBox_Text can be used to put a prompt in front of the area where the message will be typed in, although it has no text here in the

Team LRN

642Chapter 23 The Game Client

definition. The MessageBox_Edit control is the control that accepts our typed-in message. The altCommand property specifies what statement to execute when the Enter key is pressed, and the escapeCommand property specifies what to do when the Escape key is pressed. The handlers for these two functions will be discussed later in the code discussion in the "Client Code" section.

Client Code

I'm not going to make you type in great reams of program code at this stage of the game, though you don't get off the hook completely. You will have to make some changes to accommodate the new stuff, and we'll also examine the contents of some of the new stuff to see what it does.

MenuScreen Interface Code

Open the file C:\koob\control\client\initialize.cs and locate the function InitializeClient and add the following lines in the grouping with the other similar statements:

Exec("./misc/ServerScreen.cs");

Exec("./misc/HostScreen.cs");

Exec("./misc/SoloScreen.cs");

Exec("./interfaces/ServerScreen.gui");

Exec("./interfaces/HostScreen.gui");

Exec("./interfaces/SoloScreen.gui");

Like I promised, I won't make you type in all the files referenced in those exec statements; you can copy them from C:\3DGPAi1\RESOURCES\CH23 and put them into the directories under the C:\koob\control\client\ directory in the subdirectories specified in the exec statements.

Each of these files is basically one module split into two parts. The actual interface definitions are in the files with the .gui extensions, while the code that manages the interfaces are in the files with the same prefix name, but ending with the .cs extension.

If you go back to the previous code listing for MenuScreen.gui, you will see where each of the interfaces is invoked. ServerScreen is defined in ServerScreen.gui, HostScreen is defined in HostScreen.gui, and finally, SoloScreen is defined in SoloScreen.gui.

Each interface has roughly the same form. There is an OnWake method for the interface object that is called by the engine when that object is displayed by the SetContent call in the related button in the MenuScreen interface. This method prepares the interface and fills the various data fields in the interfaces.

Team LRN

Client Code

643

SoloPlay Interface Code

The SoloPlay interface that you saw in Figure 23.2 prepares a list of mission files that it finds so that you can select one of them to play with. The functional code for the SoloPlay interface, extracted from SoloPlay.cs, is shown here for discussion:

function PlaySolo()

{

%id = SoloMissionList.getSelectedId();

%mission = getField(SoloMissionList.getRowTextById(%id), 1); StopMusic(AudioIntroMusicProfile); createServer("SinglePlayer", %mission);

%conn = new GameConnection(ServerConnection); RootGroup.add(ServerConnection); %conn.setConnectArgs("Reader"); %conn.connectLocal();

}

function SoloScreen::onWake()

{

SoloMissionList.clear(); %i = 0;

for(%file = findFirstFile($Server::MissionFileSpec);

%file !$= ""; %file = findNextFile($Server::MissionFileSpec)) if (strStr(%file, "CVS/") == -1 && strStr(%file, "common/") == -1)

SoloMissionList.addRow(%i++, getMissionDisplayName(%file) @ "\t" @ %file ); SoloMissionList.sort(0);

SoloMissionList.setSelectedRow(0);

SoloMissionList.scrollVisible(0);

}

function getMissionDisplayName( %missionFile )

{

%file = new FileObject(); %MissionInfoObject = "";

if ( %file.openForRead( %missionFile ) ) { %inInfoBlock = false;

while ( !%file.isEOF() ) { %line = %file.readLine(); %line = trim( %line );

if( %line $= "new ScriptObject(MissionInfo) {" ) %inInfoBlock = true;

Team LRN

644 Chapter 23 The Game Client

else if( %inInfoBlock && %line $= "};" ) { %inInfoBlock = false;

%MissionInfoObject = %MissionInfoObject @ %line; break;

}

if( %inInfoBlock )

%MissionInfoObject = %MissionInfoObject @ %line @ " ";

}

%file.close();

}

%MissionInfoObject = "%MissionInfoObject = " @ %MissionInfoObject; eval( %MissionInfoObject );

%file.delete();

if( %MissionInfoObject.name !$= "" ) return %MissionInfoObject.name;

else

return fileBase(%missionFile);

}

The OnWake method is as described in earlier chapters—in this case the onWake method makes clear the mission list and then populates it according to the matching files it finds in the path indicated by $Server::MissionFileSpec. This variable is set in the file C:\koob\control\server\initialize.cs with the following line in the InitializeServer function:

$Server::MissionFileSpec = "*/maps/*.mis";

There are a couple of things you should understand about the way the search is done in the code presented.

First, there is the matter of the syntax used here. It can be difficult to decipher C-based code because of the looseness allowed—and Torque Script's syntax is extremely close to that of the C language and C++. You will recall that with most statements that employ a code block, such as if and for, you can use the long form or the short form, depending on your needs.

For example, the long form using braces

if (%a==1) { %x=5; }

can also be written as

if (%a==1) {

%x=5;

}

Team LRN

Client Code

645

or as

if (%a==1)

{

%x=5;

}

There are also other minor variations, but I'm sure you get the idea. The compiler doesn't care about the lines the code appears on, and it doesn't care about the amount of white space (tabs, spaces, and carriage returns). It only cares that the correct tokens and keywords are in the right place and that they make sense according to the compiler's parsing rules. Of course, white space is used to separate tokens and keywords, but the amount is not important to the parser.

The short form of these kinds of statements does depend on statement context, however. First, note that the preceding code can also be written as

if (%a==1)% x=5;

This demonstrates that the braces in the earlier example are superfluous in this particular flavor of statement. However,

if (%a==1)

%x=5;

is a valid rendition of the short form—but the conditional code that you want executed must exist as a single statement that immediately follows the conditional test. In this example, if the test is satisfied, %x is assigned the value 5. If the test is not satisfied, the ensuing assignment is not carried out.

However, using the same form

if (%a==1)

%x=5; %b=6;

if the test is satisfied, %x is assigned the value 5 as before, and %b is assigned the value 6. But (and this is a big but) if the test is not satisfied, although the ensuing assignment statement is not carried out, the one after it still is. So with this last bit of code, %b always gets assigned the value 6.

By now you may be wondering why this digression—here's why: The SoloScreen::onWake method has the following statements that search for available mission files to use to populate its list:

for(%file = findFirstFile($Server::MissionFileSpec);

%file !$= ""; %file = findNextFile($Server::MissionFileSpec)) if (strStr(%file, "CVS/") == -1 && strStr(%file, "common/") == -1)

SoloMissionList.addRow(%i++, getMissionDisplayName(%file) @ "\t" @ %file );

Team LRN

646Chapter 23 The Game Client

You might be tempted to misinterpret this code, even if you thoroughly understand programming in C or Torque Script. What we need to do is simplify the code to remove

obfuscation introduced by the line context: We'll change all instances of findFirstFile($Server::MissionFileSpec) to fFF(), all instances of findNextFile ($Server::MissionFileSpec)) to fNF(), and finally, all instances of getMissionDisplayName (%file) to gMDN(). Now the code will look like this (it won't compile, but we don't care about that):

for(%file = fFF();

%file !$= ""; %file = fNF())

if (strStr(%file, "CVS/") == -1 && strStr(%file, "common/") == -1) SoloMissionList.addRow(%i++, gMDN()@ "\t" @ %file );

If we tidy up the white space a bit, we get this:

for(%file = fFF(); %file !$= ""; %file = fNF())

if (strStr(%file, "CVS/") == -1 && strStr(%file, "common/") == -1) SoloMissionList.addRow(%i++, gMDN()@ "\t" @ %file );

And hey, presto! The code structure reveals the algorithm quite nicely. The original line wrapping made the code hard to understand and made it look wrong when it actually wasn't. There are several lessons to be learned here:

1.Make sure your programming editor lets you display long lines of maybe 150 characters or more, just in case you have them.

2.Pay attention to your function and variable name lengths. Long descriptive names are extremely useful when you are trying to understand unfamiliar or long-forgot- ten code, but there are times when they can confuse more than explain.

3.Your own code may confuse you at some later point just as much as it might confuse someone else who needs to understand it (someone you've called in to fix bugs for you, for example).

What fix do I recommend for this? Shorter names? No, use braces and indenting and put the statements in the long form in order to remove any contextual ambiguity.

for(%file = findFirstFile($Server::MissionFileSpec);

%file !$= ""; %file = findNextFile($Server::MissionFileSpec))

{

if (strStr(%file, "CVS/") == -1 && strStr(%file, "common/") == -1)

{

SoloMissionList.addRow(%i++, getMissionDisplayName(%file) @ "\t" @ %file );

} // end of if

}// end of for

Team LRN

Client Code

647

You can also add comments if they clarify what you are doing. Don't worry about insulting the intelligence of expert programmers by doing this. Any seasoned hand will greatly appreciate anything you do to make it quick and easy to understand what you are doing. Especially if they are doing code reviews for you!

Now, after that long-windedness, we can address the second issue about that code: What does it do?

The initial findFirstFile uses the variable to search the specified directory for the first instance of a matching file. If you actually do find a match, the path name is deposited in the %file variable, and you enter a loop. In each iteration of the loop, calls are made to findNextFile, which will find any new file in the sequence that matches the search criteria. If findNextFile does not find any more matching files, the %file variable is set to NULL, and the loop exits. In the loop we check the contents of the path name in %file for the existence of two potential invalid directory names: CVS (used for source code management, and not part of Torque) and common. If the file we found is not in either of those two directories, then we consider it to be valid and add it to our mission list using the

SoloMissionList.addRow method.

The findFirstFile-findNextFile construct is a powerful one. It maintains an internal list of the files that it has found on your behalf. You just need to extract the path names as they appear.

Having said all that about such a small chunk of code, I should point out that this interface is a basic one. You might consider adding a few more capabilities, such as the sequence or random map selection option you'll find next in the Host interface.

The getMissionDisplayName is a bigger and more impressive-looking bit of work, but its function is fairly straightforward, albeit with a semimagical twist to it, so to speak. It opens up a file as directed and looks through it for the line that contains the statement "%MissionInfoObject = ". It then creates an actual MissionInfoObject using that statement and uses the name property of the object to obtain the name and return the name to the calling function. This is a pretty clever way to examine a file. Pretty sensible too, when you realize that mission files are simply Torque Script files with a different extension.

This bit of code presents to you a lot of possibilities about how you can use Torque Script. One that comes to mind is a reprogrammable AI robot, where you merely read in the new instructions at run time, with the instructions written in Torque Script. No need to create your own robot control language!

Host Interface Code

The Host interface code is similar to the SoloPlay code that you've just looked at. There is nothing remarkable about it that hasn't been already mentioned, except that you should

Team LRN