Squash and stretch

The same jump with the same timing on both sides, and only the right one tweens scale. Three extra lines are the whole difference.

The editor above is running this project. Change a line and it reloads.

Godot classes used

TweenPolygon2DColorRect

The code

scripts/main.gd
extends Node

const RADIUS: float = 46.0
const GROUND_Y: float = 580.0
const TOP_Y: float = 250.0
const CROUCH: float = 0.18
const HALF: float = 0.51


func _ready() -> void:
	_add_background()
	_add_label('same jump, same timing, only the scale changes', Vector2(40, 40), Color('#999999'))
	_add_ground()

	_add_label('position only', Vector2(350, 620), Color('#999999'))
	_add_label('position + scale', Vector2(760, 620), Color('#8eef97'))

	var plain: Polygon2D = _make_ball(Color('#8da5f3'))
	plain.position = Vector2(430, GROUND_Y)
	add_child(plain)

	var juiced: Polygon2D = _make_ball(Color('#8eef97'))
	juiced.position = Vector2(850, GROUND_Y)
	add_child(juiced)

	var flat: Tween = create_tween().set_loops()
	flat.tween_interval(CROUCH)
	flat.tween_property(plain, 'position:y', TOP_Y, HALF).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
	flat.tween_property(plain, 'position:y', GROUND_Y, HALF).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)

	var juice: Tween = create_tween().set_loops()
	juice.tween_property(juiced, 'scale', Vector2(1.38, 0.62), CROUCH).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
	juice.tween_callback(juiced.set_scale.bind(Vector2(0.72, 1.38)))
	juice.tween_property(juiced, 'position:y', TOP_Y, HALF).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
	juice.parallel().tween_property(juiced, 'scale', Vector2.ONE, HALF)
	juice.tween_property(juiced, 'position:y', GROUND_Y, HALF).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
	juice.parallel().tween_property(juiced, 'scale', Vector2(0.74, 1.36), HALF)

	print('the green ball is round only at the top of the arc')


func _add_ground() -> void:
	var floor_rect: ColorRect = ColorRect.new()
	floor_rect.color = Color('#2a2f3d')
	floor_rect.position = Vector2(0, GROUND_Y)
	floor_rect.size = Vector2(1280, 8)
	add_child(floor_rect)

	var divider: ColorRect = ColorRect.new()
	divider.color = Color('#2a2f3d')
	divider.position = Vector2(639, 120)
	divider.size = Vector2(2, 460)
	add_child(divider)


# the origin sits on the contact point, so scaling squashes against the floor
func _make_ball(color: Color) -> Polygon2D:
	var points: PackedVector2Array = PackedVector2Array()

	for i in 32:
		var angle: float = TAU * i / 32
		points.append(Vector2(cos(angle), sin(angle)) * RADIUS - Vector2(0, RADIUS))

	var ball: Polygon2D = Polygon2D.new()
	ball.polygon = points
	ball.color = color

	return ball


func _add_background() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)


func _add_label(text: String, at: Vector2, color: Color) -> void:
	var label: Label = Label.new()
	label.text = text
	label.position = at
	label.add_theme_color_override('font_color', color)
	add_child(label)

More Animation examples