Torque 2D/GenreTutorials/PlatformerPhysics

From TDN

(Redirected from T2DScript/PlatformerPhysics)

Contents

Introduction


This tutorial will cover the setup of physics and collision in TGB for a platformer. Specifically, we will create a character that falls due to gravity and static platforms for the character to land on. First, though, we need to get TGB set up for a new game.


Create a new Project


Open up the Level builder and create a new project (File > New Project...).

Image:TGB_BasicTutorial1_3.JPG
Figure 1.1

Call it whatever you wish, the default for this tutorial will be Platformer. We're going to want some basic art to work with, so copy from your games/T2D/data/images/ folder the (image TBD) and tileMap.png into your new project's data/images folder. Hopefully you remember how to import images into the level builder, if not it is explained in detail in the TGB Basic Tutorial.


Physics and Collision in the Context of a Platformer


The physics requirements of a 2D platformer are not very complex and often times not specifically realistic. In many cases, if we tried to apply real world physics, gameplay would suffer. For instance, you will most likely want your character to be able to jump well over its own height. And, applying a strict 9.81 meter/second^2 falling acceleration may not ‘feel’ as good as a speed twice that or half that. Many liberties can be taken, and should be taken, if it improves the gameplay. Usually, experimentation is necessary to get the exact feel you are looking for.

What we will require is a main character that is bound by gravity, able to walk along a surface, jump, and collide with the ground, walls, and obstacles. The ground and walls just need to sit and look pretty.

Thanks to TGB and the Level Builder, it is pretty much just as simple to implement these few rules as it was to lay them out. All of the complicated stuff is handled internally by the engine. We just tell it what to do.


Script Setup


One of the most important practices is to start your projects with a good organization plan and maintain that throughout. Files can easily become unwieldy if you just start adding code randomly. Even though this tutorial only has around 25 lines of code, subsequent tutorials will largely expand that, so we need to prepare. Create two new text documents (in the Platformer/gameScripts directory) and name them 'player.cs' and 'gameData.cs'. We will add code to these shortly, but first, we must tell the engine about them. Open up game.cs in your text editor of choice and change it from this:

function startGame(%level)
{
   // Set The GUI.
   Canvas.setContent(mainScreenGui);
   Canvas.setCursor(DefaultCursor);
   
   moveMap.push();
   
   if( isFile( %level ) || isFile( %level @ ".dso"))
      sceneWindow2D.loadLevel(%level);
}

Change it to the following below by adding the 2 exec statements to our new script files:

function startGame(%level)
{
   // Load our custom scripts here
   exec("./gameData.cs");
   exec("./player.cs");
   
   // Set The GUI.
   Canvas.setContent(mainScreenGui);
   Canvas.setCursor(DefaultCursor);
   
   moveMap.push();
   
   if( isFile( %level ) || isFile( %level @ ".dso"))
      sceneWindow2D.loadLevel(%level);
}

The exec function tells the engine to load and execute a script file. Any time you add a new file, you will need to add a line right after the two you just added to exec it.






The Player


Open up the Level Builder again.

The following is a continuation of the old tutorial. This will be ripped apart and redone in the coming days... Before we define the createPlayer function, we need to set up a couple of global variables that the player will use. Open gameData.cs and add these 2 sets of variables:

$platformGroup = 0;
$playerGroup = 1;

$platformLayer = 5;
$playerLayer = 2;

$platformGroup and $playerGroup define the collision groups for our platforms and player. The purpose of these will make more sense when we set up collision. $platformLayer and $playerLayer are the layer that the platforms and player will be rendered on respectively. Objects on lower numbered layers will be drawn in front of those on higher numbered layers.

Save gameData.cs and open player.cs. This file will eventually house all of the code that involves the main player. For now, we are just going to define the createPlayer function that we called earlier from game.cs. Type this:

