Game Development Community

dev|Pro Game Development Curriculum

Mini Tutorial Community Contest #6

by Geoff Beckstrom · 03/06/2012 (8:55 am) · 51 comments

Okay everybody. With GDC on tap for the week I thought we would take a break from game design and have a mini tutorial writing contest.

The job: select 1 topic from either T2D or T3D, it could be an editor, it could be a single task - the topic is in your hands. Write a short mini tutorial and present it as a comment to this blog.

The tutorial may be written AND/OR video presented. You will have until the end of community power hour to present your tutorial and the winner will be determined by the end of the work day Friday.

Best tutorial will receive $129 in first party store credit. Lets get learning!

Game on!
#21
03/08/2012 (7:26 pm)
Part 2

We then need to obscure the sphere when it is first created so it doesn't just pop in.
The lurker weapon already has a particle effect that is created everytime it shoots.

Inside art/datablocks/weapons/lurker.cs starting at line 62 there is the datablock used for the lurkers muzzle flash particle effect.

Modify this to look like:
datablock ParticleData(GunFireSmoke)
{
   textureName          = "art/shapes/particles/smoke";
   dragCoefficient      = 0;
   gravityCoefficient   = "-1";
   windCoefficient      = 0;
   inheritedVelFactor   = 0.0;
   constantAcceleration = 0.0;
   lifetimeMS           = 500;
   lifetimeVarianceMS   = 200;
   spinRandomMin = -180.0;
   spinRandomMax =  180.0;
   useInvAlpha   = true;

   colors[0]     = "1 0.0 0.795276 1";
   colors[1]     = "1 0.0 0.866142 1";
   colors[2]     = "1 0.0 0.795276 0";

   sizes[0]      = "8";
   sizes[1]      = "5";
   sizes[2]      = "0.1";

   times[0]      = 0.0;
   times[1]      = "0.498039";
   times[2]      = 1.0;
   animTexName = "art/shapes/particles/smoke";
};
All this will do is change the colour from grey to pink and make the particles much bigger. They need to be bigger than the boulder model to hide it.

Lastly we need to remove these rigidShapes from the level.
Inside the LurkerWeaponImage::OnFire function we have a line:
%id.schedule(10000, "removeMe");

This will run a function inside "%id" (which is the rigidShape that was created) in 10000 milliseconds (or 10 seconds). The function that it will run is called removeMe.
Because this function is inside the "%id" object the function ins't make with:
function removeMe()
But is created with:
function RigidShape::removeMe()
This schedule will also send the ID of this object as the first parameter, this is what we want the function to look like:
function RigidShape::removeMe(%obj)
{   
   %part = new particleEmitterNode()
   {
      datablock = SmokeEmitterNode;
      position = %obj.getTransform();
      Emitter = GunFireSmokeEmitter;
      active = 1;
   };      
   %obj.delete();
}
This will create a new particle effect using the same particle we modified above. We want to do this to hide the object when it is deleted so it doesn't just suddelny disappear.

Hopefully that was easy to follow, if you create too many Dynamic Cube mapped objects on teh screen at once the rendering code does have some problems:

#22
03/08/2012 (8:03 pm)
@AF,
That is really cool. I will have to try your tutorial as it looks fun!
#23
03/08/2012 (8:07 pm)
This tutorial will show you how to add twin-stick shooter controller controls to your torque project. With TSS controls, the right analog stick will control which direction the player faces, while the left stick will move you in the pressed direction no matter what direction your facing.

First you will want to add a proper top down/three-quarter cam to your project. You can either use your own cam or just add something like this to the /scripts/server/gameCore.cs. Add it to the end of the GameCore::onClientEnterGame(%game, %client) function:

//Add twin stick shooter camera
// start the camera overhead  
   %client.setCameraObject(%client.camera);  
   %client.camera.setOrbitObject(%client.player, mDegToRad(50) @ " 0 0", 0, 10, 10, false, "0 0 0", true);  
   %client.camera.camDist = 10;
   %client.setFirstPerson(false); 
   %client.camera.controlMode = "OrbitObject";

Next, open up your player's datablock(example: art/datablocks/player.cs), and change the player's maxForwardSpeed, maxBackwardSpeed, maxSideSpeed to all be the same value.
Ex:

maxForwardSpeed = 9;
maxBackwardSpeed = 9;
maxSideSpeed = 9;


Finally we move on to the players controls. Open up the scripts/client/default.bind.cs script and make the following additions.

This function is simply to return the player's rotation, and keep it a positive number within the 360 degree range. Makes it easier to work with later.
*Note: the engine may already make these adjustments automatically, making this function not necessary, but all the same here it is.

function getMyRot()
{
   %player = LocalClientConnection.getControlObject();

   if (%player.getClassName() $= "Player")
   {
      //get the players rotation, and cut it down to just the yaw   
      $myRot = %player.rotation;
   
      //Number is not negative, cut out the 1
      if (strchr($myRot, "-") == Null) 
      {  
         //$myRot = strchr($myRot, "1");
         //$myRot = strReplace($myRot, "0 0 1", " ");
         $myRot = strReplace($myRot, "1 ", " ");
         $myRot = stripChars($myRot, " ");  
         //$myRot = trim($myRot);
         //$myRot = strchr($myRot, "1");
      }
      else  //cut number down, then make it the positive equivalent
      {
         $myRot = strReplace($myRot, "-1 ", " ");
         $myRot = stripChars($myRot, " ");  
         $myRot = 360 - $myRot;
      }
      return($myRot);
   }
}
#24
03/08/2012 (8:11 pm)
TSS Controls Part 2
Now, lets start with the left stick. The movement for this stick is broken down into two functions, one function for the x-axis and the other for the y-axis. What we are going to do in these two functions is first determine the direction and quandrant the player is facing, and depending on how close the player's direction is to each axis will
set the amount of force for that axis movement.

