This script allows for integration with Unity's own Third Person Controller, which is included in their own Standard Assets package. It can be used to move a character with both Direct and Point And Click methods, using the Third Person Controller script.
To use it:
- Take the existing ThirdPersonController or AIThirdPersonController prefab, and remove any existing control script (with ThirdPersonController, this is ThirdPersonUserControl; with AIThirdPersonController, this is AICharacterControl).
- Attach either AC's Player or NPC component
- In AC's Settings Manager, set your game's Movement method to either Point And Click or Direct
- Attach the script below to the character's root object
- (Optional) To allow for AC-controlled movement during e.g. cutscenes, also attach and configure a NavMeshAgent. The scene must also rely on Unity Navigation pathfinding, as covered in the AC Manual.
UnityThirdPersonIntegration.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityStandardAssets.Characters.ThirdPerson;
using AC;
public class UnityThirdPersonIntegration : MonoBehaviour
{
private NavMeshAgent navMeshAgent;
private AC.Char characterAC;
private AC.Paths characterPaths;
private ThirdPersonCharacter characterTP;
private enum ControlState { UnderDirectControl, UnityPathfinding, ACTurning };
private ControlState controlState = ControlState.ACTurning;
private Vector3 m_CamForward;
private Vector3 m_Move;
private bool m_Jump;
private float m_moveMultiplier = 0.5f;
private Camera m_mainCamera;
private Vector3 agentPosition = Vector3.zero;
private MotionControl originalMotionControl;
private void Awake()
{
m_mainCamera = KickStarter.CameraMain;
navMeshAgent = GetComponent<NavMeshAgent>();
if (navMeshAgent != null)
{
navMeshAgent.updateRotation = false;
navMeshAgent.updatePosition = false;
}
characterAC = GetComponent<Char>();
originalMotionControl = characterAC.motionControl;
characterAC.motionControl = MotionControl.JustTurning;
characterPaths = GetComponent<Paths>();
characterTP = GetComponent<ThirdPersonCharacter>();
}
private void OnEnable ()
{
characterAC = GetComponent<Char>();
originalMotionControl = characterAC.motionControl;
characterAC.motionControl = MotionControl.JustTurning;
EventManager.OnCharacterSetPath += SetCharacterPath;
}
private void OnDisable ()
{
characterAC.motionControl = originalMotionControl;
EventManager.OnCharacterSetPath -= SetCharacterPath;
}
private void Update()
{
UpdateControlState();
UpdateMovement();
}
private void FixedUpdate()
{
if (controlState == ControlState.UnderDirectControl)
{
// Do all the input checks for direct input
UpdateDirectInput();
}
}
private void UpdateControlState()
{
if (!KickStarter.stateHandler.IsInGameplay() || !characterAC.IsPlayer || characterAC.IsMovingAlongPath() || KickStarter.settingsManager.movementMethod == MovementMethod.PointAndClick)
{
if (characterAC.charState == CharState.Move)
{
controlState = ControlState.UnityPathfinding;
}
else
{
controlState = ControlState.ACTurning;
}
}
else
{
controlState = ControlState.UnderDirectControl;
}
}
private void UpdateMovement()
{
switch (controlState)
{
case ControlState.UnderDirectControl:
if (navMeshAgent != null)
{
navMeshAgent.enabled = false;
}
else
{
ACDebug.LogWarning("A NavMeshAgent component is required on " + gameObject.name, this);
}
if (!m_Jump)
{
m_Jump = Input.GetButtonDown("Jump");
}
break;
case ControlState.UnityPathfinding:
if (navMeshAgent != null)
{
navMeshAgent.enabled = true;
}
if (navMeshAgent != null && navMeshAgent.isOnNavMesh)
{
if (navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance)
{
if (characterAC.isRunning)
{
m_moveMultiplier = 1.0f;
}
else
{
m_moveMultiplier = 0.5f;
}
characterTP.Move(navMeshAgent.desiredVelocity * m_moveMultiplier, false, false);
}
else
{
characterTP.Move(Vector3.zero, false, false);
}
}
else
{
characterAC.Teleport(characterAC.GetTargetPosition(true));
ACDebug.LogWarning("Character " + name + " cannot pathfind without a NavMeshAgent that is on the NavMesh - teleporting instead.", gameObject);
}
break;
case ControlState.ACTurning:
characterTP.Move (Vector3.zero, false, false);
break;
default:
break;
}
}
private void UpdateDirectInput()
{
float h = Input.GetAxis ("Horizontal");
float v = Input.GetAxis ("Vertical");
bool crouch = Input.GetKey(KeyCode.C);
if (m_mainCamera != null)
{
// calculate camera relative direction to move:
if (KickStarter.settingsManager.directMovementPerspective)
{
Vector3 forwardVector = (characterAC.transform.position - m_mainCamera.transform.position).normalized;
Vector3 rightVector = -Vector3.Cross(forwardVector, m_mainCamera.transform.up);
m_Move = (v * forwardVector) + (h * rightVector);
}
else
{
m_CamForward = Vector3.Scale(m_mainCamera.transform.forward, new Vector3(1, 0, 1)).normalized;
m_Move = v * m_CamForward + h * m_mainCamera.transform.right;
}
}
else
{
m_Move = v * Vector3.forward + h * Vector3.right;
}
if (Input.GetKey(KeyCode.LeftShift))
{
m_Move *= 0.5f;
}
characterTP.Move (m_Move, crouch, m_Jump);
m_Jump = false;
}
private void OnTeleport()
{
if (navMeshAgent != null && navMeshAgent.isOnNavMesh)
{
navMeshAgent.Warp(transform.position);
}
}
void OnAnimatorMove()
{
if (navMeshAgent != null)
{
agentPosition.x = transform.localPosition.x;
agentPosition.y = navMeshAgent.nextPosition.y;
agentPosition.z = transform.localPosition.z;
navMeshAgent.nextPosition = agentPosition;
}
}
void SetCharacterPath(Char character, Paths paths)
{
navMeshAgent.SetDestination (characterAC.GetTargetPosition (true));
}
}