Godot 4のコンポーネント指向開発シリーズ、今回はゲームの見た目と手触りを劇的に良くする**「SquashStretch (伸縮演出)」**です。

「アニメーションの12原則」の一つにも数えられるこの技術は、キャラクターがジャンプした瞬間に**「縦に伸び」、着地した瞬間に「横に潰れる」**演出を加えます。これを入れるだけで、あなたのキャラクターはまるでゴムボールのような弾力と生命力を持ちます。


このコンポーネントは、親ノード(キャラクター)の状態を監視し、ジャンプや着地のタイミングに合わせて、スプライト(画像)のスケールを動的に変化させます。

1. コンポーネントのコード (Full Code)

以下のコードをコピーして、SquashStretch.gd という名前で保存してください。

class_name SquashStretch
extends Node

## ジャンプや着地時にスプライトを伸縮(スカッシュ&ストレッチ)させるコンポーネント
## 注意: 親のCharacterBody2D自体ではなく、その子にある「Sprite2D」を操作します。
## (PhysicsBody自体を変形させると当たり判定がおかしくなるため)

# --- 設定パラメータ ---
@export_group("Deformation Settings")
@export var stretch_strength: Vector2 = Vector2(0.6, 1.4) ## ジャンプ時の伸び率 (幅, 高さ)
@export var squash_strength: Vector2 = Vector2(1.4, 0.6)  ## 着地時の潰れ率 (幅, 高さ)
@export var recovery_speed: float = 10.0 ## 元の形(1,1)に戻る速さ

# --- 内部変数 ---
var _parent: CharacterBody2D
var _sprite: Node2D
var _was_on_floor: bool = true

func _ready() -> void:
	# 親を取得
	_parent = get_parent() as CharacterBody2D
	if not _parent:
		push_error("SquashStretch: 親が CharacterBody2D ではありません。")
		set_physics_process(false)
		return
	
	# 操作対象のスプライトを探す
	# 基本的に「Sprite2D」または「AnimatedSprite2D」という名前の子ノードを探します
	_sprite = _parent.get_node_or_null("Sprite2D")
	if not _sprite:
		_sprite = _parent.get_node_or_null("AnimatedSprite2D")
	
	if not _sprite:
		push_warning("SquashStretch: 親の下に 'Sprite2D' または 'AnimatedSprite2D' が見つかりません。")
		set_physics_process(false)

func _physics_process(delta: float) -> void:
	# 1. 常に元の形 (1.0, 1.0) に向かって滑らかに戻ろうとする (Lerp)
	_sprite.scale = _sprite.scale.lerp(Vector2.ONE, recovery_speed * delta)
	
	# 現在の接地状態を取得
	var is_on_floor = _parent.is_on_floor()
	
	# --- 状態変化の検知 ---
	
	# A. ジャンプ開始検知 (床にいた -> いなくなった && 上昇中)
	if _was_on_floor and not is_on_floor and _parent.velocity.y < 0:
		_sprite.scale = stretch_strength # びよーんと伸びる
		
	# B. 着地検知 (床にいなかった -> いるようになった)
	elif not _was_on_floor and is_on_floor:
		_sprite.scale = squash_strength # むにゅっと潰れる
	
	# 状態を保存
	_was_on_floor = is_on_floor

2. 使い方チュートリアル

このコンポーネントは、「当たり判定(Collision)」を変形させずに、「見た目(Sprite)」だけを変形させるのがコツです。

手順①:プレイヤーの構成確認

プレイヤーのシーン構成が以下のようになっていることを確認してください。スプライトの名前が重要です。

Player (CharacterBody2D)
 ├── CollisionShape2D (これは変形させたくない!)
 ├── Sprite2D (これが変形対象。名前は "Sprite2D" にする)
 ├── JumpController (ジャンプ用)
 └── GravityComponent (重力用)

※もし AnimatedSprite2D を使っている場合も、その名前なら自動で認識します。

手順②:コンポーネントのアタッチ

  1. Player の子ノードとして Node を追加し、名前を SquashStretch にします。
  2. スクリプト SquashStretch.gd をアタッチします。

手順③:スプライトの原点(Offset)調整

これが最も重要です!

通常、スプライトの中心は画像の真ん中です。そのまま縮めると「空中に浮いて縮む」ように見えてしまいます。

「足元」を基準に変形させるため、以下の設定を行ってください。

  1. Sprite2D ノードを選択。
  2. インスペクターの Offset セクションを開く。
  3. Centered のチェックを外す。
    • 画像がズレるので、Position を調整して、画像の足元が Player の原点(0,0)に来るように合わせます。
    • または、Offsety 値を調整して足元を合わせます。

手順④:実行と調整

ゲームを実行してジャンプしてみてください。

  • 飛び上がる瞬間に縦長になり、
  • 着地した瞬間に横長になり、
  • すぐに「プルン」と元の形に戻れば成功です!

3. このコンポーネントの仕組みと効果

なぜ PhysicsBody を変形させないの?

親(CharacterBody2D)自体の scale を変更すると、子ノードにある CollisionShape2D(当たり判定)まで変形してしまいます。

着地した瞬間に当たり判定が横に広がると、壁にめり込んだり、床を突き抜けたりするバグの原因になります。

そのため、このコンポーネントはあえて親ではなく、兄弟である「スプライトだけ」を探して変形させています。

lerp によるゴムの挙動

コード内の以下の行が、ゴムのような質感の正体です。

_sprite.scale = _sprite.scale.lerp(Vector2.ONE, recovery_speed * delta)

lerp(線形補間)を使って、毎フレーム「現在の形」から「正常な形(1, 1)」に向かって少しずつ近づけています。

Recovery Speed を高くすると硬いゴム(すぐ戻る)、低くすると柔らかいゼリー(ゆっくり戻る)のような質感になります。

応用:ダメージ時にも使う

このスクリプトに外部から呼べる関数を追加すれば、ダメージを受けた時の演出にも使えます。

# 追加関数の例
func apply_impact():
    # びっくりして縮こまる演出
    _sprite.scale = Vector2(1.5, 0.5) 

これを KnockbackReceiver などから呼び出せば、ダメージを受けた瞬間にキャラクターがグシャッと潰れるコミカルな表現が可能になります。