Godot4.1.3でシューティングゲームを作っていきます。
スコア処理と表示
敵を撃墜したときにスコアを加算し、画面に表示します。
github
本記事で実装したものをgithubで公開しています。
方針
ゲーム全体を管理しているgame.gdスクリプトが、スコアを管理・表示することにします。
撃墜判定は敵スクリプトのtwin_body.gdで実施しているため、敵スクリプトがgameスクリプトにスコアの加算を通知する必要があります。
敵スクリプトはsignalでスコア更新通知を送信します。signalの接続先は、外部から設定してもらうようにして、敵スクリプトが外部に依存関係を持たせないようにします。
敵スクリプトで宣言したシグナルをgameに接続する
シナリオ要素が敵を生成します。その時に接続先としてgameノードのメソッドを設定しますが、直接gameノードを呼び出すと依存関係が強くなりすぎるため、グローバル変数のノード経由で呼び出すようにします。
そのため初期設定時に、gameノード自信をglobalに登録して、シナリオ要素は敵を生成したときに、globalに登録されたノードのメソッドをシグナルの接続先として、敵に設定します。
敵が撃墜された場合シグナルを発行
敵スクリプトは撃墜されたことを判定すると、add_to_scoreシグナルを発行して、queue_freeにより消滅します。
gameスクリプトがadd_to_scoreシグナルを受信し、スコアの処理を実行し、表示を更新します。
スコア表示領域を追加する
game.tscnにスコア表示用のControlノードを追加します。
ルートノード直下にControlを追加して、「HUD」に名称を変更します。
HUDの子ノードとして、Labelを2つ追加して、名称を「score」と「score_value」に変更します。
2つのLabelはインスペクタを開き、それぞれ下記のように変更しました。
score : Label
「score」文字列を表示します。白の太字に設定します。
ノード名 | 変更箇所 |
---|---|
Label/Text | "score" |
Label/新規LabelSettings/Font/Size | 20px |
Label/新規LabelSettings/Outline/Size | 5px |
Control/Size/x | 61px |
Control/Position/x | 11px |
score_value : Label
scoreの数字を表示します。黄色の太字に設定します。
ノード名 | 変更箇所 |
---|---|
Label/Text | "score" |
Label/新規LabelSettings/Font/Size | 20px |
Label/新規LabelSettings/Font/Color | 黄色 Hex:#c2c700 |
Label/新規LabelSettings/Outline/Size | 5px |
Label/新規LabelSettings/Outline/Color | 黄色 Hex:#5eb000 |
Control/Size/x | 118px |
Control/Position/x | 86px |
撃墜された敵がadd_to_scoreシグナルを発行する
twin_body.gdを開き下記のように修正します。
extends Area3D
+signal add_to_score(int)
@export var _d_speed_mps = 20.0
@export var _d_hp = 1
+@export var _i_score = 100
func _physics_process(delta):
global_position += Vector3(0, 0, 1) * _d_speed_mps * delta
if _d_hp <= 0:
+ add_to_score.emit(_i_score)
queue_free()
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
# HPからダメージポイントを引き算する。余った分はreturn値で返す
func calc_damage(laser_power) -> int:
var remainder = 0
if _d_hp <= laser_power:
# ダメージの方が大きい
remainder = laser_power - _d_hp
_d_hp = 0
else:
# HPの方が大きい
_d_hp -= laser_power
remainder = 0
# 余ったダメージポイントを返す
return remainder
発行するシグナルを宣言します。引数は1個でこの敵を倒した時のスコアを設定します。
+signal add_to_score(int)
スコアはパラメータにします。
+@export var _i_score = 100
HPが0になったときが撃墜された時なので、スコアを設定しadd_to_scoreシグナルを発行(emit)します。
func _physics_process(delta):
global_position += Vector3(0, 0, 1) * _d_speed_mps * delta
if _d_hp <= 0:
+ add_to_score.emit(_i_score)
queue_free()
シグナルの接続先ノードをグローバル変数にする
gameノードは、グローバル変数経由でアクセスするようにします。
extends Node3D
+# gameのインスタンス
+var node_game : Node3D = null
# 生成したインスタンスの追加先
var node_lasers : Node3D = null
var node_enemies : Node3D = null
var node_bullets : Node3D = null
# Playerのインスタンス
var node_player : Area3D = null
add_to_scoreを受けてスコアを表示する処理を実装する
game.gdを開き、下記のように実装します。
extends Node3D
var _scn_scenario_stage = preload("res://scenario/scenario_stage01.tscn")
var _node_scenario_ins
+var _i_score : int = 0
func _ready():
+ # gameインスタンスを保存
+ g_val.node_game = self
# インスタンス化したシーンの追加先を設定する
g_val.node_lasers = $DynamicNodes/lasers
g_val.node_enemies = $DynamicNodes/enemy/bodies
g_val.node_bullets = $DynamicNodes/enemy/bullets
# Playerインスタンスを設定する
g_val.node_player = $Player
# ステージシナリオをシーンツリーに追加
_node_scenario_ins = _scn_scenario_stage.instantiate()
add_child(_node_scenario_ins)
func _process(delta):
# シナリオを駆動する
_node_scenario_ins.drive_scenario()
+func _on_add_to_score(add_score):
+ _i_score += add_score
+ $HUD/score_value.text = "%010d" % _i_score
game.gdでスコアを管理するので、メンバ変数として_i_scoreを宣言します。
+var _i_score : int = 0
add_to_scoreシグナルを受けて、スコアを加算し、score_valueラベルのtextを更新するメソッドを追加します。
+func _on_add_to_score(add_score):
+ _i_score += add_score
+ $HUD/score_value.text = "%010d" % _i_score
初期設定時に、gameノードを設定します。
func _ready():
+ # gameインスタンスを保存
+ g_val.node_game = $"."
敵インスタンスを生成したときに、add_to_socreシグナルの接続先を設定する
シナリオ要素が、敵を生成したときに、add_to_scoreシグナルの接続先を設定します。
produce_twin_body.gdを開き下記のように変更します。
extends Node
@export var _i_produce_num = 10
@export var _d_produce_interval_sec = 0.5
var _scn_enemy = preload("res://character/enemy/twin_body.tscn")
var _i_produced_num = 0; # 生産した数
func start_element():
$Timer_for_produce.start(_d_produce_interval_sec)
func is_blocking_to_move_on_next_scenario() -> bool:
# 敵キャラの生産が完了したら次のシナリオ要素に進ませるためfalseにする
return ( _i_produced_num < _i_produce_num )
func _on_timer_for_produce_timeout():
# 敵キャラクターのインスタンスを生成、位置を設定してシーンに追加する
var ins = _scn_enemy.instantiate()
ins.position.z = -200
ins.position.x = randf_range(-25, 25)
+ # add_to_scoreシグナルをgame.gdに接続する
+ ins.add_to_score.connect( g_val.node_game._on_add_to_score )
# 敵インスタンスをシーンツリーに追加
g_val.node_enemies.add_child(ins)
# 生産完了したら、タイマーを停止する
_i_produced_num += 1
if ( _i_produced_num >= _i_produce_num ):
$Timer_for_produce. stop()
生成した敵インスタンスがinsです。insのadd_to_scoreシグナルを、グローバル変数を介してgameノードの_on_add_to_scoreメソッドに接続します。
+ # add_to_scoreシグナルをgame.gdに接続する
+ ins.add_to_score.connect( g_val.node_game._on_add_to_score )
実行結果
敵を撃墜すると、スコアが加算されて表示が更新されることが確認できました。
以上です。