【Godot 4.0】スマホ３Dゲームを作るための勉強　その３６　HUD(Head Up Display）を追加する

　ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
　2023/4/4リリースのGodot_v4.0.2-stable_win64を使用しています。

# 目的
　ゲーム中の情報を２Dで表示する画面を追加していきます。

# ベースプロジェクト
　下記の記事を通して作成したプロジェクトをベースにしています。

https://qiita.com/FootInGlow/items/0122677eafe79e309ab8

https://github.com/footinglow/Godot4/tree/main/02_study/S35HUD1

　github(Godotのプロジェクトマネージャーからインポートして利用できます）

# 目的
　エネルギーバーを追加します。

![powerbar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/89bce74a-2f36-c9f1-abe0-653c4102ed5f.png)

# ゲーム中の情報の表示について
　ゲーム中の情報の表示はスコア、何かしらの量を表示するためのバーなどありますが、２Ｄで表示したいものがあると思います。
　２Ｄで表示したい情報については、CanvasItemを継承したノード(Node2DやControlの派生クラスがたくさんあります）を追加して、そのノードに描画を行います。
　描画位置/サイズはピクセル位置で指定します。
　描画サイズはプロジェクト設定の表示/ウインドウのビューポートの幅と高さになります。
![スクリーンショット (695).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/aa42be57-2d9e-d924-ae03-ba1de7b8a0b1.png)
　ビューポートの幅がX軸方向で、高さがY軸方向です。
　左上がx=0,y=0で、右がXプラス、下がyプラスになります。
![スクリーンショット (698).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a48800f8-8b04-8f15-6a85-8cfdaff3ce58.png)

# 表示するゲームパラメータを実装する
　エネルギーバーに表示するゲームパラメータを追加します。
　画面タッチしっぱなしにするとBulletを発射しますが、Bulletを発射するたびにエネルギーがたまる変数を追加します。
　複数のシーンのスクリプト(Player.gdとこれから追加するHUD.gd)からアクセスしたいので、シングルトン(自動読み込み）スクリプトを生成して変数を定義します。

## グローバル変数を定義するスクリプトを作成します。
　ファイルシステムのresを右クリックして新規＞スクリプトを実行します。
　![スクリーンショット (699).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/48499498-66ba-210e-8cb9-ffc6423d8ef0.png)
　global_variable.gdという名前で保存します。
![スクリーンショット (700).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/63b7debc-769d-d7b6-f3c5-806abe675536.png)
　res://global_variable.gdが作成されました。
![スクリーンショット (701).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/0c71771e-0f26-887d-c1c7-19efe1b4494c.png)

## エネルギー量を管理するためのスクリプトを実装する
　res://global_variable.gdを開いて下記のように修正します。

```GDScript
extends Node

# パラメータ
@export var m_d_energy_max : float = 100.0
@export var m_d_energy_min : float = 0.0
@export var m_d_energy_add : float = 5.0	# Bullet発射間隔が1秒の場合、20秒で100になる計算

# グローバル変数
var m_d_energy : float = 0.0

func increase_energy():
	m_d_energy = clampf(
				m_d_energy + m_d_energy_add,
				m_d_energy_min,
				m_d_energy_max
			)
```

　m_d_energyが現在のエネルギー量を表すグローバル変数です。
　最大/最小値を定義します。０～１００までにしました。また1回に追加する量をm_d_energy_addとして定義します。
　@exportしてインスペクタから変更できるようにしています。
　最後にエネルギを蓄積するAPIをincrease_energy()という名前で追加しました。
　エネルギーを増加した後の値をclampf()関数で０以下の場合は０に、100以上の場合は100になるようにクランプします（リミットする、制限するなどと同じ意味です）。

## シングルトン（自動読み込み）スクリプトして登録する
　次に実装したスクリプトをシングルトン（自動読み込み）スクリプトとして登録します。
　プロジェクトメニューのプロジェクト設定を開いて、「Autoload」タブを選択します。
　パスに先ほど作成したres://global_variable.gdを設定します。ノード名が自動で入力されるので確認後「追加」ボタンを押下します。
![スクリーンショット (702).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a796bef0-24b4-a5dd-8806-3204666c426c.png)
　追加されました。「グローバル変数」にチェックがついていることを確認してください。
　チェックがついていると、名前「GlobalVariable」でどのスクリプトからもアクセスできるようになります。
![スクリーンショット (703).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/0507793c-d038-c0c8-6d45-ba43c7611724.png)

# PlayerからBulletが発射されるとエネルギーをためるようにします。
　res://Characters/Player.gdを開きます。
　_on_timer_fire_timeout()がBullet発射処理なので、最後にエネルギーをためるメソッドを実行します。

```GDScript
func _on_timer_fire_timeout():
	# スマホタッチ中の場合、発射する
	if m_f_is_screen_touch:
		var scn_bullet = m_scn_bullet.instantiate()
		scn_bullet.transform.origin = $Marker3D.global_transform.origin
		add_sibling(scn_bullet)
		
		# エネルギーをためる
		GlobalVariable.increase_energy()
```

# エネルギーバーを描画する位置を決める
　エネルギーバーを描画する位置は直接ピクセルで指定してもよいのですが、エディター上で位置とサイズを確認したいので、Controlを配置して位置を指定しました。
## HUD描画領域全体を定義します。
　res://game_system.tscnを開きます。
　ルートノードのGameSystemを右クリックして、子ノードを追加からControlを追加します。
　「HUD」（Head Up Display）に名称を変更します。
![スクリーンショット (704).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/7c590d71-e83d-fd8b-0a03-081a26191cf9.png)
　HUDを画面全体（プロジェクト設定の表示＞ウインドウのビューポートの幅と高さ）と同じサイズにします。
　HUDを選択した状態でインスペクタのControl/Layoutを開いて、さらのTransformを開いて、Sizeをx=500px、y=900pxにします。
　中央の２Ｄ表示エリアの画面サイズを表す青い長方形と同じ大きさのＨＵＤの長方形が描画されます。
![スクリーンショット (705).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e75ddf31-8d54-2878-c43a-5eecfc827965.png)
## エネルギーバー描画領域用Controlを配置します
　HUDを右クリックして、子ノードを追加からControlを追加します。
　名称をEnergyBarに変更します。
![スクリーンショット (706).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d6a40e33-437f-0480-80cd-54423eeb812b.png)
　EnergyBarを選択した状態でインスペクタのControl/Layoutを開いて、さらのTransformを開いて、Sizeをx=450px、y=20pxにします。Positionのx=25px、y=870pxにします。
![スクリーンショット (707).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/96a65883-4b8c-ec10-8440-3ed2bb615b8c.png)

:::note
 HUDとEnergyBarをControlにしたのは、同時に選択すると２Dビュー上で位置・サイズ関係を確認することができるためです。描画はHUDのCanvasItemに対して実行しているので、EnergyBarは位置・サイズを取得するためだけに使用しています。あとでLabelを追加する時にはControlの機能を利用しています。
![スクリーンショット (709).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/5cc218ca-cbbb-15c1-b0bf-0cd77be3bf37.png)
:::

# EnergyBarを描画します
## エネルギー割合取得用のAPIを追加
　res://global_variable.gdを開いて、エネルギー割合取得用のAPIを追加します。
　エネルギーのたまり具合を％（0.0が0%、1.0が100%)で取得するようにしました。「->float」でリターン値がfloat型であることを指定しています。 
```GDScript
func get_energy_percent() ->float:
	return m_d_energy / m_d_energy_max
```
## HUDにスクリプトを生成する
　HUDを右クリックしてスクリプトをアタッチします。そのままHUD.gdと名前で保存します。
![スクリーンショット (708).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/9bcba551-3974-b2ef-1182-18d63dc089e0.png)
　下記のように修正します。
```GDScript
extends Control

func _process(delta):
	# Godotエンジンに描画更新を依頼する(_drawメソッド起動を依頼する）
	queue_redraw()
	
func _draw():
	# EnergyBarのサイズ（と使わないけど左上位置）を取得する
	var r : Rect2 = $EnergyBar.get_global_rect()
	# エネルギーバーの背景を黒で描画する
	draw_rect(r, Color.BLACK, true, -1)
	# エネルギーバーの描画領域は1ピクセルずつ小さくする
	var er = r;
	er.size     -= Vector2(2, 2)	# サイズは上下左右一回り小さくする
	er.position += Vector2(1, 1)	# 1ピクセルずつ右下に移動する
	# エネルギーの蓄積割合に従い長さを決めて黄色のバーで描画する。
	er.size.x *= GlobalVariable.get_energy_percent()	# 0.0～1.0の間
	draw_rect(er, Color.YELLOW, true, -1)
```

　_drawメソッドは1回しか起動されないため、更新したい場合queue_redraw()でGodotエンジンに描画を依頼する必要があります。
　今回は毎周期更新依頼をするようにしました。
　「var r : Rect2 = \\$EnergyBar.get_global_rect()」で先ほど２Dビューの中で配置したEnergyBarの位置とサイズをRect2型で取得しています。「var r : Rect2」のように型を指定すると、エディターのコード補完ができるので便利ですね。（var r = $EnergyBar.get_global_rect()でも動作上は問題ありません）
　draw_rect(er, Color.YELLOW, true, -1)で長方形を描画します。第3引数をtrueにすると長方形の中を塗りつぶします。ちなみにfalseにすると枠のみ描画になって、第4引数で枠の太さを指定できるみたいですね。


　実行します。画面の下の方にエネルギーバーが表示されます。発射するたびに黄色のバーが伸びて、20回発射すると一番右まで黄色のバーが伸びます。
![スクリーンショット (710).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/0d354b7e-9818-9773-e7eb-8d50556c4d8b.png)

# エネルギーのたまり具合を数値で表示する
　エネルギーのたまり具合を文字で表示しましょう。位置はエネルギーバーの中央にしたいと思うので、Controlノードのアンカーを使います。
## labelを追加する
　res://game_system.tscnを開きます
　EnergyBarを右クリックして、子ノードを追加からLabelを追加します。
　「EnergyPercent」に名称変更します。
![スクリーンショット (711).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/09cd130a-a929-019d-e20a-03427a97ab56.png)
## Controlのアンカーを使ってLabelの位置をエネルギーバーの中央にする
　画面上部の「2D」をクリックします。
　EnergyPercentを選択した状態で、ビューの右にある丸の中に十字があるアイコンをクリックして、中央を選択します。
　下図のようにEnergyPercentがエネルギーバーの中央に移動しました。
![スクリーンショット (713).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b30ce4fc-f030-9268-c168-6db2826948b4.png)

:::note
 Controlのアンカー(Anchors)機能を使用すると、親Controlに対する子ノードの位置を中央とか右、左など設定できます。ざっくり位置を決めたいときは便利ですね。
:::

## 文字色を変更する
　黄色背景の白文字では見にくいので、文字色をグレーに変更します。
　EnergyPercentを選択した状態で、インスペクタのLabel/Label Settingsの右の＜空＞をクリックして、「新規LabelSettings」を選択します。
![スクリーンショット (714).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/05c6dfc3-c141-5e17-971d-a40719c461b2.png)
　追加されたLabelSettingsをクリックして詳細を開き、さらにFontを開きます。
![スクリーンショット (716).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/44ff4295-0236-d39d-e55f-5d50d485ba12.png)
　Colorの右の白い長方形をクリックするとカラーピッカーが表示されるので、グレーに変更します。
　(R,G,B)=(80,80,80)にしました。
![スクリーンショット (718).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/89dc18bf-ee54-fea6-b662-533dd1157fc8.png)
## Labelの文字列を更新する
 res://HUD.gdを開いて、_draw()の最後に、エネルギーのたまり具体を文字列として設定する処置を追加します。

```GDScript
extends Control

func _process(delta):
	# Godotエンジンに描画更新を依頼する(_drawメソッド起動を依頼する）
	queue_redraw()
	
func _draw():
	# EnergyBarのサイズ（と使わないけど左上位置）を取得する
	var r : Rect2 = $EnergyBar.get_global_rect()
	# エネルギーバーの背景を黒で描画する
	draw_rect(r, Color.BLACK, true, -1)
	# エネルギーバーの描画領域は1ピクセルずつ小さくする
	var er = r;
	er.size     -= Vector2(2, 2)	# サイズは上下左右一回り小さくする
	er.position += Vector2(1, 1)	# 1ピクセルずつ右下に移動する
	# エネルギーの蓄積割合に従い長さを決めて黄色のバーで描画する。
	er.size.x *= GlobalVariable.get_energy_percent()	# 0.0～1.0の間
	draw_rect(er, Color.YELLOW, true, -1)

	# エネルギーのたまり具体を％（0.0%～100.0%で表示する）
	$EnergyBar/EnergyPercent.text = "%.1f%%" % ( GlobalVariable.get_energy_percent() * 100.0 )
```
　GlobalVariable.get_energy_percent()の0.0～1.0が0%～100%に対応するため100倍します。
　フォーマット文字列ではfloatの値を表示するので%fで受けます。小数点第1位まで表示するので「%.1f」としています。
　最後に「％」を文字として表示したいのですが、GDScript format stringsの中で「%」はプレイスホルダーとしての特別な機能を持つため、文字として表示するために%%と2回繰り返します。
> GDScript format strings
>https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/gdscript_format_string.html#doc-gdscript-printf

　実行します。
![スクリーンショット (719).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/733eb486-2d17-b4bd-5698-a6a9d76b20e8.png)

以上