【Godot】Godotのカットシーン入門:ボス登場の6秒をAnimationPlayerで組み、スキップできるようにする

作成: 2026-09-23最終更新: 2026-10-10

ボス登場の6秒の演出をAnimationPlayerで組み、スキップしても最後まで見たときと同じ状態で終わるようにする方法を解説します。

ボスの登場のような数秒の演出(カットシーン)は、AnimationPlayer で扉・カメラ・ボスの動きを1本の時間軸に並べて作ります。2回目以降のために、途中で飛ばせるスキップも付けておくのが定番です。

ただ、AnimationPlayer は入力を止めないので、演出の途中でもプレイヤーが歩けてしまいます。スキップを stop() で作ると、扉が閉じたまま、ボスが消えたままで操作が戻ります。

この記事では、ボス登場の6秒の演出を、スキップしても同じ状態で終わる形に組む方法を解説します。

6秒のタイムラインにそって扉が開き、カメラがボスへ寄り、ボスが吠える。横のEnterキーから、最後の時刻へまっすぐ飛ぶ矢印

この記事でわかること

  • アニメに任せるもの(最後に残る状態)と、自分で切り替えて戻すもの(入力とカメラ)の分け方
  • プロパティのトラックとメソッドのトラックの使い分け
  • スキップで seek() の3つ目の引数を true にする理由と、stop() を使わない理由
  • 最後まで見ても、スキップしても、同じ関数で終わらせる作り

動作確認環境: Godot 4.7

Sponsored

アニメに任せる部分と、自分で切り替える部分

AnimationPlayer には、ノードのプロパティを時刻ごとに決めるトラックを何本も並べられます。扉の位置、カメラの位置、ボスの表示を1本の時間軸にそろえれば、6秒の演出が1つのアニメになります。キーフレームの打ち方は AnimationPlayer入門 にあります。

ただ、アニメが決めるのは、トラックに入れたプロパティだけです。プレイヤーの入力も、どのカメラで映すかも、アニメは触りません。この2つは、演出を始める関数で切り替えて、終わらせる関数で元に戻します。

6秒の時間軸に扉・演出カメラ・ボス・字幕の4本のトラック。その外側で、始まりの関数が入力と映すカメラを切り替え、終わりの関数が元に戻す

アニメが最後まで再生されると、キーを打ったプロパティは最後の値のまま残ります。扉は開いたまま、ボスは出たままです。元へ戻したいときだけ、戻す処理を書きます。

他のエンジンでいうと: 複数のノードの動きや関数の呼び出しを時間軸に並べる AnimationPlayer は、演出作りでは Unity の Timeline や Unreal Engine の Sequencer に近い役割です。

プロパティのトラック(最後に残る状態)

扉が開いた、ボスが出た、字幕が消えた。演出が終わったあとも残ってほしいものは、プロパティのトラックに置きます。扉は position、ボスは visible、字幕も visible です。

扉・ボス・字幕はプロパティのトラックで、最後のキーの値が6秒まで残る。咆哮はメソッドのトラックの3秒の1点で鳴る

visible のように途中の値が無いものは、トラックの更新モードが Discrete(キーの時刻でぱっと切り替わる)で作られます。ボスなら0秒に false、2秒に true のキーを打てば、2秒で現れます。

ボスを出す処理を、メソッドのトラックで add_child()(ノードをシーンに足す関数)させるのは避けます。あとで説明するスキップで、その呼び出しが飛ばされるとボスがいなくなります。ボスは最初から部屋に置いておき、visible で出します。visible は描くかどうかだけです。当たりや動きを持つボスなら、演出が終わるまでは Process Mode(ノードを毎フレーム動かすかどうか)を Disabled にしておき、_finish() で戻して戦闘を始めます。

メソッドのトラック(その瞬間の出来事)

咆哮の効果音のように、その瞬間に1回起きればよい出来事は、メソッドのトラック(決まった時刻に関数を呼ぶトラック)に置きます。3秒のキーで、Main の play_roar() を呼びます。

# メソッドのトラックから 3.0 秒で呼ばれる
func play_roar() -> void:
	if running:                       # 演出の途中のときだけ鳴らす
		roar_sound.play()

if running: を付けるのは、メソッドのトラックの呼び出しが、既定ではその場ですぐ実行されず、少しあとで届くからです(AnimationPlayer の Callback Mode › Method が既定で Deferred のため)。スキップした直後に呼び出しが届いても、演出が終わっていれば鳴りません。

