Opsive and Edy’s Vehicle Physics (EVP) – Version 2

Version 2 of Third Person Controller (Ultimate Character Controller) and Edy’s Vehicle Physics Integration. As I only have the Third Person piece, I do not know how this will work with the First Person piece.

This integration allows the player to control vehicles and enter/exit smoothly. I would like to enhance this to allow for passengers, changing seats, and using weapons while in a passenger seat. Feel free to share suggested updates with code.

Below is a rough How To document. I did not elaborate on all the presets I created which include ones for the CameraController, CharacterIK, MoveWithObject, UltimateCharacterLocomotion. I also did not talk about the Animator, so watch the prior video for that piece.

Only Tested with UCC Version 2.1.5 and Unity 2019.1.

Below is the Vehicle Manager script to be added to each vehicle.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EVP;

public class EdyVehicleManager : MonoBehaviour
{
    #region Vairables - Edy's Vehicle Physics
    VehicleStandardInput m_VehicleStandardInput;
    VehicleController m_VehicleController;
    VehicleAudio m_VehicleAudio;
    #endregion

    #region Variables - Public to be used by the UCC Ability System
    [Tooltip("Create a new prefab, add the FollowVehiclePosition script, and the Kinematic Object Script.  Then set the Layer to MovingPlatform.")]
    public GameObject FollowPrefab;
    [HideInInspector]
    public GameObject newFollowPrefab;
    [Tooltip("This is the position that the UCC Character will be moved to upon entering.  Make sure it doesn't stick through the floor.")]
    public Transform drivePosition;
    [Tooltip("This is the position that the UCC Character will be moved to upon entering.  Make sure it doesn't stick through the floor.")]
    public Transform lookPosition;
    [Tooltip("This is the position of the Left Hand.  If there is a steering wheel, add it there.")]
    public GameObject LeftHandIKTarget;
    [Tooltip("This is the position of the Right Hand.  If there is a steering wheel, add it there.")]
    public GameObject RightHandIKTarget;
    [Tooltip("This is the position of the Left Elbow.")]
    public GameObject LeftElbowIKTarget;
    [Tooltip("This is the position of the Right Elbow..")]
    public GameObject RightElbowIKTarget;
    [Tooltip("This is the position of the Left Foot.  Make sure it doesn't stick through the floor.")]
    public GameObject LeftFootIKTarget;
    [Tooltip("This is the position of the Right Foot.  Make sure it doesn't stick through the floor.")]
    public GameObject RightFootIKTarget;
    [Tooltip("This is the position of the Left Knee.  Make sure it doesn't stick through the door.")]
    public GameObject LeftKneeIKTarget;
    [Tooltip("This is the position of the Right Knee.  Make sure it doesn't stick through the door.")]
    public GameObject RightKneeIKTarget;
    #endregion

    // Start is called before the first frame update
    void Start()
    {
        #region Assign the Components from Edy's
        m_VehicleStandardInput = this.GetComponent<VehicleStandardInput>();
        m_VehicleController = this.GetComponent<VehicleController>();
        m_VehicleAudio = this.GetComponent<VehicleAudio>();
        #endregion

        #region Disable the appropriate components
        m_VehicleStandardInput.enabled = false;
        m_VehicleAudio.enabled = false;
        #endregion

        #region Make sure the Vehicle doesn't move around
        m_VehicleController.throttleInput = 0.0f;
        m_VehicleController.brakeInput = 1.0f;
        #endregion

        if(drivePosition == null)
        {
            Debug.Log("Drive Position was not assigned", gameObject);
            drivePosition = transform;
        }
        if (lookPosition == null)
        {
            Debug.Log("Look Position was not assigned", gameObject);
            lookPosition = transform;
        }

        //General IK Target Warnings
        if (LeftHandIKTarget == null)
            Debug.Log("Left Hand IK Target is not set, so IK Left Hand won't work on this ", gameObject);
        if (RightHandIKTarget == null)
            Debug.Log("Right Hand IK Target is not set, so IK Right Hand won't work on this ", gameObject);
        if (LeftElbowIKTarget == null)
            Debug.Log("Left Elbow IK Target is not set, so IK Left Elbow won't work on this ", gameObject);
        if (RightElbowIKTarget == null)
            Debug.Log("Right Elbow IK Target is not set, so IK Right Elbow won't work on this ", gameObject);
        if (LeftFootIKTarget == null)
            Debug.Log("Left Foot IK Target is not set, so IK Left Foot won't work on this ", gameObject);
        if (RightFootIKTarget == null)
            Debug.Log("Right Foot IK Target is not set, so IK Right Foot won't work on this ", gameObject);
        if (LeftKneeIKTarget == null)
            Debug.Log("Left Knee IK Target is not set, so IK Left Knee won't work on this ", gameObject);
        if (RightKneeIKTarget == null)
            Debug.Log("Right Knee IK Target is not set, so IK Right Knee won't work on this ", gameObject);

        NewObject();
    }

    void NewObject()
    {
        newFollowPrefab=Instantiate(FollowPrefab, drivePosition.position, drivePosition.rotation);
        newFollowPrefab.GetComponent<FollowVehiclePosition>().positionToFollow = drivePosition;
        newFollowPrefab.GetComponent<FollowVehiclePosition>().lookPosition = lookPosition;
    }
}

Below is the Vehicle Follow script to be added to a NEW prefab and assigned in the Vehicle Manager above in the Editor.

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Rigidbody))]
public class FollowVehiclePosition : MonoBehaviour
{
    public Transform positionToFollow = null;
    public Transform lookPosition = null;

    Rigidbody m_rigidbody = null;
    Vector3 m_rotateVector3 = new Vector3();
    
    void Start()
    {
        m_rigidbody = GetComponent<Rigidbody>();
        m_rigidbody.isKinematic = true;
        m_rigidbody.useGravity = false;
        m_rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
        m_rigidbody.angularDrag = 0f;
    }

    void FixedUpdate()
    {
        TransformRotation();
        TransformMove();
    }

    void TransformRotation()
    {
        m_rotateVector3 = new Vector3(lookPosition.position.x,
            transform.position.y,
            lookPosition.position.z);
        transform.LookAt(m_rotateVector3);
    }

    void TransformMove()
    {
        transform.position = positionToFollow.position;
    }
}

Below is the Ability which will automatically show up as an Ability Option on your character.

using UnityEngine;
using Opsive.UltimateCharacterController.Character.Abilities; //Used for DetectObjectAbilityBase
using Opsive.UltimateCharacterController.Character; //Used for the UCC Character IK
using Opsive.UltimateCharacterController.Objects.CharacterAssist; //Used for the UCC AbilityIKTarget
using EVP; //Used for Edy's Vehicle Physics
using Opsive.UltimateCharacterController.Character.Abilities.Items;

/// <summary>
/// Chris "RedHawk" Ferguson copyright 2019
/// </summary>

public class EdyDriveAbility : DetectObjectAbilityBase
{
    #region Vairables - Edy's Vehicle Physics
    VehicleStandardInput m_VehicleStandardInput;
    VehicleController m_VehicleController;
    VehicleAudio m_VehicleAudio;
    EdyVehicleManager m_EdyVehicleManager;
    #endregion

    #region Variables - General
    Transform m_Vehicle;
    Collider[] m_OriginalColliders;
    GameObject InteractLocation;
    #endregion

    public override void Start()
    {
        base.Start();
    }

    //Prevent Ability from being prematurely stopped
    public override bool CanForceStopAbility()
    {
        return false;
    }