For example, lets say that the player is facing at a 0 degree angle, or completely North, and we wanted to move the player straight east. To do this, the player needs to side step to the right, with no foward movement. So we would use the full positive value for the x-axis, and no value for
the y-axis. While if we were facing East 90 degree angle, it would be the opposite. Full forward movement with no sidestep movement.

All we are doing is taking the world direction and making it relevant to the local players direction, so that he knows how much forward/backward step and sidestep to use. Each axis will return a movement value for each direction forward, backward, left, right, depending on how close the player is facing
that direction. We then compare the two axis values for each direction and return a single force value per direciton.

This is were we get the value along the x-axis. First we get the player's diretion (getMyRot), and then determine how much force depending on orientation.
#25
03/08/2012 (8:12 pm)
TSS Controls Part 3
function gamePadMoveX_TSS( %val )
{
   $padXMove = %val;   
   getMyRot();
   
   if(%val > 0)
   {
      $rtMove = %val;
      if ($myRot <= 180) 
      {
         if ($myRot <= 90) 
         {
            %moveFactor = ($myRot * (100/90)) * 0.01;
            $mvForwardAction2 = %val * ($movementSpeed * %moveFactor);
            $mvBackwardAction2 = 0;
      
            %moveFactor = ((90 - $myRot) * (100/90)) * 0.01;
            $mvRightAction2 = %val * ($movementSpeed * %moveFactor);
            $mvLeftAction2 = 0;
         }
         else
         {
            if ($myRot > 90) 
            {
               %moveFactor = ((180 - $myRot) * (100/90)) * 0.01;
               $mvForwardAction2 = %val * ($movementSpeed * %moveFactor);
               $mvBackwardAction2 = 0;
      
               %moveFactor = (($myRot - 90) * (100/90)) * 0.01;
               $mvLeftAction2 = %val * ($movementSpeed * %moveFactor);
               $mvRightAction2 = 0;
            }
         }
      }
      else  
      {
         if ($myRot > 180)
         {
            if ($myRot <= 270) 
            {
               %moveFactor = (($myRot - 180) * (100/90)) * 0.01;
               $mvForwardAction2 = 0;
               $mvBackwardAction2 = %val * ($movementSpeed * %moveFactor);
         
               %moveFactor = ((270 - $myRot) * (100/90)) * 0.01;
               $mvLeftAction2 = %val * ($movementSpeed * %moveFactor);
               $mvRightAction2 = 0;
            }
            else
            {
               %moveFactor = ((360 - $myRot) * (100/90)) * 0.01;
               $mvForwardAction2 = 0;
               $mvBackwardAction2 = %val * ($movementSpeed * %moveFactor);
      
               %moveFactor = (($myRot - 270) * (100/90)) * 0.01;
               $mvRightAction2 = %val * ($movementSpeed * %moveFactor);
               $mvLeftAction2 = 0;
            }
         }
      }

   }
   else
   {
      %val *= -1;
      $ltMove = %val;
      if ($myRot >= 180) 
      {
         if ($myRot <= 270) 
         {
            %moveFactor = (($myRot - 180) * (100/90)) * 0.01;
            $mvForwardAction2 = %val * ($movementSpeed * %moveFactor);
            $mvBackwardAction2 = 0;
      
            %moveFactor = ((270 - $myRot) * (100/90)) * 0.01;
            $mvRightAction2 = %val * ($movementSpeed * %moveFactor);
            $mvLeftAction2 = 0;
         }
         else
         {
            if ($myRot > 270) 
            {
               %moveFactor = ((360 - $myRot) * (100/90)) * 0.01;
               $mvForwardAction2 = %val * ($movementSpeed * %moveFactor);
               $mvBackwardAction2 = 0;
      
               %moveFactor = (($myRot - 270) * (100/90)) * 0.01;
               $mvLeftAction2 = %val * ($movementSpeed * %moveFactor);
               $mvRightAction2 = 0;
            }
         }
      }
      else  
      {
         if ($myRot < 180)
         {
            if ($myRot <= 90) 
            {
               %moveFactor = ($myRot * (100/90)) * 0.01;
               $mvForwardAction2 = 0;
               $mvBackwardAction2 = %val * ($movementSpeed * %moveFactor);
         
               %moveFactor = ((90 - $myRot) * (100/90)) * 0.01;
               $mvLeftAction2 = %val * ($movementSpeed * %moveFactor);
               $mvRightAction2 = 0;
            }
            else
            {
               %moveFactor = ((180 - $myRot) * (100/90)) * 0.01;
               $mvForwardAction2 = 0;
               $mvBackwardAction2 = %val * ($movementSpeed * %moveFactor);
      
               %moveFactor = (($myRot - 90) * (100/90)) * 0.01;
               $mvRightAction2 = %val * ($movementSpeed * %moveFactor);
               $mvLeftAction2 = 0;
            }
         }
      }
   }
   
   //We now have the x-axis values, call moveMe to compare to y-axis
   moveMe();
}
#26
03/08/2012 (8:13 pm)
TSS Controls Part 4

