using UnityEngine;
using Opsive.UltimateCharacterController.Character.Abilities;
using Obi;
using Opsive.UltimateCharacterController.Character;
using Opsive.UltimateCharacterController.Utility;
using Opsive.Shared.Events;
[DefaultStartType(AbilityStartType.ButtonDown)]
[DefaultStopType(AbilityStopType.ButtonToggle)]
[DefaultInputName("Action")]
[DefaultAbilityIndex(1337)]
[DefaultAllowPositionalInput(false)]
[DefaultAllowRotationalInput(true)]
[DefaultUseGravity(AbilityBoolOverride.False)]
[DefaultUseRootMotionPosition(AbilityBoolOverride.True)]
public class RopeClimb : DetectObjectAbilityBase
{
[Tooltip("A reference to the Ultimate Character Controller character.")]
[SerializeField] private GameObject m_Character;
private RopeClimb climbAbility;
private ObiActor ropeComp;
//Climbing Variables
[SerializeField] private float m_ClimbingSpeed=.5f;
private int m_MountParticle = 0;
private int m_CurrentParticleOnRopeLength = 0;
private int m_ActorParticles=0;
//True means the right hand is pulling the rope primarily, false means the left hand is.
private bool ClimbingWithRightHand=true;
[SerializeField] private Transform handRPos;
[SerializeField] private Transform handLPos;
[SerializeField] private Transform footRPos;
[SerializeField] private Transform footLPos;
private Animator m_Animator;
private float m_RopeProgress;
#region Awake
/// <summary>
/// Initialization including animation event registering.
/// </summary>
public override void Awake()
{
base.Awake();
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorLeftHand", LeftHand);
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorRightHand", RightHand);
}
#endregion
#region Ability Started
/// <summary>
/// RopeClimb ability can start when the character is inside a rope collider on an object with an obi rope actor component.
/// </summary>
protected override void AbilityStarted()
{
base.AbilityStarted();
m_Character.transform.SetParent(m_DetectedObject.transform);
ropeComp = m_DetectedObject.GetComponent<ObiActor>();
int nearestParticle = FindClosestRopeParticle();
m_CurrentParticleOnRopeLength = nearestParticle;
m_Animator = m_Character.GetComponent<Animator>();
//ToDo: Mount the nearest particle with Move Towards
//Set the nearest particle as the ability start location
}
#endregion
#region Animation Events
/// <summary>
/// These are called by animation events on the climbing ability when his corresponding hand grabs the rope.
/// </summary>
///
private void RightHand()
{
ClimbingWithRightHand = true;
Debug.Log("using the right hand to climb.");
}
private void LeftHand()
{
ClimbingWithRightHand = false;
Debug.Log("using the left hand to climb.");
}
#endregion
#region Find Nearest Rope Particle
/// <summary>
/// Find the nearest particle in the interacted rope's particle index and return it.
/// </summary>
public int FindClosestRopeParticle()
{
//Get the full length of the rope.
m_ActorParticles = ropeComp.solverIndices.Length;
var closestIndexDistance = 100f;
int closestIndex=0;
if (handRPos == null || handLPos == null)
{
Debug.LogError("Hand positions haven't been assigned on the rope climb ability.");
return closestIndex;
}
//Find nearest particle to players hand
foreach (int solverIndex in ropeComp.solverIndices)
{
Vector3 pos = ropeComp.solver.positions[solverIndex];
var handPos = ClimbingWithRightHand? handRPos.position : handLPos.position;
float distance = Vector3.Distance(handPos, pos);
if (distance < closestIndexDistance)
{
closestIndexDistance = distance;
closestIndex = solverIndex;
}
}
return closestIndex;
}
#endregion
#region Ability Stopped
/// <summary>
/// Whenever the ability stops - error or on purpose - we will run this to reset the ability variables and stop the ability's motion updating.
/// </summary>
/// <param name="force"></param>
protected override void AbilityStopped(bool force)
{
base.AbilityStopped(force);
m_RopeProgress = 0;
m_Animator.SetFloat("AbilityFloatData", 0f);
m_Animator.SetBool("Moving", false); //Make sure the moving bool isn't set by this ability anymore.
//Get a reference to the character locomotion component
var characterLocomotion = m_Character.GetComponent<UltimateCharacterLocomotion>();
characterLocomotion.UseGravity = true;
characterLocomotion.AbilityMotor = Vector3.zero;
m_Character.transform.SetParent(null);
Debug.Log("Rope Climb Ability Stopped");
}
#endregion
#region Climbing Logic
/// <summary>
/// This is the Rope Climbing Ability's Update Position logic, which basically decides how the character climbs.
/// </summary>
public override void UpdatePosition()
{
//Get a reference to the character locomotion component
var characterLocomotion = m_Character.GetComponent<UltimateCharacterLocomotion>();
if (characterLocomotion.GetAbility<RopeClimb>().IsActive)
{
base.UpdatePosition();
//Set some empty vectors to allow for the movement to smooth between them.
Vector3 upPart = new Vector3(0, 0, 0);
Vector3 downPart = new Vector3(0, 0, 0);
var vInp = Input.GetAxis("Vertical");
if (vInp != 0)
{
//if the vertical input is more than 0 in either direction, set the perche position appropriately
if (vInp > 0.2f || vInp < -0.2f)
{
m_RopeProgress += vInp * m_ClimbingSpeed * Time.deltaTime;
//Running out of rope, but still climbing? Then we will go ahead and dismount.
if (m_CurrentParticleOnRopeLength == m_ActorParticles - 2 && vInp > 0.2f || m_CurrentParticleOnRopeLength == 0 && vInp < -0.2f)
{
//Dismounting code and check for top or bottom will go here.
StopAbility(true);
//We go ahead and return, so the code ends here instead of trying to continue controlling the movement - which would cause the character an endless flight.
return;
}
}
m_Animator.SetFloat("AbilityFloatData", 1f);
m_Animator.SetBool("Moving", vInp > 0 ? true : false); //Is the character ascending or descending?
// Debug.Log("reaching for particle number:" + m_CurrentParticleOnRopeLength);
// Debug.Log(m_RopeProgress);
}
else
{
m_Animator.SetFloat("AbilityFloatData", 0f);
}
m_CurrentParticleOnRopeLength = (int)m_RopeProgress / m_ActorParticles;
upPart = ropeComp.solver.positions[m_CurrentParticleOnRopeLength + 1];
downPart = ropeComp.solver.positions[m_CurrentParticleOnRopeLength];
var handPos = ClimbingWithRightHand ? handRPos.position : handLPos.position;
//Making sure the handplacement is relatively in line with the rope. This code will have to change once the rope can move and interact with the character again.
var xTarget = Mathf.Lerp(downPart.x, upPart.x, .5f);
var zTarget = Mathf.Lerp(downPart.z, upPart.z, .5f);
//Getting an alterable copy of the handPos.
var inlineHandPos = handPos;
//Altering it's x and z values. This is not totally bugless so I commented it out for now until I solve the bugs.
/*inlineHandPos.x = xTarget;
inlineHandPos.z = zTarget;*/
//Getting the handplacement between two particles
float handPlacement = Vector3.Distance(inlineHandPos, downPart) / Vector3.Distance(downPart, upPart);
//Turning that into a Vector3
Vector3 handPlacementVector = Vector3.Lerp(upPart, downPart, handPlacement) - inlineHandPos;
/*This is for debugging the hand switching behavior.
*
var handName = ClimbingWithRightHand ? "right" : "left";
Debug.Log("Climbing with "+ handName + " hand.");*/
//Moving the handPos to the placement vector effectively.
var deltaPosition = handPlacementVector;
//Driving that movement into AbilityMotor
m_CharacterLocomotion.AbilityMotor = deltaPosition / (m_CharacterLocomotion.TimeScaleSquared * Time.timeScale * TimeUtility.FramerateDeltaTime);
}
}
#endregion
}