3秒のメソッドのキーから play_roar() が呼ばれ、演出の途中なら音が鳴り、スキップで終わったあとなら鳴らない

入力とカメラの切り替え

演出を始める関数で、プレイヤーを止めて、演出用のカメラに切り替えます。終わらせる関数で、その2つを元に戻します。

func _start() -> void:
	seen = true                        # 始めた時点で「見た」にする
	running = true
	player.set_physics_process(false)  # 演出の間は動かさない
	cutscene_camera.make_current()     # 演出用のカメラに切り替える
	cutscene.play("boss_intro")

set_physics_process(false) は、そのノードの _physics_process() を止めます。移動を _physics_process() にまとめておけば、この1行でプレイヤーが止まります。画面を映すカメラは1台だけで、最後に make_current() を呼んだカメラが映します。終わりの関数でプレイヤーのカメラの make_current() を呼べば戻ります。

始まりの関数が入力を止めてカメラを切り替え、最後まで見ても Enter でスキップしても、同じ終わりの関数が元に戻す

終わりの関数は、最後まで見たとき(animation_finished)とスキップしたときの両方から呼びます。2回呼ばれても1回だけ動くよう、先頭で running を見ます。「見た」の印の seen は、終わったときではなく始めた時点で立てます。始めた瞬間に立てておけば、演出の途中で何が起きても2回目は始まりません。

Sponsored

スキップ(seek で最後の時刻へ飛ぶ)

スキップでは、アニメを最後の時刻へ動かしてから、終わりの関数を呼びます。

# 最後の時刻へ。3つ目の true で、メソッドと音のトラックは処理しない
cutscene.seek(cutscene.current_animation_length, true, true)
_finish()

current_animation_length は、いま再生しているアニメの長さ(ここでは6秒)です。seek() の2つ目(update)の true は、飛んだ先の値をすぐ当てる指定です。扉は開ききった位置になり、ボスの visible は最後のキーの true になります。

3つ目(update_only)の true は、メソッドと音のトラックを処理しない指定です。これを省くと、行き先の時刻までで最後にあるメソッドのキー(ここでは3秒の play_roar())が1つ呼ばれ、音のトラックも処理されます。この記事の play_roar() は running の確認で鳴らずに済みます。確認の無い関数や音のトラックは、スキップした瞬間に動きます。

スキップの3通り。seek の3つ目まで true なら扉が開きボスが出て静か。3つ目を省くと途中のメソッドが呼ばれる。stop() だと扉が閉じてボスが消える

スキップに stop() を使うと、扉が閉じた位置に戻り、ボスも字幕も隠れた状態になります。引数なしの stop() は、アニメの最初の時刻の値を当て直してから止めるからです。終わりの関数で止めるときは stop(true) にして、最後の値のまま止めます。

最後の時刻へ seek() したときは、animation_finished のシグナルが出ません。スキップの処理の中で _finish() を自分で呼ぶのは、そのためです。

3つ目を true にして飛ぶので、飛ばした区間のメソッドのキーは呼ばれないまま終わります。そのため、メソッドのトラックに置くのは、飛ばしてもよい出来事だけにします。ボス戦を始めるなど、演出のあとに必ず起きることは、最後の時刻のキーではなく _finish() に書きます。

実践:ボス登場の6秒を組み、Enterで飛ばす

アクションのボス戦、RPG の重要イベント、ホラーの扉が開く瞬間 —— 決まった数秒を見せて、操作を戻す演出は同じ組み方です。部屋の入口を通ると、扉が開き、カメラが右のボスへ寄り、ボスが吠えて、カメラが戻ります。Enter(ui_accept。Space でも同じ)を押すと、その場で最後の状態へ飛びます。

入口を通ると扉が上へ開き、カメラが右へ動いて、扉の右にボスが現れて吠え、カメラが戻る部屋の場面

① 置くもの

新しい2Dのプロジェクトで始めます。main.tscn のルートは Node2D(名前 Main、メインシーンに設定)で、子に次のものを置きます。

  • Player(CharacterBody2D、位置 (200, 300))。子に Sprite2D(icon.svg、Scale 0.5)、CollisionShape2D(CircleShape2D、Radius 32)、PlayerCamera(Camera2D)
  • Trigger(Area2D、位置 (450, 300))。子に CollisionShape2D(RectangleShape2D、Size (40, 400))。入口の見えない線
  • Door(Sprite2D、位置 (900, 300)、icon.svg)。見た目だけの扉
  • Boss(Sprite2D、位置 (1100, 300)、icon.svg、Visible をオフ)
  • CutsceneCamera(Camera2D、位置 (450, 300))
  • UI(CanvasLayer。カメラが動いても画面の同じ位置に出る層)。子に Subtitle(Label、Text は「グオオオ!」、Position (500, 520)、Visible をオフ)
  • RoarSound(AudioStreamPlayer)。Stream に手元の効果音(.wav か .ogg)を入れる。無くても演出は動く
  • Cutscene(AnimationPlayer)

② 組み立て

プレイヤーは、矢印キーで動くだけです。

# player.gd(Player に付ける)
extends CharacterBody2D

func _physics_process(_delta: float) -> void:
	velocity = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") * 200.0
	move_and_slide()

Main が、演出の始まり・スキップ・終わりを受け持ちます。

# main.gd(Main に付ける)
extends Node2D

@onready var player: CharacterBody2D = $Player
@onready var player_camera: Camera2D = $Player/PlayerCamera
@onready var cutscene_camera: Camera2D = $CutsceneCamera
@onready var cutscene: AnimationPlayer = $Cutscene
@onready var roar_sound: AudioStreamPlayer = $RoarSound

var seen := false        # 一度見たら、もう流さない
var running := false     # 演出の途中か

func _ready() -> void:
	player_camera.make_current()
	$Trigger.body_entered.connect(_on_trigger_body_entered)
	cutscene.animation_finished.connect(_on_cutscene_finished)

func _on_trigger_body_entered(body: Node2D) -> void:
	if body == player and not seen:
		_start()

# 入力を止めて、カメラを切り替える
func _start() -> void:
	seen = true                        # 始めた時点で「見た」にする
	running = true
	player.set_physics_process(false)  # 演出の間は動かさない
	cutscene_camera.make_current()
	cutscene.play("boss_intro")

func _unhandled_input(event: InputEvent) -> void:   # UIが使わなかった入力がここへ届く
	if running and event.is_action_pressed("ui_accept"):
		# 最後の時刻へ。3つ目の true で、メソッドと音のトラックは処理しない
		cutscene.seek(cutscene.current_animation_length, true, true)
		_finish()                      # この seek では animation_finished が出ない

func _on_cutscene_finished(anim_name: StringName) -> void:
	if anim_name == "boss_intro":
		_finish()

# 最後まで見ても、スキップしても、ここへ来る
func _finish() -> void:
	if not running:
		return                         # 2回呼ばれても1回だけ
	running = false
	cutscene.stop(true)                # 最後の値のまま止める(引数なしだと最初に戻る)
	roar_sound.stop()
	player_camera.make_current()       # 元のカメラに戻す
	player.set_physics_process(true)   # 入力を戻す

# メソッドのトラックから 3.0 秒で呼ばれる
func play_roar() -> void:
	if running:
		roar_sound.play()

スクリプトを付けて保存したら、Cutscene に新しいアニメ boss_intro を作ります。長さは 6.0 秒、ループはオフです。トラックは次の6本です。

トラックキー
Door:position0秒 (900, 300) → 1秒 (900, 150)
CutsceneCamera:position1秒 (450, 300) → 3秒 (1000, 300) → 5秒 (1000, 300) → 6秒 (450, 300)
Boss:visible(Discrete)0秒 false、2秒 true
Boss:scale3秒 (1, 1) → 3.5秒 (1.3, 1.3) → 4秒 (1, 1)
UI/Subtitle:visible(Discrete)0秒 false、3秒 true、5秒 false
メソッド(対象は Main)3秒 play_roar()

メソッドのトラックは、Add Track から Call Method Track を選んで作ります。対象には Main を指定します。3秒の位置で右クリックして Insert Key を選ぶと、関数の一覧から play_roar を選べます。

boss_intro の6秒のタイムライン。扉は0〜1秒、カメラは1〜3秒で寄り5〜6秒で戻る、ボスは2秒で表示、3秒に咆哮のキー、字幕は3〜5秒

③ 動かして確かめる

最後まで見た場合と0.5秒でEnterを押した場合の比較。右へ歩くと、どちらも扉が開き、ボスが出ている。飛ばした側は咆哮と字幕が無い

Main を実行します。見るのは3つです。

まず、矢印キーで右へ歩きます。入口の見えない範囲に触れたところで演出が始まります(範囲は Debug メニューの Visible Collision Shapes で見えます)。扉が1秒かけて上へ開き、カメラが右へ動いてボスのほうへ寄ります。2秒で扉の右にボスが現れ、3秒で吠えて字幕が出ます。その間、矢印キーを押してもプレイヤーは動きません。動くなら、移動の処理が _physics_process() の外にあります。

次に、6秒でカメラがプレイヤーへ戻り、すぐに歩けるようになります。ボスは画面の外ですが、右へ歩くと、扉は開いたまま、ボスも出たままです。入口を戻ってもう一度通っても、演出は流れません。

仕上げに、ゲームを開き直して、扉が開いている途中(0.5秒あたり)で Enter を押します。字幕も咆哮も飛ばして、カメラがプレイヤーに戻り、すぐ歩けます。そのまま右へ歩くと、最後まで見たときと同じく、扉は開ききり、ボスが出ています。扉が閉じていてボスがいないなら、スキップで stop() を引数なしで呼んでいます。

ポイントは2つです。

  • 最後に残る状態は、プロパティのトラックに置く: 扉の位置もボスの表示もトラックにあるので、スキップは最後の時刻へ飛ぶだけで同じ状態になります。スキップのための処理をもう1本書かずに済みます
  • 終わりの関数は1つ: 最後まで見たときもスキップしたときも _finish() を通るので、カメラと入力の戻し忘れが起きません。演出の中でプレイヤーをアニメで動かすなら、AnimationPlayer 応用 の複数トラックの組み方が使えます
Sponsored

うまくいかないときは

症状原因直し方
演出中、歩きは止まるのにジャンプや攻撃はできるその入力を _unhandled_input() など、_physics_process() の外で受けている演出の間は set_process_unhandled_input(false) も呼び、終わりの関数で true に戻す
当たりを付けた扉が、絵だけ開いて通り抜けられないアニメで動かしているのが見た目の Sprite2D だけで、当たりが元の場所に残っている扉を AnimatableBody2D(アニメで動かすための物理ボディ)にして、当たりごと動かす。Cutscene の Callback Mode › Process(アニメが値を書く刻み)も Physics にする
エディタでアニメを再生しても、メソッドのキーで何も起きないエディタのプレビューでは、メソッドのトラックは呼ばれないゲームを実行して演出を始めて確かめる。それでも動かないなら、対象のノードと関数名を見る
エディタでアニメを確かめたあと、扉が開いた状態で保存される確かめた値がシーンに残った。RESET のアニメ(保存するときに当て直す元の値)に、閉じた値が入っていないRESET に「扉が閉じ、ボスと字幕が隠れた」値を入れ、Cutscene の Reset On Save をオンのままにする

おまけ:先に知っておくと良いこと

  • 「見た」を覚えておく: 部屋のシーンを読み直すと、seen は消え、扉とボスも最初の状態に戻ります。部屋を出入りするゲームなら、seen を Autoload に置きます。見ていたら _ready() で _start() を呼び、続けてスキップと同じ seek() と _finish() を呼びます。最後の状態から始まり、_finish() に書いた後始末も漏れません。セーブに入れる書き方は セーブ/ロード入門 にあります
  • ゲームを止めて演出だけ動かさない: 何も設定せずにツリー全体を一時停止すると、AnimationPlayer も Enter の受け付けも一緒に止まります。止めるのは、演出の間に動いてほしくないものだけです。敵や弾も止めたいなら、グループに入れて、始まりの関数でまとめて set_physics_process(false) にし、終わりの関数で戻します。一時停止と Process Mode の関係は Process Mode入門 にあります
  • 会話を挟むなら、演出を途中で待たせる: 台詞の文字送りをはさみたいときは、その時刻で pause() し、会話が終わってから play() で続けます。会話の作り方は 会話システム入門 にあります

まとめ

  • アニメに任せる部分と、自分で切り替える部分: 扉・ボス・字幕はアニメ、入力とカメラは始まりと終わりの関数
  • 最後に残る状態はプロパティのトラック: 再生し終わっても最後の値が残る。ボスは add_child() せず visible で出す
  • 出来事はメソッドのトラック: 飛ばしてもよいものだけを置く。呼ばれるのが少し遅れることがあるので、running を見てから鳴らす
  • スキップは seek(長さ, true, true): 3つ目を省くと途中のメソッドが呼ばれ、stop() だと最初の状態に戻る
  • 終わりの関数は1つ: animation_finished からもスキップからも呼び、2回目は running で断る

あなたのゲームで、プレイヤーが二度目にいちばん飛ばしたくなる演出はどこでしょうか。

次に読む学習パスの次の記事
アニメーション

Tweenで実現する滑らかなアニメーション

この記事を読む
Godot このセクションのノート104