Same as function above but along the Y axis
function gamePadMoveY_TSS( %val )
{
   $padYMove = %val;
   getMyRot();
   
   
   if(%val > 0)
   {
      $uMove = %val;
   
      if (($myRot <= 90) || ($myRot >= 270))
      {
         if ($myRot <= 90) 
         {
            %moveFactor = ((90 - $myRot) * (100/90)) * 0.01;
            $mvForwardAction1 = %val * ($movementSpeed * %moveFactor);
            $mvBackwardAction1 = 0;
      
            %moveFactor = ($myRot * (100/90)) * 0.01;
            $mvLeftAction1 = %val * ($movementSpeed * %moveFactor);
            $mvRightAction1 = 0;
         }
         else
         {
            if ($myRot >= 270) 
            {
               %moveFactor = (($myRot - 270) * (100/90)) * 0.01;
               $mvForwardAction1 = %val * ($movementSpeed * %moveFactor);
               $mvBackwardAction1 = 0;
      
               %moveFactor = ((360 - $myRot) * (100/90)) * 0.01;
               $mvRightAction1 = %val * ($movementSpeed * %moveFactor);
               $mvLeftAction1 = 0;
            }
         }
      }
      else  // >90 && < 270   Facing Down
      {
         if (($myRot > 90) && ($myRot < 270))
         {
            if ($myRot <= 180) 
            {
               %moveFactor = (($myRot - 90) * (100/90)) * 0.01;
               $mvForwardAction1 = 0;
               $mvBackwardAction1 = %val * ($movementSpeed * %moveFactor);
         
               %moveFactor = ((180 - $myRot) * (100/90)) * 0.01;
               $mvLeftAction1 = %val * ($movementSpeed * %moveFactor);
               $mvRightAction1 = 0;
            }
            else
            {
               %moveFactor = ((270 - $myRot) * (100/90)) * 0.01;
               $mvForwardAction1 = 0;
               $mvBackwardAction1 = %val * ($movementSpeed * %moveFactor);
      
               %moveFactor = (($myRot - 180) * (100/90)) * 0.01;
               $mvRightAction1 = %val * ($movementSpeed * %moveFactor);
               $mvLeftAction1 = 0;
            }
         }
      }
   }
   else
   {
      %val *= -1;
      $dnMove = %val;
      
      if (($myRot <= 90) || ($myRot >= 270))
      {
         if ($myRot <= 90) 
         {
            %moveFactor = ((90 - $myRot) * (100/90)) * 0.01;
            $mvForwardAction1 = 0;
            $mvBackwardAction1 = %val * ($movementSpeed * %moveFactor);
      
            %moveFactor = ($myRot * (100/90)) * 0.01;
            $mvRightAction1 = %val * ($movementSpeed * %moveFactor);
            $mvLeftAction1 = 0;
         }
         else
         {
            if ($myRot >= 270) 
            {
               %moveFactor = (($myRot - 270) * (100/90)) * 0.01;
               $mvBackwardAction1 = %val * ($movementSpeed * %moveFactor);
               $mvForwardAction1 = 0;
         
               %moveFactor = ((360 - $myRot) * (100/90)) * 0.01;
               $mvLeftAction1 = %val * ($movementSpeed * %moveFactor);
               $mvRightAction1 = 0;
            }
         }
      }
      else  // >90 && < 270   Facing Down
      {
         if (($myRot > 90) && ($myRot < 270))
         {
            if ($myRot <= 180) 
            {
               %moveFactor = (($myRot - 90) * (100/90)) * 0.01;
               $mvBackwardAction1 = 0;
               $mvForwardAction1 = %val * ($movementSpeed * %moveFactor);
         
               %moveFactor = ((180 - $myRot) * (100/90)) * 0.01;
               $mvRightAction1 = %val * ($movementSpeed * %moveFactor);
               $mvLeftAction1 = 0;
            }
            else
            {
               %moveFactor = ((270 - $myRot) * (100/90)) * 0.01;
               $mvBackwardAction1 = 0;
               $mvForwardAction1 = %val * ($movementSpeed * %moveFactor);
      
               %moveFactor = (($myRot - 180) *(100/90)) * 0.01;
               $mvLeftAction1 = %val * ($movementSpeed * %moveFactor);
               $mvRightAction1 = 0;
            }
         }
      }
   }
   //We now have the x-axis values, call moveMe to compare to y-axis
   moveMe();   
}

This is the function that takes the values from both the x-axis and y-axis to give a single value per direction. The engine then moves the player based on this value.
function moveMe()
{
   $mvForwardAction = $mvForwardAction1 + $mvForwardAction2;
   $mvLeftAction = $mvLeftAction1 + $mvLeftAction2;
   $mvRightAction = $mvRightAction1 + $mvRightAction2;
   $mvBackwardAction = $mvBackwardAction1 + $mvBackwardAction2;
}
#27
03/08/2012 (8:15 pm)
TSS Controls Part 5
This section is for controlling the Right Stick, or player orientation.
function gamepadYaw_TSS(%val)
{
   $yawMove = %val;
   if (%val != 0)
   {   
      $yawForce = %val;
   }
   getAngle();
}


