Cardinal direction character movement
by James Ford · in Torque Game Builder · 06/02/2006 (2:04 pm) · 18 replies
Im working on an overhead bomberman-style game and want the character to move up down left right, but always remain centered on a tile (not completely free movement). Heres my idea of how to do that but maybe someone knows a more simple way, (it does seem a bit overly-complex).
Here it is in psuedo-code
0. movement key released: $player.ismoving = null
1. movement key is pressed (only one movement key can be pressed at a time because it is a gamepad)
a. $player.ismoving = left/right/up/ or down
b. check adjacent destination tile for any staticsprites (players/objects)
c. check " " tile for collision status (is that tile solid or can it be passed through)
d. if both checks are passed, create a transparent player ghost static sprite in the destination tile (to keep other things from moving there while the player is
e. begin movement to the tile's position
f. when the movement is completed a callbackfunction checks the value of $player.ismoving, if Null, player stops, if not Null, automatically begin the process again
step f is the most important step....it should allow the player to hold down the directional pad and do continuous movement, but if the movement key is released during movement, movement will continue until the destination tile of the LAST movement is reached
Here it is in psuedo-code
0. movement key released: $player.ismoving = null
1. movement key is pressed (only one movement key can be pressed at a time because it is a gamepad)
a. $player.ismoving = left/right/up/ or down
b. check adjacent destination tile for any staticsprites (players/objects)
c. check " " tile for collision status (is that tile solid or can it be passed through)
d. if both checks are passed, create a transparent player ghost static sprite in the destination tile (to keep other things from moving there while the player is
e. begin movement to the tile's position
f. when the movement is completed a callbackfunction checks the value of $player.ismoving, if Null, player stops, if not Null, automatically begin the process again
step f is the most important step....it should allow the player to hold down the directional pad and do continuous movement, but if the movement key is released during movement, movement will continue until the destination tile of the LAST movement is reached
About the author
http://jamesdev.info
#2
06/02/2006 (4:57 pm)
I need the same sort of movement for my 'learning' game (a clone of Adventures of Lolo). If I get that far before I see something I'll post the movement function here, otherwise, I'll keep checking back to see if anybody else has gotten it working.
#3
Heres my code for anyone interested. This is keybinds.cs which is executed early in main.cs, then setupkeybinds is run. The shurukin attack may or may not work, Im just adding that part now. I got directions for seting up and using a joystick/gamepad in t2d from another forum...you can probably find it by searching for "t2d gamepad or joystick support". If you want i can post my main.cs too. Later I want to post this in the TDN as a tutorial just for fun.
note: My tiles that are solid/ you cant walk through them have a customdata value of 1
question 1: Is there anyway to check the collision settings for a tile? It isnt a real object so i cant use sceneobject functions . . . and setactivetile seems to return a tile object so then I could, but what is a tile active class ?? Thats why i just decided to use customdata.
question 2: Is there anyway to pass a value/paramenter to a scheduled function call? I had to do it ghetto style with my little helper functions at the bottom of my code.
06/03/2006 (12:31 pm)
Ok i went ahead and wrote it all last night, and it works =D Man, you wouldnt think character movement would be the hardest part of a game to make, but man, its complicated! In a future game im thinking of changing to the newer style of movement using two thumbsticks (I got xbox controller drivers today), where one changes your direction and the other is movement. With THIS game, I have a short delay, where if you press a direction you are not facing it will first face you and then begin movement shortly, which is like snes style games (and my cheap cpu controller only has one thumbpad).Heres my code for anyone interested. This is keybinds.cs which is executed early in main.cs, then setupkeybinds is run. The shurukin attack may or may not work, Im just adding that part now. I got directions for seting up and using a joystick/gamepad in t2d from another forum...you can probably find it by searching for "t2d gamepad or joystick support". If you want i can post my main.cs too. Later I want to post this in the TDN as a tutorial just for fun.
note: My tiles that are solid/ you cant walk through them have a customdata value of 1
question 1: Is there anyway to check the collision settings for a tile? It isnt a real object so i cant use sceneobject functions . . . and setactivetile seems to return a tile object so then I could, but what is a tile active class ?? Thats why i just decided to use customdata.
question 2: Is there anyway to pass a value/paramenter to a scheduled function call? I had to do it ghetto style with my little helper functions at the bottom of my code.
#4
06/03/2006 (12:33 pm)
function setupKeybinds()
{
$enableDirectInput = "1";
$pref::input::deadzone = 0.5;
$pref::input::sensitivity = 1;
activateDirectInput();
new ActionMap(moveMap);
moveMap.bind(joystick, "xaxis", joymovex); //left stick
moveMap.bind(joystick, "yaxis", joymovey); //left stick
moveMap.push();
}
function joymovex(%val)
{
if ($pref::Input::Deadzone < %val | - $pref::Input::Deadzone > %val)
{
$player1.joyxval = %val * $pref::Input::Sensitivity;
}
else
{
$player1.joyxval = 0;
}
if($player1.joyxval > 0)
{
$player1.keypressed = "right";
moveright($player1);
}
else if($player1.joyxval < 0)
{
$player1.keypressed = "left";
moveleft($player1);
}
else if($player1.joyxval == 0 && $player1.joyyval == 0)
{
$player1.keypressed = "null";
}
else if ($player1.joyxval == 0 && $player1.joyyval != 1)
{
if ($player1.joyyval > 0)
{
$player1.keypressed = "down";
movedown($player1);
}
else if ($player1.joyyval < 0)
{
$player1.keypressed = "up";
moveup($player1);
}
}
}
function joymovey(%val)
{
if ($pref::Input::Deadzone < %val | - $pref::Input::Deadzone > %val)
{
$player1.joyyval = %val * $pref::Input::Sensitivity;
}
else
{
$player1.joyyval = 0;
}
if($player1.joyyval > 0)
{
$player1.keypressed = "down";
movedown($player1);
}
else
if($player1.joyyval < 0)
{
$player1.keypressed = "up";
moveup($player1);
}
else
if($player1.joyyval == 0 && $player1.joyxval == 0)
{
$player1.keypressed = "null";
movereleased($player1);
}
else if ($player1.joyyval == 0 && $player1.joyxval != 1)
{
if ($player1.joyxval > 0)
{
$player1.keypressed = "right";
moveright($player1);
}
else if ($player1.joyxval < 0)
{
$player1.keypressed = "left";
moveleft($player1);
}
}
}
function moveleft(%obj)
{
// function was called by schedule() but key is no longer pressed so abort
if(%obj.keypressed !$= "left")
return;
if(%obj.ismoving == true)
return;
// if player is not facing correct direction, reface player and schedule a move later
if(%obj.facing !$= "left")
{
%obj.facing = "left";
%obj.setrotation(270);
schedule(200,0,"moveleft",%obj);
return;
}
%x = %obj.getpositionx() - 10;
%y = %obj.getpositiony();
if ( checkforobstructions(%x,%y,%obj) == true )
{
schedule(200,0,"moveleft",%obj);
return;
}
else
{
%obj.ismoving = true;
%obj.moveto(%x,%y,35,true,true,true);
}
}
function moveright(%obj)
{
if(%obj.keypressed !$= "right")
return;
if(%obj.ismoving == true)
return;
if(%obj.facing !$= "right")
{
%obj.facing = "right";
%obj.setrotation(90);
schedule(200,0,"moveright",%obj);
return;
}
%x = %obj.getpositionx() + 10;
%y = %obj.getpositiony();
if ( checkforobstructions(%x,%y,%obj) == true )
{
schedule(200,0,"moveright",%obj);
return;
}
else
{
%obj.ismoving = true;
%obj.moveto(%x,%y,35,true,true,true);
}
}
function movedown(%obj)
{
if(%obj.ismoving == true)
return;
if(%obj.keypressed !$= "down")
return;
if(%obj.facing !$= "down")
{
%obj.facing = "down";
%obj.setrotation(180);
schedule(200,0,"movedown",%obj);
return;
}
%x = %obj.getpositionx();
%y = %obj.getpositiony()+10;
if ( checkforobstructions(%x,%y, %obj) == true )
{
schedule(200,0,"movedown",%obj);
return;
}
else
{
%obj.ismoving = true;
%obj.moveto(%x,%y,35,true,true,true);
}
}
function moveup(%obj)
{
if(%obj.ismoving == true)
return;
if(%obj.keypressed !$= "up")
return;
if(%obj.facing !$= "up")
{
%obj.facing = "up";
%obj.setrotation(0);
schedule(200,0,"moveup",%obj);
return;
}
%x = %obj.getpositionx();
%y = %obj.getpositiony()-10;
if ( checkforobstructions(%x,%y,%obj) == true )
{
schedule(200,0,"moveup",%obj);
return;
}
else
{
%obj.ismoving = true;
%obj.moveto(%x,%y,35,true,true,true);
}
}
function checkforobstructions(%x,%y,%obj)
{
if ( checkforedgeofmap(%x,%y,%obj) == true )
{
echo("edge of map");
return true;
}
else if ( checkforsolidtile(%x,%y,%obj) == true)
{
echo( "solid tile in way" );
return true;
}
else if ( checkforobjects(%x,%y,%obj) == true )
{
echo( "object in way" );
return true;
}
else if (checkforplayers(%x,%y,%obj) == true )
{
echo( "player in way" );
return true;
}
else
return false;
}
function checkforplayers(%x,%y,%obj)
{
%var = "";
if(%obj.tag $= "player1")
%bit = BIT(3);
else if (%obj.tag $= "player2")
%bit = BIT(1);
%var = scenegraph2d.pickpoint(%x,%y,%bit,%bit,true);
if (%var !$= "")
{
return true;
}
else
return false;
}
function checkforobjects(%x,%y)
{
// 7 is the layer/group of objects that cannot be walked through
%var = "";
%var = scenegraph2d.pickpoint(%x,%y,BIT(7),BIT(7),true);
if (%var !$= "")
{
return true;
}
else
return false;
}
function checkforedgeofmap(%x,%y,%obj)
{
%tile = 0;
%tile = $layer0.picktile(%x,%y);
if (%tile==0)
{
return true;
}
else
return false;
}
function checkforsolidtile(%x,%y,%obj)
{
%tile = 0;
%tile = $layer1.picktile(%x,%y);
%tilex = getword(%tile,0);
%tiley = getword(%tile,1);
// customdata of 1 is flag for solid tile
if ($layer1.gettilecustomdata(%tilex,%tiley) == 1)
{
return true;
}
else
return false;
}
#5
06/03/2006 (12:35 pm)
Oh almost forgot, this function is in main.cs but you need it. It wasnt working when I had it in keybinds.csfunction t2dsceneobject::onpositiontarget(%obj)
{
%obj.ismoving = false;
switch$ (%obj.keypressed)
{
case "null":
//%obj.setatrest();
case "left":
moveleft(%obj);
case "right":
moveright(%obj);
case "up":
moveup(%obj);
case "down":
movedown(%obj);
default:
echo("default");
}
}
#6
In the actual bomberman game the player is not locked into a grid coordinate system; they can move freely around any obstacles. This is actually the first way I had setup movement, BUT, the problem with this system is how does the player get around obstacles or go down a cooridor. If the player is just made slightly smaller than the basic tile size, then he can fit, but, if you go up or down just a little bit you wont fit, as your head or feet may be in the way. Which means the player has to carefully adjust his position before he can fit between any two obstacles.
In the real bomberman game they get around this problem, which allowing free-movment causes, by automatically sliding the player into the correct position when they start walking into obstacles. I think they do this by making the boundingbox around obstacles a softened square shape. Without the hard edges, if the player walks into an obstacles, the player will slowly slide to the side of it, whichever side the player is already closer to.
As it turns out, doing it this way requires almost no code and gives superier gameplay . . . Oh well, I guess maybe it was a good exercise.
06/05/2006 (11:39 am)
I realize maybe noone else is reading this, but =D I finished implimenting this movement system, but now I dont think I like it. In the actual bomberman game the player is not locked into a grid coordinate system; they can move freely around any obstacles. This is actually the first way I had setup movement, BUT, the problem with this system is how does the player get around obstacles or go down a cooridor. If the player is just made slightly smaller than the basic tile size, then he can fit, but, if you go up or down just a little bit you wont fit, as your head or feet may be in the way. Which means the player has to carefully adjust his position before he can fit between any two obstacles.
In the real bomberman game they get around this problem, which allowing free-movment causes, by automatically sliding the player into the correct position when they start walking into obstacles. I think they do this by making the boundingbox around obstacles a softened square shape. Without the hard edges, if the player walks into an obstacles, the player will slowly slide to the side of it, whichever side the player is already closer to.
As it turns out, doing it this way requires almost no code and gives superier gameplay . . . Oh well, I guess maybe it was a good exercise.
#7
As I recall, BomberMan used a movement system like this, though it's a bit tough to tell for sure, and the minimum increment may have been smaller, but I don't recall any levels where a path was wider than 1 tile. This sort of movement scheme guarantees you can't get caught up by the corner of a tile, because you'll only ever be at one of a couple points relative to the tile (half way, or dead on).
(Completely untested proto-code below to illustrate.)
06/05/2006 (5:44 pm)
The easiest way to do a game like that would be to think of discrete units of movement. If a tile is 16 units, and you want the player to be able move in nothing smaller than 1/2 tile increments, you move them normally, but when the player lets up on the key, they keep moving until they're at a 8 unit increment, then whatever other key the user has pressed can take effect.As I recall, BomberMan used a movement system like this, though it's a bit tough to tell for sure, and the minimum increment may have been smaller, but I don't recall any levels where a path was wider than 1 tile. This sort of movement scheme guarantees you can't get caught up by the corner of a tile, because you'll only ever be at one of a couple points relative to the tile (half way, or dead on).
(Completely untested proto-code below to illustrate.)
function cPlayer::onLevelLoaded(%this, %scenegraph)
{
$player = %this;
moveMap.bindCmd(keyboard, "w", "moveUp();", "moveUpFinish();");
. . .
$player.direction = "stop";
}
function cPlayer::updateMovement(%this)
{
switch($player.direction)
{
case "stop":
$this.setLinearVelocityX( 0 );
$this.setLinearVelocityY( 0 );
// forcably set the player's positon to be sure it is sitting
// at a tile increment position. (may not be needed)
. . .
break;
case "up":
$this.setLinearVelocityX( 0 );
$this.setLinearVelocityY( -$player.speed );
break;
. . .
}
}
function moveUp()
{
if($player.direction = "stop")
{
$player.direction = "up";
$player.updateMovement();
}
}
function moveUpFinish()
{
// finish moving the player to a tile increment position,
// then call moveToCallback to set the $player.direction
// to "stop" and stop the player's movement.
$player.moveTo( [i]nextTileIncrement[/i], "moveToCallback();" );
}
function moveToCallback()
{
$player.direction = "stop";
$player.updateMovement();
}
#8
But is there anyway to make a tile have a circular bounding box? I figured I would just make an invisible sprite and put it on top of the obstacle tiles, but isnt that a lot of unnecessary overhead?
06/05/2006 (5:58 pm)
Yeah thats one way to do it, and exactly what I did first, but didnt like it. You might be right about the early bomberman games, but in bomberman4 at least, you are not limited to discrete units of movement. In it they seem to use circular boundingboxes on the obstacles so you can slide past them even if you arent lined up perfectly.But is there anyway to make a tile have a circular bounding box? I figured I would just make an invisible sprite and put it on top of the obstacle tiles, but isnt that a lot of unnecessary overhead?
#9
I'll still be going with something like what I outlined above, but that's because I'm working on a block-pushing puzzle game where precision is needed. Nothing is worse than a puzzle game that requires pixel-perfect placement *and* sub-second timing. Putting stuff on a grid system at least makes it more obvious when you over-/under-shoot.
06/05/2006 (6:06 pm)
Instead of a circular bounding box, how about a diamond-shaped one with points at the sprite's top, bottom, left, and right sides, and collision logic to let the sprite slide along a collision? You'd only get 'caught' if the center of the sprite was still along side a blocking object in the direction you were trying to move. If that doesn't look good, maybe an octagonal collision shape so you can't take the corners quite so easily. The blocking sprites would still have square collision shapes so you wouldn't 'snag' along a narrow path.I'll still be going with something like what I outlined above, but that's because I'm working on a block-pushing puzzle game where precision is needed. Nothing is worse than a puzzle game that requires pixel-perfect placement *and* sub-second timing. Putting stuff on a grid system at least makes it more obvious when you over-/under-shoot.
#10
It's pretty basic at this point (I don't have any 'hedges' in my test level yet), but the idea should come across. In the end, we're looking for essentially the same control code, I just want to force the player into moving 1/2 a tile at a time, where you don't have any such restriction. (Though, I'm still thinking it might be a good solution for you if you make the tile-increment small enough, like 1/16 or 1/32 or something.)
06/05/2006 (8:34 pm)
Here's the cardinal direction code that'll probably become the basis of my own control code. I still need to work in the 'tile increment' portion, but so far this is allowing clean cardinal-direction only movement. I'm using a 'buffer' to let the player 'ride' keys for fast reactions. I'll probably end up iterating through the buffer until I find the first non-blocked direction. That way the player could follow a stair patterned path by riding the 'up' and 'left' keys.It's pretty basic at this point (I don't have any 'hedges' in my test level yet), but the idea should come across. In the end, we're looking for essentially the same control code, I just want to force the player into moving 1/2 a tile at a time, where you don't have any such restriction. (Though, I'm still thinking it might be a good solution for you if you make the tile-increment small enough, like 1/16 or 1/32 or something.)
function cPlayer::onLevelLoaded(%this, %scenegraph)
{
$player = %this;
moveMap.bindCmd(keyboard, "w", "playerUp();", "playerUpStop();");
moveMap.bindCmd(keyboard, "s", "playerDown();", "playerDownStop();");
moveMap.bindCmd(keyboard, "a", "playerLeft();", "playerLeftStop();");
moveMap.bindCmd(keyboard, "d", "playerRight();", "playerRightStop();");
%this.direction = "stop";
}
function cPlayer::updateMovement(%this)
{
switch$ (%this.key1)
{
case "up":
%this.setLinearVelocityX( 0 );
%this.setLinearVelocityY( -$player.speed );
case "down":
%this.setLinearVelocityX( 0 );
%this.setLinearVelocityY( $player.speed );
case "left":
%this.setLinearVelocityX( -$player.speed );
%this.setLinearVelocityY( 0 );
case "right":
%this.setLinearVelocityX( $player.speed );
%this.setLinearVelocityY( 0 );
default:
%this.setLinearVelocityX( 0 );
%this.setLinearVelocityY( 0 );
}
}
function playerUp()
{
$player.addKey("up");
$player.updateMovement();
}
function playerDown()
{
$player.addKey("down");
$player.updateMovement();
}
function playerLeft()
{
$player.addKey("left");
$player.updateMovement();
}
function playerRight()
{
$player.addKey("right");
$player.updateMovement();
}
function playerUpStop()
{
$player.removeKey("up");
$player.updateMovement();
}
function playerDownStop()
{
$player.removeKey("down");
$player.updateMovement();
}
function playerLeftStop()
{
$player.removeKey("left");
$player.updateMovement();
}
function playerRightStop()
{
$player.removeKey("right");
$player.updateMovement();
}
function cPlayer::addKey(%this, %newKey)
{
%this.removeKey(%newKey);
%this.key4 = %this.key3;
%this.key3 = %this.key2;
%this.key2 = %this.key1;
%this.key1 = %newKey;
}
function cPlayer::removeKey(%this, %dropKey)
{
switch$ (%dropKey)
{
case %this.key1:
%this.key1 = %this.key2;
%this.key2 = %this.key3;
%this.key3 = %this.key4;
%this.key4 = "";
case %this.key2:
%this.key2 = %this.key3;
%this.key3 = %this.key4;
%this.key4 = "";
case %this.key3:
%this.key3 = %this.key4;
%this.key4 = "";
case %this.key4:
%this.key4 = "";
}
}
#11
Oh and I tried a diamond shaped box, then you just get hung up on the flat edges of the box instead of the corners, which is even more unnatural.
About your game, I'm curious about how they have movement setup in like, some of the final fantasy snes games, because many of them have box pushing. I'll take a look but I think most of them have free-movement for the player, but when you push a rock (or press a next to it), the rock does have discrete units of movement. As far as just moving around the map this may be more natural/fluid. Although goldensun for GBA, in many situations you push around pillars, which later you jump across the top of (so you are making a path, and sometimes these pillars will only move in discrete units and sometimes they will be freemoving (generally when there is only 1 pillar and only 1 correct spot to put it). So I guess you can mix it both ways in the same game.
I'll go ahead and update my code up above so atleast my cardinal direction code is right, even though I'm not using it.
06/06/2006 (8:13 am)
I did some experimenting and giving the player and the box circular collision boxes, turning on physics, and setting friction to 0 works great, thats exactly how they did it in bomberman 4. Also something I noticed, most of the box sprites in the game have rounded corners or are round rocks, not sharp edge boxes.Oh and I tried a diamond shaped box, then you just get hung up on the flat edges of the box instead of the corners, which is even more unnatural.
About your game, I'm curious about how they have movement setup in like, some of the final fantasy snes games, because many of them have box pushing. I'll take a look but I think most of them have free-movement for the player, but when you push a rock (or press a next to it), the rock does have discrete units of movement. As far as just moving around the map this may be more natural/fluid. Although goldensun for GBA, in many situations you push around pillars, which later you jump across the top of (so you are making a path, and sometimes these pillars will only move in discrete units and sometimes they will be freemoving (generally when there is only 1 pillar and only 1 correct spot to put it). So I guess you can mix it both ways in the same game.
I'll go ahead and update my code up above so atleast my cardinal direction code is right, even though I'm not using it.
#12
06/08/2006 (11:00 am)
Actually, after checking about the FF snes games, they all have discrete-unit-movement. However, chrono trigger and zelda use free movement '-D
#13
www.garagegames.com/mg/forums/result.thread.php?qt=45530
06/08/2006 (2:43 pm)
Now im having problems with this, i posted it here:www.garagegames.com/mg/forums/result.thread.php?qt=45530
#14
06/09/2006 (8:01 am)
I had an idea for you, why dont you use a simgroup to store the keys instead of having to sort through that array?
#15
06/19/2006 (8:30 pm)
What is a simgroup?
#16
-Vern
06/20/2006 (2:05 pm)
I think by simgroup, he meant SimSet, which is explained in the TGB Reference.pdf-Vern
#17
06/20/2006 (3:14 pm)
Er, yeah =D
#18
The SimSet looks like it could be a handy class, but other than adding and removing things from the set, I can't find anything on how to actually use it in code, or how it behaves.
06/22/2006 (8:22 pm)
I'm still not certain how a SimSet works given the documentation I found. In the TGE documentation, I see push and pop defined to do a last-in-first-out buffer like I'm working with, but those don't show up in the TGBReference.pdf. Also, I don't see how to fetch the value of the 'top' SimSet item.//create a new SimSet
%this.key = new SimSet();
// player presses up, left, and down, in that order, without releasing a key
%this.key.add("up");
%this.key.add("left");
%this.key.add("down");
// player releases left key
%this.key.remove("left");
// which of the following will fetch the 'top' value in the stack ("down")
%dir = %this.key[0]; // is this how?
%dir = %this.key[%this.key.count - 1]; // or is it this?
%dir = %this.key;
// or is it something else?The SimSet looks like it could be a handy class, but other than adding and removing things from the set, I can't find anything on how to actually use it in code, or how it behaves.
Torque Owner Michael Woerister