ぷよぷよプログラミングをGodotで実装 07 実践編 :盤面のぷよ
Godotでぷよぷよを作るチュートリアル
前回に引き続きぷよぷよプログラミング
を利用して、Godotでパズルゲームの作り方の基礎を一緒に学んでいきましょう。
YouTubeでもこの記事の内容に沿ってゲームを作っているので、動画を見ながら取り組んでみてください。
前回の記事はこちら
今回の動画はこちら
7. 状態管理処理の追加(盤面のぷよ)
ぷよを操作をしていない時の状態管理の処理を実装していきます。
7.1 ぷよの固定処理
プレイヤーが操作していたぷよが接地し、一定時間経過すると盤面に固定されるようにします。
- 操作していたぷよを盤面に設定するため、
Stage.gd
に盤面の情報を保持する配列と必要な変数を設定します。
# ぷよの盤面データを格納する2D配列
# 各セルは null (空) または PuyoNode のインスタンスを格納します
var board: Array[Array] = []
# 現在ステージにあるぷよの総数
var puyo_count: int = 0
# ぷよのシーン (インスタンス化して使用する)
const PUYO_SCENE = preload("res://scenes/game/Puyo.tscn")
- 盤面を初期化する処理を追加します。
# ステージの盤面を初期化する (JavaScriptのStage.initialize()の盤面部分に対応)
func initialize_stage() -> void:
board.clear() # 既存の盤面データをクリア
for r in range(Global.STAGE_ROWS):
board.append([])
for c in range(Global.STAGE_COLS):
board[r].append(null) # 全てのセルを空にする
puyo_count = 0
-
_ready
で初期化処理を呼び出します。
# シーンがツリーに追加され、準備ができたときに呼ばれる
func _ready() -> void:
initialize_stage() # 追加
# スクリプトで盤面のサイズを設定する場合
var background_node = get_node_or_null("Background")
background_node.size = Vector2(Global.STAGE_COLS * Global.PUYO_IMG_WIDTH, Global.STAGE_ROWS * Global.PUYO_IMG_HEIGHT)
- 盤面の情報と表示を管理する処理を追加します。
# 画面とメモリ両方にぷよをセットする (JavaScriptのStage.setPuyo()に対応)
func set_puyo(x: int, y: int, puyo_type: int) -> void:
# 既にそのマスにぷよがあれば、古いぷよを削除
if board[y][x] != null and is_instance_valid(board[y][x]):
board[y][x].queue_free()
# Puyoシーンをインスタンス化
var puyo_node: Node2D = PUYO_SCENE.instantiate()
add_child(puyo_node) # Gridノードの子として追加
puyo_node.set_puyo_type(puyo_type) # ぷよのタイプを設定
# ぷよのピクセル位置を設定
puyo_node.position = Vector2(float(x) * Global.PUYO_IMG_WIDTH, float(y) * Global.PUYO_IMG_HEIGHT)
# メモリ (board配列) にぷよノードへの参照を格納
board[y][x] = puyo_node
puyo_count += 1 # ぷよの総数を増やす
- 操作が終わったぷよを盤面に登録する処理を
Player.gd
に追加します。
# fix_puyo_logic(): プレイヤー操作中のぷよを盤面(Stage)に固定します。
func fix_puyo_logic() -> void:
# ぷよノードが有効でない場合は処理しません。
if not is_instance_valid(center_puyo_node) or not is_instance_valid(movable_puyo_node):
return
# 最終的なピクセル位置からグリッド座標を計算します。
var center_x_grid: int = int(round(center_puyo_node.position.x / Global.PUYO_IMG_WIDTH))
var center_y_grid: int = int(round(center_puyo_node.position.y / Global.PUYO_IMG_HEIGHT))
var movable_x_grid: int = int(round(movable_puyo_node.position.x / Global.PUYO_IMG_WIDTH))
var movable_y_grid: int = int(round(movable_puyo_node.position.y / Global.PUYO_IMG_HEIGHT))
# 中心ぷよをグリッドに配置します。
if center_y_grid >= 0: # 画面外でなければ
stage_node.set_puyo(center_x_grid, center_y_grid, center_puyo_node.puyo_type)
# 可動ぷよをグリッドに配置します。
if movable_y_grid >= 0: # 画面外でなければ
stage_node.set_puyo(movable_x_grid, movable_y_grid, movable_puyo_node.puyo_type)
# 操作用として作成したぷよノードをシーンツリーから削除します
if is_instance_valid(center_puyo_node):
center_puyo_node.queue_free()
if is_instance_valid(movable_puyo_node):
movable_puyo_node.queue_free()
# 参照をクリアし、次のぷよのために準備します。
center_puyo_node = null
movable_puyo_node = null
-
Main.gd
の_process
メソッド内のGameMode.FIXING_PUYO
の処理を更新します。
GameMode.FIXING_PUYO:
# ぷよを盤面に固定する処理を実行します。
player_node.fix_puyo_logic()
# TODO: 確認のため、ぷよの固定が終わったら次の操作ぷよを生成させます
current_mode = GameMode.NEW_PUYO
これで、操作中のぷよが接地すると、一定時間後に盤面に固定されるようになります。
7.2 ぷよの接触判定
ここまでの実装で操作ぷよが地面に接地するまで落下をしますが、盤面に設置されたぷよは貫通していまします。
盤面に設置されたぷよも地面同様に接地判定されるように修正を行います。
- 盤面の指定された位置にぷよがあるか確認するための処理を
Stage.gd
に追加します。
# 指定された位置のぷよを取得する
func get_puyo(x: int, y: int) -> Node:
# 座標がステージの範囲外ならnullを返す
if y < 0 or y >= Global.STAGE_ROWS or x < 0 or x >= Global.STAGE_COLS:
return null
return board[y][x]
-
Player.gd
の_is_colliding
の衝突判定に盤面のぷよを追加する
### グリッド上の衝突チェック
# 指定されたグリッド座標にぷよがあるか、またはグリッド範囲外かをチェック
func _is_colliding(check_x: int, check_y: int) -> bool:
if check_y >= Global.STAGE_ROWS or \
check_x < 0 or check_x >= Global.STAGE_COLS:
return true # 範囲外は衝突とみなす
return stage_node.get_puyo(check_x, check_y) != null # ぷよが座標にある
これで盤面のぷよに対しても接触判定が追加され、ぷよの上に設置することや回転や横移動の衝突にも影響することが確認できます。
7.3 自由落下チェックと落下アニメーション
ぷよが固定された後、盤面上のぷよと地面に隙間ができていれば、上にあるぷよが自由落下する必要があります。
- 落下するぷよが存在するか確認する処理を
Stage.gd
に追加します。
# 落下中のぷよを一時的に保持する配列
var falling_puyos: Dictionary = {}
func check_fall() -> bool:
falling_puyos.clear() # 対象リストをクリア
# 下から2段目から上の行を見ていく (最下段は落ちないため)
for c in range(Global.STAGE_COLS):
for r in range(Global.STAGE_ROWS - 2, -1, -1):
var puyo_node = get_puyo(c, r)
if puyo_node == null:
continue
var landing_point:int = r
while (landing_point + 1 < Global.STAGE_ROWS and get_puyo(c, landing_point + 1) == null):
landing_point += 1
if landing_point == r:
continue
# このぷよは下に落ちる
falling_puyos[puyo_node] = landing_point
board[landing_point][c] = puyo_node
board[r][c] = null
return !falling_puyos.is_empty()
- 自由落下アニメーションの処理を
Stage.gd
に追加します。
# 自由落下を実行する (JavaScriptのStage.fall()に対応)
func fall() -> bool:
var still_falling: bool = false
for puyo_node in falling_puyos:
if is_instance_valid(puyo_node): # ノードが有効か確認
puyo_node.position.y += Global.FREE_FALLING_SPEED # 落下速度分、Y座標を増やす
still_falling = true # まだ落下中のぷよがある
# 完全に1マス分落ちたかをチェック
# 現在のY位置が、次のグリッドセルの上端を「超えた」ら、グリッド位置を更新
if puyo_node.position.y >= float(falling_puyos[puyo_node]) * Global.PUYO_IMG_HEIGHT:
## メモリ上の位置を更新 (現在の位置から1つ下のマスへ)
puyo_node.position.y = float(falling_puyos[puyo_node]) * Global.PUYO_IMG_HEIGHT # 位置を正確にグリッドに合わせる
falling_puyos.erase(puyo_node)
return still_falling # 落下中のぷよがいなければfalseを返す
-
Main.gd
の_process
メソッド内のGameMode.CHECK_FALL
の処理を更新します。
GameMode.CHECK_FALL:
# 盤面全体で自由落下が必要なぷよがあるかチェックします。
if stage_node.check_fall():
current_mode = GameMode.FALL # 落下が必要な場合は落下アニメーションへ遷移
else:
# TODO: 確認のため、ぷよの自由落下が終わったら次の操作ぷよを生成させます
current_mode = GameMode.NEW_PUYO
-
GameMode.FIXING_PUYO
に仮設定した状態遷移をCHECK_FALL
に変更します。
GameMode.FIXING_PUYO:
# ぷよを盤面に固定する処理を実行します。
player_node.fix_puyo_logic()
# 固定後、再度自由落下チェックへ遷移します。
current_mode = GameMode.CHECK_FALL
-
Main.gd
の_process
メソッド内のGameMode.FALL
の処理を更新します。
GameMode.FALL:
# 自由落下アニメーションを実行します。
if not stage_node.fall():
# TODO: 確認のため、すべてのぷよが落ちきったら次の操作ぷよを生成させます
current_mode = GameMode.NEW_PUYO
-
GameMode.START
に仮設定していたゲームモードをCHECK_FALL
に置き換えます。
GameMode.START:
# ゲーム開始時、最初は自由落下チェックからゲームを開始します。
current_mode = GameMode.CHECK_FALL
これで操作ぷよが固定された際に地面やぷよに接していないぷよが落下するようになりました。
7.4 ぷよの消去チェックとアニメーション
自由落下が完了したら、同じ色のぷよが4つ以上連結しているかチェックし、消去処理を行います。
- 消去できるぷよが存在するか確認する処理を
Stage.gd
に追加します。
# 消去アニメーション中のぷよを一時的に保持する配列
var erasing_puyos: Array[Node2D] = []
var erase_start_frame: int = 0
# 消せるぷよがあるかどうかを判定する (JavaScriptのStage.checkErase()に対応)
func check_erase(frame: int) -> Dictionary:
var erase_info: Dictionary = { "piece": 0, "color": 0 } # 消したぷよ数と色数を格納
erasing_puyos.clear() # 消去対象リストをクリア
# 訪問済みのセルを記録する2D配列 (再帰処理での無限ループ防止)
var visited: Array[Array] = []
for r in range(Global.STAGE_ROWS):
visited.append([])
for c in range(Global.STAGE_COLS):
visited[r].append(false)
var erased_puyo_types_found: Dictionary = {} # 消去対象となったぷよのタイプを記録
# 全てのセルを走査
for r in range(Global.STAGE_ROWS):
for c in range(Global.STAGE_COLS):
var current_puyo: Node2D = get_puyo(c, r)
# ぷよが存在しない、または既に訪問済みであればスキップして次のセルへ
if current_puyo == null:
continue
if visited[r][c]:
continue
var current_group: Array[Array] = [] # 同じ色で連結しているぷよの座標リスト
var q: Array[Array] = [[c, r]] # BFSのためのキュー
visited[r][c] = true # 現在のセルを訪問済みとしてマーク
while q.size() > 0:
var pos: Array = q.pop_front() # キューから座標を取り出す
var current_col: int = pos[0]
var current_row: int = pos[1]
current_group.append([current_col, current_row]) # グループに追加
# 上下左右4方向をチェック
var directions: Array[Vector2] = [Vector2(0, 1), Vector2(0, -1), Vector2(1, 0), Vector2(-1, 0)]
for dir_vec in directions:
var next_col: int = current_col + int(dir_vec.x)
var next_row: int = current_row + int(dir_vec.y)
# 次の座標がステージ範囲外であればスキップ
if next_row < 0 or next_row >= Global.STAGE_ROWS or \
next_col < 0 or next_col >= Global.STAGE_COLS:
continue
# 既に訪問済みであればスキップ
if visited[next_row][next_col]:
continue
var neighbor_puyo: Node2D = get_puyo(next_col, next_row)
# 隣接するぷよが存在し、同じタイプであればキューに追加
if neighbor_puyo != null and neighbor_puyo.puyo_type == current_puyo.puyo_type:
visited[next_row][next_col] = true # 訪問済みとしてマーク
q.append([next_col, next_row])
# 連結したぷよの数が消去条件を満たしていれば
if current_group.size() >= Global.ERASE_PUYO_COUNT:
erase_info.piece += current_group.size() # 消したぷよ数を加算
# この色のぷよが初めて消去対象となった場合のみ色数を加算
if not erased_puyo_types_found.has(str(current_puyo.puyo_type)):
erase_info.color += 1
erased_puyo_types_found[str(current_puyo.puyo_type)] = true
# 消去対象となるぷよをerasing_puyosリストに追加し、boardからは一時的にnullにする
for pos in current_group:
var col: int = pos[0]
var row: int = pos[1]
var puyo_to_erase: Node2D = board[row][col]
if is_instance_valid(puyo_to_erase):
erasing_puyos.append(puyo_to_erase)
board[row][col] = null # 盤面から削除
erase_start_frame = frame
return erase_info
- 消去処理を
Stage.gd
に追加します。
# 消去アニメーションを実行する (JavaScriptのStage.erasing()に対応)
# 戻り値: アニメーション中ならtrue, 完了したらfalse
func erasing(current_frame: int) -> bool:
if erasing_puyos.is_empty():
return false # 消去するぷよがなければアニメーションは終了
# アニメーションの進行度 (0.0から1.0) を計算
# アニメーションは一定フレーム数で1サイクルとし、それを繰り返す
var elapsed_frames_in_cycle: int = (current_frame - erase_start_frame) % Global.ERASE_ANIMATION_DURATION
var ratio: float = float(elapsed_frames_in_cycle) / Global.ERASE_ANIMATION_DURATION
# JavaScript版の点滅アニメーション (0.25, 0.5, 0.75の閾値で表示/非表示を切り替え) を再現
var cell_is_visible: bool = true
if ratio <= 0.25:
cell_is_visible = false
elif ratio <= 0.5:
cell_is_visible = true
elif ratio <= 0.75:
cell_is_visible = false
else: # ratio > 0.75
cell_is_visible = true # 最後のフェーズは表示
for puyo_node in erasing_puyos:
if is_instance_valid(puyo_node): # ノードが有効か確認
puyo_node.visible = cell_is_visible
# アニメーションの1サイクルが完全に終わったら、ぷよを削除
# Global.ERASE_ANIMATION_DURATIONの倍数フレームごとに削除処理を行う
# current_frame > 0 は、ゲーム開始直後の0フレーム目での意図しない削除を避けるため
if elapsed_frames_in_cycle == 0 and current_frame > 0:
for puyo_node in erasing_puyos:
if is_instance_valid(puyo_node):
puyo_node.queue_free() # ノードを削除 (メモリから解放)
puyo_count -= 1 # ステージ上のぷよの総数を減らす
erasing_puyos.clear() # リストをクリア
return false # アニメーション完了
return true # アニメーション中
-
Main.gd
の_process
メソッド内のGameMode.ERASING
の処理を更新します。
GameMode.ERASING:
# ぷよ消去アニメーションを実行します。
if not stage_node.erasing(frame):
# 消去アニメーションが終了したら、再度自由落下チェックへ遷移します(連鎖の可能性)。
current_mode = GameMode.CHECK_FALL
- 連鎖数を記憶させるための変数を作成します。
var combination_count: int = 0 # 現在の連鎖数(コンボ数)
-
Main.gd
の_process
メソッド内のGameMode.CHECK_ERASE
の処理を更新します。
GameMode.CHECK_ERASE:
# 消せるぷよがあるかチェックし、消去情報を取得します。
var erase_info: Dictionary = stage_node.check_erase(frame)
if erase_info.piece > 0: # 消せるぷよがあった場合
current_mode = GameMode.ERASING # 消去アニメーションへ遷移
combination_count += 1 # 連鎖数を加算
else:
combination_count = 0 # 連鎖数をリセット
current_mode = GameMode.NEW_PUYO # 新しいぷよの生成へ遷移
-
GameMode.CHECK_FALL
、GameMode.FALL
に仮設定していたゲームモードをCHECK_ERASE
に置き換えます。
GameMode.CHECK_FALL:
# 盤面全体で自由落下が必要なぷよがあるかチェックします。
if stage_node.check_fall():
current_mode = GameMode.FALL # 落下が必要な場合は落下アニメーションへ遷移
else:
current_mode = GameMode.CHECK_ERASE # 落下がなければ消去チェックへ遷移
GameMode.FALL:
# 自由落下アニメーションを実行します。
if not stage_node.fall():
# すべてのぷよが落ちきったら、消去チェックへ遷移します。
current_mode = GameMode.CHECK_ERASE
実行して動作を確認しましょう。ぷよを4個繋げるとぷよが消去されることが確認できます。
8. 全消しとゲームオーバーの実装
一連のぷよぷよの動作が実装できたので、全消しとゲームオーバーを実装してよりゲームらしくしていきましょう。
8.1 全消しの実装
ぷよを消して盤面上からぷよが1つもない状態になった時に全消しの画像を表示させ、次のぷよを消去するまで表示させます。
-
Sprite2D
ノードを操作できるようにStage.gd
に変数を追加します。
# 全消し表示用のSprite2Dノード (MainシーンのBatankyuSpriteとは別)
var zenkeshi_sprite: Sprite2D = null
- 全消し表示用の
Sprite2D
を設定するsetup_zenkeshi_sprite
を作成します。
# 全消し表示用のSprite2Dを設定する
func setup_zenkeshi_sprite() -> void:
zenkeshi_sprite = Sprite2D.new()
add_child(zenkeshi_sprite) # Gridの子ノードとして追加
zenkeshi_sprite.texture = Global.ZENKESHI_TEXTURE # Globalからテクスチャを設定
# 位置をステージの中央に設定 (中心点がSprite2Dの原点となる場合)
# Sprite2DのoffsetをVector2(0,0)に設定している場合、positionは左上を指すので注意
zenkeshi_sprite.position = Vector2(Global.STAGE_COLS * Global.PUYO_IMG_WIDTH / 2.0, Global.STAGE_ROWS * Global.PUYO_IMG_HEIGHT / 2.0)
# 幅をステージ幅に合わせるようにスケール調整
if Global.ZENKESHI_TEXTURE:
zenkeshi_sprite.scale.x = float(Global.STAGE_COLS * Global.PUYO_IMG_WIDTH) / Global.ZENKESHI_TEXTURE.get_width()
zenkeshi_sprite.scale.y = zenkeshi_sprite.scale.x # アスペクト比を維持してYも同じスケールにする
zenkeshi_sprite.hide() # 最初は非表示
-
_ready
でsetup_zenkeshi_sprite
を呼び出して初期設定します。
# シーンがツリーに追加され、準備ができたときに呼ばれる
func _ready() -> void:
initialize_stage()
# 全消し表示用のSprite2Dをセットアップ
setup_zenkeshi_sprite() # 追加
# スクリプトで盤面のサイズを設定する場合
var background_node = get_node_or_null("Background")
background_node.size = Vector2(Global.STAGE_COLS * Global.PUYO_IMG_WIDTH, Global.STAGE_ROWS * Global.PUYO_IMG_HEIGHT)
- 全消し画像をステージに表示するために
show_zenkeshi
を作成します。
# 全消し画像をステージに表示する (JavaScriptのStage.showZenkeshi()に対応)
func show_zenkeshi() -> void:
if !zenkeshi_sprite:
return
zenkeshi_sprite.show()
zenkeshi_sprite.modulate.a = 1.0 # 不透明度を完全に表示
# 全消しアニメーション (Tweenノードを使用)
var tween = create_tween()
# Y位置をステージ下から中間点へ移動させるアニメーション
# zenkeshi_sprite.positionは中央を指しているため、計算を調整
var start_y_for_tween = Global.STAGE_ROWS * Global.PUYO_IMG_HEIGHT + zenkeshi_sprite.texture.get_height() / 2.0 # 画面下から出現
var end_y_for_tween = (Global.STAGE_ROWS * Global.PUYO_IMG_HEIGHT) / 3.0 # ステージ上部寄りの中間点
tween.tween_property(zenkeshi_sprite, "position:y", end_y_for_tween, Global.ZENKESHI_DURATION).from(start_y_for_tween)
tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
- 全消し画像を非表示にするために
hide_zenkeshi
を作成します。
# 全消し画像をステージから隠す (JavaScriptのStage.hideZenkeshi()に対応)
func hide_zenkeshi() -> void:
if !zenkeshi_sprite:
return
var tween = create_tween()
tween.tween_property(zenkeshi_sprite, "modulate:a", 0.0, Global.ZENKESHI_DURATION) # 透明度を0に
tween.set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN)
# アニメーション終了時に非表示にする
tween.finished.connect(func(): zenkeshi_sprite.hide())
- 盤面上にぷよがなくなった時に
show_zenkeshi
を呼び出すようにMain.gd
修正します。
GameMode.CHECK_ERASE:
# 消せるぷよがあるかチェックし、消去情報を取得します。
var erase_info: Dictionary = stage_node.check_erase(frame)
if erase_info.piece > 0: # 消せるぷよがあった場合
current_mode = GameMode.ERASING # 消去アニメーションへ遷移
combination_count += 1 # 連鎖数を加算
else:
# 消せるぷよがなかった場合
if stage_node.puyo_count == 0 and combination_count > 0:
# 全消しボーナス発生(ぷよが全て消え、かつ連鎖があった場合)
stage_node.show_zenkeshi() # 全消し表示アニメーションを開始
combination_count = 0 # 連鎖数をリセット
current_mode = GameMode.NEW_PUYO # 新しいぷよの生成へ遷移
- 全消しの状態から通常の状態に戻らせるように
hide_zenkeshi
を追加します。
GameMode.CHECK_ERASE:
# 消せるぷよがあるかチェックし、消去情報を取得します。
var erase_info: Dictionary = stage_node.check_erase(frame)
if erase_info.piece > 0: # 消せるぷよがあった場合
current_mode = GameMode.ERASING # 消去アニメーションへ遷移
combination_count += 1 # 連鎖数を加算
# 追加
stage_node.hide_zenkeshi() # もし全消し表示中なら隠します
else:
# 消せるぷよがなかった場合
if stage_node.puyo_count == 0 and combination_count > 0:
# 全消しボーナス発生(ぷよが全て消え、かつ連鎖があった場合)
stage_node.show_zenkeshi() # 全消し表示アニメーションを開始
combination_count = 0 # 連鎖数をリセット
current_mode = GameMode.NEW_PUYO # 新しいぷよの生成へ遷移
動作を確認してみましょう。ぷよを全消ししたら、全消しの画像が表示され、次のぷよを消した時にぷよが残っていたら全消し画像が非表示になっていることを確認できると思います。
8.2 ゲームオーバー処理
ゲームオーバーになった際の処理と、バタンキューアニメーションの表示を行います。
-
Main.tscn
シーンにバタンキュー用のノードを追加します。- Mainノードを選択し、子ノードを追加から
Sprite2D
を選択し名前をBatankyuSprite
にします。 - 「インスペクター」パネルで
Offset
のCenterd
のチェックを外します。 - 「シーン」パネルで目のアイコンをクリックし、非表示にさせます。
- Mainノードを選択し、子ノードを追加から
-
Main.gd
にBatankyuSpriteへの参照を追加します。
@onready var batankyu_sprite: Sprite2D = $BatankyuSprite # バタンキュー表示用のSprite2Dノードへの参照
-
Main.gd
の_ready
関数内でBatankyuSprite
の初期設定を行います。
# _ready(): シーンがツリーに追加され、準備ができたときに一度だけ呼ばれます。
# ゲーム全体の初期設定を行います。
func _ready() -> void:
initialize_game()
# バタンキューSpriteの初期設定
if is_instance_valid(batankyu_sprite) and Global.BATANKYU_TEXTURE != null:
batankyu_sprite.texture = Global.BATANKYU_TEXTURE
# スプライトのオフセットを調整し、原点が中心になるようにします。
# これにより、positionプロパティでスプライトの中心位置を指定しやすくなります。
batankyu_sprite.offset = -batankyu_sprite.texture.get_size() / 2
# バタンキュー画像の幅をステージ幅に合わせるようにスケール調整
var target_width = float(Global.STAGE_COLS * Global.PUYO_IMG_WIDTH)
if batankyu_sprite.texture.get_width() > 0:
batankyu_sprite.scale.x = target_width / batankyu_sprite.texture.get_width()
batankyu_sprite.scale.y = batankyu_sprite.scale.x # アスペクト比を維持してYも同じスケールにする
# 最初は画面外の上部に隠しておく
# positionはoffsetを考慮した中心座標
batankyu_sprite.position = Vector2(target_width / 2.0, -batankyu_sprite.texture.get_height() * batankyu_sprite.scale.y / 2.0)
batankyu_sprite.hide()
- バタンキューアニメーションで利用する変数を宣言します
var game_over_frame: int = 0 # バタンキューアニメーション開始時のフレーム数を記録
-
Main.gd
にゲームオーバー関連の関数を追加します。
# prepare_batankyu_logic(current_frame): バタンキューアニメーションの準備をします。
func prepare_batankyu_logic(current_frame: int) -> void:
game_over_frame = current_frame # ゲームオーバーが開始されたフレームを記録
if is_instance_valid(batankyu_sprite):
batankyu_sprite.show() # バタンキュー画像を画面に表示
# 初期位置を画面外上部に設定します。
batankyu_sprite.position = Vector2(Global.STAGE_COLS * Global.PUYO_IMG_WIDTH / 2.0, -batankyu_sprite.texture.get_height() * batankyu_sprite.scale.y / 2.0)
# batankyu_logic(current_frame): バタンキューアニメーションを実行します。
func batankyu_logic(current_frame: int) -> void:
if not is_instance_valid(batankyu_sprite) or Global.GAME_OVER_FRAME == 0:
return
# アニメーションの進行度を計算 (0.0から1.0の範囲にクランプ)
var ratio: float = float(current_frame - game_over_frame) / Global.GAME_OVER_FRAME
ratio = clampf(ratio, 0.0, 1.0)
# JavaScript版のX座標計算をGDScriptに変換(サインカーブで左右に揺れる動き)
# Math.cos(Math.PI / 2 + ratio * Math.PI * 2 * 10) * Config.puyoImgWidth
var x_offset_from_center: float = cos(PI / 2.0 + ratio * PI * 2.0 * 10.0) * Global.PUYO_IMG_WIDTH
# JavaScript版のY座標計算をGDScriptに変換(コサインカーブで上下に揺れながら中央に落下)
# (Math.cos(Math.PI + ratio * Math.PI * 2) * Config.puyoImgHeight * Global.STAGE_ROWS) / 4 + (Config.puyoImgHeight * Global.STAGE_ROWS) / 2
var stage_height: float = float(Global.PUYO_IMG_HEIGHT) * Global.STAGE_ROWS
var y_center_of_stage: float = stage_height / 2.0
var y_amplitude: float = stage_height / 4.0
var y_offset_from_center: float = cos(PI + ratio * PI * 2.0) * y_amplitude + y_center_of_stage
# バタンキューSpriteの位置を更新 (ステージ中央を基準にオフセットを加えます)
batankyu_sprite.position = Vector2(
Global.STAGE_COLS * Global.PUYO_IMG_WIDTH / 2.0 + x_offset_from_center, # ステージ中央X + 横揺れオフセット
y_offset_from_center # 縦位置(揺れ込み)
)
-
Main.gd
のGameMode.GAME_OVER
とGameMode.BATANKYU_ANIMATION
の処理を更新します。
GameMode.GAME_OVER:
# ゲームオーバー時の準備処理を行います。
prepare_batankyu_logic(frame)
current_mode = GameMode.BATANKYU_ANIMATION # バタンキューアニメーションへ遷移
GameMode.BATANKYU_ANIMATION:
# バタンキューアニメーションを実行します。
batankyu_logic(frame)
- 新しいぷよを生成時にゲームオーバの判定処理を
Player.gd
に追加します。
# 新しい操作用ぷよを生成する (JavaScriptのPlayer.createNewPuyo()に対応)
func create_new_puyo() -> bool:
# ゲームオーバー判定: 1番上の段の左から3列目にぷよが既にあったらゲームオーバー
if stage_node.board[0][2] != null:
return false # 新しいぷよを置けないため、ゲームオーバー
【以下は省略】
- 確認用においていた
GameMode.NEW_PUYO
の中のコードを削除しましょう。
GameMode.NEW_PUYO:
# 新しい操作用ぷよを生成
if not player_node.create_new_puyo():
current_mode = GameMode.GAME_OVER # 生成できなかったらゲームオーバー
else:
current_mode = GameMode.PLAYER_PLAYING # プレイヤー操作可能状態へ
# TODO:確認用
# 削除: player_node.puyo_status.y = 1
# 削除: player_node.set_puyo_position()
- ゲームオーバー時に上キーを押したらゲームを初めから再開できるようにします。
GameMode.BATANKYU_ANIMATION:
# バタンキューアニメーションを実行します。
batankyu_logic(frame)
# 上キーが押されたらゲームをリロードして再開
if player_node.key_status.up:
get_tree().reload_current_scene()
動作を確認してみましょう。ぷよを上まで積み上げるとゲームオーバーになり、上キーでゲームが再スタートされることが確認できます。
まとめ
今回はぷよの固定処理とゲームオーバの処理を作成しました。
次回は得点処理を書いていきます。いよいよ最後です。お楽しみに!!
Discussion