【Godot Engine】メモ・解決したこと【GDScript】
テキストファイルの内容をPoolStringArrayに格納する
Godot Engine 3.4.4
Godot Engine 4.0.3
Pythonでいうところのf.readlines()に相当するものが欲しかったけどなさそう。
whileで1行ずつeofまで読み込んでいく必要があるみたい。
Hello
Godot
Engine
Godot 3.x
func readlines() -> PoolStringArray:
	var txt = File.new()
	var txt_pool = PoolStringArray()
	
	txt.open("res://sample1.txt", txt.READ)
	while txt.get_position() < txt.get_len():
		txt_pool.append(txt.get_line())
	txt.close()
	
	return txt_pool
func _ready():
	print(readlines())
Godot 4.x
func readlines() -> PackedStringArray:
	var txt = FileAccess.open("res://sample1.txt", FileAccess.READ)
	var txt_packed = PackedStringArray()
	while txt.get_position() < txt.get_length():
		txt_packed.append(txt.get_line())
	txt.close()
	
	return txt_packed
func _ready():
	print(readlines())
↓
[Hello, Godot, Engine]
参考:
:= という表記について
Godot Engine 3.4.4
Pythonのセイウチ演算子(Walrus Operator)とは異なる。
var num := 10
は
var num: int = 10
と同じ。
静的に型を付けることで異なる型の値を代入しようとすると
エラーになるので代入ミスを防ぐのに役立つ。
int型に小数を代入した場合はエラーにはならないけれど、端数は切り捨てられるので注意。
func _ready():
	var num: int = 10
	num += 0.1
	print(num) # 10
	
	var num2 = 10
	num2 += 0.1
	print(num2) # 10.1
参考:
Godot Engine 3.5
Godot Engine 4.0.3
コードでシグナルを扱う
Godot 3.x
接続元ノード.connect("シグナル名", 接続先ノード, 呼び出したい関数, [引数1,引数2,...], flags=0)
自身のノードはselfで参照可能。
flagsを指定するとワンショットにさせたり遅延させたりできる。[1]
signalキーワードは
発信元ノード.emit_signal("シグナル名", 引数1, 引数2, ...)
とセットで使う。emit_signalの方は引数をArrayでまとめて渡すのではなく一つずつ渡す。
extends Node
signal test
func _ready() -> void:
	connect("test",self,"greet")
	some_func()
	
func some_func() -> void:
	emit_signal("test","Hello Godot 3!")
	print("Signal emitted!")
	
func greet(greeting: String) -> void:
	print(greeting)
Godot 4.x
シグナルはクラスになり、関数の指定方法が文字列からCallableに変わるなどいった変更が
加わりました。
Godot 3でのやり方も残されていますが、関数はCallableとして指定し、接続先ノードの指定は無くなりました。
connect時にコールバック関数の引数を指定する場合はCallableのbindメソッドを使います。
extends Node
signal test
func _ready() -> void:
	test.connect(greet)
	some_func()
	
func some_func() -> void:
	test.emit("Hello Godot 4!") 
	print("Signal emitted")
	
func greet(greeting: String) -> void:
	print(greeting)
Godot Engine 3.5.1
メモ。
翻訳機能を使う際に.csvファイルに空行があるとうまく動かないので注意。
Godot Engine 3.5.1
HTML5にエクスポートしたゲームの画面サイズがおかしいときに見直すところ
エクスポート→Canvas Resize Policy : Adaptive
プロジェクト設定→ウィンドウ→
モード : 2d(あるいはviewport)
アスペクト : keep
Godot Engine 3.5.1
辞書型はデータの追加順で順序を保持している。
JSONから読み込んだ場合は保証されない。
printで中身を表示するとアルファベット順でキーが並ぶがiterateする分には問題無し。
Godot Engine 3.5.1
Godot Engine 4.0.3
シグナルは自動読み込みで管理すると便利。
SignalManager.gdは自動読み込みに設定してグローバル変数を有効にしておく。
extends Node
signal example1
Emitter
|--Receiver
extends Node
func _ready():
	#SignalManager.emit_signal("example1") # Godot 3.x
	SignalManager.example1.emit() # Godot 4.x
