Torque 2D/GenreTutorials/PlatformerMovement
From TDN
Note: Portions of this tutorial are out of date. For a more current platformer tutorial, check out the MiniPlatformerTutorial
|
[edit] IntroductionIn this tutorial, we will be expanding our platformer demo to include a player character with user controlled movement. Specifically, I will cover moving left and right across a surface, jumping, and controlling the player in the air. It is very simple to get basic user control with TGB, but there are several quirks and special cases that we have to take care of to get everything working smoothly. You can exclude some of these things, like ramps, from your level design, but I would rather just tackle the problems and allow levels of any style. So we will. [edit] The Player Sprite
|
|
[edit] The ActionMapTo capture user input, Torque uses the concept of an action map. Basically, you, as the programmer, tell the engine which keys you want to respond to and the engine tells you when the state of those keys is toggled. The default action map called moveMap is located in the main.cs file in your project folder. It does not have any bound keys though. We will define our movement keys in our player's onLevelLoaded function. To do this, first create a new script file called player.cs in your Platformer/gameScripts directory and exec it by adding this "exec("./player.cs");" to the startGame() function in game.cs:
function startGame(%level)
{
exec("./player.cs");
// Set The GUI.
Canvas.setContent(mainScreenGui);
Canvas.setCursor(DefaultCursor);
moveMap.push();
if( isFile( %level ) || isFile( %level @ ".dso"))
sceneWindow2D.loadLevel(%level);
}
Now that the engine knows about our new file, we can write some code. In the player.cs file, create the player::onLevelLoaded function. Here are the binding functions: // Player movement functions moveMap.bindCmd(keyboard, "left", "playerLeft();", "playerLeftStop();"); moveMap.bindCmd(keyboard, "right", "playerRight();", "playerRightStop();"); moveMap.bindCmd(keyboard, "space", "playerJump();", ""); There are a couple of other things we will want to have in the onLevelLoaded function, namely a global variable reference for our player and a way to recgonize the value of gravity in the Level Builder. This way whenever we want to test different gravity values we do not have to change anything in script. Here is the complete onLevelLoaded function:
function player::onLevelLoaded(%this, %scenegraph)
{
// Define the player global variable
$player = %this;
// Get the value of gravity from the Level Builder
%force = $player.getConstantForce();
$gravity = getWord(%force, 1);
// Player movement functions
moveMap.bindCmd(keyboard, "left", "playerLeft();", "playerLeftStop();");
moveMap.bindCmd(keyboard, "right", "playerRight();", "playerRightStop();");
moveMap.bindCmd(keyboard, "space", "playerJump();", "");
}
[edit] Physics and Collision in the Context of a Platformer
[edit] Basic Movement (WIP)
function playerLeft(){
$player.moveLeft = 1;//true
}
function playerLeftStop()
{
$player.moveLeft = 0;//false
}
function playerRight()
{
$player.moveRight = 1;//true
}
function playerRightStop()
{
$player.moveRight = 0;//false
}
function playerJump()
{
//echo("nothing here yet!");
}
With this code, every time the left or right arrow keys are pressed or released, the corresponding variable will be updated. Note that we are using numbers instead of "trues" and "falses". This have its reasons, but for now just accept it ;).
$player.runSpeed = 40; //speed of the player $player.airSpeed = 16; //speed it can move when in the air $player.jumpHeight = 10; //height it can jump $player.maxRunSurfaceAngle = 35; //maximum steepness of a ramp that that the player can run on OBS: aside from maxRunSurfaceAngle, which is an angle in degrees, these don’t have units. You can just mess with the numbers until they feel right.
$platformGroup = 2; //I'm doing this because i don't know how to get a Tile Map GraphGroup :-. $playerGroup = $player.getGraphGroup();
function t2dSceneObject::onCollision(%srcObj, %dstObj, %srcRef,
%dstRef, %time, %normal, %contactCount, %contacts)
{
switch (%srcObj.getGraphGroup())
{
case $playerGroup:
//the source of the collision is the player
switch (%dstObj.getGraphGroup())
{
//the destiny is the plataform
case $platformGroup:
collidePlayerPlatform(%normal);
}
}
}
This is the collision callback function called by the engine every time a collision occurs in the scene. For a description of all the parameters, check the T2D Reference PDF that comes with the engine. The ones we are using are %srcObj, %dstObj, and %normal. %srcObj is the object doing the colliding and %dstObj is the object being collided with. %normal is the vector perpendicular to the surface of collision. Basically, it tells us the angle that the surface is facing.
function collidePlayerPlatform(%normal)
{
%move = $player.moveRight - $player.moveLeft;
$player.setLinearVelocityX(%move * $player.runSpeed);
}
Finally, we moved the player. Run the game now and you should be able to move the GarageGames logo along the surfaces of your tile map. The only thing we have left to do is defining which surfaces the player can walk up, and which surfaces it will slide down. Remember the maxRunSurfaceAngle variable we set up earlier. It’s his time to shine. Add this function to game.cs:
function isRunSurface(%normal, %maxAngle)
{
if (getWord(%normal, 1) > 0)
return 0;
%angle = mRadToDeg(mAsin(getWord(%normal, 0)));
if (mAbs(%angle) < %maxAngle)
return 1;
return 0;
}
The purpose of this function is to determine whether or not a surface, defined by %normal, is not so steep that the player can’t run on it. First we check if the y component of the normal is positive. If it is, the surface is somewhere between a ceiling and a wall, so it is definitely not walkable. The next line finds the steepness angle of the surface using a bit of trig. If that angle is less than the maximum angle, we can walk on the surface, otherwise we can’t.
function collidePlayerPlatform(%normal)
{
%move = $player.moveRight - $player.moveLeft;
$runSurface = isRunSurface(%normal, $player.maxRunSurfaceAngle);
if ($runSurface > 0)
{
$player.setLinearVelocityX(%move * $player.runSpeed);
}
}
Now, we are only updating the player’s velocity if the surface we are on is not too steep. Try it out. If you have both a 30 degree ramp and a 45 degree ramp visible in your tile map, you should find that the player can run up the 30 degree one, but not the 45 degree one. If you can’t see that part of your tile map, you can either change the position of it by changing the line "%layer.setPosition("0 0");" in the createLevel function in game.cs, or you can wait until we hook up the camera in the next tutorial. |
|
[edit] Jumping (WIP)Adding jumping is very simple when we don’t have to deal with animations. Those are later, so for now this is going to be easy. The only thing we have to check for is that the player is on the ground so you can’t jump while in mid air. First, we need to store when the jump button (in our case, the spacebar) is pressed. In actionMap.cs, add this to the jump function: $jump = %val; Now, in player.cs, add this function:
function playerJump()
{
$player.setLinearVelocityY(-10 * $player.jumpHeight);
}
Before we can call this jump function, we need a place to call it from. For this, we need to use the onUpdateScene callback provided by the engine. Add this to game.cs:
function t2dSceneGraph::onUpdateScene(%this)
{
if (%this != t2dscene.getID())
return;
updatePlayer();
$runSurface = -1;
}
First we make sure that the scenegraph that this function was called on (%this) is the scenegraph of our game. We have to do this because the editors all have scenegraphs that call this function, and we don’t care about them. Next we call updatePlayer, which we will define in a second. And finally, we set $runSurface to -1. $runSurface stores whether or not the player is currently on a run surface. Meaning, it is not in mid air. We set $runSurface to 1 or 0 when the player collides with a surface, but there is no callback that tells us when the player stops colliding with a surface. So, we set it to false here, which is called every frame, and rely on the fact that if the player is actually on a run surface, the onCollision callback will update the variable before we need it again.
function updatePlayer()
{
if ($runSurface > 0)
{
if ($jump)
playerJump();
}
}
This should all make pretty good sense. If the player is on a surface, and the jump button was pressed, then jump.
It's worthy to note that when you execute your game at this point and enter the level editor you'll get a warning in the console that says something similar to: T2D/gameScripts/game.cs (55): Unable to find object: 't2dScene' attempting to call function 'getId' This is normal. When you launch the game the warning goes away because you moved from the level builder scene to the game scene. If you plan on spending some time in the level builder you may want to comment out those lines in game.cs so the warnings do not bog down your console - I imagine they can slow down your machine after a while! (remember to uncomment those lines if you run the game again or else you'll find yourself having animation problems later on).
|
|
[edit] Air Control (WIP)Adding air control is just like adding ground control, except we apply the velocity change only when the player is not on the ground instead of only when it is.
%move = $moveRight - $moveLeft; And this to the end of it:
else if ($runSurface < 0)
{
%speed = $player.getLinearVelocityX();
if (%move)
{
if (((%speed * %move) <= 0) || (mAbs(%speed) < $player.airSpeed))
$player.setLinearVelocityX(%move * $player.airSpeed);
}
}
We are first checking to see if we are in the air, then checking if the player is trying to move, and finally checking if the desired direction of movement is different from the player’s current direction. If all of these things are true, then we update the player’s speed. The reason for all of this is we want to maintain the momentum of the player when it first left the ground. Test it, it works. When you execute T2D and look at the console you will receive a warning that says something similar to: T2D/gameScripts/player.cs (63): Unable to find object: attempting to call function 'getLinearVelocityX' Again, this is normal. When you launch the game (and get the player into the scene) the warning will go away. It is also worthy of note that this code will allow you to move onto and land on a ledge connected to a wall you are touching. Without this code you will not be able to jump over a wall if you are touching it - you will have to back up and time your jump accordingly.
function updatePlayer()
{
%move = $moveRight - $moveLeft;
if ($runSurface > 0)
{
if ($jump)
playerJump();
}
else if ($runSurface < 0)
{
%speed = $player.getLinearVelocityX();
if (%move)
{
if (((%speed * %move) <= 0) || (mAbs(%speed) < $player.airSpeed))
$player.setLinearVelocityX(%move * $player.airSpeed);
}
}
}
You should be able to experiment more with the player's movement once we get a camera system in place. |