function gamepadPitch_TSS(%val)
{
   $pitchMove = %val;
   if (%val != 0)
   {   
      $pitchForce = %val;
   }
   getAngle();
}

This function is going to give us our player direction. Using pitch force and yaw force we establish the quadrant and direction and save this in $stickAngle. We then apply the stickAngle to the player's game orientation, and update the movement to match our new player direction.
function getAngle()
{
   if (($pitchForce >= 0) && ($yawForce >= 0))   //first quadrant
   {
      if ($pitchForce > $yawForce)
      {
         $stickAngle = 0 + ($yawForce/$pitchForce * 45);
      }
      else
      {
         $stickAngle = 90 - ($pitchForce/$yawForce * 45);
      }
   }
   else
   {
      if (($pitchForce <= 0) && ($yawForce >= 0)) //second quadrant
      {
         %nwPF = $pitchForce * -1;   //use positive numbers for  calculations
         if (%nwPF > $yawForce)
         {
            $stickAngle = 180 - ($yawForce/%nwPF * 45);
         }
         else
         {
            $stickAngle = 90 + (%nwPF/$yawForce * 45);
         }
      }
      else
      {
         if (($pitchForce <= 0) && ($yawForce <= 0))   //third quadrant
         {
            %nwPF = $pitchForce * -1;   //use positive numbers for  calculations
            %nwYF = $yawForce * -1;   //use positive numbers for  calculations
            if (%nwPF > %nwYF)
            {
               $stickAngle = 180 + (%nwYF/%nwPF * 45);
            }
            else
            {
               $stickAngle = 270 - (%nwPF/%nwYF * 45);
            }
         }
         else
         {
            if (($pitchForce >= 0) && ($yawForce <= 0)) //fourth quadrant
            {
               %nwYF = $yawForce * -1;    //use positive numbers for  calculations
               if ($pitchForce > %nwYF)
               {
                  $stickAngle = 360 - (%nwYF/$pitchForce * 45);
               }
               else
               {
                  $stickAngle = 270 + ($pitchForce/%nwYF * 45);
               }
            }
         }
      }
   }
   
   getMyRot();
   %player = LocalClientConnection.getControlObject();
   %player.rotation = "0 0 1 " @ $stickAngle;

   //everytime the rotation is changed, we need to update the movement direction.
   gamePadMoveX_TSS($padXMove);
   gamePadMoveY_TSS($padYMove);
}


Comment out the old gamepad directions:

moveMap.bind( gamepad, thumbrx, "D", "-0.23 0.23", gamepadYaw );
moveMap.bind( gamepad, thumbry, "D", "-0.23 0.23", gamepadPitch );
moveMap.bind( gamepad, thumblx, "D", "-0.23 0.23", gamePadMoveX );
moveMap.bind( gamepad, thumbly, "D", "-0.23 0.23", gamePadMoveY );

And then add these underneath:

moveMap.bind( gamepad, thumbrx, "DN", "-0.23 0.23", gamepadYaw_TSS );
moveMap.bind( gamepad, thumbry, "DN", "-0.23 0.23", gamepadPitch_TSS );
moveMap.bind( gamepad, thumblx, "D", "-0.23 0.23", gamePadMoveX_TSS );
moveMap.bind( gamepad, thumbly, "D", "-0.23 0.23", gamePadMoveY_TSS );


Finally, run DeletePrefs in your game folder, add some zombies, and a little A.I. and your good to go!
#28
03/08/2012 (8:47 pm)
Quote:
Resource = Copy and Paste
Tutorial = Explain What and Why

nice point.
but these 4 letters "Mini" has changed things.
so everybody is trying to odey this line
Quote:
Write a short mini tutorial and present it as a comment to this blog.


i have made a tutorial with explaination on why and how to use t3d's multiplayer function to make an simple multiplayer game.
with proper explanation for absolutely t3d novices through a mini game, based on GUI.

but that will not be fit as an comment.
nor will be able to fit in 5 minute video.
if i post that as a thread and then submit the link here,will that be consider as an contest submition?
#29
03/08/2012 (9:09 pm)
how to play a sound when you are in a trigger


you need to create a datablock to be played this can be made anyplace
datablock SFXProfile(AreaSound)
{
   fileName = "art/sound/victory";
   description = AudioClose3d;
   preload = true;
};

you also need to make a function to run the sound place this above function AreaCaptureTrigger::onTickTrigger

function PlaySound(%obj, %sound)
{
   %obj.playAudio(0, %sound);
}


then in scripts/server/trigger.cs in the function function AreaCaptureTrigger::onTickTrigger function you need to call the playsound function


function AreaCaptureTrigger::onTickTrigger(%this,%trigger)
{

   Playsound(%obj, AreaSound);
}

#30
03/08/2012 (10:19 pm)
Here is my mess of a tutorial... C++ changes are needed (so you need to own a full version of T3D1.2)..


Heres an example of the finished result:



Lets get started with the C++ changes, shall we?

C++ Changes


Open up Visual studio, in the solution explorer, go into <project name DLL> --> Source Files --> Engine --> T3D.

All the files we will modify are inside here... (the player.cpp and player.h files)


player.h



Find the line F32 maxUnderwaterSideSpeed; inside the [u]PlayerData[/u] struct and add this under it:

// Oxygen Level Underwater
   bool canBreatheUnderwater;		///< Can the player breathe underwater?
   F32 underwaterEyePercentage;		///< Percentage where the eye is
   // Oxygen Level Underwater

