## Tank Age: Movement

I’ve been developing a top-down tank game and I needed a way to accurately represent the movement of a vehicle with independently moving treads. Controls such as left/right/up/down need to be polled from the keyboard, but their specific interaction with eachother and the rate of movement corresponding to a key being continuously held down needs to be representative of throttle/brake inputs. When a tank turns its speed needs to be negatively affected by the nature of treads; To turn a tank slows or reverses one treads which causes the tank to rotate in the direction of the slower tread.

Here’s some of what I came up with:

private void handleThrottleInput() {
Tank tank = state.getTank();

if(input.left()) {
tank.rotateLeft();
}
if(input.right()) {
tank.rotateRight();
}
if(!input.left() && !input.right()) {
if(input.up()) {
tank.increaseDirectionalForce();
}
}
if(input.down()) {
tank.decreaseDirectionalForce();
}
if(!input.up() && !input.down()) {
tank.decayDirectionalForce();
}
}


You’ll notice in the code above I don’t have ‘else’ blocks. This is so that pressing one key doesn’t exclude other keypresses from counteracting other behaviors. The other thing you see here is a request to decay the directional force of the tank. This is a cheap way to simulate momentum after having lifted off the throttle.

Here is some code for the rotation of the sprite based on the above logic:

public void rotateLeft() {
if(velocity > 80) {
angle = clampAngle(angle - Constants.ROTATEANGLEFAST);
} else {
angle = clampAngle(angle - Constants.ROTATEANGLESLOW);
}
}

public void rotateRight() {
if(velocity > 80) {
angle = clampAngle(angle + Constants.ROTATEANGLEFAST);
} else {
angle = clampAngle(angle + Constants.ROTATEANGLESLOW);
}
}

private double clampAngle(double angle) {
double result = angle % 360;
if (result < 0) {
result = result + 360;
}
return result;
}


The clampAngle code makes it easier to reason about calculations instead of having to figure out what something like 9142 degrees ends up being in a 0-360 degree spread.

Here is what the throttle methods look like:

public void increaseDirectionalForce() {
long millis = System.currentTimeMillis();
if((millis - Constants.SPEEDDELAY) < lastDirectionalForceChangeMillis) {
return;
}
if(directionalForce < 10) {
directionalForce++;
lastDirectionalForceChangeMillis = System.currentTimeMillis();
}
}

public void decreaseDirectionalForce() {
long millis = System.currentTimeMillis();
if((millis - Constants.SPEEDDELAY) < lastDirectionalForceChangeMillis) {
return;
}
if(directionalForce > -10) {
directionalForce--;
lastDirectionalForceChangeMillis = System.currentTimeMillis();
}
}

public void decayDirectionalForce() {
long millis = System.currentTimeMillis();
if(millis - Constants.SPEEDDELAY < lastDirectionalForceChangeMillis) {
return;
}
if(directionalForce > 0) {
directionalForce--;
lastDirectionalForceChangeMillis = System.currentTimeMillis();
} else if(directionalForce < 0) {
directionalForce++;
lastDirectionalForceChangeMillis = System.currentTimeMillis();
}
}

public void rotateLeft() {
sprite.rotateLeft();
long millis = System.currentTimeMillis();
if(millis - Constants.SPEEDDELAY < lastDirectionalForceChangeMillis) {
return;
}
if(directionalForce > 5) {
directionalForce--;
}
lastDirectionalForceChangeMillis = System.currentTimeMillis();
}

public void rotateRight() {
sprite.rotateRight();

long millis = System.currentTimeMillis();
if(millis - Constants.SPEEDDELAY < lastDirectionalForceChangeMillis) {
return;
}

if(directionalForce > 5) {
directionalForce--;
}

lastDirectionalForceChangeMillis = System.currentTimeMillis();
}


I had to play with the decay/force values in order to make the tank behavior feel somewhat realistic and ‘heavy’ when responding to input. This is probably a hacky way to simulate the physics involved but its good enough for the simple 2D game.