using System.Collections; using System.Collections.Generic; using UnityEngine; public class PhysicsMovement2D : MonoBehaviour { #region Define public const float OnGroundTestLength = 0.02f; [System.Serializable] public struct AccelerationBindSetting { public PhysicMaterial OtherMaterial; public AccelerationSetting Setting; } [System.Serializable] public struct AccelerationSetting { public float DestAcc; public float DragAcc; } private enum Collider2DTypes { None, Box, Sphere, Capsule }; #endregion #region Interface public Vector2 DestVelocity { get => m_destVelocity; set { m_destVelocity = value; } } public bool IsOnGround { get { return m_dummyHitted; } } #endregion #region Setting public bool MoveOnXY = false; public AccelerationSetting InAirAccSetting; public AccelerationSetting DefaultGroundAccSetting; public List AdditionalGroundSettings; #endregion #region Movement private AccelerationSetting GetCurrentAccSetting() { if (!m_dummyHitted) { return InAirAccSetting; } else { return DefaultGroundAccSetting; } } private Vector2 CalcYAxis(Vector2 xaxis) { Vector2 result; // Rotate 90 deg result.x = -xaxis.y; result.y = xaxis.x; return result; } private float CalcProjLength(Vector2 dest, Vector2 fromProj) { return Vector2.Dot(fromProj, dest) / dest.magnitude; } private float CalcAccOnAxis(float destSpeed, float curSpeed, ref AccelerationSetting accSetting) { if (destSpeed > curSpeed) { float offset = destSpeed - curSpeed; float possibleDecreaseSpeed = accSetting.DestAcc * Time.fixedDeltaTime; if (possibleDecreaseSpeed > offset) { possibleDecreaseSpeed = offset; } return possibleDecreaseSpeed / Time.fixedDeltaTime; } else if (destSpeed == curSpeed) { return 0.0f; } else { float offset = curSpeed - destSpeed; float possibleIncreaseSpeed = accSetting.DragAcc * Time.fixedDeltaTime; if (possibleIncreaseSpeed > offset) { possibleIncreaseSpeed = offset; } return -possibleIncreaseSpeed / Time.fixedDeltaTime; } } private Vector2 GetApplyAcc(Vector2 currentVel, Vector2 destVel) { Vector2 result = Vector2.zero; AccelerationSetting accSetting = GetCurrentAccSetting(); if (MoveOnXY) { float curSpeed = currentVel.magnitude; float destSpeed = destVel.magnitude; if (Mathf.Approximately(destSpeed, 0.0f)) { if (Mathf.Approximately(curSpeed, 0.0f)) { return Vector3.zero; } float possibleDecreaseSpeed = accSetting.DragAcc * Time.fixedDeltaTime; if (possibleDecreaseSpeed > curSpeed) { possibleDecreaseSpeed = curSpeed; } result = -currentVel.normalized * (possibleDecreaseSpeed / Time.fixedDeltaTime); } else { Vector2 destDir = destVel.normalized; float destProj = CalcProjLength(destDir, currentVel); result += destDir * CalcAccOnAxis(destSpeed, destProj, ref accSetting); Vector2 destYDir = CalcYAxis(destDir); float destYProj = CalcProjLength(destYDir, currentVel); result += destYDir * CalcAccOnAxis(0.0f, destYProj, ref accSetting); } } else { float curSpeed = Mathf.Abs(currentVel.x); float destSpeed = Mathf.Abs(destVel.x); if (Mathf.Approximately(destVel.x, 0.0f)) { if (currentVel.x < 0.0f) { result -= Vector2.right * CalcAccOnAxis(destSpeed, curSpeed, ref accSetting); } else { result += Vector2.right * CalcAccOnAxis(destSpeed, curSpeed, ref accSetting); } } else { Vector2 destDir = destVel.normalized; float destProj = currentVel.x; if (destVel.x < 0.0f) { destProj *= -1.0f; } result += destDir * CalcAccOnAxis(destSpeed, destProj, ref accSetting); } result.y = 0.0f; } return result; } private void GetOnGround() { // Jumping is disable in XY mode if (MoveOnXY) { m_dummyHitted = true; return; } Collider2D compareCollider = null; int addCount = 0; // Do not touch these code switch (m_colliderType) { case Collider2DTypes.None: { Vector2 colliderPos = m_rigidbody.position; m_dummyHitted = Physics2D.Raycast(m_rigidbody.position, Vector2.down, OnGroundTestLength); return; } case Collider2DTypes.Box: { Vector3 colliderPos3 = m_boxCollider.transform.position; Vector2 colliderPos = new Vector2(colliderPos3.x, colliderPos3.y); addCount = Physics2D.OverlapBoxNonAlloc(colliderPos + Vector2.down * OnGroundTestLength, m_boxCollider.size, m_boxCollider.transform.eulerAngles.z, m_dummyContainer); compareCollider = m_boxCollider; } break; case Collider2DTypes.Sphere: { Vector3 colliderPos3 = m_circleCollider.transform.position; Vector2 colliderPos = new Vector2(colliderPos3.x, colliderPos3.y); addCount = Physics2D.OverlapCircleNonAlloc(colliderPos + Vector2.down * OnGroundTestLength, m_circleCollider.radius, m_dummyContainer); compareCollider = m_circleCollider; } break; case Collider2DTypes.Capsule: { Vector3 colliderPos3 = m_capsuleCollider.transform.position; Vector2 colliderPos = new Vector2(colliderPos3.x, colliderPos3.y); addCount = Physics2D.OverlapCapsuleNonAlloc(colliderPos + Vector2.down * OnGroundTestLength, m_capsuleCollider.size, m_capsuleCollider.direction, m_capsuleCollider.transform.eulerAngles.z, m_dummyContainer); compareCollider = m_capsuleCollider; } break; default: break; } int noneSelfCollider = 0; for (int i = 0; i < addCount; i++) { if (m_dummyContainer[i]) { if (m_dummyContainer[i] != compareCollider) { noneSelfCollider++; } m_dummyContainer[i] = null; } else { break; } } m_dummyHitted = noneSelfCollider > 0; } [SerializeField] private bool m_dummyHitted; private RaycastHit2D m_dummyHit; private Collider2D[] m_dummyContainer = new Collider2D[10]; private Rigidbody2D m_rigidbody; private Collider2DTypes m_colliderType; private CircleCollider2D m_circleCollider; private BoxCollider2D m_boxCollider; private CapsuleCollider2D m_capsuleCollider; protected Vector2 m_destVelocity; private Vector2 m_fixedUpdateVelocity; private Dictionary m_settingsDic = new Dictionary(); public Rigidbody2D RigidBody { get { return m_rigidbody; } } private void FixedUpdate_Movement() { GetOnGround(); Vector2 currentVel = m_rigidbody.velocity; Vector2 applyAcc = GetApplyAcc(currentVel, m_fixedUpdateVelocity); m_rigidbody.AddForce(applyAcc * m_rigidbody.mass, ForceMode2D.Force); } #endregion private T FindComponent() { T result = GetComponent(); if (result == null) { return GetComponentInChildren(); } return result; } private void Awake() { m_rigidbody = GetComponent(); m_circleCollider = FindComponent(); if (m_circleCollider) { m_colliderType = Collider2DTypes.Sphere; } else { m_boxCollider = FindComponent(); if (m_boxCollider) { m_colliderType = Collider2DTypes.Box; } else { m_capsuleCollider = FindComponent(); if (m_capsuleCollider) { m_colliderType = Collider2DTypes.Capsule; } else { m_colliderType = Collider2DTypes.None; } } } foreach (var setting in AdditionalGroundSettings) { m_settingsDic.Add(setting.OtherMaterial, setting.Setting); } AdditionalGroundSettings.Clear(); } private void FixedUpdate() { FixedUpdate_Movement(); } private void LateUpdate() { m_fixedUpdateVelocity = m_destVelocity; m_destVelocity = Vector2.zero; } }