$TypeMasks::TerrainObjectType | $TypeMasks::ShapeBaseObjectType;
%scanTarg = ContainerRayCast(%x SPC %y SPC "500", %x SPC %y SPC "-100", %search-
Masks);
if(%scanTarg && !(%scanTarg.getType() & $TypeMasks::InteriorObjectType))
{
%newpos = GetWord(%scanTarg,1) SPC GetWord(%scanTarg,2) SPC GetWord(%scanTarg,3)
+ 1;
}
%coin = new Item("Copper "@%i) {
position = %newpos;
rotation = "1 0 0 0";
scale = "5 5 5";
dataBlock = "Copper";
collideable = "0";
static = "0";
rotate = "1";
};
MissionCleanup.add(%coin);
CoinGroup.add(%coin);
}
}
The first thing this function does is to obtain the particulars of the
MissionArea
. For this
game, you should use the Mission Area Editor (press F11 followed by F5) to expand the
MissionArea
to fill the entire available terrain tile.
The
%H
and
%W
values are the height and width of the
MissionArea
box. The variables
%west
and
%south
combined make the coordinates of the southwest corner. We uses these values
to constrain our random number selection.
Then we set up a search mask. All objects in the Torque Engine have a mask value that
helps to identify the object type. We can combine these masks using a bitwise-or opera-
tion, in order to identify a selection of different types of interest.
Then we use our random coordinates to do a search from 500 world units altitude down-
ward until we encounter terrain, using the
ContainerRayCast
function.
When the ray cast finds terrain, we add 1 world unit to the height and then use that plus
the random coordinates to build a position at which to spawn a coin. Then we spawn the
coin using the appropriate datablock, which can be found in your new copy of item.cs.
Next, we add the coin to the
MissionCleanup
group so that Torque will automatically
remove the coins when the game ends. We also add it to the
CoinGroup
in case we want to
access it later.
Triggering Events 627
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
After putting that code in, copy C:\3DGPAi1\RESOURCES\CH22\ITEM.CS over to
C:\koob\control\server\misc. You will find the datablocks for the coins (where the coin
values are assigned) in there.
Note that when we added the coins in the preceding code, the
static
parameter was set to
0. This means that the game will not create a new coin at the place where the coin was
picked up, if it is picked up. The weapons of the ammo do this, but we don't want our
coins to do it. It's a game play design decision.
In addition to the datablocks for the coins in item.cs, you will also find this code:
if (%user.client)
{
messageClient(%user.client, 'MsgItemPickup', '\c0You picked up %1', %this.pickup-
Name);
%user.client.money += %this.value;
%user.client.DoScore();
}
The last two statements in there allow the player to accumulate the money values, and
then the server notifies the client of the new score. Note that it is similar in that small way
to the checkpoint scoring.
Again, until the client code is in place, you can insert
echo
statements there to verify that
things are working properly.
Deaths
We want to track the number of times we die to further satisfy requirements, so open
C:\koob\control\server\server.cs, locate the method
GameConnection::onDeath
, and add
these lines at the end:
%this.deaths++;
%this.DoScore();
By now these lines should be familiar. We can expand the player death by adding some
sound effects and animation. Add the following to the end of C:\koob\control\server\
players\player.cs:
function Player::playDeathAnimation(%this,%deathIdx)
{
%this.setActionThread("Die1");
}
datablock AudioProfile(DeathCrySound)
{
fileName = "~/data/sound/orc_death.wav";
description = AudioClose3d;
Chapter 22
■
The Game Server628
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
preload = true;
};
function Player::playDeathCry( %this )
{
%client = %this.client;
serverPlay3D(DeathCrySound,%this.getTransform());
}
The first function,
playDeathAnimation
, will play the animation sequence we named
Die1
in
our model. After that is another audio profile, pretty straightforward, followed by the
function
playDeathCry
, which will play that profile's sound effect. These are invoked by two
lines that you should place in the
OnDisabled
function farther back up in the player.cs file.
Add these two lines to
OnDisabled
just before the call to
SetImageTrigger
:
%obj.playDeathCry();
%obj.playDeathAnimation(-1);
One more thing—copy the audio wave file C:\3DGPAi1\RESOURCES\CH22\ORC_
DEATH.WAV to C:\koob\control\data\sound in order to make the sound work.
Kills
The victim, who notifies the shooter's client when he dies, actually does the kill tracking.
So we go back to
GameConnection::onDeath
and add this:
%sourceClient = %sourceObject ? %sourceObject.client : 0;
if (%obj.getState() $= "Dead")
{
if (isObject(%sourceClient))
{
%sourceClient.incScore(1);
if (isObject(%client))
%client.onDeath(%sourceObject, %sourceClient, %damageType, %location);
}
}
This bit of code figures out who shot the player and notifies the shooter's client object of
this fact.
Now it is important to remember that all this takes place on the server, and when we refer
to the client in this context, we are actually talking about the client's connection object and
not about the remote client itself.
Okay, so now let's move on to the client side and finish filling the requirements!
Triggering Events 629
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
Moving Right Along
So, now we have our player's model ready to appear in the game as our avatar, we've got
wheels for him to get around in, and a way to figure out where he's been.
We've also put some things in the game world for the player to pursue to accumulate
points, and a way to discourage other players from accumulating too many points for
themselves (by killing them).
All of these features are created on the server. In the next chapter, we will add the features
that will be handled by the game client.
Chapter 22
■
The Game Server630
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
631
The Game Client
chapter 23
B
y now we've met most of our requirements, at least to the point of implementa-
tion. Testing them for correct operation and completeness I will leave as an exer-
cise for you, Gentle Reader, because you may (and probably will) want to modify
and enhance the requirements anyway.
According to my list, the requirements that remain outstanding are the following:
2. Internet multiplayer game play.
3. Global in-game chat.
11. All other players are enemies.
12. All point values configurable from a setup file.
14. 3 points per vehicle destroyed.
15. Ability to race around the track and score 5 points for each lap you lead. (partial)
16. Laps can only be scored in the car.
17. A 10-lap race with no time limit.
18. A 10-point bonus for winning the race.
29. When one map is finished, cycle to the next in the list.
Of this list, I will leave numbers 14, 16, 17, and 18 and the remaining portion of number
15 (scoring 5 points) to you to complete as exercises. They are variations of the coin scor-
ing and the lap and checkpoint tracking we covered in Chapter 22. The functioning code
is available in the Koob installation kit on the CD, if you need help.
Most of the remaining work requires additional client code to support the server addi-
tions we made in the last chapter—we'll add some multiplayer support, a little bit more
client support, and user interfaces to access those capabilities.
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
Client Interfaces
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 inter-
face he can use to find servers, decide which one offers an interesting game, and then con-
nect 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 win-
dow 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
■
view information about the games and credits
■
play in single-player mode (as it already has)
■
host a game
■
connect to another server
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:
Chapter 23
■
The Game Client632
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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";
};
Client Interfaces 633
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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.
SoloPlay Interface
The
SoloPlay
interface, as shown in
Figure 23.2, prepares a list of mission
files that it finds in the maps subdirec-
tory 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.
It's worth remembering that even
when you play in solo mode, under-
neath the hood, the Torque Engine is
Chapter 23
■
The Game Client634
Figure 23.1 MenuScreen interface.
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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 simi-
lar, 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.
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 fin-
ishes and the new one loads. The ran-
dom mode causes the server to ran-
domly 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 mas-
ter server resources available from the GarageGames user community.
Client Interfaces 635
Figure 23.2 SoloPlay interface.
Figure 23.3 Host interface.
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
note
The Query LAN button on the
FindServer
interface does the same thing that was done automati-
cally 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 opera-
tions 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.
Open the file C:\koob\control\client\Initialize.cs and add the following lines to the func-
tion
InitializeClient
:
Chapter 23
■
The Game Client636
Figure 23.4 FindServer interface.
Figure 23.5 ChatBox interface.
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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 key-
board 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 sig-
nals 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";
Client Interfaces 637
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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";
Chapter 23
■
The Game Client638
Team LRN
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.