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

3D Game Programming All In One (2004)

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

618 Chapter 22 The Game Server

Table 22.1 WheeledVehicleData Properties

Command

Description

MaxDamage

Specifies the maximum number of damage points a vehicle can take before it

 

becomes disabled. Destroyed and disabled states are calculated percentages of

 

this value.

DestroyedLevel

Specifies the percentage of MaxDamage that, when reached, causes the

 

vehicle's onDestroyed callback to be called by the engine.

DisabledLevel

Specifies the percentage of MaxDamage that, when reached, causes the

 

vehicle's onDestroyed callback to be called by the engine.

MaxSteeringAngle

Maximum steering angle.

TireEmitter

Same dust emitter as used by all the tires.

CameraRoll

Rolls the camera with the vehicle when it rolls.

CameraMaxDist

Farthest distance from the vehicle in third-person view.

CameraOffset

Vertical offset from the camera mount point.

CameraLag

Velocity lag of the camera in third-person view.

CameraDecay

Decay per second rate of velocity lag in third-person view.

Mass

Mass of the vehicle in quasi-kilograms.

MassCenter

Center of mass for rigid body expressed in object space 3D coordinates.

MassBox

Size of box used for moment of inertia; if 0 it defaults to the object's bounding box.

Drag

Drag coefficient. Used to counteract acceleration.

BodyFriction

Determines "stickiness" when the body brushes against the terrain or other

 

objects.

BodyRestitution

Determines, by using rigid body physics, how much deformation is reversed.

MinImpactSpeed

Specifies the speed at and above which the vehicle's onImpact callback will be

 

called by the engine.

SoftImpactSpeed

Specifies the speed at and above which the engine will play the vehicle's

 

SoftImpact sound.

HardImpactSpeed

Specifies the speed at and above which the engine will play the vehicle's

 

HardImpact sound.

Integration

Physics integration: TickSec/Rate. Higher values here yield higher integration.

 

Higher integration leads to more accurate simulation but at the potential cost of

 

CPU performance.

CollisionTol

Collision distance tolerance. A higher number means that a collision will be

 

detected sooner (objects are farther apart) than with a lower number.

ContactTol

Contact velocity tolerance. How much leeway is allowed in determining whether

 

objects have collided or have merely contacted, or brushed, each other. A higher

 

number means that a more forceful contact can occur without the contact being

 

considered a collision.

EngineTorque

Engine power, which causes acceleration, which leads to higher velocities.

EngineBrake

Braking when throttle is 0—simulates the internal "drag" of an engine that

 

tends to slow a vehicle when it is in gear.

BrakeTorque

When brakes are applied, works as the opposite of EngineTorque.

 

continued

Team LRN

 

Vehicle

619

 

 

 

MaxWheelSpeed

The maximum rotation speed of the wheels, which directly affects the speed of

 

 

the vehicle based on the wheel diameter and deformation factors. Wheel speed

 

 

derives from engine speed and other factors.

 

MaxEnergy

The maximum amount of energy available to the vehicle for conversion into

 

 

motion. Energy can be seen to be the same as fuel load.

 

JetForce

Additional boost force—a holdover term from the Tribes days. Means the same

 

 

as acceleration.

 

MinJetEnergy

The smallest amount of energy needed to apply a jetting boost.

 

JetEnergyDrain

How quickly the energy of the vehicle is drained by use of jetting.

 

JetSound

The sound played when jetting or accelerating.

 

EngineSound

The sound played when the engine is idling.

 

SquealSound

The sound played when the tires skid.

 

SoftImpactSound

The sound played when a mild collision occurs.

 

HardImpactSound

The sound played when a serious collision occurs.

 

WheelImpactSound

The sound played when the wheels and tires hit something.

 

 

 

 

Two other datablocks have significant effect on the behavior of the car: WheeledVehicleTire and WheeledVehicleSpring, shown here:

datablock WheeledVehicleTire(DefaultCarTire)

{

shapeFile = "~/data/models/vehicles/wheel.dts"; staticFriction = 4;

kineticFriction = 1.25; lateralForce = 18000; lateralDamping = 4000; lateralRelaxation = 1; longitudinalForce = 18000; longitudinalDamping = 4000; longitudinalRelaxation = 1;

};

