2020/10/29(木) [n年前の日記]
#1 [godot] Godot EngineでメインシーンとHUDシーンを合体
Godot Enigne 3.2.3 x64 の勉強を兼ねて、Apple Catcher っぽいゲームを作成中。
前回はHUDシーンを作ったので、今回はHUDシーンをゲームのメインシーンに合体させる。また、プレイヤーが林檎を取ったらスコアを増やす処理や、ゲーム開始時とゲームオーバーの処理も追加する。
今回の作業で、一応そこそこゲームっぽい見た目になってくれるはず…。
前回はHUDシーンを作ったので、今回はHUDシーンをゲームのメインシーンに合体させる。また、プレイヤーが林檎を取ったらスコアを増やす処理や、ゲーム開始時とゲームオーバーの処理も追加する。
今回の作業で、一応そこそこゲームっぽい見た目になってくれるはず…。
◎ メインシーンにHUDシーンを追加。 :
Main.tscn を開き、子ノードとしてHUDシーンのインスタンスを追加する。Mainノードを選択して、上のほうにあるインスタンス追加(?)ボタンをクリック。
assets/Hud.tscn を選択。
HUDシーンが追加された。画面にもHUDが表示されて、ますますゲーム画面っぽい見た目になってきた。
assets/Hud.tscn を選択。
HUDシーンが追加された。画面にもHUDが表示されて、ますますゲーム画面っぽい見た目になってきた。
◎ メインシーンにスクリプトを追加。 :
◎ HUDシーンのシグナルをメインシーンのスクリプト内メソッドに接続。 :
HUDシーンは、スタートボタンが押された時に new_game というシグナルを出すように作ってある。その new_gameシグナルに、メインシーンスクリプト Main.gd 内のメソッドを接続して、ゲーム開始時の処理を Main.gd 内に記述することにする。
ノード一覧ウインドウ内で、Hudノードを選択。

ノードタブをクリックすると、シグナル一覧の中に new_game というシグナルが見えるので、選択して「接続」。

Mainノードを選択して「接続」。メソッド名はそのままでいい。
メインシーンスクリプト Main.gd 内に、_on_Hud_new_game() というメソッドが追加された。
これで、スタートボタンをクリックすると _on_Hud_new_game() が呼ばれる状態になった。このメソッドの中にゲーム開始時の処理を記述していけばいい。
ノード一覧ウインドウ内で、Hudノードを選択。

ノードタブをクリックすると、シグナル一覧の中に new_game というシグナルが見えるので、選択して「接続」。

Mainノードを選択して「接続」。メソッド名はそのままでいい。
メインシーンスクリプト Main.gd 内に、_on_Hud_new_game() というメソッドが追加された。
これで、スタートボタンをクリックすると _on_Hud_new_game() が呼ばれる状態になった。このメソッドの中にゲーム開始時の処理を記述していけばいい。
◎ プレイヤーシーンのシグナルをメインシーンのスクリプト内メソッドに接続。 :
プレイヤーシーンについても、HUDシーンと同様に作業していく。
プレイヤー側では、「林檎を取った時」と「爆弾に当たった時」にカスタムシグナルを発行するようにして、そのシグナルを Main.gd 内のメソッドと接続してやれば、「林檎を取った → 点数を増やす」「爆弾に当たった → ゲームオーバーにする」といった処理が実現できる。
プレイヤーシーンスクリプト Player.gd に記述を若干追加する。内容は以下になる。
_Player.gd
スクリプトの最初のほうに、「林檎を取った時」と「爆弾に当たった時」に使うカスタムシグナルが追加してある。
そして、「林檎を取った時」は emit_signal("get_apple") を呼び、「爆弾に当たった時」は emit_signal("player_dead") を呼んでやれば、それぞれのタイミングでシグナルが発行される。
プレイヤーシーン側でカスタムシグナルを用意できたので、Main.gd 内のメソッドと接続してやる。Playerノードを選択。

ノードタブをクリックして、get_apple() を選択して「接続」。

Mainノードを選択して「接続」。
同様に、player_dead() シグナルも接続してやる。player_dead() を選択して「接続」。右クリックして「接続」を選んでも良い。

Mainノードを選択して「接続」。
これで Main.gd 内に、以下のメソッドが追加された。
Main.gd 内に、ゲーム開始時の処理、スコア増加、ゲームオーバー時の処理を書いてみた。内容は以下になる。
_Main.gd
ゲーム開始処理やゲームオーバー処理を書いたので、林檎や爆弾を発生させる ItemGenerator.gd にも若干の修正が必要になる。ゲームが開始されるまでは、林檎や爆弾を発生しないようにしたい。
ItemGenerator.gd の内容は以下になる。以前書いたスクリプトとの違いは、_ready() 内の start() をコメントアウトした部分だけ。
_ItemGenerator.gd
プレイヤー側では、「林檎を取った時」と「爆弾に当たった時」にカスタムシグナルを発行するようにして、そのシグナルを Main.gd 内のメソッドと接続してやれば、「林檎を取った → 点数を増やす」「爆弾に当たった → ゲームオーバーにする」といった処理が実現できる。
プレイヤーシーンスクリプト Player.gd に記述を若干追加する。内容は以下になる。
_Player.gd
extends Area2D
signal get_apple
signal player_dead
export var speed = 600
export var w = 96
var screen_size
var deaded = false
func _ready():
screen_size = get_viewport_rect().size
$AnimationPlayer.play("idle")
func _process(delta):
if deaded:
return
var velocity = Vector2()
if Input.is_action_pressed("ui_right"):
velocity.x += 1
$Sprite.flip_h = true
if Input.is_action_pressed("ui_left"):
velocity.x -= 1
$Sprite.flip_h = false
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimationPlayer.play("walk")
else:
$AnimationPlayer.play("idle")
position += velocity * delta
position.x = clamp(position.x, w/2, screen_size.x - (w/2))
func _on_Player_body_entered(body):
if deaded:
return
if body.kind == "Apple":
emit_signal("get_apple")
body.queue_free() # kill Apple
else:
dead()
func dead():
emit_signal("player_dead")
deaded = true
$AnimationPlayer.play("dead")
$CollisionShape2D.set_deferred("disabled", true)
# wait 3 sec
yield(get_tree().create_timer(3), "timeout")
deaded = false
$AnimationPlayer.play("idle")
$CollisionShape2D.disabled = false
スクリプトの最初のほうに、「林檎を取った時」と「爆弾に当たった時」に使うカスタムシグナルが追加してある。
signal get_apple signal player_dead
そして、「林檎を取った時」は emit_signal("get_apple") を呼び、「爆弾に当たった時」は emit_signal("player_dead") を呼んでやれば、それぞれのタイミングでシグナルが発行される。
プレイヤーシーン側でカスタムシグナルを用意できたので、Main.gd 内のメソッドと接続してやる。Playerノードを選択。

