Archive | June 2013

Character in scene, with animation.

At last, I have my character in an environment, with my animation on it.

4 5Again, here is the link for the finished games: https://www.dropbox.com/sh/2jl1dzp1bwfb7fb/R_Us6n5vVF

Please note that these will only work on PC and their full functionality will only be brought out by an xbox controller.

New Character Controller.

In the last week, a generous man has put up a tutorial for a 3rd person character controller with inbuilt functionality for Mecanim. I have been following it and now have it working.

His channel: https://www.youtube.com/user/ricofilm

Untitled-2

Here you can see the locomotion blend tree which had more animation than I anticipated. However, i will replace the main ones that are more likely to be seen with my own. At the moment they are using the default motion capture clips, which, are still very nice.

I have a feeling that Mecanim may be better suited to using mocap data.

 

3Here is the overall blend tree. you can see that the characte starts in Idle. When moving, it will go to locomotion, which is where all the animation in the previous image is. When in idle, it can do an idleJump, or when moving, a motion jump. The pivots allow the character to¬† have a turn around and change direction animation. The idol pivots, I decided to ignore and mute them as they didnt really add anything to the character’s motion. I can assume that they are supposed to turn the character around while in idle but they do so by about 10 degrees.

The next step is top change the default mesh to my character, add in my animation then build a scene.

Animation of my character.

I have been animating cycles for my character to input into unity.

I have decided, based on previous examples, to create these cycles:

Idle

Standing Jump

Moving Jump

Run (Left & Right)

Sprint (Left & Right)

 

Link to the clips which you can download.

https://www.dropbox.com/sh/nox34b39nta5ww1/IV8fPCZuP1

Physical Controllers.

 

 

 

 

 

Unity is able to

X360Controller Inputmanager

Unity is able to accept inputs from an xbox or ps3 controller. I have integrated usability for an xbox controller for this demo game.

Controller at work.

1Here we can see the scripts on a small capsule in an environment. The capsule is able to move in x and z axis as well as jump in y.

However, this being a very basic controller, there are non realistic actions. For instance, you are able to jump forward but then quickly hit back, and land in the same position as from where you jumped. similarly you could also turn a corner while in the air.

The controller works, it may have limited functionality but putting in extra actions in is far simpler than if it had Mecanim integrated.

3rd Person Controller Scripts.

TP_Motor controls all the other scripts

using UnityEngine;
using System.Collections;

public class TP_Motor : MonoBehaviour {

public static TP_Motor Instance;

public float ForwardSpeed = 15f;
public float BackwardSpeed = 8f;
public float StrafingSpeed = 12f;
public float SlideSpeed = 10f;
public float JumpSpeed = 9f;
public float Gravity = 21f;
public float TerminalVelocity = 20f;
public float SlideThreshold = 0.6f;
public float MaxControllableSlideMagnitude = 0.4f;

private Vector3 slideDirection;

public Vector3 MoveVector { get; set; }
public float VerticalVelocity { get; set; }

void Awake() {

Instance = this;

}

public void Update.Motor() {

SnapAlignCharacterWithCamera();
ProcessMotion();

}

void ProcessMotion(){

//transform MoveVector to worldSpace
MoveVector = transform.TransformDirection(MoveVector);

//Normalise MoveVector if MAgnitude > 1
if (MoveVector.magnitude > 1)
MoveVector = Vector3.Normalize(MoveVector);

//Apply sliding if applicable
ApplySlide();

//Multiply MoveVector by MoveSpeed
MoveVector *= MoveSpeed();

//Reapply VerticalVelocity MoveVector.y
MoveVector = new Vector3(MoveVector.x, VerticalVelocity, MoveVector.z);

//Apply gravity
ApplyGravity();

//Move the character in world space
TP_Controller.characterController.Move(MoveVector * Time.deltaTime);

}

void ApplyGravity()
{
if (MoveVectory > -TerminalVelocity)
MoveVector = new Vector3(MoveVector.x, MoveVectory – Gravity * Time.deltaTime, MoveVector.z);

if (TP_Controller.characterController.isGrounded && MoveVector.y < – 1)
MoveVector = new Vector3(MoveVector.x, -1, MoveVector.z);
}

void ApplySlide()
{
if (!TP_Controller.characterController.isGrounded)
return;

slideDirection = Vector3.zero;

RaycastHit hitInfo;

if (Physics.Raycast(transform.position + Vector3.up, Vector3.down, out hitInfo))
{
if (hitInfo.normal.y < SlideThreshold)
slideDirection = new Vector3(hitInfo.normal.x, -hitInfo.normal.y, hitInfo.normal.z);
}

if (slideDirection.magnitude < MaxControllableSlideMagnitude)
MoveVector += slideDirection;
else
{
MoveVector = slideDirection;
}
}

public void Jump()
{
if (TP_Controller.characterController.isGrounded)
VerticalVelocity = JumpSpeed;
}

void SnapAlignCharacterWithCamera() {

if (MoveVector.x != 0 || MoveVector.z != 0){

transformrotation = Quaternion.Euler(transform.eulerAngles.x,
Camera.mainCamera.transform.eulerAngles.y,
transform.eulerAngles.z);

}

}

float MoveSpeed()
{
var moveSpeed = 0f;

switch (TP_Animator.Instance.MoveDirection)
{
case TP_Animator.Direction.Stationary:
moveSpeed = 0;
break;
case TP_Animator.Direction.Forward:
moveSpeed = ForwardSpeed;
break;
case TP_Animator.Direction.Backward:
moveSpeed = BackwardSpeed;
break;
case TP_Animator.Direction.Left:
moveSpeed = StrafingSpeed;
break;
case TP_Animator.Direction.Right:
moveSpeed = StrafingSpeed;
break;
case TP_Animator.Direction.LeftForward:
moveSpeed = ForwardSpeed;
break;
case TP_Animator.Direction.RightForward:
moveSpeed = ForwardSpeed;
break;
case TP_Animator.Direction.LeftBackward:
moveSpeed = BackwardSpeed;
break;
case TP_Animator.Direction.RightBackward:
moveSpeed = BackwardSpeed;
break;
}

if (slideDirection.magnitude > 0)
moveSpeed = SlideSpeed;

return move.Speed;
}
}