datablock WheeledVehicleSpring(DefaultCarSpring)

{

// Wheel

suspension properties

length =

0.85;

// Suspension travel

force = 3000;

// Spring force

damping = 600;

// Spring damping

antiSwayForce = 3;

// Lateral anti-sway force

};

In the WheeledVehicleTire datablock you can see that tires act as springs in two ways: They generate lateral and longitudinal forces to move the vehicle. These distortion/spring forces are what convert wheel angular velocity into forces that act on the rigid body.

Team LRN

620 Chapter 22 The Game Server

Triggering Events

When you need your players to interact with the game world, there is a lot that is handled by the engine through the programming of various objects in the environment, as we saw with collisions with vehicles. Most other interactions not handled by an object class can be dealt with using triggers.

A trigger is essentially a location in the game world, and the engine will detect when the player enters and leaves that space (trigger events). Based on the event detected we can define what should happen when that event is triggered using event handlers or trigger callbacks. We can organize our triggering to occur when there is an interaction with a specific object.

Creating Triggers

If you recall, some of our Koob specifications require us to count the number of laps completed. What we'll do is add a trigger to the area around the start/finish line, and every time a car with a player in it passes through this area, we'll increment the lap counter for that player.

For the trigger to know what object to call onTrigger for, you need to add an additional dynamic field with the name of instance of the trigger when it is created using the Mission Editor.

Open the file C:\koob\control\server\server.cs and at the end of the onServerCreated function, add this line:

exec("./misc/tracktriggers.cs");

This will load in our definitions.

Now create the file C:\koob\control\server\misc\tracktriggers.cs and put the following code in it:

datablock TriggerData(LapTrigger)

{

tickPeriodMS = 100;

};

function LapTrigger::onEnterTrigger(%this,%trigger,%obj)

{

if(%trigger.cp $= "")

echo("Trigger checkpoint not set on " @ %trigger); else

%obj.client.UpdateLap(%trigger,%obj);

}

Team LRN

Triggering Events

621

The datablock declaration contains one property that specifies how often the engine will check to see if an object has entered the area of the trigger. In this case it is set to a 100millisecond period, which means the trigger is checked 10 times per second.

There are three possible methods you can use for the trigger event handlers: onEnterTrigger, onLeaveTrigger, and onTickTrigger.

The onEnterTrigger and onLeaveTrigger methods have the same argument list. The first parameter, %this, is the trigger datablock's handle. The second parameter, %trigger, is the handle for the instance of the trigger object in question. The third parameter, %obj, is the handle for the instance of the object that entered or left the trigger.

In this onEnterTrigger the method is called as soon as (within a tenth of a second) the engine detects that an object has entered the trigger. The code checks the cp property of the trigger object to make sure that it has been set (not set to null or ""). If the cp property (which happens to be the checkpoint ID number) is set, then we call the client's UpdateLap method, with the trigger's handle and the colliding object's handle as arguments.

You can use onLeaveTrigger in exactly the same way, if you need to know when an object leaves a trigger.

The onTickTrigger method is similar but doesn't have the %obj property. This method is called every time the tick event occurs (10 times a second), as long as any object is present inside the trigger.

Next, we need to place the triggers in our world. We are going to put five triggers in, one at the start/finish line and one at each of the checkpoints.

Launch Koob, go into camera fly mode, and then move to a position overseeing (looking down at) the start/finish line. Go into the World Editor Creator (press F11 followed by F4), and then add a trigger by choosing Mission Objects, Mission, Trigger from the Tree view at the lower right.

Once you have your trigger placed, rotate and position it as necessary underneath the start/finish banner, and resize it to fill the width and the height of the area under the banner. Make the thickness roughly about one-tenth of the width, as shown in Figure 22.3.

