Added jump double jump and mid air movement
This commit is contained in:
parent
ecde656551
commit
817edd2938
@ -2500,9 +2500,22 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
moveSpeed: 1
|
||||
laneDistance: 0.4
|
||||
jumpPower: 0.6
|
||||
jumpDuration: 1.2
|
||||
laneSwitchSpeed: 10
|
||||
maxHoldTime: 0.25
|
||||
initialJumpVelocity: 6.5
|
||||
holdUpwardAcceleration: 35
|
||||
lowJumpGravityMultiplier: 2
|
||||
fallGravityMultiplier: 2.5
|
||||
coyoteTime: 0.12
|
||||
jumpBufferTime: 0.12
|
||||
maxVerticalSpeed: 15
|
||||
allowAirLaneSwitch: 1
|
||||
groundProbe: {fileID: 0}
|
||||
groundMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
groundCheckRadius: 0.2
|
||||
groundCheckExtraDown: 0.06
|
||||
useSpeedBlendTree: 1
|
||||
speedParamName: Speed
|
||||
runSpeedParamValue: 2
|
||||
@ -2513,11 +2526,13 @@ MonoBehaviour:
|
||||
secondHitWindow: 10
|
||||
stateWaitTimeout: 3
|
||||
waitingForGameOver: 0
|
||||
fallingShortHash: 0
|
||||
validateStatesOnStart: 1
|
||||
runTag: Run
|
||||
fallTag: Fall
|
||||
fallingTag: Falling
|
||||
runShortHash: 0
|
||||
fallShortHash: 0
|
||||
fallingShortHash: 0
|
||||
--- !u!65 &1704108065
|
||||
BoxCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -6238,7 +6253,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
m_IsActive: 0
|
||||
--- !u!23 &3681778087044869278
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -8840,6 +8855,18 @@ PrefabInstance:
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6341515697253773609, guid: 515cc30092d66fe4dba290f9d6415a60, type: 3}
|
||||
propertyPath: initialJumpVelocity
|
||||
value: 4
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6341515697253773609, guid: 515cc30092d66fe4dba290f9d6415a60, type: 3}
|
||||
propertyPath: fallGravityMultiplier
|
||||
value: 1.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6341515697253773609, guid: 515cc30092d66fe4dba290f9d6415a60, type: 3}
|
||||
propertyPath: holdUpwardAcceleration
|
||||
value: 20
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7027419816435059383, guid: 515cc30092d66fe4dba290f9d6415a60, type: 3}
|
||||
propertyPath: m_Material
|
||||
value:
|
||||
@ -8848,6 +8875,10 @@ PrefabInstance:
|
||||
propertyPath: m_Name
|
||||
value: ChasePlayer
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7336801398090946996, guid: 515cc30092d66fe4dba290f9d6415a60, type: 3}
|
||||
propertyPath: m_IsActive
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
|
@ -1,76 +1,132 @@
|
||||
using System.Collections;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Endless runner controller:
|
||||
/// - Forward motion (−Z), 3 lanes with smooth switching (ground & air)
|
||||
/// - Tap = small jump, Hold (within window) = higher jump
|
||||
/// - Coyote time + jump buffer for forgiving inputs
|
||||
/// - Robust ground detection (collider-based sphere cast with optional probe)
|
||||
/// - Animator driving preserved; obstacle/fall flow preserved
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class ChasePlayerController : MonoBehaviour
|
||||
{
|
||||
// ===== Movement =====
|
||||
[Header("Movement")]
|
||||
public float moveSpeed = 5f;
|
||||
public float laneDistance = 2.5f; // 0=Left,1=Mid,2=Right
|
||||
public float jumpPower = 0.6f;
|
||||
public float jumpDuration = 1.2f;
|
||||
public float laneSwitchSpeed = 10f; // used as units per second
|
||||
[SerializeField] private float laneDistance = 2.5f; // 0=Left,1=Mid,2=Right
|
||||
[SerializeField] private float laneSwitchSpeed = 10f; // units/sec
|
||||
|
||||
// ===== Jump (Tap / Hold) =====
|
||||
[Header("Jump (Tap/Hold)")]
|
||||
[SerializeField] private float maxHoldTime = 0.25f; // hold window
|
||||
[SerializeField] private float initialJumpVelocity = 6.5f;
|
||||
[SerializeField] private float holdUpwardAcceleration = 35f;
|
||||
[SerializeField] private float lowJumpGravityMultiplier = 2.0f;
|
||||
[SerializeField] private float fallGravityMultiplier = 2.5f;
|
||||
[SerializeField] private float coyoteTime = 0.12f; // after leaving ground
|
||||
[SerializeField] private float jumpBufferTime = 0.12f; // before landing
|
||||
[SerializeField] private float maxVerticalSpeed = 15f;
|
||||
|
||||
[Header("Air Control")]
|
||||
[SerializeField] private bool allowAirLaneSwitch = true;
|
||||
|
||||
// ===== Ground Check =====
|
||||
[Header("Ground Check")]
|
||||
[Tooltip("Optional feet probe; if null, uses collider bounds.")]
|
||||
[SerializeField] private Transform groundProbe;
|
||||
[SerializeField] private LayerMask groundMask = ~0;
|
||||
[SerializeField] private float groundCheckRadius = 0.22f;
|
||||
[SerializeField] private float groundCheckExtraDown = 0.06f;
|
||||
|
||||
// ===== Animator Driving =====
|
||||
[Header("Animator Driving")]
|
||||
public bool useSpeedBlendTree = true;
|
||||
public string speedParamName = "Speed";
|
||||
public float runSpeedParamValue = 2f;
|
||||
public string runStateName = "Locomotion";
|
||||
public int baseLayer = 0;
|
||||
[SerializeField] private bool useSpeedBlendTree = true;
|
||||
[SerializeField] private string speedParamName = "Speed";
|
||||
[SerializeField] private float runSpeedParamValue = 2f;
|
||||
[SerializeField] private string runStateName = "Locomotion";
|
||||
[SerializeField] private int baseLayer = 0;
|
||||
|
||||
[Header("Fall / GameOver Logic")]
|
||||
// ===== Fall / GameOver =====
|
||||
[Header("Fall / GameOver")]
|
||||
public string fallStateName = "Fall";
|
||||
public string fallingStateName = "Falling";
|
||||
public float secondHitWindow = 10f;
|
||||
public float stateWaitTimeout = 3f;
|
||||
[SerializeField] private float secondHitWindow = 10f;
|
||||
[SerializeField] private float stateWaitTimeout = 3f;
|
||||
|
||||
public static System.Action<float> OnMoveSpeedChanged;
|
||||
|
||||
int currentLane = 1;
|
||||
Rigidbody rb;
|
||||
Animator animator;
|
||||
bool isGrounded = true;
|
||||
bool isJumping = false;
|
||||
// ----- runtime state -----
|
||||
private int currentLane = 1; // 0,1,2
|
||||
private float targetLaneX = 0f;
|
||||
|
||||
Vector2 startTouchPosition;
|
||||
bool swipeDetected = false;
|
||||
float minSwipeDistance = 50f;
|
||||
private Rigidbody rb;
|
||||
private Animator animator;
|
||||
private Collider col; // any non-trigger collider
|
||||
|
||||
float lastObstacleHitTime = -999f;
|
||||
public bool waitingForGameOver = false;
|
||||
private bool isGrounded = true;
|
||||
private bool isJumping = false;
|
||||
private float lastGroundedTime = -999f;
|
||||
private float jumpPressTime = -999f;
|
||||
private float jumpHoldTimer = 0f;
|
||||
private bool jumpHeld = false;
|
||||
|
||||
int runShortHash, fallShortHash;
|
||||
public int fallingShortHash;
|
||||
[SerializeField] bool validateStatesOnStart = true;
|
||||
[SerializeField] string runTag = "Run";
|
||||
[SerializeField] string fallTag = "Fall";
|
||||
[SerializeField] string fallingTag = "Falling";
|
||||
private Vector2 startTouchPosition;
|
||||
private bool swipeDetected = false;
|
||||
private const float MIN_SWIPE = 50f;
|
||||
|
||||
float originalMoveSpeed;
|
||||
bool unableToMove = false;
|
||||
private float lastObstacleHitTime = -999f;
|
||||
[HideInInspector] public bool waitingForGameOver = false;
|
||||
|
||||
ChaseScoreManager scoreManager;
|
||||
[SerializeField] private bool validateStatesOnStart = true;
|
||||
[SerializeField] private string runTag = "Run";
|
||||
[SerializeField] private string fallTag = "Fall";
|
||||
[SerializeField] private string fallingTag = "Falling";
|
||||
|
||||
void Start()
|
||||
public int runShortHash, fallShortHash, fallingShortHash;
|
||||
private bool hasSpeedFloat, hasIsGroundedBool, hasJumpTrigger, hasLandTrigger;
|
||||
|
||||
private float originalMoveSpeed;
|
||||
private bool unableToMove = false;
|
||||
|
||||
private ChaseScoreManager scoreManager;
|
||||
|
||||
// ========= Unity =========
|
||||
private void Start()
|
||||
{
|
||||
// Components
|
||||
rb = GetComponent<Rigidbody>();
|
||||
animator = GetComponent<Animator>();
|
||||
col = GetComponent<Collider>();
|
||||
scoreManager = FindObjectOfType<ChaseScoreManager>();
|
||||
|
||||
runShortHash = Animator.StringToHash(runStateName);
|
||||
fallShortHash = Animator.StringToHash(fallStateName);
|
||||
// Rigidbody setup (smooth & robust)
|
||||
rb.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
|
||||
rb.constraints = RigidbodyConstraints.FreezeRotation;
|
||||
|
||||
// Animator hashes
|
||||
runShortHash = Animator.StringToHash(runStateName);
|
||||
fallShortHash = Animator.StringToHash(fallStateName);
|
||||
fallingShortHash = Animator.StringToHash(fallingStateName);
|
||||
|
||||
// Validate states (optional)
|
||||
if (validateStatesOnStart)
|
||||
{
|
||||
if (!animator.HasState(baseLayer, runShortHash))
|
||||
Debug.LogError($"Run state '{runStateName}' not found");
|
||||
if (!animator.HasState(baseLayer, fallShortHash))
|
||||
Debug.LogError($"Fall state '{fallStateName}' not found");
|
||||
if (!animator.HasState(baseLayer, fallingShortHash))
|
||||
Debug.LogError($"Falling state '{fallingStateName}' not found");
|
||||
if (!animator.HasState(baseLayer, runShortHash)) Debug.LogError($"Run state '{runStateName}' not found");
|
||||
if (!animator.HasState(baseLayer, fallShortHash)) Debug.LogError($"Fall state '{fallStateName}' not found");
|
||||
if (!animator.HasState(baseLayer, fallingShortHash)) Debug.LogError($"Falling state '{fallingStateName}' not found");
|
||||
}
|
||||
|
||||
// Cache animator parameters once
|
||||
hasSpeedFloat = HasParam(speedParamName, AnimatorControllerParameterType.Float);
|
||||
hasIsGroundedBool = HasParam("IsGrounded", AnimatorControllerParameterType.Bool);
|
||||
hasJumpTrigger = HasParam("Jump", AnimatorControllerParameterType.Trigger);
|
||||
hasLandTrigger = HasParam("Land", AnimatorControllerParameterType.Trigger);
|
||||
|
||||
originalMoveSpeed = moveSpeed;
|
||||
targetLaneX = LaneToX(currentLane);
|
||||
ForceRunStart();
|
||||
}
|
||||
|
||||
@ -80,194 +136,293 @@ public class ChasePlayerController : MonoBehaviour
|
||||
OnMoveSpeedChanged?.Invoke(newSpeed);
|
||||
}
|
||||
|
||||
void Update()
|
||||
private void Update()
|
||||
{
|
||||
UpdateGrounded();
|
||||
DriveRunAnimation();
|
||||
|
||||
if (!unableToMove)
|
||||
{
|
||||
HandleInput();
|
||||
HandleSwipe();
|
||||
ReadKeyboardInput();
|
||||
ReadTouchInput();
|
||||
HandleJumpHold();
|
||||
}
|
||||
|
||||
ApplyBetterJumpGravity();
|
||||
ClampVerticalSpeed();
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
private void FixedUpdate()
|
||||
{
|
||||
MoveForward();
|
||||
MoveForwardAndSide();
|
||||
}
|
||||
|
||||
void HandleInput()
|
||||
// ========= Input =========
|
||||
private void ReadKeyboardInput()
|
||||
{
|
||||
if (isJumping) return;
|
||||
// Lane switching (in-air allowed if enabled)
|
||||
if ((isGrounded || allowAirLaneSwitch) && Input.GetKeyDown(KeyCode.LeftArrow) && currentLane > 0)
|
||||
SetLane(currentLane - 1);
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.LeftArrow) && currentLane > 0)
|
||||
if ((isGrounded || allowAirLaneSwitch) && Input.GetKeyDown(KeyCode.RightArrow) && currentLane < 2)
|
||||
SetLane(currentLane + 1);
|
||||
|
||||
// Jump
|
||||
if (Input.GetKeyDown(KeyCode.Space))
|
||||
{
|
||||
currentLane--;
|
||||
TweenToLaneX((currentLane - 1) * laneDistance);
|
||||
jumpPressTime = Time.time;
|
||||
TryStartJump();
|
||||
jumpHeld = true;
|
||||
jumpHoldTimer = 0f;
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.RightArrow) && currentLane < 2)
|
||||
if (Input.GetKeyUp(KeyCode.Space))
|
||||
{
|
||||
currentLane++;
|
||||
TweenToLaneX((currentLane - 1) * laneDistance);
|
||||
jumpHeld = false;
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
|
||||
Jump();
|
||||
}
|
||||
|
||||
void HandleSwipe()
|
||||
private void ReadTouchInput()
|
||||
{
|
||||
if (Input.touchCount != 1) return;
|
||||
var t = Input.GetTouch(0);
|
||||
|
||||
var touch = Input.GetTouch(0);
|
||||
switch (touch.phase)
|
||||
switch (t.phase)
|
||||
{
|
||||
case TouchPhase.Began:
|
||||
startTouchPosition = touch.position;
|
||||
startTouchPosition = t.position;
|
||||
swipeDetected = true;
|
||||
jumpHeld = true;
|
||||
jumpHoldTimer = 0f;
|
||||
jumpPressTime = Time.time;
|
||||
break;
|
||||
|
||||
case TouchPhase.Ended:
|
||||
if (!swipeDetected) return;
|
||||
Vector2 swipe = touch.position - startTouchPosition;
|
||||
if (!swipeDetected) { jumpHeld = false; break; }
|
||||
Vector2 swipe = t.position - startTouchPosition;
|
||||
|
||||
if (swipe.magnitude >= minSwipeDistance && !isJumping)
|
||||
if (swipe.magnitude >= MIN_SWIPE)
|
||||
{
|
||||
if (Mathf.Abs(swipe.x) > Mathf.Abs(swipe.y))
|
||||
{
|
||||
if (swipe.x > 0 && currentLane < 2)
|
||||
{
|
||||
currentLane++;
|
||||
TweenToLaneX((currentLane - 1) * laneDistance);
|
||||
}
|
||||
else if (swipe.x < 0 && currentLane > 0)
|
||||
{
|
||||
currentLane--;
|
||||
TweenToLaneX((currentLane - 1) * laneDistance);
|
||||
}
|
||||
}
|
||||
else if (swipe.y > 0 && isGrounded)
|
||||
{
|
||||
Jump();
|
||||
if ((isGrounded || allowAirLaneSwitch) && swipe.x > 0 && currentLane < 2) SetLane(currentLane + 1);
|
||||
else if ((isGrounded || allowAirLaneSwitch) && swipe.x < 0 && currentLane > 0) SetLane(currentLane - 1);
|
||||
}
|
||||
else if (swipe.y > 0) TryStartJump();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tap
|
||||
TryStartJump();
|
||||
}
|
||||
|
||||
swipeDetected = false;
|
||||
jumpHeld = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TweenToLaneX(float targetX)
|
||||
{
|
||||
float distance = Mathf.Abs(rb.position.x - targetX);
|
||||
float duration = distance / laneSwitchSpeed;
|
||||
// ========= Movement =========
|
||||
private float LaneToX(int laneIndex) => (laneIndex - 1) * laneDistance;
|
||||
|
||||
rb.DOMoveX(targetX, duration).SetEase(Ease.OutQuad);
|
||||
private void SetLane(int laneIndex)
|
||||
{
|
||||
currentLane = Mathf.Clamp(laneIndex, 0, 2);
|
||||
targetLaneX = LaneToX(currentLane);
|
||||
}
|
||||
|
||||
void MoveForward()
|
||||
private void MoveForwardAndSide()
|
||||
{
|
||||
rb.MovePosition(rb.position + Vector3.back * moveSpeed * Time.fixedDeltaTime);
|
||||
// Forward (−Z)
|
||||
Vector3 forwardStep = Vector3.back * moveSpeed * Time.fixedDeltaTime;
|
||||
|
||||
// Side toward target lane X
|
||||
float newX = Mathf.MoveTowards(rb.position.x, targetLaneX, laneSwitchSpeed * Time.fixedDeltaTime);
|
||||
Vector3 targetPos = new Vector3(newX, rb.position.y, rb.position.z) + forwardStep;
|
||||
|
||||
rb.MovePosition(targetPos);
|
||||
}
|
||||
|
||||
void Jump()
|
||||
// ========= Jump System =========
|
||||
private void TryStartJump()
|
||||
{
|
||||
if (!isGrounded) return;
|
||||
bool canCoyote = (Time.time - lastGroundedTime) <= coyoteTime;
|
||||
|
||||
isGrounded = false;
|
||||
isJumping = true;
|
||||
rb.velocity = Vector3.zero;
|
||||
rb.useGravity = false;
|
||||
if ((isGrounded || canCoyote) && !waitingForGameOver)
|
||||
{
|
||||
var v = rb.velocity;
|
||||
v.y = initialJumpVelocity;
|
||||
rb.velocity = v;
|
||||
|
||||
float forwardDisplacement = moveSpeed * jumpDuration;
|
||||
Vector3 jumpTarget = rb.position + Vector3.back * forwardDisplacement;
|
||||
isJumping = true;
|
||||
isGrounded = false;
|
||||
jumpPressTime = -999f; // consume buffer
|
||||
|
||||
rb.DOJump(jumpTarget, jumpPower, 1, jumpDuration)
|
||||
.SetEase(Ease.Linear)
|
||||
.OnStart(() =>
|
||||
{
|
||||
SafeSetTrigger("Jump");
|
||||
SafeSetBool("IsGrounded", false);
|
||||
})
|
||||
.OnComplete(() =>
|
||||
{
|
||||
rb.useGravity = true;
|
||||
isGrounded = true;
|
||||
isJumping = false;
|
||||
SafeSetTrigger("Land");
|
||||
SafeSetBool("IsGrounded", true);
|
||||
ForceRunStart();
|
||||
});
|
||||
if (hasJumpTrigger) animator.SetTrigger("Jump");
|
||||
if (hasIsGroundedBool) animator.SetBool("IsGrounded", false);
|
||||
}
|
||||
}
|
||||
|
||||
void OnCollisionEnter(Collision collision)
|
||||
private void HandleJumpHold()
|
||||
{
|
||||
if (!isJumping || !jumpHeld) return;
|
||||
|
||||
if (jumpHoldTimer < maxHoldTime && rb.velocity.y > 0f)
|
||||
{
|
||||
rb.AddForce(Vector3.up * holdUpwardAcceleration, ForceMode.Acceleration);
|
||||
jumpHoldTimer += Time.deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyBetterJumpGravity()
|
||||
{
|
||||
if (rb == null) return;
|
||||
|
||||
if (rb.velocity.y < 0f)
|
||||
{
|
||||
rb.AddForce(Physics.gravity * (fallGravityMultiplier - 1f), ForceMode.Acceleration);
|
||||
}
|
||||
else if (!jumpHeld && rb.velocity.y > 0f)
|
||||
{
|
||||
rb.AddForce(Physics.gravity * (lowJumpGravityMultiplier - 1f), ForceMode.Acceleration);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClampVerticalSpeed()
|
||||
{
|
||||
var v = rb.velocity;
|
||||
if (v.y > maxVerticalSpeed) v.y = maxVerticalSpeed;
|
||||
if (v.y < -maxVerticalSpeed) v.y = -maxVerticalSpeed;
|
||||
rb.velocity = v;
|
||||
}
|
||||
|
||||
// ========= Grounding =========
|
||||
private void UpdateGrounded()
|
||||
{
|
||||
bool groundedNow = GroundProbe(out _);
|
||||
|
||||
if (groundedNow)
|
||||
{
|
||||
if (!isGrounded)
|
||||
{
|
||||
if (hasLandTrigger) animator.SetTrigger("Land");
|
||||
if (hasIsGroundedBool) animator.SetBool("IsGrounded", true);
|
||||
isJumping = false;
|
||||
ForceRunStart();
|
||||
}
|
||||
|
||||
isGrounded = true;
|
||||
lastGroundedTime = Time.time;
|
||||
|
||||
// Buffered jump pressed just before landing
|
||||
if ((Time.time - jumpPressTime) <= jumpBufferTime)
|
||||
{
|
||||
jumpPressTime = -999f;
|
||||
TryStartJump();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isGrounded = false;
|
||||
if (hasIsGroundedBool) animator.SetBool("IsGrounded", false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GroundProbe(out Vector3 hitPoint)
|
||||
{
|
||||
hitPoint = transform.position;
|
||||
|
||||
if (col != null)
|
||||
{
|
||||
Bounds b = col.bounds;
|
||||
Vector3 feet = new Vector3(b.center.x, b.min.y + 0.02f, b.center.z);
|
||||
Vector3 origin = groundProbe ? groundProbe.position : feet;
|
||||
|
||||
// SphereCast small distance down
|
||||
if (Physics.SphereCast(origin, groundCheckRadius, Vector3.down, out RaycastHit hit,
|
||||
groundCheckExtraDown + 0.02f, groundMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
hitPoint = hit.point;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback overlap
|
||||
if (Physics.CheckSphere(origin + Vector3.down * groundCheckExtraDown, groundCheckRadius, groundMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
hitPoint = origin + Vector3.down * groundCheckExtraDown;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// No collider → simple overlap at probe or near feet
|
||||
Vector3 o = groundProbe ? groundProbe.position : (transform.position + Vector3.down * 0.95f);
|
||||
bool ok = Physics.CheckSphere(o, groundCheckRadius, groundMask, QueryTriggerInteraction.Ignore);
|
||||
if (ok) hitPoint = o;
|
||||
return ok;
|
||||
}
|
||||
|
||||
private void OnCollisionStay(Collision collision)
|
||||
{
|
||||
// Extra safety: contact with ground layer & low vertical speed => grounded
|
||||
if (((1 << collision.gameObject.layer) & groundMask) != 0 && Mathf.Abs(rb.velocity.y) < 0.1f)
|
||||
{
|
||||
isGrounded = true;
|
||||
lastGroundedTime = Time.time;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (collision.gameObject.CompareTag("Ground"))
|
||||
{
|
||||
isGrounded = true;
|
||||
SafeSetBool("IsGrounded", true);
|
||||
lastGroundedTime = Time.time;
|
||||
isJumping = false;
|
||||
if (hasIsGroundedBool) animator.SetBool("IsGrounded", true);
|
||||
ForceRunStart();
|
||||
}
|
||||
}
|
||||
|
||||
void ForceRunStart(bool ignoreGuards = false)
|
||||
// ========= Animator =========
|
||||
private void ForceRunStart(bool ignoreGuards = false)
|
||||
{
|
||||
if (!ignoreGuards && (IsInOrGoingTo(fallShortHash) || IsInOrGoingTo(fallingShortHash) || waitingForGameOver))
|
||||
return;
|
||||
|
||||
if (useSpeedBlendTree && HasParameter(speedParamName, AnimatorControllerParameterType.Float))
|
||||
{
|
||||
if (useSpeedBlendTree && hasSpeedFloat)
|
||||
animator.SetFloat(speedParamName, runSpeedParamValue);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(runStateName))
|
||||
{
|
||||
animator.CrossFadeInFixedTime(runStateName, 0.1f, baseLayer, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
void DriveRunAnimation()
|
||||
private void DriveRunAnimation()
|
||||
{
|
||||
if (IsInOrGoingTo(fallShortHash) || IsInOrGoingTo(fallingShortHash) || isJumping) return;
|
||||
|
||||
if (useSpeedBlendTree && HasParameter(speedParamName, AnimatorControllerParameterType.Float))
|
||||
if (useSpeedBlendTree && hasSpeedFloat)
|
||||
{
|
||||
animator.SetFloat(speedParamName, runSpeedParamValue, 0.1f, Time.deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
var st = animator.GetCurrentAnimatorStateInfo(baseLayer);
|
||||
var st = animator.GetCurrentAnimatorStateInfo(baseLayer);
|
||||
var nxt = animator.GetNextAnimatorStateInfo(baseLayer);
|
||||
bool inRunNow = st.shortNameHash == runShortHash || st.IsTag(runTag);
|
||||
bool inRunNow = st.shortNameHash == runShortHash || st.IsTag(runTag);
|
||||
bool goingToRun = animator.IsInTransition(baseLayer) && (nxt.shortNameHash == runShortHash || nxt.IsTag(runTag));
|
||||
|
||||
if (!isJumping && !inRunNow && !goingToRun)
|
||||
{
|
||||
animator.CrossFadeInFixedTime(runStateName, 0.1f, baseLayer, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HasParameter(string name, AnimatorControllerParameterType type)
|
||||
private bool HasParam(string name, AnimatorControllerParameterType type)
|
||||
{
|
||||
foreach (var p in animator.parameters)
|
||||
if (p.type == type && p.name == name) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SafeSetTrigger(string trig)
|
||||
{
|
||||
if (HasParameter(trig, AnimatorControllerParameterType.Trigger))
|
||||
animator.SetTrigger(trig);
|
||||
}
|
||||
|
||||
void SafeSetBool(string param, bool v)
|
||||
{
|
||||
if (HasParameter(param, AnimatorControllerParameterType.Bool))
|
||||
animator.SetBool(param, v);
|
||||
}
|
||||
|
||||
bool IsInOrGoingTo(int shortHash)
|
||||
private bool IsInOrGoingTo(int shortHash)
|
||||
{
|
||||
var st = animator.GetCurrentAnimatorStateInfo(baseLayer);
|
||||
if (st.shortNameHash == shortHash) return true;
|
||||
@ -279,49 +434,41 @@ public class ChasePlayerController : MonoBehaviour
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------- Obstacle/Fall flow -----------------
|
||||
|
||||
// ========= Obstacle / Fall flow (kept) =========
|
||||
public void OnObstacleHit()
|
||||
{
|
||||
if (waitingForGameOver) return;
|
||||
|
||||
// Second hit within window → Falling then GameOver
|
||||
if (Time.time - lastObstacleHitTime <= secondHitWindow)
|
||||
{
|
||||
waitingForGameOver = true;
|
||||
//SetMoveSpeed(0);
|
||||
moveSpeed = 0;
|
||||
StartCoroutine(PlayStateAndGameOver(fallingStateName, fallingShortHash));
|
||||
}
|
||||
else
|
||||
{
|
||||
// First hit → Fall only (no GameOver), then resume run when Fall finishes
|
||||
lastObstacleHitTime = Time.time;
|
||||
PlayStateOnce(fallStateName);
|
||||
originalMoveSpeed = moveSpeed; // remember whatever it was right now
|
||||
//SetMoveSpeed(0f); // optional: pause forward motion during stumble
|
||||
originalMoveSpeed = moveSpeed;
|
||||
moveSpeed = 0;
|
||||
|
||||
StartCoroutine(ResumeRunAfter(fallStateName, fallShortHash));
|
||||
}
|
||||
}
|
||||
|
||||
void PlayStateOnce(string stateName, float xfade = 0.08f)
|
||||
private void PlayStateOnce(string stateName, float xfade = 0.08f)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stateName) || animator == null) return;
|
||||
animator.CrossFadeInFixedTime(stateName, xfade, baseLayer, 0f);
|
||||
if (!string.IsNullOrEmpty(stateName))
|
||||
animator.CrossFadeInFixedTime(stateName, xfade, baseLayer, 0f);
|
||||
}
|
||||
|
||||
IEnumerator ResumeRunAfter(string stateName, int shortHash, float xfade = 0.1f)
|
||||
private IEnumerator ResumeRunAfter(string stateName, int shortHash, float xfade = 0.1f)
|
||||
{
|
||||
float t0 = Time.time;
|
||||
while (!IsInOrGoingTo(shortHash) && !TimedOut(t0)) yield return null;
|
||||
while (animator.IsInTransition(baseLayer)) yield return null;
|
||||
|
||||
// Wait for completion OR force resume after timeout
|
||||
t0 = Time.time;
|
||||
while (!StateFinished(shortHash) && !TimedOut(t0))
|
||||
yield return null;
|
||||
while (!StateFinished(shortHash) && !TimedOut(t0)) yield return null;
|
||||
|
||||
if (!waitingForGameOver)
|
||||
{
|
||||
@ -330,6 +477,7 @@ public class ChasePlayerController : MonoBehaviour
|
||||
animator.CrossFadeInFixedTime(runStateName, 0.1f, baseLayer, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator PlayStateAndGameOver(string stateName, int shortHash, float xfade = 0.08f)
|
||||
{
|
||||
unableToMove = true;
|
||||
@ -341,26 +489,23 @@ public class ChasePlayerController : MonoBehaviour
|
||||
|
||||
animator.CrossFadeInFixedTime(stateName, xfade, baseLayer, 0f);
|
||||
|
||||
// Wait to enter target state
|
||||
float t0 = Time.time;
|
||||
while (!IsInOrGoingTo(shortHash) && !TimedOut(t0)) yield return null;
|
||||
while (animator.IsInTransition(baseLayer)) yield return null;
|
||||
|
||||
// Wait for it to finish (non-looping recommended)
|
||||
t0 = Time.time;
|
||||
while (!StateFinished(shortHash) && !TimedOut(t0)) yield return null;
|
||||
|
||||
if (scoreManager) scoreManager.GameOver();
|
||||
}
|
||||
|
||||
bool StateFinished(int shortHash)
|
||||
private bool StateFinished(int shortHash)
|
||||
{
|
||||
var st = animator.GetCurrentAnimatorStateInfo(baseLayer);
|
||||
return st.shortNameHash == shortHash && !animator.IsInTransition(baseLayer) && st.normalizedTime >= 1f;
|
||||
}
|
||||
|
||||
bool TimedOut(float startTime)
|
||||
{
|
||||
return stateWaitTimeout > 0f && (Time.time - startTime) > stateWaitTimeout;
|
||||
}
|
||||
private bool TimedOut(float startTime) =>
|
||||
stateWaitTimeout > 0f && (Time.time - startTime) > stateWaitTimeout;
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ PhysicsManager:
|
||||
m_ClothInterCollisionDistance: 0
|
||||
m_ClothInterCollisionStiffness: 0
|
||||
m_ContactsGeneration: 1
|
||||
m_LayerCollisionMatrix: fffffffffffffffffffffffffffaffffffffffffffffffffffffffff7ffffffff7fffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
m_LayerCollisionMatrix: fffffffffffffffffffffffffffbffffffffffffffffffffffffffff7ffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
m_SimulationMode: 0
|
||||
m_AutoSyncTransforms: 0
|
||||
m_ReuseCollisionCallbacks: 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user