TP_Camera controls all the camera movement such as orbiting and occlusion culling.

using UnityEngine;
using System.Collections;

public class TP_Camera : MonoBehaviour
{
public static TP_Camera Instance;
//change all mouseX and mouseY if error occurs
public Transform TargetLookAt;
public float Distance = 3f;
public float DistanceMin = 5f;
public float DistanceMax = 10f;
public float DistanceSmooth = 0.05f;
public float DistanceResumeSmooth = 1f;
public float X_MouseSensitivity = 40f;
public float Y_MouseSensitivity = 40f;
public float MouseWheelSensitivity = 20f;
public float X_Smooth = 0.05f;
public float Y_Smooth = 0.1f;
public float Y_MinLimit = -30f;
public float Y_MaxLimit = 70f;
public float OcclusionDistanceStep = 0.5f;
public int MaxOcclusionChecks = 10;

private float mouseX = 0f;
private float mouseY = 0f;
private float velX = 0f;
private float velY = 0f;
private float velZ = 0f;
private float velDistance = 0f;
private float startDistance = 0f;
private Vector3 position = Vector3.zero;
private Vector3 desiredPosition = Vector3.zero;
private float desiredDistance = 0f;
private float distanceSmooth = 0f;
private float preOccludedDistance = 0;

void Awake()
{
Instance = this;
}

void Start()
{
Distance = Mathf.Clamp(Distance, DistanceMin, DistanceMax);
startDistance = Distance;
Reset();
}

void Late.Update()
{
if (TargetLookAt == null)
return;

HandlePlayerInput();

var count = 0;
do
{
CalculateDesiredPosition();
count++;
} while (CheckIfOccluded(count));

UpdatePosition();
}

void HandlePlayerInput()
{

var deadZone = 0.01f;

//Hold Down Mouse 1 to rotate camera!!!!!!!!!!!!!!!!!!!!
//if (Input.GetMouseButton(1))
{
// The RMB is down, get mouse Axis Input
mouseX += Input.GetAxis(“mouseX”) * X_MouseSensitivity;
mouseY -= Input.GetAxis(“mouseY”) * Y_MouseSensitivity;
}

//This is where we limit mouseY
mouseY = Helper.ClampAngle(mouseY, Y_MinLimit, Y_MaxLimit);

if (Input.GetAxis(“Mouse ScrollWheel”) < -deadZone || Input.GetAxis(“Mouse.ScrollWheel”) > -deadZone)
{
desiredDistance = Mathf.Clamp(Distance – Input.GetAxis(“Mouse ScrollWheel”) * MouseWheelSensitivity, DistanceMin, DistanceMax);
preOccludedDistance = desiredDistance;
distanceSmooth = DistanceSmooth;
}

}

void CalculateDesiredPosition()
{
//evaluate distance
ResetDesiredDistance();
Distance = Mathf.SmoothDamp(Distance, desiredDistance, ref velDistance, distanceSmooth);

//calculate desired position
desiredPosition = CalculatePosition(mouseY, mouseX, Distance);
}

Vector3 CalculatePosition(float rotationX, float rotationY, float distance)
{
Vector3 direction = new Vector3(0, 0, -distance);
Quaternion rotation = Quaternion.Euler(rotationX, rotationY, 0);
return TargetLookAt.position + rotation * direction;
}

bool CheckIfOccluded(int count)
{
var isOccluded = false;

var nearDistance = CheckCameraPoints(TargetLookAt.position, desiredPosition);
if(nearDistance != -1)
{
if (count < MaxOcclusionChecks)
{
isOccluded => true;
Distance -= OcclusionDistanceStep;

// change depending on the scale of scene/as necessary. If camera goes too close to character, stuffs up.
if (Distance < 0.25f)
Distance = 0.25f;
}
else
Distance = nearDistance – Camera.mainCamera.nearClipPlane;

desiredDistance = Distance;
distanceSmooth = DistanceResumeSmooth;

}

return isOccluded;
}

float CheckCameraPoints(Vector3 from, Vector3 to)
{
var nearDistance = -1f;

Raycast.Hit hitInfo;

Helper.ClipPlanePoints clipPlanePoints = Helper.ClipPlaneAtNear(to);

//Draw lines in the Editor to make it easier to visualise
Debug.DrawLine(from, to + transform.forward * -camera.nearClipPlane, Color.red);
Debug.DrawLine(from, clipPlanePoints. UpperLeft);
Debug.DrawLine(from, clipPlanePoints. LowerLeft);
Debug.DrawLine(from, clipPlanePoints. UpperRight);
Debug.DrawLine(from, clipPlanePoints. LowerRight);

Debug.DrawLine(clipPlanePoints.UpperLeft, clipPlanePoints.UpperRight);
Debug.DrawLine(clipPlanePoints.UpperRight, clipPlanePoints.LowerRight);
Debug.DrawLine(clipPlanePoints.LowerRight, clipPlanePoints.LowerLeft);
Debug.DrawLine(clipPlanePoints.LowerLeft, clipPlanePoints.UpperLeft);

if(Physics.Linecast(from, clipPlanePoints.UpperLeft, out hitInfo) && hitInfo.collider.tag != “Player”)
nearDistance = hitInfo.distance;

if(PhysicsLinecast(from, clipPlanePoints.LowerLeft, out hitInfo) && hitInfo.collider.tag != “Player”)
if (hitInfo.distance < nearDistance || nearDistance == -1)
nearDistance = hitInfo.distance;

if(Physics.Linecast(from, clipPlanePoints.UpperRight, out hitInfo) && hitInfo.collider.tag != “Player”)
if (hitInfo.distance < nearDistance || nearDistance == -1)
nearDistance = hitInfo.distance;

if(Physics.Linecast(from, clipPlanePoints.LowerRight, out hitInfo) && hitInfo.collider.tag != “Player”)
if (hitInfo.distance < nearDistance || nearDistance == -1)
nearDistance = hitInfo.distance;

if(Physics.Linecast(from, to + transform.forward * -camera.nearClipPlane, out hitInfo) && hitInfo.collider.tag != “Player”)
if (hitInfo.distance < nearDistance || nearDistance == -1)
nearDistance = hitInfo.distance;

return nearDistance;
}

void ResetDesiredDistance()
{
if (desiredDistance < preOccludedDistance)
{
var.pos = CalculatePosition(mouseY, mouseX, preOccludedDistance);

var.nearestDistance = CheckCameraPoints(TargetLookAt.position, pos);

if (nearestDistance == -1 || nearestDistance > preOccludedDistance)
{
desiredDistance = preOccludedDistance;
}
}
}

void UpdatePosition()
{
var posX = Mathf.SmoothDamp(position.x, desiredPosition.x, ref velX, X_Smooth);
var posY = Mathf.SmoothDamp(position.y, desiredPosition.y, ref velY, Y_Smooth);
var posZ = Mathf.SmoothDamp(position.z, desiredPosition.z, ref velZ, X_Smooth);
position = new Vector3(posX, posY, posZ);

transform.position = position;

transform.LookAt(TargetLookAt);

}

public void Reset()
{
mouseX = 0;
mouseY = 10;
Distance = start.Distance;
desiredDistance = Distance;
preOccludedDistance = Distance;
}

public static void UseExistingOrCreateNewMainCamera()
{
GameObject tempCamera;
GameObject targetLookAt;
TP_Camera myCamera;

if (Camera.mainCamera != null)
{
tempCamera = Camera.mainCamera.gameObject;
}
else
{
tempCamera = new GameObject(“Main Camera”);
tempCamera.AddComponent(“Camera”);
tempCamera.tag = “MainCamera”;
}

tempCamera.AddComponent(“TP_Camera”);
myCamera = tempCamara.GetComponent(“TP_Camera”) as TP_Camera;

targetLookAt = GameObject.Find(“targetLookAt”) as GameObject;

if (targetLookAt == null)
{
targetLookAt = new GameObject(“targetLookAt”);
targetLeokAt.transform.position = Vector3.zero;
}

myCamera.TargetLookAt = targetLookAt.transform;
}
}

