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!
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!
About the author
Las Vegas studio director and operations manager.
#22
That is really cool. I will have to try your tutorial as it looks fun!
03/08/2012 (8:03 pm)
@AF, That is really cool. I will have to try your tutorial as it looks fun!
#23
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:
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.
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
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.
03/08/2012 (8:11 pm)
TSS Controls Part 2Now, 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 3function 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
Same as function above but along the Y axis
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.
03/08/2012 (8:13 pm)
TSS Controls Part 4Same 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
This section is for controlling the Right Stick, or player orientation.
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.
Comment out the old gamepad directions:
And then add these underneath:
Finally, run DeletePrefs in your game folder, add some zombies, and a little A.I. and your good to go!
03/08/2012 (8:15 pm)
TSS Controls Part 5This 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
nice point.
but these 4 letters "Mini" has changed things.
so everybody is trying to odey this line
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?
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
you need to create a datablock to be played this can be made anyplace
you also need to make a function to run the sound place this above function AreaCaptureTrigger::onTickTrigger
then in scripts/server/trigger.cs in the function function AreaCaptureTrigger::onTickTrigger function you need to call the playsound function
03/08/2012 (9:09 pm)
how to play a sound when you are in a triggeryou 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
Heres an example of the finished result:
Lets get started with the C++ changes, shall we?
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)
Find the line F32 maxUnderwaterSideSpeed; inside the [u]PlayerData[/u] struct and add this under it:
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 /// @} ):
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:
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:
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...)
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
Find the IMPLEMENT_CALLBACK inside the player.cpp file, above the PlayerData constructor (PlayerData::PlayerData()), and add this to it:
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:
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:
This exposes those datablock values so they can be set in code
(cont...)
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 UnderwaterThis exposes those datablock values so they can be set in code
(cont...)
#32
Inisde the PlayerData::packData(BitStream* stream) function, find the line stream->write(maxUnderwaterSideSpeed); and add this below it:
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:
This writes the data from the torquescript datablocks
Inside the Player constructor (Player::Player()), find the line mInWater = false; and add this below it:
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:
Sets our function so it will return our boolean above when called
(cont...)
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 UnderwaterSets our function so it will return our boolean above when called
(cont...)
#33
Find DefineEngineMethod( Player, getControlObject, S32 line and add this above it:
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:
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...)
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 UnderwaterAllows 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 UnderwaterThis 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
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 )
This just creates a GUI which we can show/hide the gui progress ctrl
(cont...)
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 UnderwaterThis just creates a GUI which we can show/hide the gui progress ctrl
(cont...)
#35
In game/scripts/client/init.cs, inside the initClient function, add this in there someplace:
This just allows our GUI be avail for use
In game/scripts/client/client.cs, add this to the end of the file:
This basically gives some functions that the server can call for the client to modify the bar or show the user
(cont...)
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 UnderwaterThis 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 UnderwaterThis basically gives some functions that the server can call for the client to modify the bar or show the user
(cont...)
#36
Inside game/scripts/server/player.cs, add this to the end of the file:
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...)
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 UnderwaterThis 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
(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:
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
Should have 2 backslash n's at the end. (some only just have one, such as the addFields)
EDIT2: musta just been notepad++
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
1) Open tsShape.cpp, and find
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:
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
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:
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:
Good. Got it? Go!
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
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.

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.

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.

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

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.

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

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.

You can change the color under base color.

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

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

You'll have to look up a terrain tutorial if you want to make your river sit in your terrain better.
03/09/2012 (9:03 am)
River tutorialLoad 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.

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.

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.

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

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.

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

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.

You can change the color under base color.

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

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

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
Torque Owner A F
Default Studio Name
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:
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:
But is created with:
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: