using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine.Unity;
public class PlayerController : MonoBehaviour
{
#region components
private CharacterController controller;
private Animator animator;
private LedgeGrabber grabber;
private SkeletonAnimator skelAnim;
private GameObject root;
#endregion
#region movement
//[HideInInspector]
public Vector3 velocity = Vector3.zero;
private float gravity = -100f;
[HideInInspector]
public float walkSpeed = 20;
[HideInInspector]
public float runSpeed = 40;
[HideInInspector]
public float curSpd;
[Range(0, 1)]
public float airControlPercent;
private float speedSmoothTime = 0.1f;
private float speedSmoothVelocity;
public bool onGround = false;
#endregion
#region jumps
private float jumpHeight = 10f;
public bool canJump = false;
private float timer = .5f;
public bool landing = false;
public bool canLeap = false;
#endregion
#region animation
private float animSpdPer;
private AnimatorStateInfo info;
#endregion
#region slope and wall
private float slopeAngle;
public bool maxSlope = false;
public bool normSlope = false;
private Vector3 slopeNormal;
public bool wallSliding = false;
private float wallSlideSpdMax = 16f;
private int dirX;
[HideInInspector]
public int wallDir;
public Vector2 jumpOff = Vector2.zero;
public Vector2 wallLeap = Vector2.zero;
#endregion
#region platform
[HideInInspector]
public GameObject platform;
#endregion
[Header("Ledge data intake")]
#region Ledge data
public GameObject ledge;
public BoxCollider targetLedge;
[HideInInspector]
public Vector3 ledgePos = Vector3.zero;
[HideInInspector]
public bool stallMovement = false;
[HideInInspector]
public bool climbingNow = false;
#endregion
[Space(50)]
public float _timer = 0;
void Start()
{
controller = GetComponent<CharacterController>();
animator = GetComponent<Animator>();
grabber = GetComponentInChildren<LedgeGrabber>();
skelAnim = GetComponent<SkeletonAnimator>();
root = GameObject.Find("root");
}
void Update()
{
ApplyAnimatorParam();
ResetAnimatorParam();
GrabberListener();
Move();
JumpGate();
ResetTimer();
}
void Move()
{
if (!stallMovement)
{
if (controller.isGrounded)
{
if (!maxSlope && !normSlope)
{
velocity = GroundVelocity(ref velocity);
onGround = true;
if (Input.GetKey(KeyCode.Space))
{
Jump();
onGround = false;
}
}
if (maxSlope)
{
velocity = MaxSlopeVelocity(ref velocity);
onGround = true;
}
if (normSlope)
{
velocity = NormSlopeVelocity(ref velocity);
onGround = true;
if (Input.GetKey(KeyCode.Space))
{
Jump();
canJump = false;
onGround = false;
}
}
}
if (wallSliding)
{
WallSlideVelocity(ref velocity);
onGround = false;
}
ApplyGravity();
controller.Move(velocity * Time.deltaTime);
}
FaceDir();
}
void TimeIt()
{
_timer += 1 * Time.deltaTime;
if (_timer >= 1.2f)
{
canLeap = true;
}
else
canLeap = false;
}
void ResetTimer()
{
if (_timer != 0)
{
if (Input.GetKeyUp(KeyCode.Space))
{
_timer = 0;
}
}
}
Vector3 GroundVelocity(ref Vector3 vel)
{
bool running = Input.GetKey(KeyCode.LeftShift) && controller.isGrounded;
Vector2 input = new Vector2(Input.GetAxis("Horizontal"), 0);
Vector2 inputDir = input.normalized;
float targetSpeed = ((running) ? runSpeed : walkSpeed) * inputDir.magnitude;
curSpd = Mathf.SmoothDamp(curSpd, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime * 3));
animSpdPer = Mathf.Lerp(0, 1, curSpd);
vel = new Vector3(curSpd, 0, 0);
if (Input.GetKey(KeyCode.D))
{
return vel;
}
if (Input.GetKey(KeyCode.A))
{
return -vel;
}
return Vector3.zero;
}
Vector3 MaxSlopeVelocity(ref Vector3 vel)
{
float velY = velocity.y + slopeNormal.y * gravity * Time.deltaTime;
float velX = Mathf.Sign(slopeNormal.x) * (Mathf.Abs(velY / 2) / Mathf.Tan(slopeAngle * Mathf.Deg2Rad));
if (dirX == 1)
{
#region leap ready
if (Input.GetKey(KeyCode.Space))
{
if (_timer <= 1.2f)
{
TimeIt();
}
velY = Mathf.SmoothDamp(velY, 0f, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
velX = Mathf.SmoothDamp(velX, 0f, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
animator.SetBool("leapRdy", true);
root.transform.localEulerAngles = new Vector3(0, 0, 0);
}
#endregion
#region leap
if (Input.GetKeyUp(KeyCode.Space))
{
if (canLeap)
{
LeapOnUp(ref velocity);
animator.SetBool("leapRdy", false);
animator.SetBool("leap", true);
}
}
#endregion
else
vel = new Vector3(velX, velY, 0);
}
if (dirX == -1)
{
#region leap ready
if (Input.GetKey(KeyCode.Space))
{
if (_timer <= 1.2f)
{
TimeIt();
}
velY = Mathf.SmoothDamp(velY, 0f, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
velX = Mathf.SmoothDamp(velX, 0f, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
animator.SetBool("leapRdy", true);
root.transform.localEulerAngles = new Vector3(0, 0, 0);
}
#endregion
#region leap
if (Input.GetKeyUp(KeyCode.Space))
{
if (canLeap)
{
LeapOnUp(ref velocity);
animator.SetBool("leapRdy", false);
animator.SetBool("leap", true);
}
}
#endregion
else
vel = new Vector3(-velX, velY, 0);
}
return vel;
}
Vector3 NormSlopeVelocity(ref Vector3 vel)
{
bool running = Input.GetKey(KeyCode.LeftShift) && controller.isGrounded;
Vector2 input = new Vector2(Input.GetAxis("Horizontal"), 0);
Vector2 inputDir = input.normalized;
float targetSpeed = ((running) ? runSpeed : walkSpeed) * inputDir.magnitude;
curSpd = Mathf.SmoothDamp(curSpd, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
float moveDist = Mathf.Abs(curSpd);
float velX = Mathf.Cos(slopeAngle * Mathf.Deg2Rad) * moveDist * Mathf.Sign(curSpd);
float velY = Mathf.Sin(slopeAngle * Mathf.Deg2Rad) * moveDist;
vel = new Vector3(velX, velY, 0);
if (Input.GetKey(KeyCode.D))
{
return vel;
}
if (Input.GetKey(KeyCode.A))
{
return -vel;
}
return Vector3.zero;
}
Vector3 WallSlideVelocity(ref Vector3 vel)
{
if (Input.GetKeyDown(KeyCode.S))
{
WallJumpOff(ref velocity);
animator.SetBool("jumped", true);
animator.SetBool("wallSlide", false);
}
if (Input.GetKey(KeyCode.Space))
{
if (_timer <= 1.2f)
{
TimeIt();
}
wallSlideSpdMax -= _timer * 10;
animator.SetBool("leapRdy", true);
}
if (Input.GetKeyUp(KeyCode.Space))
{
if (canLeap)
{
LeapOnUp(ref velocity);
animator.SetBool("leap", true);
}
}
else
{
vel.y = -wallSlideSpdMax;
vel.x = 0;
wallSlideSpdMax = 16f;
}
return vel;
}
Vector3 LeapOnUp(ref Vector3 vel)
{
if (canLeap)
{
#region on wall
if (wallSliding)
{
wallSliding = false;
maxSlope = false;
if (wallDir == 1)
{
vel.y = Mathf.Abs(wallLeap.y);
vel.x = -wallDir * wallLeap.x;
}
if (wallDir == -1)
{
vel.y = Mathf.Abs(wallLeap.y);
vel.x = -wallDir * wallLeap.x;
}
wallSlideSpdMax = 16f;
animator.SetBool("leapRdy", false);
animator.SetBool("wallSlide", false);
}
#endregion
#region on max slope
if (maxSlope)
{
maxSlope = false;
if (dirX == 1)
{
vel.y = Mathf.Abs(wallLeap.y);
vel.x = dirX * wallLeap.x;
}
if (dirX == -1)
{
vel.y = Mathf.Abs(wallLeap.y);
vel.x = dirX * wallLeap.x;
}
animator.SetBool("leapRdy", false);
canLeap = false;
}
#endregion
}
return vel;
}
float GetModifiedSmoothTime(float smoothTime)
{
if (controller.isGrounded)
{
return smoothTime;
}
if (airControlPercent == 0)
{
return float.MaxValue;
}
return smoothTime / airControlPercent;
}
void FaceDir()
{
#region on ground, jump and fall
if (!wallSliding && !maxSlope)
{
if (velocity.x > 0)
{
transform.localEulerAngles = new Vector3(0, 0, 0);
}
if (velocity.x < 0)
{
transform.localEulerAngles = new Vector3(0, 180, 0);
}
}
#endregion
#region on wall slide
if (wallSliding)
{
if (!controller.isGrounded)
{
if (wallDir == -1)
{
transform.localEulerAngles = new Vector3(0, 180, 0);
}
if (wallDir == 1)
{
transform.localEulerAngles = new Vector3(0, 0, 0);
}
}
}
#endregion
#region on max slope
if (maxSlope)
{
if (dirX == 1)
{
controller.transform.localEulerAngles = new Vector3(0, 180, 0);
root.transform.rotation = Quaternion.Euler(0, 0, slopeAngle);
}
if (dirX == -1)
{
controller.transform.localEulerAngles = new Vector3(0, 0, 0);
root.transform.rotation = Quaternion.Euler(0, 0, -slopeAngle);
}
if (animator.GetBool("leapRdy") == true)
{
root.transform.localEulerAngles = new Vector3(0, 0, 0);
}
}
#endregion
}
void ApplyGravity()
{
if (!controller.isGrounded || maxSlope || !wallSliding)
{
velocity.y += gravity * Time.deltaTime;
if (velocity.y <= -5f)
{
onGround = false; // here onGround conflicts and flickers
}
}
else
velocity.y = 0;
}
void Jump()
{
if (canJump)
{
float jumpVelocity = Mathf.Sqrt(-2 * gravity * jumpHeight);
velocity.y = jumpVelocity;
animator.SetBool("jumped", true);
slopeAngle = 0;
maxSlope = false;
normSlope = false;
transform.parent = null;
landing = false;
animator.SetBool("onGround", false);
}
}
void JumpGate()
{
#region check landing
if (!maxSlope && transform.parent == null)
{
if (velocity.y < -2f)
{
float rayLength = 7f;
Ray landingCheck = new Ray(transform.position, Vector3.down);
RaycastHit hit; // = new RaycastHit();
Debug.DrawRay(transform.position, -Vector3.up * rayLength);
if (Physics.Raycast(landingCheck, out hit, rayLength))
{
//if (Mathf.Abs(hit.normal.x) <= Mathf.Abs(slopeNormal.x))
if(Mathf.Abs(hit.collider.transform.localEulerAngles.z) <= controller.slopeLimit)
{
if (animator.GetBool("onGround") == false)
{
landing = true;
animator.SetBool("landing", true);
canJump = false;
Invoke("ResetJump", .3f);
animator.SetBool("onGround", true);
}
}
}
else
animator.SetBool("onGround", false);
}
}
#endregion
#region gate time between jumps
#endregion
}
void ResetJump()
{
landing = false;
animator.SetBool("landing", false);
animator.SetBool("onGround", true);
canJump = true;
}
void WallJumpOff(ref Vector3 vel)
{
wallSliding = false;
if (wallDir == 1)
{
vel.y = velocity.y + Mathf.Abs(velocity.y + jumpHeight);
vel.x = velocity.x + -wallDir * jumpOff.x;
controller.Move(vel * Time.deltaTime);
}
if (wallDir == -1)
{
vel.y = velocity.y + Mathf.Abs(velocity.y + jumpHeight);
vel.x = velocity.x + -wallDir * jumpOff.x;
controller.Move(vel * Time.deltaTime);
}
}
void GrabberListener()
{
// LedgeGrabber.cs should output some data and this will take it to pass to some others
if (grabber.targetLedge != null && grabber.ledge != null)
{
if (grabber.normalLedge ^ grabber.platLedge)
{
ledge = grabber.ledge;
targetLedge = grabber.targetLedge;
ledgePos = grabber.ledgePos;
GetModY();
//animator.SetTrigger("onLedge"); // this is only place where sets onLedge parameter to true
}
}
if (grabber.targetLedge == null)
{
ledge = null;
targetLedge = null;
}
}
private void climbStart(int value = 1)
{
animator.SetBool("climbing", true);
climbingNow = true;
if (grabber.normalLedge)
{
stallMovement = true;
}
if (grabber.platLedge)
{
stallMovement = true;
transform.parent = ledge.transform;
}
}
private void climbEnd(int value = 1)
{
float modX = 3f;
#region climb normal ledge
if (grabber.normalLedge)
{
Vector3 climb = new Vector3(ledgePos.x - (-modX * 1.5f), transform.position.y + GetModY(), transform.position.z);
GetComponent<Transform>().position = climb;
}
#endregion
#region climb platform ledge
if (grabber.platLedge)
{
float oldX = transform.position.x;
transform.parent = ledge.transform;
Vector3 climbR = new Vector3(oldX - (-modX), transform.position.y + GetModY(), transform.position.z);
Vector3 climbL = new Vector3(oldX - (modX), transform.position.y + GetModY(), transform.position.z);
if (ledgePos.x > transform.position.x)
{
GetComponent<Transform>().position = climbR;
}
else
{
GetComponent<Transform>().position = climbL;
}
}
#endregion
stallMovement = false;
animator.SetBool("climbing", false);
climbingNow = false;
animator.SetBool("onGround", true);
canJump = true;
}
//private void StallPlayer()
//{
// // this will stall Player's movement or parent it if it's a mobile platform
// if (grabber.normalLedge)
// {
// stallMovement = true;
// }
// if (grabber.platLedge)
// {
// stallMovement = true;
// transform.parent = ledge.transform;
// }
//}
//private void MovePlayerOnLedge()
//{
// // this will actually move player
// float modX = 3f;
// #region climb normal ledge
// if (grabber.normalLedge)
// {
// Vector3 climb = new Vector3(ledgePos.x - (-modX * 1.5f), transform.position.y + GetModY(), transform.position.z);
// GetComponent<Transform>().position = climb;
// }
// #endregion
// #region climb platform ledge
// if (grabber.platLedge)
// {
// float oldX = transform.position.x;
// transform.parent = ledge.transform;
// Vector3 climbR = new Vector3(oldX - (-modX), transform.position.y + GetModY(), transform.position.z);
// Vector3 climbL = new Vector3(oldX - (modX), transform.position.y + GetModY(), transform.position.z);
// if (ledgePos.x > transform.position.x)
// {
// GetComponent<Transform>().position = climbR;
// }
// else
// {
// GetComponent<Transform>().position = climbL;
// }
// }
// #endregion
// animator.SetBool("onLedge", false);
// stallMovement = false;
//}
private float GetModY()
{
float grabberTop = Mathf.Abs(controller.bounds.min.y);
float ledgeTop = Mathf.Abs(targetLedge.bounds.max.y);
return Mathf.Abs(ledgeTop - grabberTop);
}
void ApplyAnimatorParam()
{
animator.SetFloat("moveVel", Mathf.Abs(velocity.x));
if (wallSliding)
{
animator.SetBool("wallSlide", true);
}
if (maxSlope)
{
animator.SetBool("maxSlope", true);
}
else
root.transform.localEulerAngles = new Vector3(0, 0, 0);
}
void ResetAnimatorParam()
{
if (animator.GetCurrentAnimatorStateInfo(0).IsName("jump"))
{
animator.SetBool("jumped", false);
}
if (animator.GetCurrentAnimatorStateInfo(0).IsName("fall"))
{
animator.SetBool("leap", false);
}
if (animator.GetCurrentAnimatorStateInfo(0).IsName("trans_jumpToFall"))
{
animator.SetBool("jumped", false);
}
if (animator.GetCurrentAnimatorStateInfo(0).IsName("wallSlide"))
{
animator.SetBool("jumped", false);
animator.SetBool("onGround", false);
}
if (animator.GetBool("wallSlide") == true)
{
animator.SetBool("onGround", false);
}
if (animator.GetCurrentAnimatorStateInfo(0).IsName("groundMovement"))
{
animator.SetBool("onGround", true);
}
if (controller.isGrounded)
{
animator.SetBool("jumped", false);
animator.SetBool("wallSlide", false);
}
if (!maxSlope)
{
animator.SetBool("maxSlope", false);
}
if (!canLeap)
{
animator.SetBool("leapRdy", false);
}
//if (animator.GetCurrentAnimatorStateInfo(0).IsName("climbLedge"))
//{
// if (grabber.ledge == null && grabber.targetLedge == null && !grabber.normalLedge)
// {
// Debug.LogWarning("climb anim is fired, but ledge is null");
// Debug.Break();
// }
// if (grabber.ledge == null && grabber.targetLedge == null && !grabber.platLedge)
// {
// Debug.LogWarning("climb anim is fired, but ledge is null");
// Debug.Break();
// }
//}
}
private void OnControllerColliderHit(ControllerColliderHit hit)
{
float targetAngle = hit.collider.transform.localEulerAngles.z;
float angleOutput = (float)(System.Math.Round(targetAngle, 3));
if (angleOutput > 180)
{
angleOutput -= 360f; // this is necessary since some anlges are not correctly passed and be over what it should be
}
if (hit.normal == new Vector3(0, 1, 0))
{
maxSlope = false;
}
#region normal slope
if (angleOutput != 0)
{
if (Mathf.Abs(angleOutput) < controller.slopeLimit)
{
slopeAngle = angleOutput;
normSlope = true;
}
}
else
normSlope = false;
#endregion
#region max slope detection
if (controller.slopeLimit <= Mathf.Abs(angleOutput))
{
slopeAngle = angleOutput;
slopeNormal = hit.normal;
maxSlope = true;
}
#endregion
#region wall detection
if (controller.collisionFlags == CollisionFlags.Sides)
{
if (hit.normal == new Vector3(-1, 0, 0) ^ hit.normal == new Vector3(1, 0, 0))
{
if (hit.collider.bounds.max.y >= controller.bounds.max.y - 5f)
{
if (hit.collider.bounds.size.y >= controller.bounds.size.y + 3f)
{
if (hit.gameObject.tag != "Platform")
{
Vector3 wallR = new Vector3(1, 0, 0);
Vector3 wallL = new Vector3(-1, 0, 0);
if (hit.normal == wallR || hit.normal == wallL)
{
wallSliding = true;
wallDir = (hit.normal == wallR) ? 1 : -1;
}
}
}
}
}
}
else
{
wallSliding = false;
wallDir = 0;
}
#endregion
#region slope direction detection
if (angleOutput != 0)
{
//if (hit.point.x > root.transform.position.x)
//{
// dirX = 1;
//}
//else
//{
// dirX = -1;
//}
if (angleOutput > 0)
{
dirX = 1;
}
else
dirX = -1;
}
#endregion
#region platform detection
if (hit.gameObject.GetComponent<PlatformComponent>() != null)
{
if (hit.gameObject.tag == "Platform")
{
platform = hit.gameObject;
transform.parent = platform.transform;
if (true)
{
}
}
}
else
{
transform.parent = null;
}
#endregion
}
}
编辑)嗯,这是PC移动的整个脚本。提及GrabberListener等等,因为它的另一个类与当前的问题无关。运动。 在坡度角度小于坡度限制PC的速度的地面上,y在0到更小之间闪烁。我需要它留在它停留的位置,以便跟踪另一个功能的速度。 CharacterController可以没有100%可靠的地面检查吗?甚至CollisionFlag也会在空中发射,而不是附近有任何可碰撞的物体,所以我并不太依赖它...
答案 0 :(得分:0)
我无法理智地回答,因为您没有显示Move
和ApplyGravity
方法的执行顺序。
请注意,controller.collisionFlags
会在最后一次CharacterController.Move
回调后存储碰撞掩码状态,因此如果您在ApplyGravity
Move
之前执行Update
},你正在检查前一帧是否有碰撞。
这也可能是由浮点近似误差引起的问题,尤其是帧速率非常高(Time.deltaTime
非常小),除非您启用了Vsync。
你可以在这里阅读一些有用的帖子:https://forum.unity3d.com/threads/controller-isgrounded-doesnt-work-reliably.91436/
顺便说一下,顺便提一下,为什么你到处都在使用velocity = Method(ref velocity);
?当您要将返回值存储到完全相同的变量时,将参数作为引用传递没有任何意义。