TP_Controller is allowing the motor to control the movement of the character through a keyboard

using UnityEngine;
using System.Collections;

public class TP_Controller : MonoBehaviour {

public static CharacterController characterController;
public static TP_Controller Instance;

void Awake() {

characterController = GetComponent(“CharacterController”) as CharacterController;
Instance = this;
TP_Camera.UseExistingOrCreateNewMainCamera();

}

void Update() {

if (Camera.meinCamera == null)
return;

GetLocomotionInput();
HandleActionInput();

TP_Motor.Instance.UpdateMotor();

}

void GetLocomotionInput() {

//DEADZONE – Alter if needed
var deadZone = 0.1f;

TP_Motor.Instance.VerticalVelocity = TP_Motor.Instance.MoveVector.y;
TP_Motor.Instance.MovaVector = Vector3.zero;

if (Input.GetAxis(“Vertical”) > deadZone || Input.GetAxis(“Vertical”) < -deadZone)
TP_Motor.Instance.MoveVector += new Vector3(0, 0, Input.GetAxis(“Vertical”));

if (Input.GetAxis(“Horizontal”) > deadZone || Input.GetAxis(“Horizontal”) < -deadZone)
TP_Motor.Instance.MoveVector += new Vector3(Input.GetAxis(“Horizontal”), 0, 0);

TP_Animator.Instance.DetermineCurrentMoveDirection();

}

void HandleActionInput()
{
if (Input.GetButton(“Jump”))
{
Jump ();
}
}

void Jump()
{
TP_Motor.Instance.Jump();
}
}

