mieki256's diary



2020/11/16(月) [n年前の日記]

#1 [godot] Godot EngineでHUDシーンを作成

Godot Engine 3.2.3 x64 を使って3D表示の簡単なシューティングゲームっぽいものを作る。

今回はプレイヤーや敵のHPを表示するためのHUDシーンを作成する。そのついでに、プレイヤーや敵に弾が当たったらHPが減るような処理も追加する。

フォントファイルをインポート。 :

画面に文字情報を表示するためには何かしらのフォントファイルが必要になる。Godot Engine はフォントファイルとして ttf や otf が使えるので、それらファイルをプロジェクトフォルダにインポートしてやればフォントが使えるようになる。

今回は、ドットコロンさん(?)がCC0で公開している Vegurフォント Version 0.701 を利用させてもらうことにした。ありがたや。

_Vegur | ドットコロン

vegur_0701.zip をDLして解凍すると、Vegur-Bold.otf, Vegur-Light.otf, Vegur-Regular.otf の3ファイルが得られる。太字の Vegur-Bold.otf を使うことにする。

ファイル一覧ウインドウに、Vegur-Bold.otf をドラッグアンドドロップ。これでファイルがコピーされてインポートされる。

3d_tuto11_create_hud_ss01.png


件のフォントファイルを res://imports/ 以下に置くことにした。違う場所にコピーされてしまった時は、ファイル一覧ウインドウ内でファイルをドラッグすれば置き場所を変更できる。

3d_tuto11_create_hud_ss02.png

HUD用のシーンを作成。 :

シーンを新規作成して以下のような構成でノードを追加。HUDのようなものは、ルートノードを Control にしておけばいいのだろうか。ちょっと自信無し。

3d_tuto11_create_hud_ss03.png


ノードをリネーム。

3d_tuto11_create_hud_ss04.png

Hud (Control)
  │
  ├─ PlayerHp (Label)
  │
  └─ EnemyHp (Label)

Labelのプロパティを変更していく。 :

Labelノードを設定して文字表示をする。PlayerHp (Label) を選択して、Textプロパティに「Player: 50/50」を入力。これは画面の左上に表示する予定。

3d_tuto11_create_hud_ss05.png


EnemyHp (Label) を選択して、Textプロパティに「Enemy: 990/990」を入力。これは画面の右上に表示したいので、Align を Right にしておく。これで右詰め表示されるはず。

3d_tuto11_create_hud_ss06.png


フォントを設定する。Custom Fonts → Font の横の「空」をクリックして「新規 DynamicFont」を選択。「DynamicFont」をクリックすればプロパティが表示されるので、Font Data に Vegur-Bold.otf を指定すれば Vegurフォントが使われるようになる。
  • Size で文字サイズを指定。
  • Use Filter を有効にすれば文字が少し滑らかになって表示される。
  • Font Color Shadow を有効にすれば文字に影がつく。
  • Shadow Offset X, Y で影のオフセット位置を変更できる。

3d_tuto11_create_hud_ss07.png


表示位置を調整。それらしい感じになった。

3d_tuto11_create_hud_ss08.png

Hudシーンを保存。 :

res://assets/ の下に Hud.tscn としてシーンを保存。

3d_tuto11_create_hud_ss09.png

スクリプトを書く。 :

Hudに、スクリプトファイルもアタッチする。res://scripts/ の下に Hud.gd として保存。

3d_tuto11_create_hud_ss10.png


Hud.gd にスクリプトを記述していく。

3d_tuto11_create_hud_ss11.png

スクリプトの内容は以下。とりあえず、PlayerHp、EnemyHp の表示を更新するためのメソッドだけ用意しておく。

_Hud.gd
extends Control

func _ready():
    pass # Replace with function body.

#func _process(delta):
#   pass

func update_player_hp(hp, hpmax):
    $PlayerHp.text = "Player: %d/%d" % [hp, hpmax]
    
func update_enemy_hp(hp, hpmax):
    $EnemyHp.text = "Enemy: %d/%d" % [hp, hpmax]

MainシーンにHudシーンを追加。 :

MainシーンにHudシーンのインスタンスを追加する。Main.tscn を開いて、Mainノードを選択。上のほうにあるインスタンス追加ボタンをクリックして、res://assets/Hud.tscn を選べば Hud が追加される。

3d_tuto11_create_hud_ss12.png

Mainシーンにスクリプトを用意。 :

Mainシーンにもスクリプトファイルを用意する。Mainを選んでスクリプトをアタッチ。res://scripts/Main.gd として保存した。

3d_tuto11_create_hud_ss13.png


スクリプトを記述する。

3d_tuto11_create_hud_ss14.png

内容は以下。

_Main.gd
extends Node

var player_hp_max = 0
var enemy_hp_max = 0

func _ready():
    player_hp_max = $Player.hp
    enemy_hp_max = $EnemyZako.hp

#func _process(delta):
#   pass

func damage_player():
    var hp = $Player.hp
    $Hud.update_player_hp(hp, player_hp_max)

func damage_enemy():
    var hp = $EnemyZako.hp
    $Hud.update_enemy_hp(hp, enemy_hp_max)

  • 初期化時に Player や Enemy のHPを取得して変数に記録。
  • Player 又は Enemy がダメージを受けた際、現在のHPを使ってHP表示を更新するメソッドを用意。

プレイヤーのスクリプトを修正。 :

Mainシーンのスクリプト内で、Player や Enemy は HP を持っていることを前提にした記述を追加したけれど、まだ Player側ではそんな変数を持たせてないのでそのあたりを追加する。

プレイヤーのスクリプトファイル、res://scripts/Player.gd を編集。内容は以下。

_Player.gd
extends KinematicBody

signal player_damaged
signal player_died

# export (PackedScene) var PlayerBullet
var PlayerBullet = preload("res://assets/PlayerBullet.tscn")

const SPEED = 32
var velocity = Vector3()
var bullets

var hp = 50
var damage = 0

func _ready():
    hp = 50
    # bullets = get_tree().root.get_node("Main/PlayerBullets")
    bullets = $"/root/Main/PlayerBullets"

#func _process(delta):
#   pass

func _physics_process(delta):
    # move
    velocity = Vector3()
    if Input.is_action_pressed("ui_right"):
        velocity.x = 1
    elif Input.is_action_pressed("ui_left"):
        velocity.x = -1
    if Input.is_action_pressed("ui_down"):
        velocity.z = 1
    elif Input.is_action_pressed("ui_up"):
        velocity.z = -1
    
    # Do not multiply by delta when using move_and_slide()
    velocity = velocity.normalized() * SPEED
    move_and_slide(velocity)
    
    if damage > 0:
        hp -= damage
        damage = 0
        if hp <= 0:
            hp = 0
            emit_signal("player_died")
        emit_signal("player_damaged")

func _on_ShotTimer_timeout():
    var bullet = PlayerBullet.instance()
    var pos = Vector3(translation.x, translation.y, translation.z - 1.5)
    bullet.translation = pos
    bullets.add_child(bullet)

  • 最初のあたりで、カスタムシグナルとして signal player_damaged と signal player_died を用意した。
  • hp という変数を用意した。
  • damage という変数を用意した。
  • 別の何かが damage に 0以外の値を入れたら、自分はその分ダメージを受けたのだ、ということにする。
  • hp から damage 分を引いて、hp が 0 になってなければ自分はまだ生きてる。0なら死んでる。
  • ダメージを受けた時は emit_signal("player_damaged") を呼んで player_damaged シグナルを発行する。
  • 死んだ時は emit_signal("player_died") を呼んで player_died シグナルを発行する。

プレイヤーがダメージを受けた時に発行されるカスタムシグナル、player_damaged にメソッドを接続する。Player を選択して、ノードタブをクリック → シグナルをクリック → player_damaged() を右クリックして「接続」。

3d_tuto11_create_hud_ss15.png


Main を選択して、メソッド名の欄に「damage_player」と入力して「接続」。メソッド名に括弧はつけなくていい。

3d_tuto11_create_hud_ss16.png

これで、プレイヤーがダメージを受けると Main.gd の damage_player() が呼ばれる状態になった。

敵のスクリプトを修正。 :

敵シーンにもプレイヤーシーンと同様の修正を施していく。

res://scripts/EnemyZako.gd を開いて修正。内容は以下。

_EnemyZako.gd
extends Area

signal enemy_damaged
signal enemy_died

export (PackedScene) var enemybullet

var base_pos = Vector3()
var angle = 0
export var move_w = 22
export var move_h = 12
var bullets

var hp = 990
var damage = 0
var attack_point = 10

func _ready():
    base_pos = translation
    angle = 0
    bullets = get_tree().root.get_node("Main/EnemyBullets")

#func _process(delta):
#   pass

func _physics_process(delta):
    move(delta)
    
func move(delta):
    angle += delta
    translation.x = base_pos.x + move_w * cos(deg2rad(angle * 90))
    translation.z = base_pos.z + move_h * sin(deg2rad(angle * 70))
    
func _on_ShotTimer_timeout():
    var angle_add = 12
    var angle = 90 - angle_add * 3
    var spd = 0.2
    for i in range(7):
        _shot(translation, angle, spd)
        _shot(translation, angle, spd * 0.8)
        angle += angle_add
    
func _shot(pos, angle, spd):
    var bullet = enemybullet.instance()
    bullet.translation = pos
    bullet.set_dir_and_speed(angle, spd)
    bullets.add_child(bullet)

func _on_EnemyZako_area_entered(area):
    if is_queued_for_deletion():
        return
    # print("playerbullet hit the enemy")
    if area.is_in_group("playerbullets"):
        hp -= area.attack_point
        if hp <= 0:
            hp = 0
            emit_signal("enemy_died")
        emit_signal("enemy_damaged")

func _on_EnemyZako_body_entered(body):
    if is_queued_for_deletion():
        return
    # print("player hit the enemy")
    if body.is_in_group("player"):
        body.damage = attack_point

修正内容は、Player.gd に対して行ったこととほとんど同じで、カスタムシグナル名が違う程度。

ただ、Enemy は自分でプレイヤーの弾(Areaノード)と当たったかどうかを調べるので、プレイヤーの弾が attack_point という変数を持っていることを前提にして、その値で hp を減らす処理をしている。

敵がダメージを受けた時に発行されるカスタムシグナル、enemy_damaged にメソッドを接続する。EnemyZako を選択して、ノードタブをクリック → シグナルをクリック → enemy_damaged() を右クリックして「接続」。

3d_tuto11_create_hud_ss17.png


Main を選択して、メソッド名の入力欄に「damage_enemy」と入力して「接続」。

3d_tuto11_create_hud_ss18.png

これで、敵がダメージを受けると Main.gd の damage_enemy() が呼ばれる状態になった。

敵弾のスクリプトを修正。 :

敵弾のスクリプト res://scripts/EnemyBullet.gd を修正する。内容は以下。

_EnemyBullet.gd
extends Area

var velocity = Vector3()
var direction = 0
var speed = 3
var attack_point = 10

func _ready():
    pass # Replace with function body.

#func _process(delta):
#   pass

func _physics_process(delta):
    translate(velocity * speed * 60 * delta)

func set_dir_and_speed(angle, spd):
    direction = angle
    speed = spd
    var x = cos(deg2rad(angle))
    var z = sin(deg2rad(angle))
    velocity = Vector3(x, 0, z)
    
func _on_KillTimer_timeout():
    queue_free()

func _on_EnemyBullet_body_entered(body):
    if is_queued_for_deletion():
        return
    # print("EnemyBullet Hit!")
    if body.is_in_group("player"):
        body.damage = attack_point
    queue_free()

  • 変数 attack_point を追加して、プレイヤーへの攻撃力を入れておく。
  • プレイヤーと当たった時は、プレイヤーが変数 damage を持っているはずだから、damage に自分の攻撃力を入れてやる。
  • プレイヤー側は、damage が0以外なら何かが当たってダメージを受けたという処理をする。

プレイヤーの弾のスクリプトを修正。 :

プレイヤーの弾のスクリプト res://scripts/PlayerBullet.gd を修正する。内容は以下。

_PlayerBullet.gd
extends Area

var velocity = Vector3()
var speed = 90

var attack_point = 10

func _ready():
    velocity = Vector3(0, 0, -1)

#func _process(delta):
#   pass

func _physics_process(delta):
    translate(velocity * speed * delta)

func _on_Timer_timeout():
    queue_free()

func _on_PlayerBullet_area_entered(area):
    if is_queued_for_deletion():
        return
    # print("PlayerBullet Hit!")
    queue_free()

  • 変数 attack_point を追加して、敵への攻撃力を入れておく。
  • 敵と当たった時は、敵側でこの attack_point を参照して処理をする。


これで、プレイヤーの弾が敵に当たったり、敵弾がプレイヤーに当たったりすると、それぞれのHPが減って、HUD上の表示が更新される状態になった。

しかし、このままだとプレイヤーに敵弾に当たったことが分かりづらい。次回はプレイヤーや敵がダメージを受けた時の簡単なエフェクト(?)をつけてみる。

#2 [nitijyou] 浴室のミニクリプトン電球を交換

朝、親父さんから、浴室(風呂場)に2つある照明の向かって右側がつかなくなったからチェックしてくれ、と頼まれた。確認した感じでは電球が切れてたようで。予備のミニクリプトン電球は親父さんが既に買っていた。親父さん用の押し入れの上のほうの黄色いプラスチック製の箱の中に2個セットの封を切ってない製品があったので1個を使用。まだ1個残ってるので次回電球が切れた時はソレを使えばよい。と、こうしてメモしておくけれど自分のことだからきっと絶対に忘れてしまうだろうなコレ…。

おそらくだけど、交換に使った製品は LDS100V54W・W・K・2P だと思う。100V、60W型(54W)、E17口金35mm径、ではなかろうか。パッケージがそんな感じの見た目だし。今回使ったのはホワイトだけど、クリアの製品もあるらしい。型番は LDS100V54W・C・K になる模様。

考えてみたらLED電球に交換するのもアリだったのかもしれない。ググったところ、E17口金、60W相当、密閉器具対応のLED電球は既にあるようだし。LED電球にすれば電球色以外にも昼白色や昼光色を選べるので、浴室内の雰囲気が結構変わるかもしれない。ただ、まだ値段が高目ではあるけれど…。

以上、1 日分です。

過去ログ表示

Prev - 2020/11 -
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

カテゴリで表示

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


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

Powered by hns-2.19.6, HyperNikkiSystem Project