This commit is contained in:
2026-06-14 23:57:44 +07:00
parent 20f9010787
commit 78d7b2f5a7
5775 changed files with 4796241 additions and 5 deletions

View File

@@ -0,0 +1,130 @@
/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.ThirdPersonController.Character.Abilities
{
using Opsive.Shared.Events;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.MovementTypes;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
/// <summary>
/// Follows a path specified by the 2.5D movement type. Ensures the character stays at a consistant horizontal offset.
/// </summary>
[DefaultStartType(AbilityStartType.Manual)]
public class FollowPseudo3DPath : Ability
{
public override bool IsConcurrent { get { return true; } }
private MovementTypes.Pseudo3D m_Pseudo3DMovementType;
private int m_PathIndex;
private float m_Offset;
/// <summary>
/// Initialize the default values.
/// </summary>
public override void Awake()
{
base.Awake();
EventHandler.RegisterEvent<MovementType, bool>(m_GameObject, "OnCharacterChangeMovementType", OnCharacterChangeMovementType);
}
/// <summary>
/// The specified movement type has been activated or deactivated.
/// </summary>
/// <param name="movementType">The movement type that has been activated or deactivated.</param>
/// <param name="active">Is the movement type active?</param>
private void OnCharacterChangeMovementType(MovementType movementType, bool active)
{
if (!(movementType is MovementTypes.Pseudo3D)) {
return;
}
if (!IsActive && active) {
// If the movement type is active then the ability should start if a path exists.
var pseudo3DMovementType = movementType as MovementTypes.Pseudo3D;
if (pseudo3DMovementType.Path != null) {
StartAbility();
return;
}
} else if (IsActive && !active) {
// If the movement type is no longer active then the ability should stop.
StopAbility();
}
}
/// <summary>
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
/// </summary>
/// <returns>True if the ability can be started.</returns>
public override bool CanStartAbility()
{
if (!base.CanStartAbility()) {
return false;
}
return (m_CharacterLocomotion.ActiveMovementType is MovementTypes.Pseudo3D) && (m_CharacterLocomotion.ActiveMovementType as MovementTypes.Pseudo3D).Path != null;
}
/// <summary>
/// The ability has started.
/// </summary>
protected override void AbilityStarted()
{
base.AbilityStarted();
m_Pseudo3DMovementType = m_CharacterLocomotion.ActiveMovementType as MovementTypes.Pseudo3D;
// Store the horizontal offset between the character and the path. This offset will ensure the character stays at a constant horizontal position.
var pathRotation = Quaternion.LookRotation(m_Pseudo3DMovementType.Path.GetTangent(m_Transform.position, ref m_PathIndex));
var closestPoint = m_Pseudo3DMovementType.Path.GetClosestPoint(m_Transform.position, ref m_PathIndex);
m_Offset = MathUtility.InverseTransformPoint(m_Transform.position, pathRotation, closestPoint).x;
}
/// <summary>
/// Stop the ability if the path no longer exists.
/// </summary>
public override void Update()
{
if (m_Pseudo3DMovementType.Path == null) {
StopAbility();
}
}
/// <summary>
/// Update the controller's position values.
/// </summary>
public override void UpdatePosition()
{
// Retireve the closest point and tangent of the path at the target position. This position will be used when determining if the character needs to change
// horizontal offsets.
var targetPosition = m_Transform.position + m_CharacterLocomotion.MoveDirection;
var closestPoint = m_Pseudo3DMovementType.Path.GetClosestPoint(targetPosition, ref m_PathIndex);
var pathRotation = Quaternion.LookRotation(m_Pseudo3DMovementType.Path.GetTangent(targetPosition, ref m_PathIndex));
// Update the offset if the character can move along the relative x axis. The depth term is used in relation to the camera's depth axis.
if (m_Pseudo3DMovementType.AllowDepthMovement && m_CharacterLocomotion.RawInputVector.y != 0) {
m_Offset -= MathUtility.InverseTransformDirection(m_CharacterLocomotion.MotorThrottle, pathRotation).x;
}
// Update the target position based on the difference between the current offset and the stored offset.
var offset = MathUtility.InverseTransformPoint(targetPosition, pathRotation, closestPoint).x;
targetPosition = MathUtility.TransformPoint(targetPosition, pathRotation, Vector3.right * (offset - m_Offset));
m_CharacterLocomotion.MoveDirection = targetPosition - m_Transform.position;
}
/// <summary>
/// The character has been destroyed.
/// </summary>
public override void OnDestroy()
{
base.OnDestroy();
EventHandler.UnregisterEvent<MovementType, bool>(m_GameObject, "OnCharacterChangeMovementType", OnCharacterChangeMovementType);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f2b3cd302b28cf54ebb3823309cb9bbf
timeCreated: 1528491971
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 415769a5d0a2f5b4baea70728223b0d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,179 @@
/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.ThirdPersonController.Character.Abilities.Items
{
using Opsive.Shared.Events;
using Opsive.Shared.Game;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.Abilities.Items;
using Opsive.UltimateCharacterController.Game;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
/// <summary>
/// Pulls back the item if the character gets too close to a wall. This will prevent the item from clipping with the wall.
/// </summary>
[DefaultStopType(AbilityStopType.Automatic)]
public class ItemPullback : ItemAbility
{
[Tooltip("The collider used to detect when the character is near an object and should pull back the items.")]
[SerializeField] protected Collider m_Collider;
[Tooltip("The layers that the collider can collide with.")]
[SerializeField] protected LayerMask m_CollisionLayers = ~(1 << LayerManager.IgnoreRaycast | 1 << LayerManager.TransparentFX | 1 << LayerManager.SubCharacter |
1 << LayerManager.Overlay | 1 << LayerManager.VisualEffect | 1 << LayerManager.Water);
[Tooltip("The maximum number of collisions that should be detected by the collider.")]
[SerializeField] protected int m_MaxCollisionCount = 5;
public Collider Collider { get { return m_Collider; } set { m_Collider = value; } }
public LayerMask CollisionLayers { get { return m_CollisionLayers; } set { m_CollisionLayers = value; } }
private Transform m_ColliderTransform;
private Collider[] m_HitColliders;
/// <summary>
/// Initialize the default values.
/// </summary>
public override void Awake()
{
base.Awake();
if (m_Collider == null || (!(m_Collider is CapsuleCollider) && !(m_Collider is SphereCollider))) {
Debug.LogError("Error: Only Capsule and Sphere Colliders are supported by the Item Pullback ability.");
m_Collider = null;
Enabled = false;
return;
}
m_HitColliders = new Collider[m_MaxCollisionCount];
m_ColliderTransform = m_Collider.transform;
m_Collider.gameObject.SetActive(false);
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCameraWillChangePerspectives", OnChangePerspectives);
}
/// <summary>
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
/// </summary>
/// <returns>True if the ability can be started.</returns>
public override bool CanStartAbility()
{
return HasCollision();
}
/// <summary>
/// Is there a collision between the item pullback collider and another object?
/// </summary>
/// <returns>True if there is a collision with the item pullback collider.</returns>
private bool HasCollision()
{
int hitCount;
if (m_Collider is CapsuleCollider) {
var capsuleCollider = m_Collider as CapsuleCollider;
if (capsuleCollider.radius == 0) {
return false;
}
Vector3 startEndCap, endEndCap;
MathUtility.CapsuleColliderEndCaps(capsuleCollider, m_ColliderTransform.position, m_ColliderTransform.rotation, out startEndCap, out endEndCap);
hitCount = Physics.OverlapCapsuleNonAlloc(startEndCap, endEndCap, capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider), m_HitColliders, m_CollisionLayers, QueryTriggerInteraction.Ignore);
} else { // SphereCollider.
var sphereCollider = m_Collider as SphereCollider;
if (sphereCollider.radius == 0) {
return false;
}
hitCount = Physics.OverlapSphereNonAlloc(m_ColliderTransform.position, sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider), m_HitColliders, m_CollisionLayers, QueryTriggerInteraction.Ignore);
}
for (int i = 0; i < hitCount; ++i) {
// Objects which are children of the character aren't considered a collision.
if (m_HitColliders[i].transform.IsChildOf(m_Transform)) {
continue;
}
// Projectiles shouldn't prevent the pullback ability.
if (m_HitColliders[i].gameObject.GetCachedComponent<Objects.Projectile>() != null) {
continue;
}
// It only takes one object for the ability to be in a collision state.
return true;
}
return false;
}
/// <summary>
/// Called when another ability is attempting to start and the current ability is active.
/// Returns true or false depending on if the new ability should be blocked from starting.
/// </summary>
/// <param name="startingAbility">The ability that is starting.</param>
/// <returns>True if the ability should be blocked.</returns>
public override bool ShouldBlockAbilityStart(Ability startingAbility)
{
return CanStopAbility(startingAbility);
}
/// <summary>
/// Called when the current ability is attempting to start and another ability is active.
/// Returns true or false depending on if the active ability should be stopped.
/// </summary>
/// <param name="activeAbility">The ability that is currently active.</param>
/// <returns>True if the ability should be stopped.</returns>
public override bool ShouldStopActiveAbility(Ability activeAbility)
{
return CanStopAbility(activeAbility);
}
/// <summary>
/// Can the Item Pullback ability stop the specified ability?
/// </summary>
/// <param name="ability">The ability that may be able to be stopped.</param>
/// <returns>True if the ability can be stopped.</returns>
private bool CanStopAbility(Ability ability)
{
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
if (ability is Use) {
var useAbility = ability as Use;
return useAbility.UsesItemActionType(typeof(UltimateCharacterController.Items.Actions.ShootableWeapon));
}
if (ability is Reload) {
return true;
}
#endif
return false;
}
/// <summary>
/// Can the ability be stopped?
/// </summary>
/// <returns>True if the ability can be stopped.</returns>
public override bool CanStopAbility()
{
return !HasCollision();
}
/// <summary>
/// The character perspective between first and third person has changed.
/// </summary>
/// <param name="firstPersonPerspective">Is the character in a first person perspective?</param>
private void OnChangePerspectives(bool firstPersonPerspective)
{
// Item Pullback does not work in first person mode.
Enabled = !firstPersonPerspective;
}
/// <summary>
/// The character has been destroyed.
/// </summary>
public override void OnDestroy()
{
base.OnDestroy();
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCameraWillChangePerspectives", OnChangePerspectives);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 453263f004c12c74e871dd76b4dfa8c4
timeCreated: 1522777146
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,68 @@
/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.ThirdPersonController.Character.Abilities.Items
{
using Opsive.UltimateCharacterController.StateSystem;
using UnityEngine;
/// <summary>
/// Allows the ItemPullback collider to be resized/positioned based upon the state.
/// </summary>
public class ItemPullbackCollider : StateBehavior
{
[Tooltip("The offset to apply to the transform relative to the starting local position.")]
[SerializeField] protected Vector3 m_LocalPositionOffset;
[Tooltip("The offset to apply to the radius to the starting radius.")]
[SerializeField] protected float m_RadiusOffset;
public Vector3 LocalPositionOffset { get { return m_LocalPositionOffset; } set { m_LocalPositionOffset = value; } }
public float RadiusOffset { get { return m_RadiusOffset; } set { m_RadiusOffset = value; } }
private Transform m_Transform;
private Collider m_Collider;
private Vector3 m_LocalPosition;
private float m_Radius;
/// <summary>
/// Initialize the default values.
/// </summary>
protected override void Awake()
{
base.Awake();
m_Collider = GetComponent<Collider>();
if (!(m_Collider is CapsuleCollider) && !(m_Collider is SphereCollider)) {
Debug.LogWarning("Warning: The ItemPullbackCollider only supports capsule and sphere colliders.");
enabled = false;
return;
}
m_Transform = transform;
m_LocalPosition = m_Transform.localPosition;
if (m_Collider is CapsuleCollider) {
m_Radius = (m_Collider as CapsuleCollider).radius;
} else { // SphereCollider
m_Radius = (m_Collider as SphereCollider).radius;
}
}
/// <summary>
/// Callback when the StateManager has changed the active state on the current object.
/// </summary>
public override void StateChange()
{
m_Transform.localPosition = m_LocalPosition + m_LocalPositionOffset;
if (m_Collider is CapsuleCollider) {
(m_Collider as CapsuleCollider).radius = m_Radius + m_RadiusOffset;
} else { // SphereCollider
(m_Collider as SphereCollider).radius = m_Radius + m_RadiusOffset;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 156bcc35929b82b49bb622963c6def6a
timeCreated: 1522787067
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: