Game Development Community

Physics Demo

by Matthew Langley · in Torque Game Builder · 03/14/2005 (9:38 am) · 17 replies

Note: here is a standalone T2D Physics Demo

This uses a modified recent example by Melv (displaying settings that make a block bounce accross walls)... I have added a gui that has sliders to control some of the major physics factors, as well as constant force direction and speed sliders and impulse force (degrees and speed)... to test out physics.

razedskyz.com/games/torque/tutorials/T2D/physicsDemo2.JPG
There is also a button to create a new block, all of the blocks data are stored in

$blocks
$blocks::ammount
$blocks::names[]

when you add a new block it adds to names, for example, first block is

$blocks::names[0]...

if you did

echo($blocks::names[0]);

it would produce "block0" in the console... a simple naming convention...

all of the sliders apply when you release the mouse button, except the degrees and force for setImpulse... you set the degrees, then the force ammount and click the setImpulse button. All of the sliders effect every block, have some loops that apply it to all of them.

Just a demo that you can play around with physics, test friction, interaction, etc etc...



ok lets start...


first go to your Torque 2D\SDK\Example\T2D\client folder... create a new text file (right click -> go to new -> go to "Text Document")... name it "slidersGUI.gui" ... click yes on any warning prompts (simple windows making sure you want to change the extension - the .txt to .gui)...

now copy this code in the file and save it

if(!isObject(GuiPhysicsTextProfile)) new GuiControlProfile (GuiPhysicsTextProfile)
{
   fontColor = "255 0 0";
   fontColorLink = "255 96 96";
   fontColorLinkHL = "0 0 255";
   autoSizeWidth = true;
   autoSizeHeight = true;
};


//--- OBJECT WRITE BEGIN ---
new GuiControl(slidersGUI) {
   profile = "GuiDefaultProfile";
   horizSizing = "right";
   vertSizing = "bottom";
   position = "0 20";
   extent = "225 480";
   minExtent = "8 2";
   visible = "1";

   new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "76 0";
      extent = "35 18";
      minExtent = "8 2";
      visible = "1";
      text = "Friction";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiFrictionSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 10";
      extent = "204 40";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.set(Friction,guiFrictionSlider.value);";
      range = "0.000000 0.500000";
      ticks = "10";
      value = "0";
   };

   new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "76 40";
      extent = "35 18";
      minExtent = "8 2";
      visible = "1";
      text = "Restitution";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiRestSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 50";
      extent = "204 40";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.set(Restitution,guiRestSlider.value);";
      range = "0.000000 1.000000";
      ticks = "10";
      value = "1";
   };

continued...

About the author

Was a GG Associate and then joined GG in 2005. Lead tool dev for T2D and T3D. In 2011 joined mobile company ngmoco/DeNA and spent about 4 years working game and server tech. 2014 joined startup Merigo Games developing server technology.


#1
03/14/2005 (9:40 am)
new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "76 80";
      extent = "35 18";
      minExtent = "8 2";
      visible = "1";
      text = "Relaxation";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiRelaxSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 90";
      extent = "204 40";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.set(Relaxation,guiRelaxSlider.value);";
      range = "0.000000 1.000000";
      ticks = "10";
      value = "0.5";
   };

   new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "76 120";
      extent = "35 18";
      minExtent = "8 2";
      visible = "1";
      text = "Density";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiDensSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 130";
      extent = "204 40";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.set(Density,guiDensSlider.value);";
      range = "0.000000 1.000000";
      ticks = "10";
      value = "0.01";
   };

   new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "76 160";
      extent = "35 18";
      minExtent = "8 2";
      visible = "1";
      text = "Damping";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiDampSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 170";
      extent = "204 40";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.set(Damping,guiDampSlider.value);";
      range = "0.000000 5.000000";
      ticks = "10";
      value = "0";
   };

   new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "65 200";
      extent = "40 18";
      minExtent = "8 2";
      visible = "1";
      text = "Max Angular Velocity";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiMaxAngularSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 210";
      extent = "204 40";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.set(MaxAngularVelocity,guiMaxAngularSlider.value);";
      range = "0.000000 5000.000000";
      ticks = "10";
      value = "0";
   };

   new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "65 240";
      extent = "40 18";
      minExtent = "8 2";
      visible = "1";
      text = "Max Linear Velocity";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiMaxLinearSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 250";
      extent = "204 40";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.set(MaxLinearVelocity,guiMaxLinearSlider.value);";
      range = "0.000000 2000.000000";
      ticks = "10";
      value = "0";
   };