This is some variables we check with, they get exposed to TorqueScript through the PlayerData datablock so the scripters can easily change them.




Go to the end of that same PlayerData struct, find the existing DECLARE_CALLBACK 's and add this below them (but above /// @} ):

// Oxygen Level Underwater

   // our callbacks declared for when the player's head does enter and leave water

   DECLARE_CALLBACK( void, onHeadEnterWater, ( Player* obj, const char* type ) );
   DECLARE_CALLBACK( void, onHeadLeaveWater, ( Player* obj, const char* type ) );

   // Oxygen Level Underwater

This allows C++ to raise a function inside TorqueScript when we want it to. (in this case, when the players head enters and exits the water)




inside the Player class (still in the header file), find the line bool mSwimming; and add this below it:

// Oxygen Level Underwater
   // our boolean to check with
   bool mHeadInWater;		///< Is true if WaterCoverage is greater than the eye point
   // Oxygen Level Underwater

This gives us a easy value to check with to see if the player is underwater or not.




Lastly, still inside the Player class, find the line bool canSwim(); and add this below it:

// Oxygen Level Underwater
   bool headUnderwater();								   ///< Is the players head underwater?
   // Oxygen Level Underwater

This gives us a public function that anyone can use to see if the players head is underwater or not. (gets exposed to torquescript later for use)


(cont...)
#31
03/08/2012 (10:20 pm)
Part 2:


player.cpp


Find the IMPLEMENT_CALLBACK inside the player.cpp file, above the PlayerData constructor (PlayerData::PlayerData()), and add this to it:

// Oxygen Level Underwater

// our callbacks implemented for when the players head does enter or leave water
// you would check these in torquescript and do something with it when it does go under

IMPLEMENT_CALLBACK( PlayerData, onHeadEnterWater, void, ( Player* obj, const char* type ), ( obj, type ),
   "@brief Called when the player's head goes underwater.\n\n"
   "@param obj The Player object\n"
   "@param type The type of liquid the player has entered\n" );

IMPLEMENT_CALLBACK( PlayerData, onHeadLeaveWater, void, ( Player* obj, const char* type ), ( obj, type ),
   "@brief Called when the player's head leaves a liquid.\n\n"
   "@param obj The Player object\n"
   "@param type The type of liquid the player's head has left\n" );

// Oxygen Level Underwater

This implements our callbacks from the header file so they are ready to call when we want them to from C++




Inside the PlayerData constructor, find the line that says maxUnderwaterSideSpeed = 6.0f; and put this below it:

// Oxygen Level Underwater
   canBreatheUnderwater = false;
   underwaterEyePercentage = 0.95f;
   // Oxygen Level Underwater

This sets our datablocks default values, if the user doesnt specify them in the player's datablock




Inside the PlayerData::initPersistFields() function, find the line that says endGroup( "Movement: Swimming" ); and add this above it:

// Oxygen Level Underwater
	  addField( "canBreatheUnderwater", TypeBool, Offset(canBreatheUnderwater, PlayerData),
         "@brief Can the player breathe underwater?\n\n" );
	  
	  addField( "underwaterEyePercentage", TypeF32, Offset(underwaterEyePercentage, PlayerData),
		  "@breif Percentage where the eye point is from the base of the object\n\n"
		  "Setting to 0 will have the eye point at the feet of the swim bounding box\n"
		  "Setting it to 1.0 will have the eye point at the time of the swim bounding box\n"
		  "The default of 0.95 is usually fine for most models");
	  // Oxygen Level Underwater

This exposes those datablock values so they can be set in code


(cont...)
#32
03/08/2012 (10:22 pm)
Part 3:



Inisde the PlayerData::packData(BitStream* stream) function, find the line stream->write(maxUnderwaterSideSpeed); and add this below it:

// Oxygen Level Underwater
   stream->write(canBreatheUnderwater);
   stream->write(underwaterEyePercentage);
   // Oxygen Level Underwater

This writes the new data back to torquescript




Inisde the PlayerData::unpackData(BitStream* stream) function, find the line stream->read(&maxUnderwaterSideSpeed); and add this below it:

// Oxygen Level Underwater
   stream->read(&canBreatheUnderwater);
   stream->read(&underwaterEyePercentage);
   // Oxygen Level Underwater

This writes the data from the torquescript datablocks




Inside the Player constructor (Player::Player()), find the line mInWater = false; and add this below it:

// Oxygen Level Underwater
   // set the initial value for boolean
   mHeadInWater = false;
   // Oxygen Level Underwater

Sets the default value for our boolean. The user wont start drowning, will he?




Under the Player::canSwim() function, (outside of it, not inside of it), add this:

// Oxygen Level Underwater
bool Player::headUnderwater()
{
	// only used by the defineEngineMethod getHeadUnderwaterState

	return mHeadInWater;
}
// Oxygen Level Underwater

Sets our function so it will return our boolean above when called

(cont...)
#33
03/08/2012 (10:23 pm)
Part 4:

