CharacterMovementController.cs (14879B)
1 using Slimecing.Environment; 2 using Slimecing.Environment.Moving; 3 using Slimecing.SimpleComponents.Movement; 4 using UnityEngine; 5 6 namespace Slimecing.Characters 7 { 8 [RequireComponent(typeof(Rigidbody))] 9 [RequireComponent(typeof(Rotatable))] 10 [RequireComponent(typeof(Collider))] 11 public abstract class CharacterMovementController : MonoBehaviour 12 { 13 [Header("Components")] 14 [SerializeField] protected Rigidbody rb; 15 public Rigidbody playerRigidbody => rb; 16 [SerializeField] protected Rotatable rotatable; 17 [SerializeField] private Collider playerCollider; 18 19 [Header("Player Movement Settings")] 20 [SerializeField] protected float movementSpeed; 21 [SerializeField] private bool useGravity; 22 public bool UseGravity { get => useGravity; 23 set => useGravity = value; 24 } 25 [SerializeField] protected Vector3 gravityDirection = Vector3.down; 26 public Vector3 GravityDirection { get => gravityDirection; 27 set => gravityDirection = value; 28 } 29 [SerializeField] protected float gravityForce; 30 public float GravityForce { get => gravityForce; 31 set => gravityForce = value; 32 } 33 [SerializeField] protected float simulatedFallingMass; 34 public float SimulatedFallingMass { get => simulatedFallingMass; 35 set => simulatedFallingMass = value; 36 } 37 [SerializeField] protected float minVelocityMagnitude; 38 39 [Header("Slope Settings")] 40 [SerializeField] private Vector3 slopeCheckPoint; 41 [SerializeField] protected float slopeCheckRadius; 42 [SerializeField] protected float maxAngle; 43 44 [Header("Ledge Settings")] 45 [SerializeField] private Vector3 ledgeCheckPoint; 46 [SerializeField] private float ledgeCheckRadius; 47 48 [Header("Grounding Settings")] 49 [SerializeField] private Vector3 castOrigin; 50 [SerializeField] protected float groundProbingDistance; 51 [SerializeField] protected float groundingSphereCastRadius; 52 [SerializeField] private LayerMask hitLayerMask; 53 54 [Header("Other Settings")] 55 [SerializeField] private bool preserveInteractableRigidbodyVelocity; 56 const int MaxHitsBuffer = 3; 57 58 protected Vector2 move; 59 protected Vector3 calculatedGravity; 60 61 private bool _isGrounded; 62 public bool IsGrounded() => _isGrounded; 63 private bool _canBeGrounded; 64 65 private Vector3 _bodyVelocity; 66 private Vector3 _attachedRigidbodyVelocity; 67 private Rigidbody _lastAttachedRigidbody; 68 private bool _isMovingFromAttachedRigidbody; 69 70 private Vector3 _calculatedTargetVelocity; 71 public Vector3 calculatedTargetVelocity 72 { 73 get => _calculatedTargetVelocity; 74 set => _calculatedTargetVelocity = value; 75 } 76 77 public Rigidbody attachedRigidbody { get; private set; } 78 79 public Vector3 internalTransformPosition { get; set; } 80 81 private RaycastHit _internalGroundHit; 82 private RaycastHit _internalSlopeHit; 83 private RaycastHit _lastGoodNormalHit; 84 private RaycastHit[] _internalCharacterHits = new RaycastHit[MaxHitsBuffer]; 85 86 public Vector2 MoveInput => move; 87 88 protected void Awake() 89 { 90 internalTransformPosition = Vector3.zero; 91 calculatedGravity = gravityDirection * gravityForce; 92 } 93 94 private void OnEnable() 95 { 96 MoverSimulationManager.CheckAlive(); 97 MoverSimulationManager.RegisterCharMover(this); 98 } 99 100 private void OnDisable() 101 { 102 MoverSimulationManager.UnregisterCharMover(this); 103 } 104 105 public abstract void GetMoveInputH(float h); 106 public abstract void GetMoveInputV(float v); 107 108 public void AddForceTo(Vector3 direction, float amount) 109 { 110 rb.AddForce(direction.normalized * amount, ForceMode.Impulse); 111 } 112 113 private void OnDrawGizmos() 114 { 115 Gizmos.color = Color.cyan; 116 Gizmos.DrawWireSphere(transform.TransformPoint(slopeCheckPoint), slopeCheckRadius); 117 118 Gizmos.color = Color.black; 119 Gizmos.DrawWireSphere(transform.TransformPoint(ledgeCheckPoint), ledgeCheckRadius); 120 121 Color changeColor = _isGrounded ? Color.green : Color.red; 122 Gizmos.color = changeColor; 123 Gizmos.DrawRay(transform.TransformPoint(castOrigin), gravityDirection.normalized * groundingSphereCastRadius); 124 Gizmos.DrawWireSphere(transform.TransformPoint(castOrigin), groundingSphereCastRadius); 125 126 Debug.DrawLine(transform.position, transform.position + PlayerUpVector() * 2, Color.cyan); 127 Debug.DrawLine(transform.position, transform.position + Forward() * 2, Color.yellow); 128 } 129 130 public virtual void TickCharacter(float deltaTime) 131 { 132 GroundCheck(groundProbingDistance, groundingSphereCastRadius); 133 134 InteractableRigidbodyCheck(deltaTime); 135 136 Rotate(deltaTime); 137 Move(deltaTime); 138 139 if (!_isGrounded && useGravity) rb.AddForce(simulatedFallingMass * calculatedGravity); 140 141 if (rb.velocity.magnitude < minVelocityMagnitude) 142 { 143 rb.velocity = Vector3.zero; 144 } 145 146 _isGrounded = false; 147 } 148 149 private void InteractableRigidbodyCheck(float deltaTime) 150 { 151 if (!IsGrounded()) return; 152 _lastAttachedRigidbody = attachedRigidbody; 153 if (_lastGoodNormalHit.collider.attachedRigidbody) 154 { 155 Rigidbody validRigidbody = GetInteractableRigidbody(_lastGoodNormalHit.collider); 156 if (validRigidbody) 157 { 158 attachedRigidbody = validRigidbody; 159 } 160 } 161 else 162 { 163 attachedRigidbody = null; 164 } 165 166 Vector3 tmpVelocityFromInteractableRigidbody = Vector3.zero; 167 if (attachedRigidbody) 168 { 169 tmpVelocityFromInteractableRigidbody = 170 GetVelocityFromRigidbodyMovement(attachedRigidbody, transform.position, deltaTime); 171 } 172 173 if (preserveInteractableRigidbodyVelocity && _lastAttachedRigidbody != null && 174 attachedRigidbody != _lastAttachedRigidbody) 175 { 176 var velocity = rb.velocity; 177 velocity += _attachedRigidbodyVelocity; 178 velocity -= tmpVelocityFromInteractableRigidbody; 179 rb.AddForce(velocity, ForceMode.VelocityChange); 180 } 181 182 _attachedRigidbodyVelocity = Vector3.zero; 183 if (attachedRigidbody) 184 { 185 _attachedRigidbodyVelocity = tmpVelocityFromInteractableRigidbody; 186 Vector3 newForward = Vector3 187 .ProjectOnPlane( 188 Quaternion.Euler(attachedRigidbody.angularVelocity * (Mathf.Rad2Deg * deltaTime)) * Forward(), 189 PlayerGroundedUpVector()).normalized; 190 rb.rotation = Quaternion.LookRotation(newForward, PlayerGroundedUpVector()); 191 } 192 193 rb.position += _attachedRigidbodyVelocity * deltaTime; 194 internalTransformPosition = _attachedRigidbodyVelocity * deltaTime; 195 } 196 197 private Vector3 PlayerGroundedUpVector() 198 { 199 return transform.rotation * Vector3.up; 200 } 201 202 private Vector3 GetVelocityFromRigidbodyMovement(Rigidbody interactiveRb, Vector3 transformPosition, float deltaTime) 203 { 204 if (!(deltaTime > 0f)) return Vector3.zero; 205 Vector3 moverVelocity = interactiveRb.velocity; 206 207 if (interactiveRb.angularVelocity == Vector3.zero) return moverVelocity; 208 209 Vector3 centerOfRot = interactiveRb.position + interactiveRb.centerOfMass; 210 Vector3 centerOfRotOffset = transformPosition - centerOfRot; 211 Quaternion rotationFromMoverRigidbody = Quaternion.Euler(interactiveRb.angularVelocity * (Mathf.Rad2Deg * deltaTime)); 212 Vector3 finalPointPos = centerOfRot + (rotationFromMoverRigidbody * centerOfRotOffset); 213 moverVelocity += (finalPointPos - transformPosition) / deltaTime; 214 215 return moverVelocity; 216 217 } 218 219 private Rigidbody GetInteractableRigidbody(Collider col) 220 { 221 if (!col.attachedRigidbody) return null; 222 223 if (col.attachedRigidbody.gameObject.GetComponent<RigidbodyMover>()) 224 { 225 return col.attachedRigidbody; 226 } 227 228 if (!col.attachedRigidbody.isKinematic) 229 { 230 return col.attachedRigidbody; 231 } 232 233 return null; 234 } 235 236 protected abstract void Move(float deltaTime); 237 238 protected abstract void Rotate(float deltaTime); 239 240 public float GetGroundAngle() 241 { 242 if (!_isGrounded) return 90f; 243 return Vector3.Angle(_internalGroundHit.normal, transform.forward); 244 } 245 246 private bool IsStableOnNormal(Vector3 normal) 247 { 248 return Vector3.Angle(transform.up, normal) <= maxAngle; 249 } 250 251 protected Vector3 PlayerUpVector() 252 { 253 if (!_isGrounded) return transform.up; 254 if (_internalSlopeHit.transform == null) return transform.up; 255 return _internalSlopeHit.normal; 256 } 257 protected Vector3 Forward() 258 { 259 if (!_isGrounded) return transform.forward; 260 if (_internalSlopeHit.transform == null) return transform.forward; 261 return Vector3.Cross(transform.right, _internalSlopeHit.normal); 262 } 263 public void SetBackToNormalForces() 264 { 265 rb.velocity = new Vector3(move.x * movementSpeed, rb.velocity.y, move.y * movementSpeed); 266 } 267 268 public void GroundCheck(float length, float radius) 269 { 270 var ray = 271 new Ray( 272 transform.TransformPoint(castOrigin), 273 gravityDirection); 274 275 if (GroundSweep(ray, radius, length, out var groundSweepHit)) 276 { 277 _canBeGrounded = true; 278 SlopeColliderValidate(groundSweepHit, slopeCheckRadius); 279 } 280 else 281 { 282 _canBeGrounded = false; 283 _isGrounded = false; 284 } 285 } 286 287 private bool GroundSweep(Ray position, float radius, float distance, out RaycastHit closestHit) 288 { 289 closestHit = new RaycastHit(); 290 int num = 291 Physics.SphereCastNonAlloc( 292 position, 293 radius, 294 _internalCharacterHits, 295 distance, 296 hitLayerMask, 297 QueryTriggerInteraction.Ignore 298 ); 299 300 bool foundValidHit = false; 301 float closestDistance = Mathf.Infinity; 302 303 for (int i = 0; i < num; i++) 304 { 305 if (_internalCharacterHits[i].distance > 0f) 306 { 307 if (_internalCharacterHits[i].distance < closestDistance) 308 { 309 closestHit = _internalCharacterHits[i]; 310 closestHit.distance -= 0.1f; 311 closestDistance = _internalCharacterHits[i].distance; 312 _internalGroundHit = closestHit; 313 foundValidHit = true; 314 } 315 } 316 } 317 318 return foundValidHit; 319 } 320 321 private void OnCollisionStay(Collision collision) 322 { 323 if (_canBeGrounded) 324 { 325 float minFlatness = 1f - Mathf.Sin(Mathf.Deg2Rad * maxAngle); 326 for (int i = 0; i < collision.contactCount; i++) 327 { 328 if (i > MaxHitsBuffer) break; 329 330 ContactPoint c = collision.GetContact(i); 331 332 float flatness = Vector3.Dot(Vector3.up, c.normal); 333 if (flatness < 0f) 334 continue; 335 336 if (flatness >= minFlatness) 337 { 338 _isGrounded = true; 339 } 340 } 341 } 342 } 343 344 private void SlopeColliderValidate(RaycastHit tempHit, float radius) 345 { 346 Collider[] colBuffer = new Collider[MaxHitsBuffer]; 347 int num = 348 Physics.OverlapSphereNonAlloc( 349 transform.TransformPoint(slopeCheckPoint), 350 radius, 351 colBuffer, 352 hitLayerMask, 353 QueryTriggerInteraction.Ignore 354 ); 355 356 if (LedgeCheck(ledgeCheckRadius)) { 357 _internalSlopeHit = _lastGoodNormalHit; 358 return; 359 } 360 361 for (int i = 0; i < num; i++) 362 { 363 RaycastHit hit; 364 Physics.Linecast(transform.position, colBuffer[i].transform.position, out hit, hitLayerMask, QueryTriggerInteraction.Ignore); 365 if (((colBuffer[i].transform != tempHit.transform) || (hit.normal != tempHit.normal)) && IsStableOnNormal(tempHit.normal) && CheckIfValidCollider(hit.collider)) 366 { 367 if (IsStableOnNormal(hit.normal)) 368 { 369 _internalSlopeHit = hit; 370 _lastGoodNormalHit = _internalSlopeHit; 371 } 372 373 return; 374 } 375 } 376 377 if(IsStableOnNormal(tempHit.normal)) 378 { 379 _internalSlopeHit = _internalGroundHit; 380 _lastGoodNormalHit = _internalSlopeHit; 381 return; 382 } 383 384 _internalSlopeHit = _lastGoodNormalHit; 385 } 386 387 private bool LedgeCheck(float radius) 388 { 389 Collider[] colBuffer = new Collider[MaxHitsBuffer]; 390 int num = 391 Physics.OverlapSphereNonAlloc( 392 transform.TransformPoint(ledgeCheckPoint), 393 radius, 394 colBuffer, 395 hitLayerMask, 396 QueryTriggerInteraction.Ignore 397 ); 398 399 if (num.Equals(0)) 400 { 401 return true; 402 } 403 404 for (int i = 0; i < num; i++) 405 { 406 if (CheckIfValidCollider(colBuffer[i])) 407 { 408 return false; 409 } 410 } 411 412 return true; 413 } 414 415 private bool CheckIfValidCollider(Collider col) 416 { 417 return col != playerCollider && col != null; 418 } 419 420 } 421 }