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シグナルを出すからそれを待つ、という処理をしているのではないかなと…。たぶん。理解が間違ってるかもしれないけど。
[ ツッコむ ]
以上、1 日分です。