    /// <summary>
    /// The ability has started.
    /// </summary>
    protected override void AbilityStarted()
    {
        base.AbilityStarted();
        //DO GET IN STUFF
        #region Assign Edy's Vehicle Pieces
        m_Vehicle = m_DetectedObject.GetComponentInParent<VehicleStandardInput>().transform;
        m_EdyVehicleManager = m_Vehicle.GetComponentInChildren<EdyVehicleManager>();
        m_VehicleStandardInput = m_Vehicle.GetComponent<VehicleStandardInput>();
        m_VehicleController = m_Vehicle.GetComponent<VehicleController>();
        m_VehicleAudio = m_Vehicle.GetComponent<VehicleAudio>();
        #endregion

        #region UCC Colliders and Positioning

        //Locate the Colliders and make them IsTrigger TRUE
        m_OriginalColliders = m_CharacterLocomotion.Colliders;
        m_OriginalColliders[0].isTrigger = true;

        m_CharacterLocomotion.GetComponentInParent<UltimateCharacterLocomotion>().GetAbility<MoveWithObject>().Target = m_EdyVehicleManager.newFollowPrefab.transform;
        //Use the position contained in the Vehicle Manager
        if (m_EdyVehicleManager == null)
        {
            m_CharacterLocomotion.SetPosition(m_Vehicle.transform.position);
            m_CharacterLocomotion.SetRotation(m_Vehicle.transform.rotation);
            Debug.Log("There was no EdyVehicleManager on this Vehicle", m_GameObject);
        }
        else
        {
            m_CharacterLocomotion.SetPosition(m_EdyVehicleManager.newFollowPrefab.transform.position);
            m_CharacterLocomotion.SetRotation(m_EdyVehicleManager.newFollowPrefab.transform.rotation);
        }

        //IK Targets - TURN ON
        CharacterIK m_characterIK = m_CharacterLocomotion.gameObject.GetComponent<CharacterIK>();
        m_characterIK.SetAbilityIKTarget(m_EdyVehicleManager.LeftFootIKTarget.transform, m_EdyVehicleManager.LeftFootIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(m_EdyVehicleManager.RightFootIKTarget.transform, m_EdyVehicleManager.RightFootIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(m_EdyVehicleManager.LeftHandIKTarget.transform, m_EdyVehicleManager.LeftHandIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(m_EdyVehicleManager.RightHandIKTarget.transform, m_EdyVehicleManager.RightHandIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(m_EdyVehicleManager.LeftKneeIKTarget.transform, m_EdyVehicleManager.LeftKneeIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(m_EdyVehicleManager.RightKneeIKTarget.transform, m_EdyVehicleManager.RightKneeIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(m_EdyVehicleManager.LeftElbowIKTarget.transform, m_EdyVehicleManager.LeftElbowIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(m_EdyVehicleManager.RightElbowIKTarget.transform, m_EdyVehicleManager.RightElbowIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        #endregion

        #region Edy's Vehicle Physics Stuff
        m_VehicleStandardInput.enabled = true;
        m_VehicleAudio.enabled = true;
        #endregion
        InteractLocation = m_DetectedObject;
    }


    /// <summary>
    /// The Ability has ended
    /// </summary>
    /// <param name="force"></param>
    protected override void AbilityStopped(bool force)
    {
        base.AbilityStopped(force);
        //DO GET OUT STUFF
        #region Edy's Vehicle Physics Stuff
        //Stop the vehicle and put the brakes on
        m_VehicleController.throttleInput = 0.0f;
        m_VehicleController.brakeInput = 1.0f;
        //Disable the EVP Input
        m_VehicleStandardInput.enabled = false;
        //Disable the EVP Audio
        m_VehicleAudio.enabled = false;
        #endregion

        #region UCC Colliders and Positioning
        m_CharacterLocomotion.GetComponentInParent<UltimateCharacterLocomotion>().GetAbility<MoveWithObject>().Target = null;

        m_CharacterLocomotion.SetPosition(InteractLocation.transform.position);
        m_CharacterLocomotion.SetRotation(InteractLocation.transform.rotation);

        //Turn Colliders back to Trigger On
        m_OriginalColliders[0].isTrigger = false;

        //IK Targets - TURN OFF
        CharacterIK m_characterIK = m_CharacterLocomotion.gameObject.GetComponent<CharacterIK>();
        m_characterIK.SetAbilityIKTarget(null, m_EdyVehicleManager.LeftFootIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(null, m_EdyVehicleManager.RightFootIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(null, m_EdyVehicleManager.LeftHandIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(null, m_EdyVehicleManager.RightHandIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(null, m_EdyVehicleManager.LeftKneeIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(null, m_EdyVehicleManager.RightKneeIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(null, m_EdyVehicleManager.LeftElbowIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        m_characterIK.SetAbilityIKTarget(null, m_EdyVehicleManager.RightElbowIKTarget.GetComponent<AbilityIKTarget>().Goal, 0f);
        #endregion
    }

    public override bool CanStopAbility()
    {
        return ExitDriverBlocked(InteractLocation.GetComponent<Collider>());
    }

    private bool ExitDriverBlocked(Collider exitCollider)
    {
        Collider[] overlap = new Collider[1];
        int hitCount = 0;
        if (exitCollider is BoxCollider)
        {
            var boxCollider = exitCollider as BoxCollider;
            hitCount = Physics.OverlapBoxNonAlloc(exitCollider.transform.TransformPoint(boxCollider.center), Vector3.Scale(boxCollider.size, boxCollider.transform.lossyScale) / 2,
                                overlap, exitCollider.transform.rotation, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore);
        }
        if (hitCount > 0)
            return false;
        else
            return true;
    }


    public override bool ShouldBlockAbilityStart(Ability startingAbility)
    {
        return startingAbility is ItemAbility;
    }
}

As always, feel free to make suggestions on how I can improve this.  Please make those suggestions on the Opsive Forum under my Edy’s integrations post. https://opsive.com/forum/index.php?threads/edys-vehicle-physics.720/