Game Development Community

But the GDC demo made it look so easy...

by Chris Vallance · in Torque Game Builder · 03/28/2006 (10:30 pm) · 20 replies

Okay, this whole programming thing is, um, HARD. I like to think of myself as a reasonably smart guy, but the last 5 days have forced me to re-evaluate my position. Granted I haven't written a program since the ol' Apple //e was put out to pasture but , MAN!

At this point, all I wanna do is move a sprite around the screen with keyboard inputs. I've tried different tutorials, pulled apart the demo code a dozen different ways, and my game so far consists of hitting the WASD keys and wondering if the computer even knows about it. Is there some way to flag a sprite in the level editor as being player-controlled?

I'm also a little confused about the connection (if any) between the level editor and the GUI editor. It looks like the spacescroller is all GUI, so I don't even know if the code would be completely different for a similar game which used a tile map instead. Basically, my goal is to make a similar game that is more like Defender, with a composed map and some freedom of movement.

Please forgive my complete lack of programming savvy. Any help would be greatly appreciated.

#1
03/29/2006 (1:02 am)
When I first learned TorqueScript, the basic tutorial was a big help for me in learning how to bind keys. I think it can explain the basics better than I can, but I can at least show you how to get it working under beta 2.

The code I posted below is a rather modified game.cs file that I used in another thread. I have commented out the default moveMap and loadlevel calls and instead I am telling TGB to use the setupT2DScene function. Inside this function I setup the scene, create a player ship (remember to copy the playership.png file from the spacescroller image folder to the T2D one), and setup the keyboard bindings for moving upwards. The rest of the binding functions are shown in the basic tutorial.

Since you are interested in getting the player to move, look at this line:

$player = new t2dStaticSprite() { scenegraph = t2dScene; };
A global variable $player is used to associate it with the newly created sprite.

playerMap.bindCmd(keyboard, "w", "playerUp();", "playerUpStop();");
Here we tell the engine what keyboard inputs to bind, for this line it's the w key. When the w key is pressed down, the engine calls the playerUp() function. When the key is released, it calls the playerUpStop() function.

function playerUp()
{
   // Set the player moving up.
   $player.setLinearVelocityY( -10 );
}
Here the global variable $player is used because that's what the sprite is associated with. So it's able to tell the correct sprite to move upwards -10 world units per second.

Here is the complete code, as mentioned above, to get this to work: Setup game.cs to look like this and save it. Run T2D.exe, and go to Run Game. It will ask you to save the level probably, save it with whatever name you want to your levels folder. This saved level will be ignored because we commented out the loadlevel function. Now you should see the player ship sprite on the screen and can move it upwards when you launch from Run Game.

//---------------------------------------------------------------------------------------------
// startGame
// All game logic should be set up here. This will be called by the level builder when you
// select "Run Game" or by the startup process of your game to load the first level.
//---------------------------------------------------------------------------------------------
function startGame(%level)
{
   // Set The GUI.
   Canvas.setContent(mainScreenGui);
   Canvas.setCursor(DefaultCursor);
   
   //moveMap.push();
   //sceneWindow2D.loadLevel(%level);
   setupT2DScene();
}

//---------------------------------------------------------------------------------------------
// endGame
// Game cleanup should be done here.
//---------------------------------------------------------------------------------------------
function endGame()
{
   sceneWindow2D.endLevel();
   moveMap.pop();
}

function setupT2DScene()
{
   // Create our scenegraph
   new t2dSceneGraph(t2dScene);
   // Associate Scenegraph with Window.
   sceneWindow2D.setSceneGraph( t2dScene );
   // Add custom game code here...
   datablock t2dImageMapDatablock(playershipImageMap)
   {
      imageMode = full;
      imageName = "~/data/images/playerShip";
   };

   // Create the player sprite
   $player = new t2dStaticSprite() { scenegraph = t2dScene; };
   $player.setPosition("-35 0");
   $player.setSize( "14 7" );
   $player.setImageMap( playershipImageMap );
   
   // Create a new action map.
   new ActionMap(playerMap);
   // Bind keys to actions.
   playerMap.bindCmd(keyboard, "w", "playerUp();", "playerUpStop();");
   playerMap.bindCmd(keyboard, "s", "playerDown();", "playerDownStop();");
   playerMap.bindCmd(keyboard, "a", "playerLeft();", "playerLeftStop();");
   playerMap.bindCmd(keyboard, "d", "playerRight();", "playerRightStop();");
   playerMap.bindCmd(keyboard, "space", "playerFire();", "");
   // Activate the action map.
   playerMap.push();
}

