Open21

【Godot Engine】メモ・解決したこと【GDScript】

まくらけっとまくらけっと

テキストファイルの内容をPoolStringArrayに格納する

Godot Engine 3.4.4
Godot Engine 4.0.3

Pythonでいうところのf.readlines()に相当するものが欲しかったけどなさそう。
whileで1行ずつeofまで読み込んでいく必要があるみたい。

sample1.txt
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]

参考:
https://docs.godotengine.org/ja/stable/classes/class_file.html#class-file-method-eof-reached

まくらけっとまくらけっと

:= という表記について

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

参考:
https://docs.godotengine.org/ja/stable/tutorials/scripting/gdscript/static_typing.html

まくらけっとまくらけっと

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)
脚注
  1. connectメソッドについてはこちら ↩︎

まくらけっとまくらけっと

Godot Engine 3.5.1

HTML5にエクスポートしたゲームの画面サイズがおかしいときに見直すところ
エクスポート→Canvas Resize Policy : Adaptive
プロジェクト設定→ウィンドウ→
モード : 2d(あるいはviewport)
アスペクト : keep

まくらけっとまくらけっと

Godot Engine 3.5.1
Godot Engine 4.0.3

シグナルは自動読み込みで管理すると便利。

SignalManager.gdは自動読み込みに設定してグローバル変数を有効にしておく。

SignalManager.gd
extends Node

signal example1
シーン構成
Emitter
|--Receiver
Emitter.gd
extends Node

func _ready():
	#SignalManager.emit_signal("example1") # Godot 3.x
	SignalManager.example1.emit() # Godot 4.x
Receiver.gd
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

※以前に記事でまとめた内容を一部編集してコードスニペットとしてこちらに移行。

Godot3
var window = JavaScript.get_interface("window")
if window:
	yield(get_tree().create_timer(3.0), "timeout")
	window.alert("Hello Godot Engine 3")
Godot4
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://などから画像や音源を読み込む場合について。

load_external_res.gd
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で読み込む分には問題無いです)

参考:
https://github.com/godotengine/godot-docs/issues/2148
https://www.reddit.com/r/godot/comments/1095a0g/custom_ogg_streams_in_godot_4/

脚注
  1. https://github.com/godotengine/godot/issues/61091 ↩︎

まくらけっとまくらけっと

Godot Engine 4.0.3

テキストファイルをエクスポートする

res://内の.txtファイルはリリースビルドではエクスポートされないので注意。
必要であればエクスポート時のオプション「リソース以外のファイル/フォルダーをエクスポートするためのフィルター」で*.txtを明示的に指定する必要あり。

※拡張子が.csvのものはローカライズ用のファイルとして扱われるためか、指定してもリリースビルドでは読み込めません。なのでその意図がない場合は.txtなどに変更しましょう。

まくらけっとまくらけっと

Godot Engine 4.0.3

タイルにアニメーションを実装する

ちょっとハマったのでメモ。

タイルセットのセットアップからAnimatedTextureを追加して各フレームにテクスチャを追加するやり方だとうまくいきません。

代わりにアニメーション用のスプライトシートを用意する必要があります。

  1. スプライトシートをタイルセットに追加
  2. 「アトラスにタイルを自動的に作成しますか?」→「いいえ」
  3. ベースタイルのとこからスプライトシートの先頭のタイル(=1フレーム目)のみ右クリック→タイルの作成
  4. 選択メニューからアニメーションのColumnsにフレーム数を指定
  5. Framesに要素を追加

2で「はい」を選択すると3で全てのタイルが作成されている状態になりますが、この状態だとFramesに要素を追加することができないので注意してください。

参考:
https://www.reddit.com/r/godot/comments/ylcpqb/animatedtexture_in_tilemaps_godot_40beta4

まくらけっとまくらけっと

Godot Engine 4.0.3

外部の翻訳ファイル(csv)を読み込む

外部(res://外)の翻訳ファイルを読み込む必要がある場合
Translationオブジェクトを生成してからTranslationServerに追加すればOKです。

res://内に翻訳ファイルを追加したときにエディタ上でやっていたのと同じことをコードで
実装するわけです。

external_trans.csv
_,en,ja
MOB1,Zombie,ゾンビ
MOB2,Skeleton,スケルトン
MOB3,Spider,クモ
load_external_translation
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)

※エラー処理などは適宜行ってください

参考:
https://www.reddit.com/r/godot/comments/s0hcep/importing_csv_translation_in_runtime
https://docs.godotengine.org/ja/stable/classes/class_translation.html
https://docs.godotengine.org/ja/stable/classes/class_translationserver.html

まくらけっとまくらけっと

Godot Engine 4.2.0

ラジオボタンを実装する

ラジオボタン専用のノードはありませんが、簡単に実装できます

  1. CheckBoxノードを追加
  2. BaseButton -> Button Groupに新規 Button Group(リソース)を追加
  3. 他の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 Maskclick_mask.pngを設定します。

あとはtexture_normaltexture_normal.pngを設定すれば完了です。
透過部分に判定が無いことを確認してみてください。

まくらけっとまくらけっと

Godot Engine 4.2.0

res://内にとあるファイルが存在するかどうかの確認は
FileAccess.file_existsResourceLoader.existsを使い分けよう。

リソースとしてインポートされる画像や音楽などのファイルはResourceLoader.exists
その他のファイルはFileAccess.file_existsで判定するのが良さそう。

というのも公式ドキュメント曰く「エクスポートバージョンでは.pngなどの素材ファイルそのものはゲームに含まれておらず.import形式のものが利用される」から。[1]

.txtなどのリソースではないものはResourceLoader.existsでチェックできませんし、そもそもデフォルトのオプションではエクスポート自体されないので注意。[2]

強引ですがリソースとしてインポートされるものであればFileAccess.file_exists("res://img1.png.import")みたく書いちゃえば動きます。

ちなみにプロジェクトアイコンとして設定されている画像はそのままでもエクスポートされているようです。

脚注
  1. https://docs.godotengine.org/ja/4.x/classes/class_fileaccess.html#class-fileaccess-method-file-exists ↩︎

  2. https://zenn.dev/link/comments/252a43567a3a43 ↩︎

まくらけっとまくらけっと

Godot Engine 4.2.2

ui_text_から始まる組み込みのインプットマップを除去すると
文字の入力を受け付けるノード(Line/Text/Code)Editの動作に支障をきたすので注意。

例えばLineEditのシグナルtext_submittedに関してはui_text_submitに登録されているキーに依存するのでそれらを除去するとシグナルが発行されません。

また、focus_modeNoneにすると入力自体受け付けなくなります(そりゃそう)。

自然な仕様ではありますが、組み込みのインプットマップを除去するときは気をつけましょう。

まくらけっとまくらけっと

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))と書いても
接続時点の値(空文字列)がバインドされるので意味はない。

参考:
https://docs.godotengine.org/ja/4.x/classes/class_callable.html#class-callable-method-unbind

まくらけっとまくらけっと

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"

エディタのヘルプから確認できるライセンステキストと、内容は同じになっているはず...。