Now switch to the World Editor (F3), locate your new object in the hierarchy at the upper right, and click it. In the Inspector frame, click the Dynamic Fields button, and then click Add. When you get the Add Dynamic Field dialog box (see Figure 22.4), enter "cp" in the name field and "0" in the value field, and click OK. Then click the Apply button to commit the changes to the object. What we've done is added a property to the object and named it "cp" with the value 0. We can access this property later from within the program code. The next checkpoint will be numbered 1, the one after that will be 2, next is 3, and finally 4, which is the fifth checkpoint. The numbering proceeds in a counterclockwise direction.

Team LRN

622 Chapter 22 The Game Server

Figure 22.3 Placing a trigger.

Figure 22.4 The Add Dynamic Field dialog box.

Laps and Checkpoints

Go ahead and add those checkpoints now, using the same technique as just noted. You can copy and paste the first trigger object to create the rest if you like—just remember to change the cp property accordingly.

t i p

Some objects behave a little oddly when added via copy and paste. After pasting an object into the world, even though it will be visually selected in the view of the world, it still needs to be selected in the Inspector hierarchy in the upper-right frame. There are times when this may not be strictly necessary, but if you move, rotate, or resize the object by directly manipulating it via the gizmo handles, the changes will not be reflected in the Inspector frame until you reselect the object in the hierarchy.

Now we have the ability to measure progress around the track. We have to add code to use these triggers, and that will be done as part of the scoring system, which is in the next section.

Scoring

We need to keep track of our accomplishments and transmit those values to the client for display.

Open the file C:\koob\control\server\server.cs and put the following code at the end of the

GameConnection::CreatePlayer method:

%client.lapsCompleted = 0; %client.cpCompleted = 0; %client.ResetCPs();

%client.position = 0; %client.money = 0; %client.deaths = 0; %client.kills = 0; %client.score = 0;

Team LRN

Triggering Events

623

These are the variables we use to track various scores. Now add the following methods to the end of the file:

function GameConnection::ResetCPs(%client)

{

for (%i = 0; %i < $Game::NumberOfCheckpoints; %i++) %client.cpCompleted[%i]=false;

}

function GameConnection::CheckProgress(%client, %cpnumber)

{

for (%i = 0; %i < %cpnumber; %i++)

{

if (%client.cpCompleted[%i]==false) return false;

}

%client.cpCompleted = %cpnumber; return true;

}

function GameConnection::UpdateLap(%client,%trigger,%obj)

{

if (%trigger.cp==0)

{

if (%client.CheckProgress($Game::NumberOfCheckpoints))

{

%client.ResetCPs(); %client.cpCompleted[0] = true; %client.lapsCompleted++; %client.DoScore();

if(%client.lapsCompleted >= $Game::NumberOfLaps) EndGame();

}

else

{

%client.cpCompleted[0] = true; %client.DoScore();

}

}

else if (%client.CheckProgress(%trigger.cp))

{

%client.cpCompleted[%trigger.cp] = true; %client.DoScore();

}

}

Team LRN

624Chapter 22 The Game Server

function GameConnection::DoScore(%client)

{

%scoreString =

%client.score

@

"Lap:" @ %client.lapsCompleted @

"CP:" @ %client.cpCompleted+1 @

" $:"

@ %client.money

@

"

D:"

@

%client.deaths

@

"

K:"

@

%client.kills;

 

commandToClient(%client, 'UpdateScore', %scoreString);

}

Starting from the last, the DoScore method merely sends a string containing scores to the client using the messaging system. The client code to handle this string will be presented in Chapter 23.

Before that is the meat of these particular functions—UpdateLap. You will recall that this is the method that is called for the client from the onEnterTrigger method.

The first thing UpdateLap does is to check to see if this is the first checkpoint, because it has a special case. Because we will start and drive through the first checkpoint at the start/finish line, it can be legitimately triggered without any other trigger events having occurred. We want to check for this condition. We check this by calling CheckProgress to see how many triggers have been passed. If the answer is none (a false return value), then we are starting the race, so we mark this checkpoint as having been completed and update our score to reflect that fact.

If this isn't the first checkpoint, then we want to check if all the checkpoints up until this checkpoint have been completed for this lap. If so, then mark this one completed and update the score; otherwise just ignore it.

Now finally, if we are back at checkpoint 0 and when we check to see if all the other checkpoints have been passed the result is true, then we are finishing a lap. So we increment the lap, reset the checkpoint counters, mark this checkpoint completed, update the score, and then check to see if the race is over—if not, we continue.

