mieki256's diary



2020/10/29(木) [n年前の日記]

#1 [godot] Godot EngineでメインシーンとHUDシーンを合体

Godot Enigne 3.2.3 x64 の勉強を兼ねて、Apple Catcher っぽいゲームを作成中。

前回はHUDシーンを作ったので、今回はHUDシーンをゲームのメインシーンに合体させる。また、プレイヤーが林檎を取ったらスコアを増やす処理や、ゲーム開始時とゲームオーバーの処理も追加する。

今回の作業で、一応そこそこゲームっぽい見た目になってくれるはず…。

メインシーンにHUDシーンを追加。 :

Main.tscn を開き、子ノードとしてHUDシーンのインスタンスを追加する。Mainノードを選択して、上のほうにあるインスタンス追加(?)ボタンをクリック。

godot_tuto14_main_add_hud_ss01.png


assets/Hud.tscn を選択。

godot_tuto14_main_add_hud_ss02.png


HUDシーンが追加された。画面にもHUDが表示されて、ますますゲーム画面っぽい見た目になってきた。

godot_tuto14_main_add_hud_ss03.png

メインシーンにスクリプトを追加。 :

メインシーンにスクリプトを追加する。Mainノードを選択して、スクリプトをアタッチ。

godot_tuto14_main_add_hud_ss04.png


res://scripts/Main.gd として保存・作成する。

godot_tuto14_main_add_hud_ss05.png


スクリプトを編集するエディタ画面が開かれた。

godot_tuto14_main_add_hud_ss06.png

HUDシーンのシグナルをメインシーンのスクリプト内メソッドに接続。 :

HUDシーンは、スタートボタンが押された時に new_game というシグナルを出すように作ってある。その new_gameシグナルに、メインシーンスクリプト Main.gd 内のメソッドを接続して、ゲーム開始時の処理を Main.gd 内に記述することにする。

ノード一覧ウインドウ内で、Hudノードを選択。

godot_tuto14_main_add_hud_ss07.png


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

godot_tuto14_main_add_hud_ss08.png


Mainノードを選択して「接続」。メソッド名はそのままでいい。

godot_tuto14_main_add_hud_ss09.png


メインシーンスクリプト Main.gd 内に、_on_Hud_new_game() というメソッドが追加された。

godot_tuto14_main_add_hud_ss10.png


これで、スタートボタンをクリックすると _on_Hud_new_game() が呼ばれる状態になった。このメソッドの中にゲーム開始時の処理を記述していけばいい。

プレイヤーシーンのシグナルをメインシーンのスクリプト内メソッドに接続。 :

プレイヤーシーンについても、HUDシーンと同様に作業していく。

プレイヤー側では、「林檎を取った時」と「爆弾に当たった時」にカスタムシグナルを発行するようにして、そのシグナルを 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

godot_tuto14_main_add_hud_ss12.png

そして、「林檎を取った時」は emit_signal("get_apple") を呼び、「爆弾に当たった時」は emit_signal("player_dead") を呼んでやれば、それぞれのタイミングでシグナルが発行される。

プレイヤーシーン側でカスタムシグナルを用意できたので、Main.gd 内のメソッドと接続してやる。Playerノードを選択。

godot_tuto14_main_add_hud_ss11.png


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

godot_tuto14_main_add_hud_ss13.png


Mainノードを選択して「接続」。

godot_tuto14_main_add_hud_ss14.png


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

godot_tuto14_main_add_hud_ss15.png


Mainノードを選択して「接続」。

godot_tuto14_main_add_hud_ss16.png


これで Main.gd 内に、以下のメソッドが追加された。
  • _on_Player_get_apple() : プレイヤーが林檎を取った時に呼ばれるメソッド
  • _on_Player_player_dead() : プレイヤーが爆弾に当たった時に呼ばれるメソッド
それぞれのメソッドに、処理を書いていけばいい。

godot_tuto14_main_add_hud_ss17.png


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や効果音をつけてみたり、背景画を表示したりする予定。

スクリプトの補足。 :

スクリプト内に、以下のような記述が出てくるけれど。
yield(get_tree().create_timer(3), "timeout")

これは Godot Engine において「n秒間待ってからその後の処理を続ける」というお決まりの書き方らしい。

おそらくだけど、その場でTimerノードを発生させて、Timerノードがn秒経過するとtimeoutシグナルを出すからそれを待つ、という処理をしているのではないかなと…。たぶん。理解が間違ってるかもしれないけど。

以上、1 日分です。

過去ログ表示

Prev - 2020/10 - Next
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project