extends Node
func _ready() -> void:
	#SignalManager.connect("example1",self,"foo") # Godot 3.x
	SignalManager.example1.connect(foo) # Godot 4.x
	
func foo() -> void:
	print("Signal emitted!")
Signal emitted!
Godot Engine 3.5.2
ブラウザゲームでセーブ機能を実現するにはuser://上でセーブファイルの読み書きをすればOK。
ブラウザ上なら自動的にIndexedDB上に保存されるはず。
JavaScriptの呼び出しと待機処理
Godot Engine 3.5.2
Godot Engine 4.0.2
※以前に記事でまとめた内容を一部編集してコードスニペットとしてこちらに移行。
var window = JavaScript.get_interface("window")
if window:
	yield(get_tree().create_timer(3.0), "timeout")
	window.alert("Hello Godot Engine 3")
var window = JavaScriptBridge.get_interface("window")
if window:
	await get_tree().create_timer(4.0).timeout
	window.alert("Hello Godot Engine 4")
Godot Engine 4.0.3
外部のリソースを読み込む
load関数が読み込めるのはres://内のものに限るのでuser://などから画像や音源を読み込む場合について。
func load_external_texture(path: String) -> Texture2D:
	var image := Image.new()
	image.load(path)
	return ImageTexture.create_from_image(image)
func load_external_sound(path: String) -> AudioStreamMP3:
	var file := FileAccess.open(path, FileAccess.READ)
	var audio := AudioStreamMP3.new()
	audio.data = file.get_buffer(file.get_length())
	file.close()
	
	return audio
似た方法で.oggも読み込もうとしたものの、どうやら[1]によると4系だとv4.0.3時点では
読み込めないようです。(loadで読み込む分には問題無いです)
参考:
Godot Engine 4.0.3
テキストファイルをエクスポートする
res://内の.txtファイルはリリースビルドではエクスポートされないので注意。
必要であればエクスポート時のオプション「リソース以外のファイル/フォルダーをエクスポートするためのフィルター」で*.txtを明示的に指定する必要あり。
※拡張子が.csvのものはローカライズ用のファイルとして扱われるためか、指定してもリリースビルドでは読み込めません。なのでその意図がない場合は.txtなどに変更しましょう。
Godot Engine 4.0.3
タイルにアニメーションを実装する
ちょっとハマったのでメモ。
タイルセットのセットアップからAnimatedTextureを追加して各フレームにテクスチャを追加するやり方だとうまくいきません。
代わりにアニメーション用のスプライトシートを用意する必要があります。
- スプライトシートをタイルセットに追加
 - 「アトラスにタイルを自動的に作成しますか?」→「いいえ」
 - ベースタイルのとこからスプライトシートの先頭のタイル(=1フレーム目)のみ右クリック→タイルの作成
 - 選択メニューからアニメーションのColumnsにフレーム数を指定
 - Framesに要素を追加
 
2で「はい」を選択すると3で全てのタイルが作成されている状態になりますが、この状態だとFramesに要素を追加することができないので注意してください。
参考:
Godot Engine 4.0.3
外部の翻訳ファイル(csv)を読み込む
外部(res://外)の翻訳ファイルを読み込む必要がある場合
Translationオブジェクトを生成してからTranslationServerに追加すればOKです。
res://内に翻訳ファイルを追加したときにエディタ上でやっていたのと同じことをコードで
実装するわけです。
_,en,ja
MOB1,Zombie,ゾンビ
MOB2,Skeleton,スケルトン
MOB3,Spider,クモ
func load_external_translation() -> void:
	var file := FileAccess.open("user://external_trans.csv", FileAccess.READ)
	var file_content: Array[PackedStringArray]
	file_content = []
	
	while file.get_position() < file.get_length():
		file_content.append(file.get_csv_line())
	file.close()
	
	var locales := file_content[0] as PackedStringArray
	for locale_idx in len(locales)-1:
		var translation := Translation.new()
		translation.locale = locales[locale_idx+1]
		
		var message_lines := file_content.slice(1)
		for message_line_idx in len(message_lines):
			var key := message_lines[message_line_idx][0] as String
			var translated := message_lines[message_line_idx][locale_idx+1].c_unescape() as String
			translation.add_message(key, translated)
		
		TranslationServer.add_translation(translation)
※エラー処理などは適宜行ってください
参考:
Godot Engine 4.2.0
ラジオボタンを実装する
ラジオボタン専用のノードはありませんが、簡単に実装できます
- 
CheckBoxノードを追加 - 
BaseButton->Button Groupに新規 Button Group(リソース)を追加 - 他の
CheckBoxにも先程と同じButton Groupをコピー&貼り付けでグループ化完了 
複数のグループが必要なら、グループ毎にButton Groupに名前を付けて保存する方が望ましいですね
Godot Engine 4.2.0
Grow Directionを設定する(4.x)
layout_directionを変更するだけで事足りることが多いと思いますが一応・・・。
3.xでは目立つ所にありましたが、4.xからは変更されています
次のようにプロパティを辿る必要があります
Control -> Layout Mode = Anchors -> Anchor Preset = カスタム -> Grow Direction
特にLabelノードでは重宝しますね
Godot Engine 4.2.0
任意の形状のボタンを作る
TextureButtonにtexture_normalをセットするだけではボタンの形状はsizeと同じになるので透過部分もクリック判定を持ちます。
透過部分をマスクするには次の通りです。
まずtexture_normal用のテクスチャとclick_mask用のテクスチャをpng形式で用意します。同じもので問題ありません。ここではそれぞれをtexture_normal.png, click_mask.pngとします。
次にそれらをres://に追加後, click_mask.pngを選択したらインポートタブの「名前を付けてインポート」からBitMapを選び、作成元をAlphaにして再インポートをクリックします。
これでclick_mask.pngをBitMapリソースとして設定できるようになりました。
TextureButton -> Textures -> Click Maskにclick_mask.pngを設定します。
あとはtexture_normalにtexture_normal.pngを設定すれば完了です。
透過部分に判定が無いことを確認してみてください。
Godot Engine 4.2.0
res://内にとあるファイルが存在するかどうかの確認は
FileAccess.file_existsとResourceLoader.existsを使い分けよう。
リソースとしてインポートされる画像や音楽などのファイルはResourceLoader.exists
その他のファイルはFileAccess.file_existsで判定するのが良さそう。
というのも公式ドキュメント曰く「エクスポートバージョンでは.pngなどの素材ファイルそのものはゲームに含まれておらず.import形式のものが利用される」から。[1]
.txtなどのリソースではないものはResourceLoader.existsでチェックできませんし、そもそもデフォルトのオプションではエクスポート自体されないので注意。[2]
強引ですがリソースとしてインポートされるものであればFileAccess.file_exists("res://img1.png.import")みたく書いちゃえば動きます。
ちなみにプロジェクトアイコンとして設定されている画像はそのままでもエクスポートされているようです。
Godot Engine 4.2.2
ui_text_から始まる組み込みのインプットマップを除去すると
文字の入力を受け付けるノード(Line/Text/Code)Editの動作に支障をきたすので注意。
例えばLineEditのシグナルtext_submittedに関してはui_text_submitに登録されているキーに依存するのでそれらを除去するとシグナルが発行されません。
また、focus_modeをNoneにすると入力自体受け付けなくなります(そりゃそう)。
自然な仕様ではありますが、組み込みのインプットマップを除去するときは気をつけましょう。
Godot Engine 4.2.2
ダブルクリックを検知できるようにする
Controlを継承したノードであれば以下のコードでOKです。
extends Button
signal double_clicked
func _ready() -> void:
	gui_input.connect(_on_gui_input)
func _on_gui_input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.double_click:
		double_clicked.emit()
		print("double clicked!")
Godot Engine 4.2.2
 Callable.unbindの活用例
次のようなシーンがあるとする。
Control:
    LineEdit
    Button
そしてButtonがクリックされたとき、あるいはEnterキーが押されたときに
入力されたテキストをもとに何か処理をしたい、という場合を想定する。
text_submittedは入力されたテキストを接続先の関数にバインドするのでそれをunbindする例。
extends Control
func _ready() -> void:
	$Button.pressed.connect(foo)
	$LineEdit.text_submitted.connect(foo.unbind(1))
func foo() -> void:
	print($LineEdit.text)
※unbindする代わりに関数を修正して$Button.pressed.connect(foo.bind($LineEdit.text))と書いても
接続時点の値(空文字列)がバインドされるので意味はない。
参考:
Godot Engine 4.3
ライセンステキストを生成する
以前までは
ライセンスの遵守
にはGodot自体のライセンス表記に加えてGodotが使用する一部のサードパーティライブラリ(FreeType, mbedTLS, ENet)については別途ライセンス表記が必要であると記載されていましたが、現在ではその記載は無くなっています。
代わりにサードパーティのライセンス表記については
COPYRIGHT.txt
をゲームと一緒に同包するのがよいとのこと。
ではブラウザ版のゲームなどではどう対応するのがいいのかというと、Engineシングルトンから直接ライセンス情報を取得することができるので、これで対応してみることに。
バージョンが変わっても正しく取得できるらしいので便利ですね。
RichTextLabelに追記するサンプルコードは以下の通り。テキストは適宜整形。
func add_credit_text() -> void:
	$RichTextLabel.text += "\n----------------------------------------\n"
	var copyright_info := Engine.get_copyright_info()
	var license_info := Engine.get_license_info()
	
	for info: Dictionary in copyright_info:
		for part: Dictionary in info["parts"]:
			$RichTextLabel.text += "%s: %s, (C) %s\n"%[info["name"], part["license"], part["copyright"]]
			
	$RichTextLabel.text += "\n------------------------------------------------------------------------------------\n\n"
	
	for license_name: String in license_info:
		$RichTextLabel.text += "%s: \n%s\n"%[license_name, license_info[license_name]]
		$RichTextLabel.text += "------------------------------------------------------------------------------------\n"
エディタのヘルプから確認できるライセンステキストと、内容は同じになっているはず...。
Godot Engine 4.3
 ユニークノードをget_node()で取得するときの注意点
ユニークノードを表す記号が%なのでフォーマット指定子を使うときはエスケープすること。
var unique_node_name1 := "UniqueNodeName1"
var unique_node1 := get_node("%{0}".format([unique_node_name1]))
var unique_node_name2 := "UniqueNodeName2"
var unique_node2 := get_node("%%%s"%unique_node_name2)
var node_num := 3
var unique_node_name3 := "%%UniqueNodeName%s"%node_num
var unique_node3 := get_node(unique_node_name3)
Godot Engine 4.3
インスタンス化するときシーンに値を渡す
インスタンス化してからプロパティを設定する関数を呼び出したりなど方法はありましたが、
この方法は読みやすくてかなり良さそうです。
参考元動画:
func _ready() -> void:
	var enemy: Enemy = Enemy.new_enemy(100, 10, "スライム")
	add_child(enemy)
Node2D
    ┠╴Sprite2D
    ┖╴NameLabel
class_name Enemy
extends Node2D
const ENEMY_SCENE: PackedScene = preload("res://enemy.tscn")
@export var name_label: Label
var health: int
var damage: int
static func new_enemy(new_health: int, new_damage: int, new_name: String) -> Enemy:
	var enemy: Enemy = ENEMY_SCENE.instantiate()
	
	enemy.health = new_health
	enemy.damage = new_damage
	enemy.name_label.text = new_name
	
	return enemy