The previous method, CheckProgress, is called from UpdateLap and receives the current checkpoint ID number as a parameter. It then loops through the checkpoint array for this client and verifies that all lower-numbered checkpoints have been set to true (they have been passed). If any one of them is false, then this checkpoint is out of sequence and not legitimate. The function then returns false; otherwise all is in order, and it returns true.

And then first, but not least (grins), is the method ResetCPs. This simple method just riffles through the checkpoint array setting all entries to false.

Now there are a few odds and ends to deal with: Earlier in this file, server.cs, is the StartGame function. Locate it, and add these lines after the last code in there:

Team LRN

Triggering Events

625

$Game::NumberOfLaps = 10;

$Game::NumberOfCheckpoints = 5;

Of course, you should adjust these values to suit yourself. You might want to set NumberOfLaps to a lower number, like 2, for testing purposes. Speaking of testing, if you want to test this, but without having addressed the client-side code first, then you can add some echo statements and view the output in the console window (invoked by pressing the Tilde key). A good place to put such a statement would be just before the CommandToClient call in DoScore. It would look like this:

echo( "Score " @ %scoreString );

Money

Another requirement was to have randomly scattered coins in the game world.

Open C:\koob\control\server\server.cs and locate the function StartGame and add the following line to the end of the function:

PlaceCoins();

Then place the following function just after the StartGame function:

function PlaceCoins()

{

%W=GetWord(MissionArea.area,2);

%H=GetWord(MissionArea.area,3); %west = GetWord(MissionArea.area,0);

%south = GetWord(MissionArea.area,1);

new SimSet (CoinGroup);

for (%i = 0; %i < 4; %i++)

{

%x = GetRandom(%W) + %west;

%y = GetRandom(%H) + %south;

%searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::TerrainObjectType | $TypeMasks::ShapeBaseObjectType;

%scanTarg = ContainerRayCast(%x SPC %y SPC "500", %x SPC %y SPC "-100", %searchMasks);

if(%scanTarg && !(%scanTarg.getType() & $TypeMasks::InteriorObjectType))

{

%newpos = GetWord(%scanTarg,1) SPC GetWord(%scanTarg,2) SPC GetWord(%scanTarg,3)

+ 1;

}

%coin = new Item("Gold "@%i) { position = %newpos;

Team LRN

626 Chapter 22 The Game Server

rotation = "1 0 0 0"; scale = "5 5 5"; dataBlock = "Gold"; collideable = "0"; static = "0";

rotate = "1";

};

MissionCleanup.add(%coin);

CoinGroup.add(%coin);

}

// repeat above for silver coin for (%i = 0; %i < 8; %i++)

{

%x = GetRandom(%W) + %west;

%y = GetRandom(%H) + %south;

%searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::TerrainObjectType | $TypeMasks::ShapeBaseObjectType;

%scanTarg = ContainerRayCast(%x SPC %y SPC "500", %x SPC %y SPC "-100", %searchMasks);

if(%scanTarg && !(%scanTarg.getType() & $TypeMasks::InteriorObjectType))

{

%newpos = GetWord(%scanTarg,1) SPC GetWord(%scanTarg,2) SPC GetWord(%scanTarg,3)

+ 1;

}

%coin = new Item("Silver "@%i) { position = %newpos;

rotation = "1 0 0 0"; scale = "5 5 5"; dataBlock = "Silver"; collideable = "0"; static = "0";

rotate = "1";

};

MissionCleanup.add(%coin);

CoinGroup.add(%coin);

}

// repeat above for copper coin for (%i = 0; %i < 32; %i++)

{

%x = GetRandom(%W) + %west;

%y = GetRandom(%H) + %south;

%searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::InteriorObjectType |

Team LRN

Triggering Events

627

$TypeMasks::TerrainObjectType | $TypeMasks::ShapeBaseObjectType;

%scanTarg = ContainerRayCast(%x SPC %y SPC "500", %x SPC %y SPC "-100", %searchMasks);

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 operation, 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 downward 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.

Team LRN