Last active
March 15, 2021 13:36
-
-
Save hoonsubin/40fa4d79be931852d3a974fe2709ddb3 to your computer and use it in GitHub Desktop.
A Unity game component for simulating a 3D projectile arc in 2D world space
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
public class TopDown2DProjectileMotion : MonoBehaviour | |
{ | |
public Rigidbody2D throwTarget; | |
public float throwRadious = 6f; | |
public float maxThrowHeight = 3f; | |
public float gravityMultiplier = -18; | |
public bool debugPath; | |
private float _currentHeight; | |
private Vector3 _endPoint; | |
private float _peakHeight; | |
private float _originHeight; | |
private bool _passedPeak; | |
private bool _onGround; | |
private bool _fired; | |
void Start() | |
{ | |
throwTarget.gravityScale = 0.0f; | |
_currentHeight = maxThrowHeight; | |
_originHeight = maxThrowHeight; | |
} | |
void Update() | |
{ | |
if (!_fired && Input.GetKeyDown(KeyCode.Space)) | |
{ | |
//Launch(); | |
ThrowObject(throwTarget, Camera.main.ScreenToWorldPoint(Input.mousePosition)); | |
} | |
if (debugPath && !_fired) | |
{ | |
DrawPath(); | |
} | |
if (_fired) | |
{ | |
if (Mathf.Abs(_peakHeight - throwTarget.position.y) <= 0.25f) | |
{ | |
_passedPeak = true; | |
} | |
if (Vector3.Distance(throwTarget.position, _endPoint) <= 0.25f && _passedPeak) | |
{ | |
_onGround = true; | |
} | |
else | |
{ | |
_onGround = false; | |
} | |
if (_onGround) | |
{ | |
throwTarget.velocity = Vector3.zero; | |
throwTarget.gravityScale = 0.0f; | |
_passedPeak = false; | |
_fired = false; | |
} | |
} | |
} | |
void ThrowObject(Rigidbody2D throwingObject, Vector2 throwTargetPos) | |
{ | |
_fired = true; | |
// fixme: maybe there is a better way than directly changing the gravity every time the object launches | |
Physics2D.gravity = Vector3.up * gravityMultiplier; | |
throwingObject.gravityScale = 1.0f; | |
throwingObject.velocity = CalculateLaunchData(throwingObject.position, throwTargetPos).initialVelocity; | |
} | |
LaunchData CalculateLaunchData(Vector2 throwingPosition, Vector2 landingPosition) | |
{ | |
Vector3 throwingPos3D = new Vector3(throwingPosition.x, throwingPosition.y, 0f); | |
Vector3 landingPos3D = new Vector3(landingPosition.x, landingPosition.y, 0f); | |
Vector3 dir = (landingPos3D - throwingPos3D).normalized; | |
if (Vector3.Distance(throwingPos3D, landingPos3D) > throwRadious) | |
{ | |
landingPos3D = dir * throwRadious + throwingPos3D; | |
} | |
float displacementY = landingPos3D.y - throwingPosition.y; | |
float displacementX = landingPos3D.x - throwingPosition.x; | |
if (displacementY < 0) | |
{ | |
_currentHeight = _originHeight - _originHeight * Mathf.Abs(displacementY) / throwRadious; | |
} | |
else | |
{ | |
_currentHeight = _originHeight + _originHeight * Mathf.Abs(displacementY) / throwRadious; | |
} | |
float a = displacementY - _currentHeight; | |
if (a > 0) | |
{ | |
a = 0; | |
} | |
float time = Mathf.Sqrt(-2 * _currentHeight / gravityMultiplier) + Mathf.Sqrt(2 * (a) / gravityMultiplier); | |
Vector3 velocityY = Vector3.up * Mathf.Sqrt(-2 * gravityMultiplier * _currentHeight); | |
Vector3 velocityX = Vector3.right * displacementX / time; | |
Vector3 initialVelocity = velocityX + velocityY; | |
return new LaunchData(initialVelocity * -Mathf.Sign(gravityMultiplier), time); | |
} | |
void DrawPath() | |
{ | |
LaunchData launchData = CalculateLaunchData(throwTarget.position, Camera.main.ScreenToWorldPoint(Input.mousePosition)); | |
Vector3 previousDrawPoint = throwTarget.position; | |
int resolution = 30; | |
_peakHeight = float.MinValue; | |
for (int i = 1; i <= resolution; i++) | |
{ | |
float simulationTime = i / (float)resolution * launchData.timeToTarget; | |
Vector3 displacement = launchData.initialVelocity * simulationTime + Vector3.up * gravityMultiplier * simulationTime * simulationTime / 2f; | |
Vector3 drawPoint = new Vector3(throwTarget.position.x, throwTarget.position.y, 0.0f) + displacement; | |
Debug.DrawLine(previousDrawPoint, drawPoint, Color.green); | |
previousDrawPoint = drawPoint; | |
if (i == resolution) | |
{ | |
_endPoint = drawPoint; | |
} | |
if (drawPoint.y > _peakHeight) | |
{ | |
_peakHeight = drawPoint.y; | |
} | |
} | |
} | |
private void OnDrawGizmos() | |
{ | |
Gizmos.color = Color.red; | |
Gizmos.DrawWireSphere(throwTarget.position, throwRadious); | |
Gizmos.color = Color.green; | |
Gizmos.DrawSphere(_endPoint, 1.0f); | |
} | |
struct LaunchData | |
{ | |
public readonly Vector3 initialVelocity; | |
public readonly float timeToTarget; | |
public LaunchData(Vector3 initialVelocity, float timeToTarget) | |
{ | |
this.initialVelocity = initialVelocity; | |
this.timeToTarget = timeToTarget; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment