Tweening across a parabola as though it were a jump and there were gravity

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By rainswell

Hi guys. I’ve been working on something for a while and I’d appreciate some help if anyone is interested.

Example:

Other Example:

What I want to do is have a character ‘jump’ from a point, to another point, while moving through a point in between. I want this to be visualized as a parabola. I also want it to appear like a ‘natural’ jump.

I’ve done everything but making the jump look natural. What I’m doing so far is calculating two parabolas through code, one on the left side of the central point and one on the right, then adding both of their points to a Curve2D. I add the Curve2D as the curve property of a Path2D, slap a PathFollow2D on there and iterate over it using its unit_offset property. This causes the character to move along the parabola at a constant rate. So far so good.

But here’s where I’m stuck. I want to move across this curve as if I’m jumping, meaning I want the character to slow down at the top of the curve and speed up again as he comes back down. I can’t figure out a way to do this. Currently I’m just tweening from 0 to 1 and setting the PathFollow2D’s unit_offset property to that. The problem is that if I interpolate using say, TRANS_QUAD and EASE_IN_OUT, the slowing takes place exactly at the x center of the curve. Which is a big problem for curves like the second one above, because the exact center is not the highest point.

What I’ve tried:

  • Literally split the arc in two (treating it as 2 curves rather than combining each half-parabola into one). This allows me to calculate the proper speed and do EASE_OUT on one side and EASE_IN on the other. This doesn’t work though because when I interpolate the character through them there’s a ‘catch’ at the central point, and there seems to be no way to get rid of that (removing the central point from one of the curves so it isn’t ‘duplicated’ does the opposite of help)

I also haven’t included any code because there’s a decent amount. If you think it would help you to see it outside the conceptual realm, just let me know. I have separate scripts for the standard arc and the split arc I tried so let me know which you’d like to see.

Please feel free to give me any questions along the lines of “Why are you doing it that way, my guy?” This is just me working and I tend to overcomplicate things. As far as I can tell this is the only way to accomplish this.

Thank you again.

:bust_in_silhouette: Reply From: klaas

Hi,
check out this excellent tutorial:

and look at his code on github:

maybe one of his methods fits your needs

I appreciate the resource!

rainswell | 2021-07-09 20:33

i’ve found an implementation of it in one of my projects.
Haven’t cleaned it from additional code but i think it gets the point

extends Spatial
class_name Grenade
var max_height:float = 6.0

var lateral_speed:float = 6.0
var divergence:Vector3 = Vector3(2,0,2)
var fire_velocity:Vector3 = Vector3()
var gravity:float = 0
var target:Vector3

var damage_group:String = ""
var damage_radius = 3
var damage:float = 5

func _ready():
	set_physics_process( false )

func _physics_process(delta):
	translate(fire_velocity * delta)
	fire_velocity.y -= gravity * delta
	if translation.y < target.y:
		print("greande reached destination")
		Main.__explosion.instance().detonate(global_transform.origin)
		damage()
		queue_free()


func target(group):
	damage_group = group
	return self

func damage():
	var units = get_tree().get_nodes_in_group(damage_group)
	for unit in units:
		if translation.distance_to(unit.global_transform.origin) < damage_radius + unit.radius:
			print("damage")
			unit.injure(damage)



func throw( origin:Vector3, nTarget:Vector3 ):
	target = nTarget + Vector3(rand_range(-divergence.x,divergence.x),rand_range(-divergence.y,divergence.y),rand_range(-divergence.z,divergence.z))
	if not solve_ballistic_arc_lateral( origin, target):
		queue_free()
		return
	
	translation = origin
	Main.level.add_child(self)
	set_physics_process( true )


func solve_ballistic_arc_lateral( proj_pos:Vector3, target_pos:Vector3):

	# Handling these cases is up to your project's coding standards
	if proj_pos != target_pos and lateral_speed > 0 and max_height > proj_pos.y:
		print_debug( "fts.solve_ballistic_arc called with invalid data");
	
	var diff:Vector3 = target_pos - proj_pos
	var diffXZ:Vector3 = Vector3(diff.x, 0, diff.z)
	var lateralDist:float = diffXZ.length()
	
	if (lateralDist == 0):
		return false
	
	var time:float = lateralDist / lateral_speed
	
	fire_velocity = diffXZ.normalized() * lateral_speed
	
	# System of equations. Hit max_height at t=.5*time. Hit target at t=time.
	#
	# peak = y0 + vertical_speed*halfTime + .5*gravity*halfTime^2
	# end = y0 + vertical_speed*time + .5*gravity*time^s
	# Wolfram Alpha: solve b = a + .5*v*t + .5*g*(.5*t)^2, c = a + vt + .5*g*t^2 for g, v
	var a:float = proj_pos.y;       # initial
	var b:float = max_height;       # peak
	var c:float = target_pos.y;     # final
	
	gravity = -4*(a - 2*b + c) / (time* time)
	fire_velocity.y = -(3*a - 4*b + c) / time
	
	return true

klaas | 2021-07-09 20:52

Thank you! I’ll look this over.

rainswell | 2021-07-09 21:03