3D Game Programming All in One (Course Technology PTR Game Development Series)

We are going to add code to allow users to run a server and to allow players to connect to a server. In order to make that connection, we will want to provide the user with an interface he can use to find servers, decide which one offers an interesting game, and then connect to the server.

Another thing we need to do is make sure that when the user quits a server, he returns to his selection interface rather than simply exiting as Koob does now.

Additionally, we need to add a capability to the playing interface to provide a chat window with a text entry where players can type in messages to send to other players. Maybe they'll want to exchange recipes or something. Yeah, that's it—recipes! It's not like they're going to taunt anyone anyway, is it?

In Chapter 6 you saw the MasterScreen interface module that combined these interfaces. In this chapter we'll look at the same issue but in a slightly different way, in order to show how easy it is to make different—yet equally valid—design decisions.

Also, we'll need to modify a few of the files, like the MainScreen interface, to more closely conform to our needs.

In a later section we'll add the code required to make these interfaces functional.

MenuScreen Interface

We will make some changes to our main menu screen so that it provides the user with the additional choices to

Open your MenuScreen.gui file and locate the following line:

command = "LaunchGame();";

This line is a property statement in a GuiButtonCtrl. Delete the entire control, from where it says

new GuiButtonCtrl() {

down to the closing brace ("}").

In the place of the deleted control, insert the following:

new GuiButtonCtrl() { profile = "GuiButtonProfile"; horizSizing = "right"; vertSizing = "top"; position = "30 138"; extent = "120 20"; minExtent = "8 8"; visible = "1"; command = "Canvas.setContent(SoloScreen);"; text = "Play Solo"; groupNum = "-1"; buttonType = "PushButton"; helpTag = "0"; }; new GuiButtonCtrl() { profile = "GuiButtonProfile"; horizSizing = "right"; vertSizing = "top"; position = "30 166"; extent = "120 20"; minExtent = "8 8"; visible = "1"; command = "Canvas.setContent(ServerScreen);"; text = "Find a Server"; groupNum = "-1"; buttonType = "PushButton"; helpTag = "0"; }; new GuiButtonCtrl() { profile = "GuiButtonProfile"; horizSizing = "right"; vertSizing = "top"; position = "30 192"; extent = "120 20"; minExtent = "8 8"; visible = "1"; command = "Canvas.setContent(HostScreen);"; text = "Host Game"; groupNum = "-1"; buttonType = "PushButton"; helpTag = "0"; }; new GuiButtonCtrl() { profile = "GuiButtonProfile"; horizSizing = "right"; vertSizing = "top"; position = "30 237"; extent = "120 20"; minExtent = "8 8"; visible = "1"; command = "getHelp();"; helpTag = "0"; text = "Info"; groupNum = "-1"; buttonType = "PushButton"; };

You may, if you wish, use the built-in GUI Editor (press F10) to do this. Make sure that you set all of the properties to match those just listed.

The significant thing to note about these controls is the command property. Each one replaces a displayed MenuScreen interface with a new interface, according to its function, with the exception of the Info button.

The Info button uses the getHelp feature of the common code base. It searches all of the directories nested under the root main directory looking for files with the extension .hfl, and then it lists them in alphanumeric order. If you preface the file name with a number, such as 1., 2., and so on, it will sort them numerically.

This should give you a main menu that looks like Figure 23.1.

Figure 23.1: MenuScreen interface.

SoloPlay Interface

The SoloPlay interface, as shown in Figure 23.2, prepares a list of mission files that it finds in the maps subdirectory in the control\data directory tree. From this list, you can select the map or mission you want to play. Its code and definition can be found in the SoloScreen modules.

Figure 23.2: SoloPlay interface.

It's worth remembering that even when you play in solo mode, underneath the hood, the Torque Engine is still running in two parts: a client and a server. They are just closely coupled with no cross-network calls being made.

Host Interface

The Host interface is somewhat similar, as you can see in Figure 23.3, but offers more options: the ability to set a time limit and a score limit, plus map selection modes. Its code and definition can be found in the HostScreen modules.

Figure 23.3: Host interface.

If both time and score limits are set, the first one reached ends the game. A setting of 0 makes that limit infinite. The sequence mode causes the server to step through the maps in order as shown in the listing, as each game finishes and the new one loads. The random mode causes the server to randomly select a map for each game. The time limit is saved by the control in the variable $Game::Duration, and the score limit is saved as $Game::MaxPoints.

FindServer Interface

The FindServer interface, shown in Figure 23.4, lets you browse for servers. Its code and definition can be found in the ServerScreen modules. It will find servers that are running on the local LAN you are connected to (if you are connected to one, of course), and it will attempt to reach out via the Internet to contact the master servers at GarageGames and find games for you to connect to. You are not required to use the GarageGames master servers, but then you will have to write your own master server software to connect to. This can be done using Torque Script but is beyond the scope of this book. There are master server resources available from the GarageGames user community.

Figure 23.4: FindServer interface.

Note

The Query LAN button on the FindServer interface does the same thing that was done automatically in Chapter 6 when you clicked on the Connect to Server button on the main menu screen. The discussion in the Chapter 6 section called ServerScreen Code Module describes how the Connect to Server button operations are performed, which is the same as how the Query LAN button operations work here.

ChatBox Interface

In order to display chat messages from other players, we need to put a control in our main player interface. We also need to have a control that will allow us to type in messages to be sent to other players, as depicted in Figure 23.5.

Figure 23.5: ChatBox interface.

Open the file C:\koob\control\client\Initialize.cs and add the following lines to the function InitializeClient:

Exec("./interfaces/ChatBox.gui"); Exec("./interfaces/MessageBox.gui");

These exec statements load the new files that will provide our chat interface. 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.

Now open the file C:\koob\control\client\misc\presetkeys.cs and add the following keyboard input binding statements to the end of the file:

function pageMessageBoxUp( %val ) { if ( %val ) PageUpMessageBox(); } function pageMessageBoxDown( %val ) { if ( %val ) PageDownMessageBox (); } PlayerKeymap.bind(keyboard, "t", ToggleMessageBox ); PlayerKeymap.bind(keyboard, "PageUp", PageMessageBoxUp ); PlayerKeymap.bind(keyboard, "PageDown", PageMessageBoxDown );

The first two functions are glue functions that are called by two of the key bindings at the bottom and then make the appropriate call to the functions that scroll the messages in the message box. We need these functions in order to filter out the key up and key down signals from the engine. We only want the action to take place when the key is pressed. We can do this by checking the value of %val when we enter the function—it will be nonzero when the key is pressed and zero when it is released.

Then there is a binding that calls ToggleMessageBox, which is defined in MessageBox.cs (one of the files we've recently copied that we will examine shortly).

In the interface files there are a couple of concepts you should note. To illustrate, look at the definition of the ChatBox interface, contained in ChatBox.gui:

new GuiControl(MainChatBox) { profile = "GuiModelessDialogProfile"; horizSizing = "width"; vertSizing = "height"; position = "0 0"; extent = "640 480"; minExtent = "8 8"; visible = "1"; modal = "1"; 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"; 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.

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.

Figure 23.6: MessageBox interface.

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; 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 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.

Категории