function playerUp()
{
   // Set the player moving up.
   $player.setLinearVelocityY( -10 );
}

function playerUpStop()
{
   // If we're moving up then nullify any upward movement.
   if ( $player.getLinearVelocityY() < 0 )
      $player.setLinearVelocityY( 0 );
}
#2
03/29/2006 (6:56 am)
It occured to me that my post above only helps if you want to ignore the current level editor, which is helpful for getting the existing "out of date" tutorials working, but doesn't help you get to know the level editor better.

Here is what you can do to get a sprite created in the level editor to move upwards.

1. Start with a fresh copy of TGB or simply go to File->New Level

2. Click and drag the GG logo (the very first static sprite shown in the list) into the middle of the scene view.

3. Go to the edit tab. It should be showing ggLogoImageMap as the name of the image map. Under the Edit tab you should see section names like Static Sprite, Scene Object, Scripting, Collision, etc. Open up the Scripting section.

4. In the Name field, type in the name Player and hit enter.

5. Go to File->Save As... and save the level. Feel free to call it whatever you want. I went with test.

6. Quit out of TGB.

7. Using Notepad, Torsion, or whatever IDE you prefer, open up in the Games/T2D folder main.cs. At the bottom of this script find setupKeybinds and copy the moveMap.bindCmd lines in:

//---------------------------------------------------------------------------------------------
// setupKeybinds
// Bind keys to actions here..
//---------------------------------------------------------------------------------------------
function setupKeybinds()
{
   new ActionMap(moveMap);
   //moveMap.bind("keyboard", "a", "doAction", "Action Description");

   // Bind keys to actions.
   moveMap.bindCmd(keyboard, "w", "playerUp();", "playerUpStop();");
   moveMap.bindCmd(keyboard, "s", "playerDown();", "playerDownStop();");
   moveMap.bindCmd(keyboard, "a", "playerLeft();", "playerLeftStop();");
   moveMap.bindCmd(keyboard, "d", "playerRight();", "playerRightStop();");
}
Save and close main.cs.

8. Now open up game.cs found in the folder Games/T2D/gameScripts. Add the player movement functions in after the endGame function. I've copied the entire game.cs file below.

//---------------------------------------------------------------------------------------------
// startGame
// All game logic should be set up here. This will be called by the level builder when you
// select "Run Game" or by the startup process of your game to load the first level.
//---------------------------------------------------------------------------------------------
function startGame(%level)
{
   // Set The GUI.
   Canvas.setContent(mainScreenGui);
   Canvas.setCursor(DefaultCursor);
   
   moveMap.push();
   sceneWindow2D.loadLevel(%level);
}

//---------------------------------------------------------------------------------------------
// endGame
// Game cleanup should be done here.
//---------------------------------------------------------------------------------------------
function endGame()
{
   sceneWindow2D.endLevel();
   moveMap.pop();
}

//---------------------------------------------------------------------------------------------
// Player Movement Functions
//---------------------------------------------------------------------------------------------
function playerUp()
{
   // Set the player moving up.
   Player.setLinearVelocityY( -10 );
}

function playerUpStop()
{
   // If we're moving up then nullify any upward movement.
   if ( Player.getLinearVelocityY() < 0 )
      Player.setLinearVelocityY( 0 );
}
Notice how Player is used here, that lets the engine know what sprite to move. It is also a bit different from the setup I had in the previous post where I used a variable to associate movement to a sprite. Hopefully, it is not too confusing.

Save and close game.cs.

9. Run TGB again. Your sprite with the GG Logo should still be there, if not you will have to load the level again (File->Load...).

10. Select Launch->Run Game. You should see the GG Logo on a black T2D background. Try pressing the w key, the logo should move upwards.

Copying the from the basic tutorial the functions PlayerDown, etc, for the a, s, and d keys into game.cs will allow for complete movement in all directions. Hope this helps.
#3
03/29/2006 (8:20 am)
Mike have you had any luck with the player being able to fire. I came accross your thread today. I wish would seem this before I started on movement. It took while. I have been play around with the code but no luck as of yet.
#4
03/29/2006 (8:48 am)
This is the way we basically are doing it in Mighty Fist...

in script you can create a script class like this now

new ScriptClass(Player);

then you can create this "onAdd" function

function Player::onAdd(%this)
{
   $Player = %this;
}

