【Godot4.x】ブロック崩しサンプル

プロック崩しのサンプルを作ったので、簡単に説明をします。

プロック崩しサンプル

プロジェクトのダウンロード

プロジェクトファイルは GitHubにアップロードしているので、こちらからダウンロードできます。

Mainシーン

Mainシーンでゲーム全体の管理をしています。

ノードの構成

ノードの構成は以下のとおりです。

  • WallLayer: 外壁レイヤー。パドルやボールはここから出られない
  • BlockLayer: 壊せるブロックを管理するレイヤー
  • MainLayer: ボールとパドルがあるレイヤー
  • UILayer: UIを配置するレイヤー

Mainシーンの状態

Mainシーンは以下の4つの状態を持っています。

## 状態.
enum eState {
	READY, # 開始.
	MAIN, # メイン.
	GAMEOVER, # ゲームオーバー.
	GAMECLEAR, # ゲームクリア.
}

まずゲーム開始直後に表示される “READY” を1秒間表示した後、MAIN状態でゲームプレイを行います。もしボールが画面下に落下したら、残りボールを減らして次のボールを出現させます。残りのボールがなくなったら GAMEOVER 状態となります。

ブロックがすべてなくなれば GAMECLEAR状態となります。

ボールの落下判定

ボールの落下判定はボールが一定の高さを超えたときとしました。

const DEADLINE_Y = 800 # 死亡判定.

## 更新 > メイン.
func _update_main(delta:float) -> void:
	...
	if _ball.position.y > DEADLINE_Y:
		# 死亡.
		damage()

ゲームクリア判定

ゲームクリア判定(ブロックがすべてなくなったかどうか)は、ブロックレイヤー(BlockLayer)から子ノードがすべてなくなったかどうかで行っています。

## 更新 > メイン.
func _update_main(delta:float) -> void:
	if _block_layer.get_child_count() == 0:
		# ブロックがすべてなくなればクリア.
		_state = eState.GAMECLEAR
		return
	
	...

オブジェクトの種類ごとに CanvasLayer をわけておくと、こういった判定がやりやすくておすすめです。

再登場するボールの位置

Maker2D というノードは、位置情報を明示的に定義するのに便利です。

今回はボールの出現位置を定義するのに使用しました。

ブロック(Block)オブジェクト

ブロックは Block.tscn / .gd で実装しています。

Blockオブジェクト自体は大したことはしていませんが、色情報をエディタ上で反映させたいため、”@tool” キーワードを使用しています。

@tool # エディタでも _processを呼び出したい.
extends StaticBody2D

...

## 更新.
func _process(delta: float) -> void:
	# 色を設定.
	_spr.modulate = COLOR_TBL[color]

“@tool” キーワードを指定すると、エディタ上でも “_process()” が実行されるようになります。

これによりインスペクタから直接色を変更できるようになります。

ボール(Ball)オブジェクト

ボールは Ball.tscn / .gd で実装しています。

跳ね返り処理

跳ね返り処理は move_and_collide() の戻り値の KinematicCollision2D が有効かどうかで判定できます。

## 更新.
func _update(delta: float) -> void:
	# 回転.
	_spr.rotation += _rot_speed * delta
	_spr.rotation *= 0.995 # 減衰する.

	# 衝突処理.
	var col = move_and_collide(_velocity * delta)
	_collide(col)

衝突処理の _collide()

## 衝突処理.
func _collide(col:KinematicCollision2D) -> void:
	if col == null:
		return # 衝突していない.
	
	# 衝突した.
	var collider = col.get_collider()
	if collider is Block:
		# ブロックならダメージを与える.
		collider.damage(1)
	
	# 法線を取得する.
	var n = col.get_normal()
	# 回転.
	var delta_angle = n.angle_to(_velocity)
	_rot_speed += delta_angle * 3

	# 跳ね返り処理.
	if collider is Paddle:
		# パドルの場合は法線を無視して跳ね返り方向が変わる.
		var dx = position.x - collider.position.x
		var rate = abs(dx / 64.0)
		if rate > 1.0:
			rate = 1.0 # 1.0を超えない想定.
		_velocity.x = 0.9 * _speed * rate * sign(dx)
		# 常に上方向に跳ね返る.
		_velocity.y = (0.9 * rate - 1) * _speed
	else:
		_velocity = _velocity.bounce(n)
			
	# 加速する.
	_speed += SPEED_UP
	if _speed > MAX_SPEED:
		_speed = MAX_SPEED # 最高速度は超えない.
	_velocity = _velocity.normalized() * _speed

最初の注意点として、衝突するオブジェクトは Paddle(パドル)Block(ブロック) の2種類があります。

それぞれ異なる処理が必要となります。

  • Blockとの衝突: 消滅処理 (damage) の呼び出しをする。衝突の法線に沿って跳ね返る
  • Paddleとの衝突: 跳ね返り方向を特殊な方法で行う

BallとBlockが衝突した際には Block が消えてほしいので、Block の damage() を呼び出して消滅処理を行います。

また Paddle と衝突したときには、物理法則とは異なる特殊な跳ね返りを行います。具体的には中心から放射上の方向に跳ね返るようにします。

物理法則とあっていませんが、このようにパドルのどこにぶつけるかで移動方向を調整できると、ゲームプレイの自由度が上がっておすすめです。