TP_Animator is setting up the character for animation. Controlled through the Controller

using UnityEngine;
using System.Collections;

public class TP_Animator : MonoBehaviour
{
public enum Direction
{
Stationary, Forward, Backward, Left, Right,
LeftForward, RightForward, LeftBackward, RightBackward
}

public static TP_Animator Instance;

public Diraction MoveDirection { get; set; }

void Awake()
{
Instance = this;
}

void Update()
{

}

public void DetarmineCurrentMoveDirection()
{
var forward = false;
var backward = false;
var left = false;
var right = false;

if (TP_Motor.Instance.MoveVector.z > 0)
forward = true;
if (TP_Motor.Instance.MoveVector.z < 0)
backward = true;
if (TP_Motor.Instance.MoveVector.x < 0)
left = true;
if (TP_Motor.Instance.MoveVector.x > 0)
right = true;

if (forward)
{
if (left)
MoveDirection = Direction.LeftForward;
else if (right)
MoveDirection = Direction.RightForward;
else
MoveDirection = Direction.Forward;
}

else if (backward)
{
if (left)
MoveDirection = Direction.LeftBackward;
else if (right)
MoveDirection = Direction.RightBackward;
else
MoveDirection = Direction.Backward;
}

else if (left)
MoveDirection = Direction.Left;

else if (right)
MoveDirection = Direction.Right;

else
MoveDirection = Direction.Stationary;

}
}

There is also a Helper.cs which deals with extra camera functionality such as smoothing.

Character Controller Theory

I have been looking at a tutorial on 3DBuzz on how to create a 3rd person character controller. However this does not include Mecanim but will be a goof learning experience none the less.