Now if you specify "Player" in an object's class or superclass in the Level Builder it will link its namespace to this script class and call the onAdd when its created... now all you have to do is reference $player in your scripts just like the basic tutorial :)

So when you want to test movement you just hit that play button and try it.
#5
03/29/2006 (9:05 am)
Michael,

To get a missle to fire:

1. Copy the playership.png and playermissile.png over from the spacescroller folder to the T2D image folder.

2. Run TGB. Start a new level. On the create tab, create a new Image Map for the playership, and a new Image Map for the playermissle. I left the names as they were given by TGB.

3. Drag the playerShip into the scene view. Go to the edit tab - Scripting section and type Player in the Name field then hit enter.

4. Important!!: On the project tab, in the object tree, click on t2dSceneGraph. Go to the edit tab - Scripting section and in the name field type in t2dScene and hit enter. Save the level with whatever name you want.

5. In Games/T2D, main.cs setupKeybinds function should look like this:
//---------------------------------------------------------------------------------------------
// setupKeybinds
// Bind keys to actions here..
//---------------------------------------------------------------------------------------------
function setupKeybinds()
{
   new ActionMap(moveMap);
   //moveMap.bind("keyboard", "a", "doAction", "Action Description");
   
   // Bind keys to actions.
   moveMap.bindCmd(keyboard, "w", "playerUp();", "playerUpStop();");
   moveMap.bindCmd(keyboard, "s", "playerDown();", "playerDownStop();");
   moveMap.bindCmd(keyboard, "a", "playerLeft();", "playerLeftStop();");
   moveMap.bindCmd(keyboard, "d", "playerRight();", "playerRightStop();");
   moveMap.bindCmd(keyboard, "space", "playerFire();", "");
}

6. In Games/T2D/gameScripts/game.cs, add this function:
function playerFire()
{
   // Create player projectile.
   %projectile = new t2dStaticSprite() { scenegraph = t2dScene; };
   %projectile.setPosition( Player.getPosition() );
   %projectile.setSize( "3 1.5" );
   %projectile.setImageMap( playerMissile );
   %projectile.setWorldLimit( KILL, "-52 -40 52 40" );
   %projectile.setLinearVelocityX( 35 );
}

This should get a missle to fire from the ship when the space bar is pressed.
#6
03/29/2006 (3:47 pm)
Worked like a charm. And thanks for the clear explanation, it was a big help.
#7
03/29/2006 (6:44 pm)
This helps me! Thanks Mike, Mat, and Chris for asking this question.
#8
03/30/2006 (6:37 am)
Follow-up question:

At this point, the player movement we've defined is very direct. I've been experimenting with the physics commands to introduce thrust and inertia, but I have yet to hit on the right solution.

I've tried using the setImpulseForce command, and it works, as long as I tap the direction key repeatedly. I thought of repeating this command four or five times for each movement function, with pauses in between to simulate acceleration, but there are two problems:

1) I can't figure out how to insert a timed pause between commands

and

2) There must be a more elegant way to do this.

Any ideas?
#9
03/30/2006 (7:14 am)
Use the "schedule" function to have periods. For example if you wanted the player to increase speed every 1/4 second while the key is pressed you would use this (this moves them left):
moveMap.bindCmd(keyboard, "a", "leftPressed();","leftReleased();");

function leftPressed()
{
   $leftPressed = 1;
   movePlayerLeft();
}

function movePlayerLeft()
{
   if ($leftPressed)
   {
      $player.setImpulseForce("5 0");
      schedule(250,0,"movePlayerLeft");
   }
}

function leftReleased()
{
   $leftPressed = 0;
}

And for 2) as far as I'm aware there isn't a more elegant way to do this, but I may be wrong :)
#10
03/30/2006 (7:46 am)
Thank Mike I had one word wrong. I using playerMap the it is in the tutorial.
#11
03/30/2006 (8:15 am)
Tom, I try your code I cant seem to get to work. Mike and Chris is help us with the TGB_LevelBuilder_Beta2
#12
03/31/2006 (11:52 am)
Mike you have been great help. I manage get collision working on the player missile but I cant get the particle effect to work and the enemy to fire back at me. Have you had any luck on this.
#13
04/15/2006 (8:57 am)
@Matthew: That new class stuff looks interesting. Where can I find more information on that? What functions do those classes support and what else can be done with it? The new beta is looking great, btw! :))
#14
04/15/2006 (11:50 am)
You actually define the functions in a ScriptObject, as well as the fields.
new ScriptObject(foo)
{
class = "Face";
};

