Small Personal Project

Over the weekend when I got a little bit of time to myself I thought I would pick up an old challenge I set myself a while ago that was stopped by University work. The challenge was simple enough and revolved around Blizzards game Overwatch. Overwatch is a team based fps game with unique characters, each one of these characters will have their own abilities that allow them to get an edge in combat.

Since Overwatch’s release I had set myself the challenge of remaking each character within the Unity game engine, and over this weekend I found myself a few hours to begin remaking the crowd favourite, Tracer. Tracer’s two main abilities are:

  • Dash – Moves tracer in the direction she is facing a set distance
  • Rewind – Moves tracer back along here previous path a set distance

So to begin I created a fairly simple character object with a controller that I would then attach my control script to. This control script is a generic script set up to allow movement of any character. Below I have listed the function to actually move the player character, this function is called from within the fixed update.

	private void MovePlayer(){

		//Set up initial values for movement
		Vector3 desiredMove = new Vector3(Input.GetAxis("Horizontal"), 0.0f , Input.GetAxis("Vertical"));

		//Copied from Unity standard first person controller
		RaycastHit hitInfo;
		Physics.SphereCast(transform.position, playerController.radius,
							Vector3.down, out hitInfo,
							playerController.height/2f, Physics.AllLayers,
							QueryTriggerInteraction.Ignore);

		//Rotate the vector that will be moved along by the camera rotation
		desiredMove = Quaternion.AngleAxis (characterTargetRot.eulerAngles.y, Vector3.up) * desiredMove;
		
		//Assign the x and z components
		forwardMovement.x = desiredMove.x*movementSpeed;
		forwardMovement.z = desiredMove.z*movementSpeed;

		//Check that the player is currently on the ground
		if (playerController.isGrounded) {

			if (Input.GetButtonDown ("Jump")) {

				forwardMovement.y = jumpSpeed;
			}
		} else {

			forwardMovement += Physics.gravity * Time.fixedDeltaTime;
		}
		//Move function founf as part of the character controller
		CollisionFlags temp = playerController.Move (forwardMovement * Time.fixedDeltaTime);
	}

The movement is only the first step of the character control though as you also need to factor in the camera movement this is achieved in the function below.

	private void RotatePlayer(){

		//Set rotation values using defined sensitivity
		float yRot = Input.GetAxis("Mouse X") * XSensitivity;
		float xRot = Input.GetAxis("Mouse Y") * YSensitivity;

		//Set character target rotation
		characterTargetRot *= Quaternion.Euler (0f, yRot, 0f);
		//Set camera target rotation
		cameraTargetRot *= Quaternion.Euler (-xRot, 0f, 0f);

		//Call the clamp function
		cameraTargetRot = ClampRotationAroundXAxis (cameraTargetRot);

		//Set each rotation
		this.transform.localRotation = characterTargetRot;
		PlayerCamera.transform.localRotation = cameraTargetRot;

	}

The ClampRotationAroundXAxis function for those who are interested is just reusing the clamp function found in the move camera script that can be found in the Unity standard assets. However I will include the code here for reuse.

 	Quaternion ClampRotationAroundXAxis(Quaternion q)
	{
		q.x /= q.w;
		q.y /= q.w;
		q.z /= q.w;
		q.w = 1.0f;

		float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan (q.x);

		angleX = Mathf.Clamp (angleX, -90f, 90f);

		q.x = Mathf.Tan (0.5f * Mathf.Deg2Rad * angleX);

		return q;
	}

Now to move onto the main point of all of this recreating the abilities. To set up the controller for abilities I decided to use a bit of composition. I’ve illustrated below with a very basic UML (that I hope I’ve done correctly).

uml-characterabilities

With this method I am able to easily swap between “ability set’s” which eventually once weaponry is added in a similar fashion should allow me to mix and match character traits for some silliness.

So let’s start with the dash ability that tracer has, this ability was simple to implement however could do with some refinement in the future. The code is listed below for those interested.

	//Dash Ability
	public override void triggerAbility2(){

		//Set up initial desired movment for forward only
		Vector3 desiredMove = new Vector3(0.0f, 0.0f, 1.0f);

		//Rotate the movement accordingly
		desiredMove = Quaternion.AngleAxis (this.transform.localRotation.eulerAngles.y, Vector3.up) * desiredMove;

		RaycastHit hitInfo;
		//Raycast in front of the player in the direction of the dash
		if (Physics.Raycast (this.transform.position, desiredMove * dashSpeed, out hitInfo, dashSpeed)) {
			//if there is a collision move the player to the collision point
			this.transform.position = hitInfo.point;
		} else {
			//if not dash normally
			this.transform.position += desiredMove * dashSpeed;
		}
	}

The rewind ability is a bit more complex than the dash ability as it has to record the players movement over a fixed period of time then move backwards through that recorded input. To record the movement I was originally thinking of using array’s a vector or maybe one of the list options given by unity, in the end I decided on a queue. To get a queue to work I just had to add position to the queue at each update step (and remove the oldest if the queue got too big) as shown below.

	void Update () {

		if (!rewinding) {
			//Add position to queue
			rewindQueue.Enqueue (this.gameObject.transform.position);

			if (rewindQueue.Count > rewindTicks) {
				//remove oldest position from queue if the queue is large enough
				rewindQueue.Dequeue ();
			}

			rewindCount = rewindQueue.Count - 1;
		}
	}

So now that I’m recording position how do I cycle though it all as I can’t take from the start of the queue only the end. After looking around I found a solution that was a little odd but I liked. When the player activates the rewind ability the entire queue is pushed onto a stack as shown below.

	//Rewind Ability
	public override void triggerAbility1(){

		int count = rewindQueue.Count;

		if (!rewinding) {
			for (int i = 0; i < count; ++i) { 				//move onto stack each queue element 				rewindStack.Push ((Vector3)rewindQueue.Dequeue ()); 			} 			rewinding = true; 			StartCoroutine (rewindAbility ()); 		} 	} 

At the end of that function you can see that there is a call to the StartCoroutine function. Coroutines are essentially small functions that will work in their own time that you can specify. In this case I want the player to retrace their steps at a constant rate shown below. I am moving back two position each run through as I seem to have found an upper limit to how quickly a Coroutine can be called and had to improvise.

 	IEnumerator rewindAbility(){ 		//Remove player control 		playerMovement.setControl (false); 		//Start to loop through the stack 		while (rewindCount > 0) {

			this.transform.position = rewindStack.Pop();

			rewindCount--;

			this.transform.position = rewindStack.Pop();

			rewindCount--;
			//wait for fixed time before continuing loop
			yield return rewindWaitTime;
		} 

		//return player control
		playerMovement.setControl (true);

		rewinding = false;
	}

Once we add all these pieces together we get the result shown in the video below:

Overall I think it’s a good start and hopefully I will be able to continue it soon.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s