This week we looked at getting our tank to move.
Tanks, and all tracked vehicles, have a very particular way of moving. By turning their tracks on both sides in the same direction, they can move forwards or backwards. By turning their tracks in opposite directions, they can turn around. In our game, we don’t model the tracks explicitly, but we do want our tank to behave in this same way.
In our previous two projects, we didn’t concern ourselves too much with how the keys we pressed caused actions to occur in the game. It mostly “just worked”.
In Roll-A-Ball we used the method Input.GetAxis() with axes called “Horizontal” and “Vertical” and these allowed us to control our player object.
The Input Manager allows control axes to be defined. It can be found under the Edit|Project Settings|Input menu. We can add as many control schemes as we need for our game here.
In the case of Tanks, we need controls for two players so we have Horizontal1, Vertical1 and Fire1 and also then Horizontal2, Vertical2, and Fire2.
When we retrieve the axis values in our code, it will have a value between -1 (full negative) and 1 (full positive). A value of zero indicates no input on this axis. When we’re dealing with keys, the only possible values are (-1, 0, 1) but if we had an alternative controller such as a gamepad, we would have values in-between too.
Calculating How Much to Move
The tank’s vertical axis controls how much to move forwards and backwards. To calculate how much the tank should move in one physics frame and in what direction we use the following code:
Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;
It’s a vector, so it it has direction as well as magnitude. The direction comes from transform.forward – this vector always points in the tank’s forward direction, regardless of how the tank has been turned, and is always 1 unit long. It’s worth noting that there are companion vectors called right and up.
The m_MovementInputValue part is how much the user is currently pressing the keys in the vertical axis. As already discussed, it will be either -1 (full speed backwards), 0 (no movement) or 1 (full speed forwards).
The m_Speed property is a value we’ve set which sets the tanks maximum forwards/backwards speed. We have it set to 12 (distance units per second).
If we multiply all of these three things above together, we get a vector that points from the tank’s current position to the location the tank will be at in one second, if nothing else changes.
This is good, but it isn’t taking into account how often we’re doing our physics calculations (i.e several times a second). Time.deltaTime is the time since the last physics calculation. It will be some fraction of a second. Multiplying by this, we get a vector that points from the tank’s current position to the location the tank will be at after one physics calculation. That is what we want.
Calculating How Much to Turn
Calculating how much to turn is easier. This is the code:
float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;
It’s a float describing how many degrees we’re turning.
As before, m_TurnInputValue represents the user input. The value is either -1 (turn anticlockwise), 0 (don’t turn) or 1 (turn clockwise).
The m_TurnSpeed property is a value we’ve set which sets the tanks maximum turning rate. We have it set to 180 (degrees per second).
Finally, for the same reason as before, we multiply by Time.deltaTime to get the number of degrees we’re turning in this physics calculation.
Applying the Movement
The movement was hard to calculate, but it’s easy to apply:
m_Rigidbody.MovePosition (m_Rigidbody.position + movement);
We just tell the Rigidbody to move to a new position. That new position is simply the old position with the movement vector we calculated added on.
Applying the Turn
The turn was easy to calculate, but applying it is a little more complex. It’s not important to understand why it work the way it does, just know that if you’re applying an additional rotation to something that you can’t just “add it on” as we did with the positioning. Here is the code:
Quaternion turnRotation = Quaternion.Euler (0.0f, turn, 0.0f);
m_Rigidbody.MoveRotation (m_Rigidbody.rotation * turnRotation);
To turn the rotation into something we can use, we need to construct a Quaternion. A Quaternion is a special way of storing a rotation. The Quaternion.Euler() method allows us to create a Quaternion from X,Y,Z axis rotations. Here our turn was about the Y axis and the values for the X and Z rotations are simply zero.
Once we have our Quaternion, you might imagine that we add it to the Rigidbody’s existing rotation, but that wouldn’t work. We actually have to multiply it to get the correct new rotation for our Rigidbody.
Changing the Engine Sound
Another thing we spent a good bit of time on was checking to see if the tank was moving and changing the engine sound between an idle and driving sound.
We used the Mathf.Abs() function to get the absolute value of m_MovementInputValue and m_TurnInputValue. The absolute value of something is the value with the negative sign ignored. We compared these to a very small value to determine if the tank was currently stationary, or not.
Once we determined if the tank was stationary or not, we checked the engine sound that was playing. If it wasn’t the one it should be, we swapped in the correct one, taking time to randomly alter the pitch a little so that the two tanks won’t sound identical.
The up-to-date files for the project are here. Please download these and we will all start from this point next week!