continued....
#2
03/14/2005 (9:41 am)
new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "76 320";
      extent = "25 28";
      minExtent = "8 2";
      visible = "1";
      text = "Constant Force Speed";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiForceSpeedSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 330";
      extent = "204 41";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.setForce(guiForceDirecSlider.value, guiForceSpeedSlider.value);";
      range = "0.000000 5000.000000";
      ticks = "10";
      value = "0";
   };

   new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "76 360";
      extent = "25 28";
      minExtent = "8 2";
      visible = "1";
      text = "Impulse Direction (Degrees)";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiImpulseDirectionSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 370";
      extent = "204 41";
      minExtent = "8 2";
      visible = "1";
      range = "0.000000 360.000000";
      ticks = "10";
      value = "0";
   };

   new GuiTextCtrl() {
      profile = "GuiPhysicsTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "76 400";
      extent = "25 28";
      minExtent = "8 2";
      visible = "1";
      text = "Impulse Force Speed";
      maxLength = "255";
   };
   new GuiSliderCtrl(guiImpulseForceSlider) {
      profile = "GuiSliderProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 410";
      extent = "204 41";
      minExtent = "8 2";
      visible = "1";
      range = "0.000000 5000.000000";
      ticks = "10";
      value = "0";
   };
   new GuiButtonCtrl() {
      profile = "GuiButtonProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "0 440";
      extent = "80 15";
      minExtent = "8 2";
      visible = "1";
      command = "slidersGUI.setImpulse( guiImpulseDirectionSlider.value, guiImpulseForceSlider.value );";
      text = "Impulse Force!";
      groupNum = "1";
      buttonType = "PushButton";
   };

   new GuiButtonCtrl() {
      profile = "GuiButtonProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "85 440";
      extent = "105 15";
      minExtent = "8 2";
      visible = "1";
      command = "createBlock();";
      text = "Add a Bouncy Block";
      groupNum = "1";
      buttonType = "PushButton";
   };

};
//--- OBJECT WRITE END ---

function slidersGUI::setForce(%this, %direc, %speed)
{
	for(%i=0;%i<$blocks::ammount;%i++)
	{
		eval($blocks::names[%i] @ ".setConstantForcePolar(%direc, %speed);");
		echo("setting force direc:" SPC %direc SPC "and speed:" SPC %speed);
	}
}

function slidersGUI::set(%this, %type, %val)
{
	
	for(%i=0;%i<$blocks::ammount;%i++)
	{
		eval($blocks::names[%i] @ ".set" @ %type @ "(%val);");
		echo("Setting the" SPC %type SPC "to" SPC %val);
	}

}

function slidersGUI::setImpulse(%this, %direc, %force)
{
	for(%i=0;%i<$blocks::ammount;%i++)
	{
		eval($blocks::names[%i] @ ".setImpulseForcePolar(%direc, %force);");
		echo ("setting impulse force for num:" SPC %i SPC "direc = " @ %direc SPC "force = " @ %force);
	}
}

continued ...
#3
03/14/2005 (9:41 am)
Now right click and create another new text file... name it "bounce.cs" (click yes again if needed)... copy the following code into it and save it. (note: the following code was modified from one of Melv's examples)

function pongBounce()
{
   // Setup some immovable walls and allow them to receive collisions only...
   
   // Left.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "-40 0" );
   %sprite.setSize( "2 75" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   // Right.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "40 0" );
   %sprite.setSize( "2 75" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   // Top.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "0 -33" );
   %sprite.setSize( "80 2" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   // Bottom.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "0 33" );
   %sprite.setSize( "80 2" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   
   // Bouncy Material.
   datablock fxCollisionMaterialDatablock2D(bouncyMaterial)
   {
      friction = 0;
      restitution = 1.0;
      relaxation = 0.5;
      density = 0.01;
      forceScale = 1;
      damping = 0;
   };   

   $blocks::ammount = 0;
}

function createBlock()
{

   $blocks::names[$blocks::ammount] = "block" @ $blocks::ammount;
   // Create Bouncy Ball.
   %ball = new fxStaticSprite2D($blocks::names[$blocks::ammount]) { scenegraph = t2dSceneGraph; };
   %ball.setSize( 4 );
   %ball.setImageMap( tileMapImageMap );
   %ball.setCollisionActive( true, true );
   %ball.setCollisionPhysics( true, true );
   //%ball.setLayer( 0 );
   //%ball.setGroup( 0 );
   %ball.setCollisionMasks( BIT(0), BIT(0) );
   %ball.setCollisionMaterial( bouncyMaterial );
   %ball.setMaxAngularVelocity( 0 );
   
   // Give it a random direction.
   %ball.setImpulseForcePolar( getRandom()*360, 700 );

   $blocks::ammount++;
}


ok now we're almost done... go to your client.cs and open it...

after
exec("./mainScreenGui.gui");

add this

exec("./bounce.cs");
exec("./slidersGUI.gui");

then after

Canvas.setCursor(DefaultCursor);

add this

mainScreenGui.add(slidersGUI);

what this does is first it "exec"s the .gui and .cs files, that way the function and gui calls will be made... then it addes the slidersGUI onto the main gui, this way you can use the sliders and still use the main menu.

ok last step

now go down to...
// ************************************************************************
	//
	// Add your custom code here...
	//
	// ************************************************************************

make sure you aren't calling anything else here (for this example)... you can use the "//" to comment out other calls... like
//createPlayer();

put the following there

pongBounce();
#4
03/14/2005 (9:41 am)
Thats it... now fire up your T2D.exe... you should see the sliders on the left (I recommend adding the debug buttons from this resource debug gui as well)

and nothings happening... well to make things more interesting click on the "Add a bouncy block" button, you can add as many as you want!

Remember to apply impulses (especially usefull when things stop - sometimes due to adding damping) you need to set the degree, somwhere from 0 - 360... 0 by default works (will push them up) and then set the force slider after it, at the default of 0 it does nothing when you click the button!

:)
#5
03/14/2005 (10:13 am)
Cool. :)

I would definately recommend changing that forces (constant and impulse) to "polar" and "speed" as I think they might be a little more intuitive as well as more fun to play with. :)

- Melv.
#6
03/14/2005 (10:36 am)
Good point, the polar constant force seems to work much more intuitive, lol still trying to break out of the x and y axis thinking :)


modified the gui in the post
#7
03/14/2005 (10:46 am)
This looks cool to play with to get more of a feel for the physics. I'll have to try it out when I get home.

Just a minor interface thought, but if you make the angle vary from -180 to +180 and default to the center then it implies up and the left/right (rather than wrapping around to the left).
#8
03/14/2005 (10:57 am)
True, personally I think of it as 0 - 360... though I can see how you see -180 to +180...

I am going to create a step by step tutorial on creating everything in here from the objects, setting collision, etc... to the gui and storing data and cycling through that data... mainly some basic stuff :) (then if you don't already know how to modify gui controls you can modify it to your own preference)
#9
03/14/2005 (1:09 pm)
Excellent tutorial and resource. The physics of T2D really puts it in a class of its own and this helps a lot in teaching us how it works and what you can do with it.
#10
03/14/2005 (9:32 pm)
Glad you enjoyed it... figured it was just good for testing out physics...


ok here is Part 1 of the tutorial... its a bit lengthy





Physics Demo Tutorial!

Overview: Ok now that you have seen the basic physics demo, its really just a bunch of blocks that bounce around on the screen, albeit that you can control some interesting physics factors real-time. All in all the nifty part about it is that you can do that real-time through the magnificent Torque gui interface! So how do you do it... if you know this already then you probably won't have much use for this tutorial (other than setting some basic physics values lol)...

before we begin: I highly recommend copying your entire Torque 2D folder and working from the copy (so you don't change anything in your main folder).

so we start... first lets create the objects we need in game before we make an interface to manipulate them...


---------------------------------------------------------
Step 1: The Walls
---------------------------------------------------------

Creating the walls (this was used from Melv's example)... well before we want a bouncing box we should probably put some walls in there.

lets create a new file... right click, go to new, and then create a new text document... change the name to "bounce.cs"... now open it for editing

lets start this off in a function... with a basic comment

function pongBounce()
{
   // Setup some immovable walls and allow them to receive collisions only...

First lets create the left wall by setting a local variable called "%sprite" equal to a new static sprite.. like this
// Left.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };

remember you must assign a scenegraph to put the sprite in on creation... in this case its simply the default "t2dSceneGraph" you should be used to from the tutorial..

next lets set some basic values for this wall

%sprite.setPosition( "-40 0" );
   %sprite.setSize( "2 75" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();

ok lets start with "%sprite.setPosition( "-40 0");"

heres a grid of the world.

the red outline is our view of the world through the camera... what I'll call our "visible world"

www.razedskyz.com/games/torque/tutorials/T2D/t2dgrid.jpg
first let me explain how the "world" is set up....

you can see it goes from x location -50 to x location posotive 50

thats 100 x units accross

while it goes roughly -37.5 y location to 37.5 y location

thats 75 y units up and down

this is specified in the client.cs
in this line of code
sceneWindow2D.setCurrentCameraPosition( "0 0 100 75" );

this specifies that the center of the camera (or you could say the the "visible world") is 0,0 ... and it extends a total of 100x and 75y units... placing the outer extents -50 to 50 x... and -37.5 to 37.5 y...

now right off the top you may be asking yourself why this... most 2d is referenced by pixel, with 0,0 (the origin) being the top left, or a similar system... well this gives you a huge ammount of flexibility... rather than simply going by pixel, T2D uses a local location system that you define... one advantage straight off the top is if you change resolutions... say you were at 640 x 480... and you designed it using pixel locations... what if you changed to 800 x 600... exactly, either the game goes screwy or the engine tries to guesstimate... I dont know about you but I prefer not to have guesstimation in my game lol... T2D gives you the reigns, it does the calculations... this also makes scaling very independent and powerful...
#11
03/14/2005 (9:33 pm)
Ok moving on...

so we have
%sprite.setPosition( "-40 0");

that means the center of this wall will be near the left border of the "visible world"... good start


next we have

%sprite.setSize( "2 75");

... you may have guessed it... 2 units wide along the x axis, while 75 units along the y... this should fill the screen (considering we already went over the visible world size :) )

next is
%sprite.setImageMap( tileMapImageMap );

this simply sets the sprites image to the datablock (already defined) "tileMapImageMap"... now you may notice from working with T2D already, if not then I'll let you know, tileMapImageMap is a multi-frame imagemap... it is basically one image file divided into 4 areas that match a certain width and height specification so they can be accessed by T2D as four seperat images... quite nifty and efficient (been used in 2D Games for a long time)... by default if it is a multi-frame sprite and you don't specify which frame on the "setImageMap()" command, it defaults to 0... if we wanted to use the second frame we would do
%sprite.setImageMap( tileMapImageMap, 1);
(note: this can all be read in the wonderful reference documentation included!) :)

ok next line...

%sprite.setCollisionActive( false, true );

ok this sets the collision for the wall... first parameter is send, the second is receive... as you can see it will not send collisions, though it will recieve them... this can be a bit tough to understand at first... this wont be "sending" its collision information... its a wall, we want it to remain static... however, it will "receive" collision calls from other objects, this way the block can then "bounce" off it...

this leads us to the next setting

%sprite.setImmovable();
by setting it Immovable we ensure its a sturdy wall... you know the old saying about having a good foundation... ok well not much to do with this; however, making this immovable ensures proper physics will be applied.


so we have our left wall! here is the code for the rest of the walls... I won't explain each one considering they use the same properties, just different positions to surround the screen.... altogether you should have

// Setup some immovable walls and allow them to receive collisions only...
   
   // Left.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "-40 0" );
   %sprite.setSize( "2 75" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();

   // Right.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "40 0" );
   %sprite.setSize( "2 75" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   // Top.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "0 -33" );
   %sprite.setSize( "80 2" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   // Bottom.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "0 33" );
   %sprite.setSize( "80 2" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();

again this will simply set some immovable walls around the screen that will receive collision from other objects. (as Melv's comment specifies).
#12
03/14/2005 (9:34 pm)
---------------------------------------------------------
Step 2: The Bouncy Block
---------------------------------------------------------

Don't ask why the block bounces, it doesn't have much of a motivation except for our ammusement (and using images everyone already has)... Ok lets set up the bouncy material

// Bouncy Material.
   datablock fxCollisionMaterialDatablock2D(bouncyMaterial)
   {
      friction = 0;
      restitution = 1.0;
      relaxation = 0.5;
      density = 0.01;
      forceScale = 1;
      damping = 0;
   };

ok... so first we put this line of code

datablock fxCollisionMaterialDatablock2D(bouncyMaterial)

if you arent used to datablocks then its a good idea to become so, many things in any of the Torque engines use datablocks... the best way to describe a datablock and its use is to lead you down the logical path of them...

lets say you want to make a bunch of bouncy blocks...

say you create them... then each time you set its parameters to be bouncy... now not only is this tedious, but it is repitious... repition often is a sign of optimization in code... so instead of setting all of the bouncy blocks collision paramaters individually, we create a data set called "datablocks"... now think of these as static configurations for commonly used data... in this case every bouncy block needs to start with the same collision settings, so we create a Material datablock that holds these settings and then simply assign that datablock to the boxes on creation... this also allows us to change the values of the datablocks (before run time) to change how many objects start out... quite efficient... think of them as static data that is commonly used...

so we create an "fxCollisionMaterialDatablock2D" datablock named "bouncyMaterial"...

then we set numerous settings for the material

friction = 0;
restitution = 1.0;
relaxation = 0.5;
density = 0.01;
forceScale = 1;
damping = 0;

these are the physics settings we want to make the blocks bouncy... now all the blocks that we assign this "bouncyMaterial" to will start out with the same settings! Saves much time as I'm sure you can imagine...

don't forget the ending
};

it is easy to forget the ; at the end... but keep in mind functions use { and }
and functions cannot be in another functions... so other structures use an ending }; instead... (just a way to think of things to keep an eye out for renegade }'s and ;'s
they can be sneaky little buggers

ok the material is set, though I added another line of code

$blocks::ammount = 0;

this sets the ammount variable in the global variable $blocks namespace to 0... this is simply an initiation setting in preperation for storing all the blocks as we create them dynamically in the engine... to be able to change the settings for all of them in real time we need to create some storage, this is one of multiple ways to do it...


now we're off to a new funciton so end this with
}
#13
03/14/2005 (9:34 pm)
Right now you should have

function pongBounce()
{
   // Setup some immovable walls and allow them to receive collisions only...
   
   // Left.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "-40 0" );
   %sprite.setSize( "2 75" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   // Right.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "40 0" );
   %sprite.setSize( "2 75" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   // Top.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "0 -33" );
   %sprite.setSize( "80 2" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   // Bottom.
   %sprite = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
   %sprite.setPosition( "0 33" );
   %sprite.setSize( "80 2" );
   %sprite.setImageMap( tileMapImageMap );
   %sprite.setCollisionActive( false, true );
   %sprite.setImmovable();
   
   // Bouncy Material.
   datablock fxCollisionMaterialDatablock2D(bouncyMaterial)
   {
      friction = 0;
      restitution = 1.0;
      relaxation = 0.5;
      density = 0.01;
      forceScale = 1;
      damping = 0;
   };   

   $blocks::ammount = 0;
}
#14
03/14/2005 (9:35 pm)
Now the new function... lets start it

function createBlock()
{

a simply function start... now lets add some meat
$blocks::names[$blocks::ammount] = "block" @ $blocks::ammount;

ok now this line can look scary to those that are new... let me break this down...

this takes the $blocks global variable namespace (as described previously) and sets a new variable for it... names... this variable is also an array...

------
for those that don't know code too well or are refreshing an array is a way to store multitudes of data under one variable... for example
data[0] = 1;
data[1] = 2;
data[2] = 3;
etc...
this allows a numerical index to easily loop through data, in this case this is invaluable considering we will have to loop through the bouncy blocks to change their settings
------

ok... so we have
$blocks::names[$blocks::ammount]

now when this runs the first time "ammount" is set to 0... (we set that at the end of the last function)... so basically its the same as saying

$blocks::names[0] =

(later we incriment this so each time we all this function it sets a new numerical index of the names array)...

now we set this equal to
"block" @ $blocks::ammount;

this is nothing to be scared by (if your new to Torquescript)...

basically we'ere assembling a string here, we start off with "block"... the @ sign means we are then adding additional data onto the string... we already know $blocks::ammount equals 0 (this time through)... so basically we are making this string
"block0"... the next time we run through this it should add a number and name the next block
"block1"...

so
$blocks::names[$blocks::ammount] = "block" @ $blocks::ammount;
really equals (the first time through)
$blocks::names[0] = "block0";

that should be much more readable... this is just a simple way to accomplish adding dynamic values that can later be referenced... we are creating alist of names that can later be cycled through to set the bouncy block's values... this brings us to the question that arises next... so if we set the array list to equal these names, how then can we set the bouncy blocks to equal the same name... that brings us to our next line

// Create Bouncy Ball.
%ball = new fxStaticSprite2D($blocks::names[$blocks::ammount]) { scenegraph = t2dSceneGraph; };

here we set a local variable "%ball"as a new staticSprite2D... similar to what we've already done.

what is different is instead of using empty ()'s we have included something... we put the value of
$blocks::names[$blocks::ammount] into the ()'s...
from what we already know $blocks::ammount equals 0...
so its saying $blocks::names[0]... and we just set that equal to "block0"

you may have guessed it, on creation if you pass a name into the ()'s during the creation of an object, it sets that object's name to that... so we are crating a new sprite and setting its name to "block0"...
later we can reference the object by using
block0.setDensity(0);...
etc... this makes applying dynamic physics settings much easier.


then we set some basic paramaters (some you should already know)

%ball.setSize( 4 );
   %ball.setImageMap( tileMapImageMap );
   %ball.setCollisionActive( true, true );
   %ball.setCollisionPhysics( true, true );
   %ball.setCollisionMasks( BIT(0), BIT(0) );
   %ball.setCollisionMaterial( bouncyMaterial );
   %ball.setMaxAngularVelocity( 0 );
   
   // Give it a random direction.
   %ball.setImpulseForcePolar( getRandom()*360, 700 );

   $blocks::ammount++;
#15
03/14/2005 (9:35 pm)
Ok, setImageMap you already know...
setCollisionActive you already know... this time we are telling it to send and recieve collision data...

next we have
%ball.setCollisionPhysics( true, true );

this is set up the same as setCollisionActive... you are telling it to send physics data and receive physics data

then we have
%ball.setCollisionMasks( BIT(0), BIT(0) );

this tells the bouncy block that it will collide with objects in group 0 and on layer 0 (which the walls defaulted to, so this is basically saying the bouncy block will collide with the walls)

then
%ball.setMaxAngularVelocity( 0 );

this simply tells the engine that the maximum rotation velocity of the blocks is 0... so it stops them from rotating

then there is

// Give it a random direction.
%ball.setImpulseForcePolar( getRandom()*360, 700 );

this (as the comment says) gives the ball a random direction... and applies "700" force to it in that direction... resulting in it shooting off in a random direction...

getRandom returns a number between 0 and 1... so to get the desired range, simply multiply it by the max number you want and you get a number between 0 and that number...

lastly we have

$blocks::ammount++;

this is that increment I mentioned earlier, this prepared this function for the next block... resulting in the next block being named block1 rather than block0... then block2 after that, etc etc... creating unique references for them to be easily cycled through later.

end the function with a
}

and you should have this as the function

function createBlock()
{

   $blocks::names[$blocks::ammount] = "block" @ $blocks::ammount;
   // Create Bouncy Ball.
   %ball = new fxStaticSprite2D($blocks::names[$blocks::ammount]) { scenegraph = t2dSceneGraph; };
   %ball.setSize( 4 );
   %ball.setImageMap( tileMapImageMap );
   %ball.setCollisionActive( true, true );
   %ball.setCollisionPhysics( true, true );
   //%ball.setLayer( 0 );
   //%ball.setGroup( 0 );
   %ball.setCollisionMasks( BIT(0), BIT(0) );
   %ball.setCollisionMaterial( bouncyMaterial );
   %ball.setMaxAngularVelocity( 0 );
   
   // Give it a random direction.
   %ball.setImpulseForcePolar( getRandom()*360, 700 );

   $blocks::ammount++;
}




ok, now we got the objects created... not really a lot of work though took a bit to explain it all, hopefully you can understand this proccess a bit better... the next part I will go over is the gui interface and the function calls to make to change the setting real-time
#16
03/15/2005 (1:57 am)
Very cool. :)

I didn't put a clamp on certain things as it does allow you to do some advanced stuff but you do have to be very careful not to get into crazy physical situations here.

One thing I would definately recommend is to not go much higher than 1 for the friction. Friction at 1 is so staggeringly high already and most surfaces wouldn't get anywhere near that but to allow 5 is so bad you wouldn't believe. A limit of 0.5 is more than enough for 99% of surfaces. Think of that on an almost exponential scale and you'd get the idea of how sensitive it is. It's one of the properties that when set too high, can cause tremendous rebounds which cause pretty inaccurate velocities causing the swept-collision checks to fail. You will often see objects whizzing off to NAN land. ;)

It would be nice to have an additional slider for the maximum linear/angular velocity as well. Seeing the rotations working is so nice, especially as it took so long to get it working correctly. ;)

Very neat app. :)

- Melv.
#17
03/15/2005 (8:19 am)
Ahh, thx for the info on Friction, in fact last night I was finally figuring out that the friction was causing that to happen...

lol in fact when I was writing the section explaining max angular velocity I was thinking the same exact thing, that it would be very cool to set a slider for that... I'm glad you got it working, it is a very cool feature lol, I'm having way too much fun bouncing box's around =/

*updated the original to now include a slider for max angular/linear velocity*