Find DefineEngineMethod( Player, getControlObject, S32 line and add this above it:

// Oxygen Level Underwater

// Gives the player a check to see if he is underwater at any given time
// (without havintg to rely on the head enter/exit water commands
DefineEngineMethod( Player, getHeadUnderwaterState, bool, (),,
	"@breif Gets the current head underwater state.\n\n"
	"@return boolean of players head under water.\n\n")
{

	return object->headUnderwater();
}
// Oxygen Level Underwater

Allows TorqueScript to call our head underwater boolean function to check when the user's head is underwater (instead of just being told when the head enters/leaves)




Last source change...





Inside the Player::updateMove(const Move* move) function, find the line Pose desiredPose = mPose; (almost at the bottom of the updateMove function), add this above it:

// Oxygen Level Underwater
   // Enter/Leave Liquid (head)
	
   	if ( mInWater && mWaterCoverage > 0.0f)
	{
		
	    MatrixF tempEyePos;					// create a matrix to store the eye pos
		
		getEyeBaseTransform(&tempEyePos);	// load the eye position into the matrix

		F32 playerHeightToWorld = getPosition().z;				// get where the players feet are
		F32 eyePosToWorld = tempEyePos.getPosition().z;			// get where the players eyes are

		F32 eyeHeight = eyePosToWorld - playerHeightToWorld;	// get the eye height relative to the feet

		if ( mDataBlock->underwaterEyePercentage > 1.0f )
		{
			mDataBlock->underwaterEyePercentage = 0.95f;
		}

		bool headIsUnderwater = ( mWaterCoverage > mDataBlock->underwaterEyePercentage );		// put the eye height check into an easy boolean

		// check if we are in water and check if player's head is in water
		if ( !mHeadInWater && headIsUnderwater )
		{
			mHeadInWater = true;

			if ( !isGhost() )
			{
				// tell torquescript that the head is in the water
				mDataBlock->onHeadEnterWater_callback( this, mLiquidType.c_str() );
				//Con::warnf("Head entered water...");
			}
		}
		else if ( mHeadInWater && !headIsUnderwater )
		{

			mHeadInWater = false;

			if ( !isGhost() )
			{
				// tell torquescript that the head has left the water
				mDataBlock->onHeadLeaveWater_callback( this, mLiquidType.c_str() );
				//Con::warnf("Head exited water...");

			}
		}

	}

	// Oxygen Level Underwater

This does most of the work, it checks if the head is at a cetain point with the water level, changes the boolean we were using to check with and runs the call so TorqueScript can do something when the head enters/leaves the water

(cont...)
#34
03/08/2012 (10:24 pm)
Part 5:

TorqueScript changes


First, we will create a GUI which will show the oxygen level. (you are free to use your own, just create a GuiProgressCtrl and name it "oxygenBar"

Create a new file called OxygenCounter.gui under game/art/gui and put this inside:
(put this image into the same folder, called "airBar.png": i.imgur.com/8JLjl.png )

// Oxygen Level Underwater

//--- OBJECT WRITE BEGIN ---
%guiContent = new GuiControl(OxygenCounter) {
   position = "0 0";
   extent = "1024 768";
   minExtent = "8 2";
   horizSizing = "right";
   vertSizing = "bottom";
   profile = "GuiDefaultProfile";
   visible = "1";
   active = "0";
   tooltipProfile = "GuiToolTipProfile";
   hovertime = "1000";
   isContainer = "1";
   canSave = "1";
   canSaveDynamicFields = "1";
      noCursor = "1";

   new GuiBitmapBorderCtrl(oxygenBar_BorderControl) {
      position = "4 5";
      extent = "242 190";
      minExtent = "8 2";
      horizSizing = "right";
      vertSizing = "bottom";
      profile = "GuiDefaultProfile";
      visible = "1";
      active = "1";
      tooltipProfile = "GuiToolTipProfile";
      hovertime = "1000";
      isContainer = "1";
      canSave = "1";
      canSaveDynamicFields = "0";

      new GuiProgressCtrl(oxygenBar) {
		 // if you want it to fill from up to down do this: http://www.garagegames.com/community/resources/view/21546
		 // then you can comment out the 2 lines below
         //horizontalFill = "0";
         //invertFill = "1";
         maxLength = "100";
         margin = "0 0 0 0";
         padding = "0 0 0 0";
         anchorTop = "1";
         anchorBottom = "0";
         anchorLeft = "1";
         anchorRight = "0";
         position = "72 39";
         extent = "96 100";
         minExtent = "8 2";
         horizSizing = "right";
         vertSizing = "bottom";
         profile = "GreenGuiToolTipProfile";
         visible = "1";
         active = "1";
         tooltipProfile = "GuiToolTipProfile";
         hovertime = "1000";
         isContainer = "0";
         canSave = "1";
         canSaveDynamicFields = "0";
      };
      new GuiBitmapCtrl(oxygenBar_BackgroundImage) {
         bitmap = "art/gui/airBar.png";
         wrap = "0";
         position = "-14 -19";
         extent = "267 227";
         minExtent = "8 2";
         horizSizing = "right";
         vertSizing = "bottom";
         profile = "GuiDefaultProfile";
         visible = "1";
         active = "1";
         tooltipProfile = "GuiToolTipProfile";
         hovertime = "1000";
         isContainer = "0";
         canSave = "1";
         canSaveDynamicFields = "0";
      };
   };
};
//--- OBJECT WRITE END ---


// Oxygen Level Underwater

This just creates a GUI which we can show/hide the gui progress ctrl

(cont...)
#35
03/08/2012 (10:25 pm)
Part 6:

In game/scripts/client/init.cs, inside the initClient function, add this in there someplace:

// Oxygen Level Underwater
   exec("art/gui/OxygenCounter.gui");   
   // Oxygen Level Underwater

This just allows our GUI be avail for use




In game/scripts/client/client.cs, add this to the end of the file:

// Oxygen Level Underwater

// gets called from the server to show the oxygen counter and set its value to 100
function clientCmdEnableOxygenDecrease()
{
   //warn("---------------------------- starting to drown..."); 
   Canvas.pushDialog(OxygenCounter);
   setOxygenLevel(100);
   
}

// gets called from the server to hide the oxygen counter and set its value to 100
function clientCmdDisableOxygenDecrease()
{
   //warn("---------------------------- not drowning...");
   Canvas.popDialog(OxygenCounter);
   setOxygenLevel(100);
}

// called locally to set the oxygen bars level (takes in a number between 0 and 100, 
// converts it into a number thats between 0 and 1, and sets the oxygen bar with it
function setOxygenLevel(%amount)
{
   
   // we pass in the full amount (eg, a number inbetween 0 and 100) 
   // and convert it to a number that we can set the oxygenbar to
   
   %newAmount = (%amount / 100);
   oxygenBar.setValue(%newAmount);
   //echo("setOxygenLevel called with new amount: " SPC %newAmount);   
   
}

// exposes the above function to the server, for easy setting from there
function clientCmdSetOxygenBarLevel(%amount)
{
   setOxygenLevel(%amount);
   
   //echo("clientCmdSetOxygenBarLevel called with amount: " SPC %amount);
}

// Oxygen Level Underwater

This basically gives some functions that the server can call for the client to modify the bar or show the user

(cont...)
#36
03/08/2012 (10:26 pm)
Part 7:

Inside game/scripts/server/player.cs, add this to the end of the file:

// Oxygen Level Underwater

// does all the work for handling the oxygen level
function depleteOxygenLevel( %obj, %curOxygenLevel, %amountToDepleteBy, %scheduleAmount )
{
   %newOxygenLevel = ( %curOxygenLevel - %amountToDepleteBy ); // creates us a oxygen level to work with
   // ^ %newOxygenLevel would be 100 if the head leaves the water and on first enter
   commandToClient( %obj.client, 'SetOxygenBarLevel', %newOxygenLevel ); // always setting the oxygen bar level
   
   if (%curOxygenLevel > 0) // check the oxygen level is more than 0
   {
      if (%obj.getHeadUnderwaterState())
      {
         // just keep swimming...
         schedule( %scheduleAmount, 0, "depleteOxygenLevel", %obj, %newOxygenLevel, %amountToDepleteBy, %scheduleAmount );  
      }
   }
   
   if (%newOxygenLevel < 0) 
   {
      //echo(%obj.client SPC "Drowning.");         
      %obj.damage(0, %obj.getPosition(), %amountToDepleteBy, "Drown");
      %curHealth = mceil((%obj.getDatablock().maxDamage - %obj.getDamageLevel()));       
      
      if (%curHealth > 0 && %obj.getHeadUnderwaterState())
      {
         // just keep dying                             
         schedule(%scheduleAmount, 0, "depleteOxygenLevel", %obj, %newOxygenLevel, %amountToDepleteBy, %scheduleAmount);
      }
   }
}

// our function called from C++
function Armor::onHeadEnterWater(%this, %obj, %type)
{
   //warn("Player::server Armor::onHeadEnterWater");
   //echo("c4this:"@ %this @" object: "@ %obj @"'s head just entered water of type: "@ %type @"");
   
   commandToClient(%obj.client, 'EnableOxygenDecrease'); // shows the GUI on the client
   
   if (%obj.getDatablock().canBreatheUnderwater) // true if the player can breathe underwater
   {
      // The player can breathe underwater
      return; // dont continue because we dont want the players oxygen depleting
   }
   
   %curOxygenLevel = 100;  // the default oxygen level. Used to reset the number when the head goes in/out of water
   %amountToDepleteBy = 5; // a number between 0 and 100 to kill the player
   %scheduleAmount = 500;  // milliseconds (500 = half a second)
   
   depleteOxygenLevel(%obj, %curOxygenLevel, %amountToDepleteBy, %scheduleAmount);
}

// our function called form C++
function Armor::onHeadLeaveWater(%this, %obj, %type)
{
   //warn("Player::server Armor::onHeadLeaveWater");
   //echo("c4this:"@ %this @" object: "@ %obj @"'s head just left water of type: "@ %type @"");
   
   commandToClient(%obj.client, 'DisableOxygenDecrease'); // hides the GUI on the client

   if (%obj.getDatablock().canBreatheUnderwater) // true if the player can breathe underwater
   {
      // The player can breathe underwater
      return; // dont continue because we dont want the players oxygen depleting
   }
   
   %curOxygenLevel = 100;  // the default oxygen level. Used to reset the number when the head goes in/out of water
   %amountToDepleteBy = 5; // a number between 0 and 100 to kill the player
   %scheduleAmount = 500;  // milliseconds (500 = half a second)
   
   depleteOxygenLevel(%obj, %curOxygenLevel, %amountToDepleteBy, %scheduleAmount);
}

// Oxygen Level Underwater

This does most of the work, it has our function which handles the oxygen going down and then the player dying after hes ran out of air.
This also has our functions that C++ calls for torquescript to starts or stops the oxygen level

(cont...)
#37
03/08/2012 (10:27 pm)
Part 8:
(final)

Last TorqueScript change....

Inside game/art/datablocks/player.cs, find your DefaultPlayerData creation (datablock PlayerData(DefaultPlayerData))
Inside the function, find the line maxUnderwaterSideSpeed = [someNumber]; and add this below it:

// Oxygen Level Underwater
   canBreatheUnderwater = false;
   underwaterEyePercentage = 0.95;
   // Oxygen Level Underwater

These are the 2 values that was passed from C++
The first, set to false, will allow the player to drown, set to true, the player will be able to stay underwater for as long as he/she wants

The second, it is the percentage of the eye from the base of the feet. In our case, the eye position on the character is 95% from the base of the foot
If your characters mouth/eyes/etc were in the middle of his body, you would set the value around 0.5


And we're finished..





-----------------------------
EDIT: noticed while posting those 8 (they are all 1!), that backslash N wasnt showing up.

So lines like this:w
"@return boolean of players head under water.nn")

Should have 2 backslash n's at the end. (some only just have one, such as the addFields)


EDIT2: musta just been notepad++
#38
03/09/2012 (1:30 am)
How to add a custom model format to the game:


1) Open tsShape.cpp, and find

template<> void *Resource<TSShape>::create(const Torque::Path &path)


Scroll down to the if/else-if block that checks the file extension, and add the check for your own file extension as it's own else-if block. The contents of the block should look something like this:

ret = load_______Shape(path);
	   readSuccess = (ret != NULL);

where _______ is something descriptive related to the type of shape you want to load. Which leads us to the next point. At the top of the file, include the line
extern TSShape* load_______Shape(const Torque::Path &path);

right after your includes. You'll need to then define the method in it's own file, and of course include the appropriate headers.

It should look something like this:

TSShape* load________Shape(const Torque::Path &path)
{	
	TSShape* tss = NULL;
	
	________ShapeLoader _______sl;
	tss = ________sl.generateShape(path);
	
	
	TSShapeConstructor* tscon = TSShapeConstructor::findShapeConstructor(path.getFullPath());
	if (tscon) Con::warnf("We got some options for %s, maybe we should look at them?", path.getFullPath().c_str());
	
	return tss;
}

Again, _______ should be substituted with something descriptive. You can look at the TSShapeConstructor for info if you need it, otherwise you can ignore it, or even chop that code out entirely. This part depends on your needs.

2) This is where the real work begins - you'll need to subclass TSShapeLoader (being sure to reference the method you just defined as a friend in the class header). This mostly consists of overriding the pure virtual function enumerateScene defined in the parent class, and that's where your geometry and texture-loading logic should go. This will consist of subclassing the AppNode, AppMaterial, AppMesh, and related classes in ts/loader directory of your project. The important part of enumerateScene is populating the subshapes vector (a member of your _______ShapeLoader class) with instances of the Subshape class, which have been appropriately initialized, and propulating the AppMesh::appMaterials vector with instances of your AppMaterial subclass(es).