function Face::bar()
{
echo( "BBQ!" );
}
You can set the class, and the superclass of a script object through the 'class' and 'superclass' fields. You can then write functions for the class in script, as shown above. It's really quite a powerful system. It's not new, though ;)
#15
04/15/2006 (11:59 am)
@Pat: Actually, it's been changed for TGB--all objects have class and superclass, and they are set in the level editor. You do not (any longer) have to declare the objects as ScriptObjects.
#16
04/20/2006 (8:54 pm)
So how exactly do we do this? I'm trying the following and it doesn't work:

new ScriptClass(Invader);

function Invader::Initialize(%obj)
{
%obj.setLinearVelocityX(5);
}

....

function setupT2DScene()
{
...
Invader::Initialize(Enemy0);
Invader::Initialize($Enemy1);
...
}

Where Enemy0 and Enemy1 are objects I've placed using the level editor and named in the Edit Pane "Enemy0" and "Enemy1" with class "Invader". The console tells me that there aren't any Enemy0 or Enemy1 objects to SetLinearVelocityX on.

What I really want is to be able to put these things into an array (hence the names) using the Level Editor so that I can put:

for (%i = 0; %i < 10; %i++) {
Invader::Initialize($Enemy[%i]);
}

into the level load function.

It's possible that what I'm seeing is the failure of the system to load the actual level I'm creating (and in fact I don't see any of the objects I'm trying to play with on the screen, but I am calling sceneWindow2D.loadLevel(%level) in startGame() (which then calls setupT2dScene())....

Ideas? Pointers?
#17
04/21/2006 (8:54 am)
The syntax is a bit off in the setupT2DScene. Use it like this:

function setupT2DScene()
{
...
Invader.Initialize(Enemy0);
Invader.Initialize(Enemy1);
...
}
#18
04/21/2006 (8:32 pm)
So how exactly do we do this? I'm trying the following and it doesn't work:

new ScriptClass(Invader);

function Invader::Initialize(%obj)
{
%obj.setLinearVelocityX(5);
}

....

function setupT2DScene()
{
...
Invader::Initialize(Enemy0);
Invader::Initialize($Enemy1);
...
}

Where Enemy0 and Enemy1 are objects I've placed using the level editor and named in the Edit Pane "Enemy0" and "Enemy1" with class "Invader". The console tells me that there aren't any Enemy0 or Enemy1 objects to SetLinearVelocityX on.

What I really want is to be able to put these things into an array (hence the names) using the Level Editor so that I can put:

for (%i = 0; %i < 10; %i++) {
Invader::Initialize($Enemy[%i]);
}

into the level load function.

It's possible that what I'm seeing is the failure of the system to load the actual level I'm creating (and in fact I don't see any of the objects I'm trying to play with on the screen, but I am calling sceneWindow2D.loadLevel(%level) in startGame() (which then calls setupT2dScene())....

Ideas? Pointers?
#19
04/21/2006 (8:41 pm)
Hmm. Odd timing there.

Thanks for the tip. I've figured out some other things that seem obvious in retrospect.

1) I can use the Object Tree display in the Project Tab to make sure that I do, in fact, have names assigned to objects. My first attempt didn't seem to stick for reasons I don't understand.

2) The correct syntax (at least a syntax which works) is to use neither % nor $ preceding the object name, as indicated in Mike Lilligreen's note, but still using the scope operator:
Invader::Initialize(Enemy0);
	Invader::Initialize(Enemy1);
	Invader::Initialize(Enemy2);

3) Most importantly, is that the %obj passed in to Invader::Initialize is theoretically an object of type Invader. It is not a t2dStaticSprite. So I've had some success with:
%obj.sprite = new t2dStaticSprite() { scenegraph =t2dScene; };
	%obj.sprite.setPosition(%obj.getPosition());
	%obj.sprite.setSize(%obj.getSize());
	%obj.sprite.setImageMap(playershipImageMap);
	%obj.sprite.fireLinkPoint = %obj.sprite.addLinkPoint(0.45, 0.2);
	%obj.sprite.SetLinearVelocityX(5);
which is (obviously) placeholder, because it's using the wrong imagemap and therefore the wrong linkpoint and so not. But the size and position are correctly taken from the item placed in the Level Editor, and that's a major step forward.

Putting a stupid question out into public always makes me think a bit more about it. Thanks for the response.
#20
04/21/2006 (8:42 pm)
Sorry for the duplicate post. I must have unthinkingly hit Reload.