ノードタブをクリックして、get_apple() を選択して「接続」。

Mainノードを選択して「接続」。
同様に、player_dead() シグナルも接続してやる。player_dead() を選択して「接続」。右クリックして「接続」を選んでも良い。

Mainノードを選択して「接続」。
これで Main.gd 内に、以下のメソッドが追加された。
- _on_Player_get_apple() : プレイヤーが林檎を取った時に呼ばれるメソッド
- _on_Player_player_dead() : プレイヤーが爆弾に当たった時に呼ばれるメソッド
Main.gd 内に、ゲーム開始時の処理、スコア増加、ゲームオーバー時の処理を書いてみた。内容は以下になる。
_Main.gd
extends Node
var game_title = "NEWTOTOTONE"
var score = 0
func _ready():
score = 0
#func _process(delta):
# pass
func _on_Hud_new_game():
new_game()
func _on_Player_get_apple():
add_score()
func _on_Player_player_dead():
game_over()
func new_game():
score = 0
$Hud.update_score(score)
$ItemGenerator.start()
func add_score():
score += 10
$Hud.update_score(score)
func game_over():
$ItemGenerator.stop()
$Hud.show_message("GAME OVER")
yield(get_tree().create_timer(3), "timeout")
# todo: kill all items
$Hud.show_message(game_title)
$Hud.show_startbutton()
- _on_Hud_new_game() : スタートボタンが押されたときに呼ばれるメソッド。
- _on_Player_get_apple() : プレイヤーが林檎を取った時に呼ばれるメソッド。
- _on_Player_player_dead() : プレイヤーに爆弾が当たった時に呼ばれるメソッド。
- new_game() : ゲーム開始時の処理。
- add_score() : スコアを増やす処理。
- game_over() : ゲームオーバー時の処理。
ゲーム開始処理やゲームオーバー処理を書いたので、林檎や爆弾を発生させる ItemGenerator.gd にも若干の修正が必要になる。ゲームが開始されるまでは、林檎や爆弾を発生しないようにしたい。
ItemGenerator.gd の内容は以下になる。以前書いたスクリプトとの違いは、_ready() 内の start() をコメントアウトした部分だけ。
_ItemGenerator.gd
extends Node2D
export (PackedScene) var Apple
export (PackedScene) var Bomb
export var w = 96
var rnd = RandomNumberGenerator.new()
var screen_size
var born_rate = 100
var born_enable = false
func _ready():
rnd.randomize()
screen_size = get_viewport_rect().size
born_rate = 100
born_enable = false
# start() # Delete in production environment
func start():
born_enable = true
born_rate = 100
func stop():
born_enable = false
func generate_item():
if born_enable:
var x0 = w / 2
var x1 = screen_size.x - (w / 2)
var x = rnd.randi_range(x0, x1)
var item
if rnd.randi_range(0, 100) > max(20, born_rate):
item = Bomb.instance()
else:
item = Apple.instance()
item.position.x = x
item.position.y = -w
$Items.add_child(item)
func update_rate():
if born_enable:
born_rate -= 3
if born_rate <= 1:
born_rate = 100
◎ 実行結果。 :
F6キーを押して実行してみる。
次回は、もっとゲームらしくなるように雑多な装飾をしてみる。BGMや効果音をつけてみたり、背景画を表示したりする予定。
- スタートボタンを押すとゲームが始まってくれた。
- 林檎を取るとスコアが増えてる。
- 爆弾に当たるとゲームオーバーになる。
次回は、もっとゲームらしくなるように雑多な装飾をしてみる。BGMや効果音をつけてみたり、背景画を表示したりする予定。
◎ スクリプトの補足。 :
スクリプト内に、以下のような記述が出てくるけれど。
これは Godot Engine において「n秒間待ってからその後の処理を続ける」というお決まりの書き方らしい。
おそらくだけど、その場でTimerノードを発生させて、Timerノードがn秒経過するとtimeoutシグナルを出すからそれを待つ、という処理をしているのではないかなと…。たぶん。理解が間違ってるかもしれないけど。
yield(get_tree().create_timer(3), "timeout")
これは Godot Engine において「n秒間待ってからその後の処理を続ける」というお決まりの書き方らしい。
おそらくだけど、その場でTimerノードを発生させて、Timerノードがn秒経過するとtimeoutシグナルを出すからそれを待つ、という処理をしているのではないかなと…。たぶん。理解が間違ってるかもしれないけど。
[ ツッコむ ]
以上です。











