Godot4 ビッグカツブロック崩し17 ブロック崩しの面を配列で複数設定する

※この連載の全ての記事は、タグ「ビッグカツ」の検索一覧から探すことができます。
※この連載で作ったゲームは「BigBreakOut(ゲームの作り方の記事付き) | フリーゲーム投稿サイト GodotPlayer」でプレイできます。

昔から人気の駄菓子「ビッグカツ」フリー素材画像が公開されたので、無料・軽快な 2D / 3D 用のゲームエンジン Godot Engine 4 を使って、ビッグカツ画像を使ったブロック崩しを作成します。

「ビッグカツブロック崩し」作成の第17回では、ブロック崩しの各面のブロックの配置を別シーンに保存しておき、それをステージのシーンで順次 instantiate で実体化して、ステージのシーンの子ノードとして add_child で追加して、クリアすると次の面が表示される実装例を紹介します。

※ GodotEngine 4.3 を使用しています。.NET 版ではありません。
※スクリプトは自己責任でご使用ください。

前回の記事

前回は、ステージのシーンに配置したブロック群別のシーンに保存する手順を紹介しました。

ステージのスクリプトに面を切り替える進行の処理を追加

以下の記事で、ブロックが全て消えたらクリアと判定する処理を書いたスクリプトを Stage ルートノードに割り当てました。

その stage.gd を以下のスクリプトで上書きします。

上書きすることで、各面で表示するブロック群のシーンを設定する配列を追加し、その配列のとおりに、クリアすると次の面のブロック群が配置されます。
面をクリアした際に、クリックなどの入力があるまで一時停止するために、ゲームの進行状況を表す列挙子 Phase も追加されています。
※詳細はスクリプト内のコメントを参照してください。

extends Node2D
class_name Stage
## ゲーム進行を管理するクラスです。
## [member array_level_scene] へのレベルシーンの設定が必要です。

## ボールの初期位置です。
## $Ball の最初の位置を初期位置として保存して、各レベルの始まりにその位置にボールを配置します。
var ball_start_position: Vector2

## レベルごとに読み込むシーン (.tscn) を設定する配列です。1面から順番に設定してください。
## 
## 設定するシーンはレベルシーンと呼びます。
## レベルシーンは、 Node クラスのルートノードの下に block.tscn の Block の子ノードを1個以上配置したシーンです。
## ブロックはボールが届く範囲に配置してください。
## 例えば、以下のようなレベルシーンがあります。
## Level1(Node クラス)
##   |
##   +- Block1 (block.tscn)
##   |
##   +- Block2 (block.tscn)
##   |
##   +- Block3 (block.tscn)
@export var array_level_scene: Array[PackedScene]

## 現在のレベルです。[member array_level_scene] の要素番号として用います。
## 初期値は -1 ですが、 next_level 関数内で 1 増やされて 0 番目の [member array_level_scene] 要素が実体化します。
var current_level_index: int = -1

## 現在読み込んでいるレベルのシーンのインスタンス(子ノード)です。
var current_level_scene:Node = null

## 現在のゲームのプレイ段階の種類です。
## タイトルシーンと異なり、ゲームオーバーやゲームクリアはプレイ時の画面の上に UI を表記するので
## シーンを切り替えずに、ラベルやボタンなどを必要に応じて表示するため、同一のシーン内でフェーズを変えて
## 見た目や入力による反応を切り替えます。
enum Phase
{
	## レベルのプレイ開始直前のフェーズです。
	LevelReady,
	## レベルプレイ中のフェーズです。ボールを失うと GameOver に移行します。
	Playing,
	## レベルをクリアした直後のフェーズです。
	## 次の面がなければすぐに GameClear に移行します。
	## 次の面があれば、 Next ボタンをクリック後、次の面があれば StageStart に移行します。
	LevelClear,
	## ゲームオーバーのフェースです。タイトルボタンをクリックすると Title に移行します。
	GameOver,
	## ゲームクリアのフェーズです。タイトルボタンをクリックすると Title に移行します。
	GameClear,
}
## 現在のゲームの進行段階、フェーズを表します。
var current_phase = Phase.LevelReady

## Called when the node enters the scene tree for the first time.
## ノードが初めてシーン ツリーに入るときに呼び出されます。
func _ready():
	# プロパティの設定の確認
	if array_level_scene.size() < 1:
		printerr("Stage シーンの Stage ルートノードのプロパティ ArrayLevelScene にレベルシーンを1つ以上設定してください。")
	# ボールの初期位置を保存します。
	ball_start_position = $Ball.global_position
	# 1 面を読み込みます。
	current_level_index = -1 # next_level 関数内で +1 されて 0 になります。
	next_level() # 最初のレベルに進みます。
	return

## Called every frame. 'delta' is the elapsed time since the previous frame.
## フレームごとに呼び出されます。 delta は、前のフレームからの経過時間です。
## delta を使用しない場合は warning が出ないように _delta とリネームします。
func _process(_delta):
	# プレイ中
	if current_phase == Phase.Playing:
		# レベルシーンの持つ子ノード(ブロック)が全て消えたらレベルクリアに移ります。
		if current_level_scene.get_child_count() < 1:
			on_level_clear()
			
	# Enter / Space キー / マウス左ボタン(クリック) が押された直後の場合
	if Input.is_action_just_pressed("ui_accept") or Input.is_action_just_pressed("ui_click"):
	#if Input.is_action_just_pressed("ui_accept") or Input.is_action_just_pressed("ui_right") or Input.is_action_just_pressed("ui_left"):
		# ゲームの進行状況によって、入力があった際の挙動を変えます。
		match current_phase:
			Phase.LevelReady:
				# プレイ前の状態で入力されたら、ボールを動かして、プレイを開始します。
				on_playing()
			Phase.LevelClear:
				# 現在のレベルをクリアした状態で入力があった場合は、次のレベルに進みます。
				next_level()
			Phase.GameOver:
				# TODO : Retry と Give Up を選択して Give Up の場合はタイトルに戻ります。
				get_tree().change_scene_to_file("res://title_scene.tscn")
			Phase.GameClear:
				# ゲームクリア状態で、入力があればタイトルに戻ります。
				get_tree().change_scene_to_file("res://title_scene.tscn")
			_:
				# 上記以外
				printerr("想定していない Phase の列挙子の値です。")
				# TODO: Ball が下に落ちた場合の処理を追加

## 次のレベルに進みます。レベルシーンを実体化して、Phase.StageReady の状態になります。
## 次のレベルシーンがない場合は、ゲームクリアの処理に移ります。
func next_level():
	# 次のレベルの要素番号に更新します。
	current_level_index += 1
	# [member array_level_scene] の要素数(面の数)を超えて、次のレベルがない場合
	if array_level_scene.size() < (current_level_index + 1):
		# ゲームクリア
		on_game_clear()
	else:
		# 直前のレベルシーンを解放します。
		if current_level_scene != null:
			current_level_scene.queue_free()
		# 次の面のレベルシーンをインスタンス化します。
		current_level_scene = array_level_scene[current_level_index].instantiate()
		# Stage ルートノードの子ノードとしてレベルシーンのインスタンスを追加します。
		add_child(current_level_scene)
		# ボールを初期位置に配置します。
		$Ball.global_position = ball_start_position
		# ゲームの進行状況を StageReady にします。
		current_phase = Phase.LevelReady

## 現在のレベルのプレイ開始時に呼び出されます。
func on_playing():
	# ゲームの進行状況を更新します。
	current_phase = Phase.Playing
	print("current_phase = Phase.Playing")
	# ボールの一時停止を解除します。
	$Ball.is_pause = false
	return

## 現在のレベルをクリアした際に呼び出されます。
func on_level_clear():
	# ゲームの進行状況を更新します。
	current_phase = Phase.LevelClear
	print("current_phase = Phase.LevelClear")
	# ボールを一時停止します。
	$Ball.is_pause = true
	return

## 全てのレベルをクリアしてゲームをクリアした際に呼び出されます。
func on_game_clear():
	# ゲームの進行状況を更新します。
	current_phase = Phase.GameClear
	print("current_phase = Phase.GameClear")
	return

## ゲームオーバーになった際に呼び出されます。
func on_game_over():
	# ゲームの進行状況を更新します。
	current_phase = Phase.GameOver
	print("current_phase = Phase.GameOver")
	return

109 ~ 115 行目では、新たな面のブロック群を動的に実体化して配置します。

配列のシーンを instantiate実体化して、ステージのシーンの子ノードとして add_child で追加して、ステージのシーンに動的に配置しています。
以前に読み込んだシーンがあれば、queue_free 関数で解放してから、新たなシーンを実体化します。

スクリプトで公開した配列にシーンを設定

スクリプトを上書きしたら、シーンに直接配置しているブロック群のノードを削除します。

Godot4 ビッグカツブロック崩し ステージシーンに置き換えられて配置されたレベルシーンの子ノードを削除します.

そして、用意した各面のシーン(レベルシーン)設定する配列に、別シーンとして保存したブロック群のシーンを割り当てます。

便宜的に呼んでいるレベルシーンは、以下のように Node クラスのノードの下にブロックのノードだけを集めたシーンです。

  • Level1 (Node クラス)
    • Block1 (Block.tscn, ブロックのシーンの子ノード)
    • Block2 (Block.tscn, ブロックのシーンの子ノード)
    • Block3 (Block.tscn, ブロックのシーンの子ノード)

ステージのシーンでブロックを配置した後、ブランチをシーンに保存することで簡単に作れます。
詳細は以下の記事を参照してください。

Godot4 ビッグカツブロック崩し ステージシーンの配列プロパティにレベルシーンを設定1

シーンドックで Stage ルートノードを選択して、レベルシーン(下図では level_1.tscn)をファイルシステムドックから、 インスペクタードックの ArrayLevelScene 配列追加します。
配列への追加のために、その配列の項目をクリックすると表示される「要素を追加」ボタンを押して、配列の要素を1つ追加して、その追加された枠レベルシーンの tscn ファイルドラッグ&ドロップで設定します。

ArrayLevelScene 配列は先ほどのスクリプトの 23 行目で定義した配列です。
@export アノテーションをつけることで、エディタのインスペクタードックで編集出来るようにしています。

Godot4 ビッグカツブロック崩し ステージシーンの配列プロパティにレベルシーンを設定2

F6 キーで、開いているステージのシーン実行すると、配列に設定した level_1.tscn レベルシーン実体化され、動的にブロック群が追加で配置されました。

Godot4 ビッグカツブロック崩し ステージノードに設定したレベルシーンを実体化して動的にブロックが配置されました

同様に、2面(Level2ノード)を作成して、 Level2 ノードを右クリックして表示されるメニュー「ブランチをシーンとして保存」を選択して、 level_2.tscn に保存します。
#回転ツールを使って、ブロックを縦にしてみました。

Godot4 ビッグカツブロック崩し ステージシーンの配列プロパティに2面のレベルシーンを設定1

保存したレベルシーンのファイル level_2.tscn を、先ほどの ArrayLevelScne 配列に要素を追加した1番目の枠(0番目は level_1.tscn を割り当てています)にドラッグ&ドロップで設定します。

Godot4 ビッグカツブロック崩し ステージシーンの配列プロパティに2面のレベルシーンを設定2

テスト

F6 キーで、開いているステージのシーンを実行します。

  1. ステージのシーンに、配列の0番目で指定した level_1.tscn のシーンが追加配置されて、1面が表示されます。
  2. プレイ前や、クリア時クリックやスペースキーを押すことで、次の状態に進み、プレイが始まったり、次の面やタイトル画面に移動します。
  3. ブロックを全て消して1面をクリアして、クリックすると配列の1番目で指定した level_2.tscn のシーンが追加配置されて2面が表示されました。
  4. 全ての面をクリアするとタイトル画面に移動して、新しく最初からプレイできます。

まとめ

「ビッグカツブロック崩し」作成の第17回では、ブロック崩しの各面のブロックの配置を別シーンに保存しておき、それをステージのシーンで順次 instantiate で実体化して、ステージのシーンの子ノードとして add_child で追加して、クリアすると次の面が表示される実装例を紹介しました。

参照サイト Thank You!

記事一覧 → Compota-Soft-Press

コメント

Ads Blocker Image Powered by Code Help Pro

お願い - Ads Blocker Detected

このサイトは広告を掲載して運営しています。

ポップアップを閉じて閲覧できますが、よろしければ

このサイト内の広告を非表示にする拡張機能をオフにしていただけませんか?

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

タイトルとURLをコピーしました