Super Antithesis Kart was developed with C# in Unity.

Our team of five developers needed to be able to quickly collaborate and test our game’s various systems without difficulty in order to compete in the Ludum Dare 41 game jam. As such the 3D player controller I wrote is largely decoupled and has special considerations to help other developers to easily customize the behavior of the component.

The full source can be found here.

Tooltips and Organization

If the end user of the game is the player, end user of the code as it manifests in the engine itself is any developer who may need to use it. In Unity’s case this means providing organized and documented variables of relevance for specifically the inspector window.

/// <summary>
/// 3D MouseLook and Transform movement
/// </summary>
public class PlayerController : MonoBehaviour 
{
	[Header("Movement")]
 	[Tooltip("Normal movement speed in Editor units")]
	public float walkingSpeed		= 2.5f;
 	[Tooltip("Slowed movement speed used when walk key is held in Editor units")]
	public float slowedWalkingSpeed	= 1.0f;

	[Header("Mouse Look")]
 	[Tooltip("Mouse look movement sensitivity")]
	public float mouseSensitivity	= 50f;
	[Tooltip("Reference to the child viewing camera object of Player")]
	public GameObject viewCamera;
	[Tooltip("Maximum magnitude of Y-Axis angle (prevents looking straight up/down)")]
	public float yAxisAngleClamp	= 75f;
	[Header("Headbob")]
	[Tooltip("Headbob magnitude")]
	public float headbobMagnitude	= 0.05f;
	[Tooltip("Headbobbing speed")]
	public float headbobSpeed 		= 4.8f;
	[Tooltip("Headbob smoothing transition from moving to not moving.")]
	public float headbobTransitionSpeed = 20f;

	[Header("Interactions")]
 	[Tooltip("Minimum distance to Interactable objects in Editor Units")]
 	public float interactionRange = 1f;

Debug Consideration

Under a tight deadline the debug code needed to intrude as little as possible in order to maintain code clarity. As such, I use a delegate for movement behavior to distinguish between noclip mode and normal player movement.

	[Header("Debug")]
	[Tooltip("Enable NoClip")]
	public bool noclip = false;
	delegate void MovementUpdate();
	private MovementUpdate movementUpdate;

By using this pattern it is simple to switch between two cohesive movement update functions:

void MoveUpdate()
{
	Vector3 visionForward 	= new Vector3 (viewCamera.transform.forward.x, 0,
						viewCamera.transform.forward.z).normalized;
	Vector3 visionRight 	= viewCamera.transform.right;

	bool isMoving = false;

	if (Input.GetAxisRaw("Vertical") > 0)
	{
		 // Move Forward
		_characterController.Move(visionForward * _moveSpeed * Time.deltaTime);
		isMoving = true;
	} else if (Input.GetAxisRaw("Vertical") < 0)
	{
		 // Move Backward
		 _characterController.Move(-visionForward * _moveSpeed * Time.deltaTime);
		isMoving = true;
	}

	if (Input.GetAxisRaw("Horizontal") > 0)
	{
		// Strafe Right
		_characterController.Move(visionRight * _moveSpeed * Time.deltaTime);
		isMoving = true;
		
	} else if (Input.GetAxisRaw("Horizontal") < 0)
	{
		// Strafe Left
		_characterController.Move(-visionRight * _moveSpeed * Time.deltaTime);
		isMoving = true;
	}

	// Simple Z-Axis position clamping
	transform.position = new Vector3(transform.position.x,
					 _initialY,
				 	 transform.position.z);

	if (!isMoving)
	{
		_characterController.Move(Vector3.zero);
	}
}

void NoClipMoveUpdate()
{
	Vector3 visionForward 	= viewCamera.transform.forward;
	Vector3 visionRight 	= viewCamera.transform.right;
	if (Input.GetAxisRaw("Vertical") > 0)
	{
		transform.Translate(visionForward * _moveSpeed); // Move Forward
	} else if (Input.GetAxisRaw("Vertical") < 0)
	{
		transform.Translate(-visionForward * _moveSpeed); // Move Backward
	}

	if (Input.GetAxisRaw("Horizontal") > 0)
	{
		transform.Translate(visionRight * _moveSpeed);// Strafe Right
	} else if (Input.GetAxisRaw("Horizontal") < 0)
	{
		transform.Translate(-visionRight * _moveSpeed);// Strafe Left
	}
}