function createPlayer()
{

Here is how we create the actual player object:

   $player = new t2dStaticSprite()
   {
      scenegraph = t2dscene;
   };
   $player.setImageMap(playerImageMap);

This code just creates an instance of a static sprite in the scenegraph, stores it in $player, and sets it to use our playerImageMap. Now, anytime in any script that you need a reference to the player, you can just use $player. Here is the rest of the code to set up the player’s collision and physics:

   $player.setCollisionActive(true, true);
   $player.setCollisionPhysics(true, false);
   $player.setCollisionResponse(CLAMP);
   $player.setCollisionCallback(true);
   $player.setCollisionMaxIterations(2);

   $player.setLayer($playerLayer);
   $player.setGraphGroup($playerGroup);
   $player.setCollisionMasks(BIT($platformGroup), BIT($platformLayer));
   
   $player.setCollisionPolyPrimitive(8);

   $player.setMaxLinearVelocity(250);



The call to setCollisionActive sets the player to both send and receive collisions. setCollisionPhysics tells the engine to use physics when this object is causing collisions, but not when it is receiving them. setCollisionResponse is the real demonstration of the power of T2D’s collision system. Setting this to ‘CLAMP’ causes the player to never be allowed to penetrate the object it is colliding with, but it is allowed to slide along its surface. Sounds exactly like what we want. Actually, it’s not perfect. We will have to add a small amount of code when we get to the movement tutorial.

The next line tells the engine to notify the scripting engine when a collision involving the player occurs by calling the “onCollision” callback function. We need to be notified of the collisions so we can make the aforementioned tweaks.

You can ignore the next line. It is telling the engine to iterate the collision responses twice per frame. I don’t think this should be necessary, but it is. If it’s not there, the engine will not use all of the frame’s elapsed time in determining the new position of the player. As a result, objects will occasionally move extremely slowly across surfaces. This is not important to understand, just make sure you include it in your code.

T2D has 32 layers labeled 0 through 31. The purpose of the layers is to determine the draw order of objects. An object placed on layer 1 will be drawn in front of an object on layer 2. We are placing the player on the $playerLayer, which we set to 2.

The call to setGraphGroup places the player in the $playerGroup that we defined in game.cs. Seperating objects into collision groups gives us the power to tell the engine which objects we want to be able to collide with each other. We do this with setCollisionMasks. The first parameter is the group mask. It says we want the player to be able to collide with objects in the platform group. The second parameter is the layer mask. It says we want the player to be able to collide with objects on the platform layer. The bit function used in both parameters converts a number into its equivalent mask.

For a more detailed description of any of the above function calls, you can check out the T2D reference documentation that ships with the engine.

Finally, we need to close the function. Add a curly brace at the end of the file:

}

Now we need to create a spawn function for the player that positions the player and sets up level dependent variables. Here it is:

function resetPlayer(%pos)
{
   $player.setPosition(%pos);
   $player.setConstantForce(0 SPC $gravity, true);
}

This function takes one parameter, the position of the player. Now we can call this function when a level is loaded to position the player where the level wants it. We also set up gravity here with setConstantForce so each level can have different amounts of gravity.






The Platforms


Eventually we will be using tile maps for our levels, but for now we are going to set up a platform as an image map so you can see the collision in action. First, the image map. Add this after the player’s image map datablock in datablocks.cs:

new t2dImageMapDatablock(platformImageMap)
{
   imageMode = "full";
   imageName = "~/data/images/tileMap";
};

Open up gameData.cs again and add this function after the variables:

function createLevel()
{
   $gravity = 700;
   $spawnPoint = "-20 -30";
   resetPlayer($spawnPoint);
   
   %platform = new t2dStaticSprite() {
      scenegraph = t2dscene;
   };
   %platform.setImageMap(platformImageMap);
   
   %platform.setCollisionActive(false, true);
   %platform.setGraphGroup($platformGroup);
   %platform.setLayer($platformLayer);
   
   %platform.setSize("40 2");
   %platform.setPosition("-20 30");
}

The first two lines set the level variables gravity and spawnPoint. The next line sets the position of the player by calling the previously defined resetPlayer function with spawnPoint as the parameter.

The next two lines should be familiar. Create the object and set its image map.

You should recognize setCollisionActive from the player set up. This time, though, we are telling the object to only receive collisions. And, we set its group as $platformGroup and layer as $platformLayer.

The last two lines position the platform at the bottom of the screen and make it long and skinny.

If you run the game now, you should see the GarageGames logo falling and landing on the platform. Kind of boring so far. Here is a chunk of code that will add more platforms, allowing you to see the full scope of the 'CLAMP' method of collision response. Add it to the end of the createLevel function:

   %platform = new t2dStaticSprite() { scenegraph = t2dscene; };
   %platform.setImageMap(platformImageMap);
   
   %platform.setCollisionActive(false, true);
   %platform.setGraphGroup($platformGroup);
   %platform.setLayer($platformLayer);
   
   %platform.setSize("40 2");
   %platform.setPosition("20 10");
   %platform.setRotation(-30);
   
   
   %platform = new t2dStaticSprite() { scenegraph = t2dscene; };
   %platform.setImageMap(platformImageMap);
   
   %platform.setCollisionActive(false, true);
   %platform.setGraphGroup($platformGroup);
   %platform.setLayer($platformLayer);
   
   %platform.setSize("40 2");
   %platform.setPosition("-10 -15");
   %platform.setRotation(30);
   
   
   %platform = new t2dStaticSprite() { scenegraph = t2dscene; };
   %platform.setImageMap(platformImageMap);
   
   %platform.setCollisionActive(false, true);
   %platform.setGraphGroup($platformGroup);
   %platform.setLayer($platformLayer);
   
   %platform.setSize("10 2");
   %platform.setPosition("-39 25");
   %platform.setRotation(90);

Go ahead and play around with creating your own platforms and tweaking some of the variables in the code (like $gravity for instance). The best way to learn is to experiment and see how the changes you make affect the program.

If you are new to programming, the above code could be potentially confusing. If this is the case, you should spend some time figuring out what each piece of code does and make sure you understand everything that is going on. The topics will only be getting more complex from here on out, and everything will be building on the foundation you just created, so it is important that you understand the concepts we just covered.

If something isn't working right, run back over the tutorial to make sure you followed the steps correctly. If everything is in order and you think you're ready to move on, then let's go.