Tank move and barrel mouse follow script

This entry is part 3 of 4 in the series [TankZ - A game made to learn Unity](https://manuel-rauber.com/series/tankz-a-game-made-to-learn-unity/ "TankZ - A game made to learn Unity")
After we’ve set up the first visual representation of a tank, we want to move the tank and its barrel should follow the mouse. The following things will have to be done to achieve this goal:
  • Get state of input keys (we will use WASD for moving).
  • Get position of the mouse.
  • Add force to the tank according to the W (forward) and S (backward) key.
  • Add rotation to the tank according to A (left) and D (right) key.
  • Calculate the rotation to rotate the barrel so it targets the mouse.

The following script is used to get the tank rollin’:

using UnityEngine;
using System.Collections;

public class PlayerTank : MonoBehaviour {
	/// <summary>
	/// The maximum speed the tank can achieve
	/// </summary>
	public float Speed = 80f;

	/// <summary>
	/// Maximum turn speed of the tank
	/// </summary>
	public float TurnSpeed = 1.3f;

	/// <summary>
	/// A game object which represents the barrel. It is used for the mouse follow function
	/// </summary>
	public GameObject Cannon;

	/// <summary>
	/// Maximum turn rate of the cannon
	/// </summary>
	public float CannonTurnRate = 30f;

	/// <summary>
	/// Holds the current "power input" (-1..1). -1 Backwards, +1 forwards (S and W key)
	/// </summary>
	private float _powerInput;

	/// <summary>
	/// Holds the current "turn input" (-1..1). -1 turn left, +1 turn right (A and D key)
	/// </summary>
	private float _turnInput;

	/// <summary>
	/// Holds the current mouse position
	/// </summary>
	private Vector3 _mousePosition;

	/// <summary>
	/// The actual tank body where the force and rotation will be applied
	/// </summary>
	private Rigidbody2D _tankBody;

	public void Awake() 
	{
		// Get the rigidbody
		_tankBody = GetComponent<Rigidbody2D> ();

		// If no cannon is assigned we throw an error. This means using a cannon is mandatory
		if (Cannon == null) {
			throw new MissingReferenceException("No cannon attached!");
		}
	}

	public void Update() 
	{
		// The input of W and S key
		_powerInput = Input.GetAxis ("Vertical");

		// The input of A and D key
		_turnInput = Input.GetAxis ("Horizontal");

		// Current mouse position
		_mousePosition = Input.mousePosition;
	}

	public void FixedUpdate()
	{
		MoveTank ();
		CannonMouseFollow ();
	}

	private void MoveTank() 
	{
		// Add a relative force according to power input and speed settings
		_tankBody.AddRelativeForce (new Vector2 (0f, _powerInput * Speed));

		// rotate the tank according to the turn input and turn speed
		_tankBody.MoveRotation (_tankBody.rotation - _turnInput * TurnSpeed);
	}

	private void CannonMouseFollow() 
	{
		// Translate the mouse position into a point in the world
		var position = Camera.main.ScreenToWorldPoint (_mousePosition);

		// Calculate the rotation to make the cannon look at the mouse position
		var rotation = Quaternion.LookRotation (position - Cannon.transform.position, Vector3.back);

		// Since we don't want to do a instant rotation, calculate the rotation towards the target rotation according to the turn state
		var targetRotation = Quaternion.RotateTowards (Cannon.transform.rotation, rotation, CannonTurnRate);

		// Assign the new rotation to the cannon
		Cannon.transform.rotation = new Quaternion (0, 0, targetRotation.z, targetRotation.w);
	}
}

What is happening here? At first some public fields are declared. Those fields are visible within the editor and we can manipulate them from other scripts, too.


PlayerTank script

It allows me to use this script for other tanks and change the values to get different behaviors (think of another type of tank for example). The public fields are:

  • Speed: Maximum speed of the tank (more a maximum applied force to the tank to make it move).
  • TurnSpeed: Maximum turn rate of the whole tank.
  • Cannon: The GameObject which is used as a cannon. We will use it for rotating the cannon individually and let it follow the mouse.
  • CannonTurnRate: The turn rate of the cannon when following the mouse. It simulates an inertia to not move instantly around.

The next fields are some private fields needed to save the input values and the actual tank body, where all forces (speed and turn rate) will be applied to:

  • _powerInput: A float value from -1 to 1 of the vertical input axis. In our case this is W (_powerInput = 1) and S (_powerInput = -1). The value is a float since a controller could be used for input allowing all other values between -1 and 1.
  • _turnInput: A float value from -1 to 1 of the horizontal input axis. In out case this is A (_turnInput = -1) and D (_turnInput = 1).
  • _mousePosition: The position of the mouse on the screen. Be careful: The mouse does not have to be within the game window when running the game to get the position. Its input is always grabbed regardless where the mouse is – as long as the game window has focus.
  • _tankBody: A Rigidbody2D, where our forces will be applied.

Fields setup done! Let’s move on to the implemented methods:

  • Awake: Called when the GameObject gets instantiated and initializes the _tankBody  field. It also checks if a Cannon has been set, otherwise it will throw an exception making the cannon a mandatory object for the script.
  • Update: Is called on every frame and sets the input fields to the most recent values from the input axis.
  • FixedUpdate: Is called every fixed framerate frame. It should always be used when using a RigidBody component. In this case we call the two following methods:
  • MoveTank: This method simply adds a force to the _tankBody  in order to make it move. Therefor the input state is multiplied with the speed.
  • CannonMouseFollow: This method is used for making the cannon follow the mouse. At first we will translate the mouse coordinates to world coordinates by using the Camera.main.ScreenToWorldPoint()-method. Then we use Quaternion.LookRotation()to calculate where we need to rotate the cannon to, to let it look in direction of the mouse. Since we don’t want to rotate the cannon instantly, we use Quaternion.RotateTowards(). Its third parameter is a delta of how much degree rotation per fixed update is allowed. We use the CannonTurnRate to limit this. In the default settings we allow a maximum of 30 degrees rotation per fixed update.

That’s the magic behind the move and follow script. :-) I’m pretty sure it can be tweaked and optimized in much ways, but for a first start, I’m really happy with tank’s behavior.

Check out the full source code at GitHub and a running sample.