[2d] Throw item in the air with rotation and land on the exact position

Godot Version

godot-4

Question

Hey friendly people! I want to spawn a weapon in my player position and then throw it in the air with a rotation and then I want it to land exactly in the middle of the weapon rack. I’ve tried tween approach, but this look very unnatural. So I guess there is an approach with RigidBody2d, but from my level it seems impossible to calculate correct initial impulse power and direction. So is there a way to do that? :smiley:

There are probably more elegant ways. But what about path2d? Then you add the weapon as Child and then the progress. Didnt use it for a while.. but could be an option, if it should take the same direction

1 Like

@hydraware 's method would work, or you could do a 2D ballistics solver (which sounds intimidating, but really isn’t that complicated). Personally, I’d probably fudge it with interpolation:

const FINAL_ANGLE:  float = PI      # 180 degrees
const ANGLE_TRAVEL: float = TAU * 2 # 720 degreees
const TRAVEL_TIME:  float = 2.25    # seconds
const THROW_HEIGHT: float = 50.0    # pixels

var time:    float
var src_pos: Vector2
var tgt_pos: Vector2

func fling_start(src: Vector2, tgt: Vector2) -> void:
    time    = 0.0
    src_pos = src
    tgt_pos = tgt

func fling_update(delta: float) -> bool:
    bool done = false
    time += delta

    if time >= TRAVEL_TIME:
        time = TRAVEL_TIME
        done = true

    var t: float = time / TRAVEL_TIME # Gives us between 0.0 and 1.0

    var x: float = lerp(src_pos.x, tgt_pos.x, t) # Get the horizontal position.
    var y: float = lerp(src_pos.y, tgt_pos.y, t) # Account for height difference.

    # Add a height curve.  t is (0..1), we subtract half and multiply by PI to
    # make the range (-PI/2 .. PI/2), which gives us the "hill" part of cosine.
    y += cos((t - 0.5) * PI) * THROW_HEIGHT

    # Set the position to our interpolated value, and interpolate a value for
    # rotation. Note that we're lerping backwards for the rotation so that we
    # wind up at FINAL_ANGLE.

    position = Vector2(x, y)
    rotation = FINAL_ANGLE + lerp(ANGLE_TRAVEL, 0.0, t)

    return done
2 Likes

You should stick with Tweens, they have tons of properties to tweak; can you post what you made with them so far?

At first blush it seems a bit complicated to use Tween to do a parabolic arc. The basic positions could be fed to Tween for lerping, as could the angle, but how does one get the “ballistic” altitude component with Tween?

Yeah, for tween it didn’t work smoothly enough, because the movement of the weapon was just with a consistent speed, but when we throw into the air items their speed changes.

So I explored a bit more and came in with this RigidBody2d and applying central force + torque. And on collision with bottom of that weapon rack I just freeze the object and reset it’s position.

Funny enough looks pretty smooth :smiley:
Here I made a short video how the code worked in the end: https://www.youtube.com/watch?v=Ho2eErJ2J2Y&ab_channel=IndieInk

func spawn_weapon() -> void:
	_current_weapon = CREATED_WEAPON.instantiate()
	
	add_child(_current_weapon)
	
	_current_weapon.global_position = get_tree().get_first_node_in_group(Player.GROUP_NAME).global_position
	
	animation_created_weapon(_current_weapon)


func animation_created_weapon(weapon: RigidBody2D) -> void:
	weapon.add_constant_torque(50)
	var middle: Vector2 = weapon_landing_marker.global_position - weapon.global_position
	
	middle.y -= LAUNCH_IN_THE_AIR_FORCE
	
	weapon.apply_central_force(middle/ 2)


func _on_weapon_landing_body_entered(body: Node2D) -> void:
	if body is CreatedWeapon:
		body.sleeping = true
		body.global_position = weapon_landing_marker.global_position
		body.rotation = 0
1 Like

If you are using lerp over time then it’s usually easy to convert to a tween, here’s approximately what you wrote as a Tween

const travel_time: float = 2.0
const arc_height: float = -200.0

var tween_x = sword.create_tween().set_parallel()
tween_x.tween_property(sword, "position:x", target_pos.x, travel_time)
tween_x.tween_property(sword, "rotation", TAU*2, travel_time).from(0)

var tween_arc := sword.create_tween().set_trans(Tween.TRANS_SINE)
tween_arc.tween_property(sword, "position:y", target_pos.y + arc_height, travel_time/2.0).set_ease(Tween.EASE_OUT)
tween_arc.tween_property(sword, "position:y", target_pos.y, travel_time/2.0).set_ease(Tween.EASE_IN)

Here’s a little video of it too!

5 Likes

Ah, ok, that makes sense.

Whoa! That looks dope!

And thank you all for your replies! I actually also played with tweens and lerps, so it was very helpful in my learning! <3

1 Like