Game Development Community

Don't Repeat Yourself, and stealing good ideas from other languages.

by Jon Frisby · 12/17/2006 (10:40 am) · 1 comments

In my last blog entry I did a breakdown of the code I had just written by the feature it supported. Two features stood out as needing much more code than the others: Basic movement/jumping (62 lines of code) and dying/respawning (46 lines).

Let's look at WHY so much code was required for such simple tasks:

Death/respawning. A bunch of this code is devoted to not calling methods on objects that don't exist. Instead of just putting:
moveMap.bindCmd(keyboard, "left", "$p1.playerLeftStart();", "$p1.playerLeftStop();");
We need to put something like:
moveMap.bindCmd(keyboard, "left", "p1Left();", "p1LeftStop();");
...
function p1Left()
{
  if((!isObject($p1)) || (!$p1.getEnabled()))
    return;
  $p1.playerLeft();
}
Some of this is attributable to my using the deletion of the player object to signal death, but even if I just disabled it, apparently when an object is disabled it to no longer belongs to its script class and so calling script methods on it doesn't work.

So how else might we construct the language to allow the more terse form? Well, if we took a page from Objective-C's book we could: Objective-C works in terms of "passing messages to objects". If you try to pass a message to an object that doesn't exist, it simply ignores the attempt. Or, alternatively, if TorqueScript obeyed the class attribute for an object when it was disabled, we could get away with this as well.


What about the movement code? Well, in TGB a very common pattern arises when you have mutually exclusive movement directions available (left/right, up/down):

function PlayerClass::playerLeft(%this)
{
  %this.moveLeft = true;
  %this.moveRight = false;
  %this.setLinearVelocityX(-$xSpeed);
}

function PlayerClass::playerLeftStop(%this)
{
  if (%this.moveLeft) 
  {
    %this.moveLeft = false;
    %this.setLinearVelocityX(0);
  }
}
In fact, I seem to recall this being recommended in the official documentation back in the 1.0.x days but I'm too lazy to go and confirm that.

The reason for this idiom is to handle cases where the player presses and holds left, then presses and holds right, then releases left. You don't want the player to stop moving right just because the left key was released.

What if the engine had some sort of built-in way to support this idiom? Something like:
moveMap.bindCmd(keyboard, "left", "$p1.setLinearVelocityX(-$xSpeed);", "$p1.setLinearVelocityX(0);", "myLeftRightExclusivityGroup");
The behavior of this would be that if you specify this optional extra parameter to bindCmd, it would create exclusivity groups, much like radio button groups in a GUI. If you did something like above (press left, press right, release left), then what would actually happen in your script code is that the left-button-released code would be called before the right-button-pressed code even though you were still holding the left button -- and of course, it WOULDN'T actually be called when you released the left key since it knows you've switched directions...

Of course, that's a bit of a trivial case. Often you'll WANT to have a direction flag on the player object so you can determine if you've hit a wall but the player is still holding the movement key. So what if we did something like this instead:
moveMap.bindCmd(keyboard, "left", $p1, "%obj.setLinearVelocityX(-$xSpeed);", "%obj.setLinearVelocityX(0);", "leftRightDirection", "left");
Now we've changed things up a bit. We're telling bindCmd about a specific object, referring to it in the code snippets, we've changed our group specifier to be a field name (see below), and added a constant we can handily use as a value in that field.

With a construct like this, the engine could add a tagged field to $p1 named leftRightDirection containing "left", "right", or "" as appropriate based on what the player's direction was.

I think I've got a resource to go write...

-JF

#1
12/17/2006 (11:49 am)
As a quick follow-up, I implemented an "actionmap.cs" that implements the above idea, taking care of both problems (sort of -- the method-on-a-nonexistant-object problem is only addressed in the context of input handling).

I replaced 40 lines of game-specific code with 31 lines of clean, reusable code.

Grab it here:

http://www.mrjoy.com/actionmap.cs

Just exec it, and call bindExclusiveCommand:
moveMap.bindExclusiveCommand(keyboard, "left", $player, "%obj.setLinearVelocityX(-$xSpeed);", "%obj.setLinearVelocityX(0);", "leftRightDirection", "left");

You can set up as many groups as you want by using other values than "leftRightDirection", and bind as many keys to a group as you want.

-JF