Subshapes have three important vectors to populate, which are documented as follows:
Vector<AppNode*>           branches;         ///< Shape branches
		Vector<AppMesh*>           objMeshes;        ///< Object meshes for this subshape
		Vector<S32>                objNodes;         ///< AppNode indices with objects attached


Good. Got it? Go!
#39
03/09/2012 (9:03 am)
River tutorial
Load up Torque3d, from the toolbox you will need to choose to launch a map in world edit mode, or create a new project and then launch it in world edit mode.

Once you have Torque 3D open, look at the top of your tool bar and you should see an icon of a winding road with a drop of water. If you are unsure simply hover over it and the pop up help will inform you if you have the road or the river editor.
Click it.
img594.imageshack.us/img594/2048/rivertool1.png

Your toolbar on the left side will change to the river editor.
Look for the icon that has a river/water drop/+ sign. This will allow you to create a river.

img513.imageshack.us/img513/4628/rivertool2.png

Once you have the river creator selected you will want to then find a convenient spot onscreen to click on...this will place the first section of your river.

This will be the start point for your river. Every time you click a new spot it will put a new point on your river. This will allow you to maneuver it around different obstacles or place it strategically. Once you are done placing it double click to end the river creation.

img38.imageshack.us/img38/4033/rivertool3.png

Now we need to tweak our settings.
Click the arrow tool for selecting.

img593.imageshack.us/img593/2076/rivertool4.png


The first thing we should do is change our width and depth to something suitable to the map we are creating. You will need to click on each node and change the width and depth.

img72.imageshack.us/img72/246/rivertool5.png


You can also use the scale point(curvy line with two arrows pointing outward) icon to change the width.

img20.imageshack.us/img20/2719/rivertool55.png


You can also select each point and use the move tool (the square dot with 3 directional arrows) and adjust it to sit properly on/within the terrain.

img402.imageshack.us/img402/3253/rivertool6.png

You can change the color under base color.

img51.imageshack.us/img51/6396/rivertool8.png

After you have finished moving and adjusting the width and depth you now have your river completed.

img853.imageshack.us/img853/1246/rivertool9.png

A final picture of our river from within its green confines.

img802.imageshack.us/img802/4060/rivertool10.png

You'll have to look up a terrain tutorial if you want to make your river sit in your terrain better.
#40
03/09/2012 (10:27 am)
@david: nice tutorial but you should have picked a level without fog so the pics would look better - it's hard to see the water with so much fog