Skip to content

Instantly share code, notes, and snippets.

@hoonsubin
Last active March 15, 2021 13:36
Show Gist options
  • Save hoonsubin/40fa4d79be931852d3a974fe2709ddb3 to your computer and use it in GitHub Desktop.
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
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