【Godot】エディタ拡張で簡易CSVエディタを実装する方法

今回はエディタ拡張(Godot Engineのエディタに直接機能を追加)に簡易CSVエディタを追加する方法について解説します。

エディタ拡張にCSVエディタを実装する方法

プラグインを新規作成する

プラグインを新規作成するには、メニューから「プロジェクト > プロジェクト設定」を選択します。

プロジェクト設定から「プラグイン」タブを選び「作成」ボタンをクリックします。

プラグイン作成ダイアログが表示されるので以下のように入力します。

  • プラグイン名: SimpleCsvEditor
  • サブフォルダー: SimpleCsvEditor
  • 説明: なし
  • 作者: なし
  • バージョン: なし
  • 言語: GDScript
  • スクリプト名: SimpleCsvEditor.gd
  • 今すぐアクティブ化?: チェックを入れる

プラグイン名とサブフォルダーに「SimpleCsvEditor」、スクリプト名に「SimpleCsvEditor.gd」と指定して「作成」ボタンを押します。

すると「ファイルシステム」に「addons」というフォルダが作られ、その階層の下に「SimpleCsvEditor」と「SimpleCsvEditor.gd」が作成されます。

tool # エディタ上で動作するツールの宣言
extends EditorPlugin # プラグインクラスを使うために必要

# プラグインが有効化されたときの処理
func _enter_tree() -> void:
	pass

# プラグインが無効化されたときの処理
func _exit_tree() -> void:
	pass

今回はエディタ下部にあるパネルに追加するプラグインを作成しますが、下記の公式ドキュメントではカスタムノードの作成や、左右のパネルに追加する方法が書かれているので興味があれば参考にしてよいと思います。

ただこの時点では何もしないので、「SimpleCsvEditor.gd」は編集せずにしておきます。

ちなみに「プロジェクト設定」を開き、プラグインの設定を開くと、今回作成した「SimpleCsvEditor」が追加されています。

ここのステータスの「有効」にチェックが入っていないと正常に動作しないので、もしチェックが入っていない場合は、チェックを入れておいてください。

CSVエディタの作成

CSVエディタのシーンを作成する

今回実装するCSVエディタのシーンを追加します。「SimpleCsvEditor」フォルダを右クリックして新規シーンを選びます。

CsvEditor」という名前で作成を行います。

続けて「+その他のノード」を選びます。

ノードの型は「VBoxContainer」で作成します。

作成したノード名を「CsvEditor」にリネームしておきます。

Windowsであれば「Ctrl+S」、macOSであれば「Cmd+S」で保存します。

すると、ファイルシステムに「CsvEditor.tscn」(シーンファイル)が作成されています。

ロードとセーブUIの追加

ロードとセーブUIを追加します。

ただボタン周りのUIを整列させたいので、まずは「HBoxContainer」を追加します。

続けて HBoxContainer の下に「Button」「VSeparator」「Button」の順で作成を行います。

そして Button ノードを以下のようにリネームします。

  • 最初のButtonをButtonLoad」にリネーム
  • Button2をButtonSave」にリネーム

ButtonLoadの Text を「LOAD」、ButtonSaveの Text を「SAVE」と設定します。

CSVデータの表示・編集用のUIノードを追加

次にCSVデータの表示・編集用UIノードを追加します。

ルートノードに直接ぶら下がるようにしたいので、「CsvEditor」ノードを右クリックして「+子ノードを追加」を選びます。

ノードの型は「GridContainer」を選んで作成します。

ロード・セーブの実装

ロード・セーブを実装するためにスクリプトを追加します。「CsvEditor」ノードを選択してスクリプトをアタッチします。

extends VBoxContainer

# LineEdit(入力テキストボックス)にはルートノードのテーマが適用されます

# CSVファイルのパス.
export var path_csv: String

onready var btn_load = $HBoxContainer/ButtonLoad
onready var btn_save = $HBoxContainer/ButtonSave
onready var grid = $GridContainer

func _ready():
	pass

func _load():
	# CSVファイルから読み込んでテキストボックスに反映する
	for child in grid.get_children():
		child.queue_free()
	
	# ファイルオブジェクト作成
	var f = File.new()

	# "data.txt" ファイルを開く
	f.open(path_csv, File.READ)

	# CSVを1行ずつ読み込む
	var data = []
	var max_line = 0
	while f.eof_reached() == false: # ファイルの終端チェック
		var line = f.get_csv_line()
		if line[0] == "":
			continue # 空行を読み飛ばす

		max_line = max(max_line, line.size())
		data.append(line)
	
	grid.columns = max_line
	for line in data:
		for i in range(max_line):
			var edit = LineEdit.new()
			# テーマファイルはルートノードに設定されているものを使う
			edit.theme = theme
			
			if i < line.size():
				edit.text = line[i]
			grid.add_child(edit)

	# ファイルを閉じる
	f.close()
	
func _save():
	# 編集した値をCSVファイルへ保存する
	# ファイルオブジェクト作成
	var f = File.new()
	
	f.open(path_csv, File.WRITE)
	
	var idx = 0
	var line = ""
	for child in grid.get_children():
		if line == "":
			# 最初の列
			line += child.text
		else:
			line += "," + child.text
		idx += 1
		if idx%grid.columns == 0:
			# 改行
			f.store_string(line + "\n")
			line = ""
	f.store_string(line)
	
	f.close()	

“_load()” はロード処理で、CSVを読み込んで “GridContainer” に “LineEdit” を追加します。

“_save()” はセーブ処理で、”GridContainer” にある “LineEdit” の情報を CSVファイルに出力する処理となります。

テストデータの追加

読み書きする CSVファイルが必要となるので、以下のテキストファイルを作成して「data.txt」として保存します。

id,name,hp,mp,str,vit
1,hero,100,20,10,5
2,slime,5,0,3,1

RPGを想定したパラメータ定義ファイルとなります。

「.txt」ファイルはファイルシステムから追加できないことがあるので、エクスプローラー (macOSの場合はFinder)でプロジェクトがあるフォルダに直接追加します。

CSVファイルのパスはインスペクタから指定します。

Script Variables > Path Csv」に「res://data.txt」と入力します。

Buttonノードにシグナルを追加する

ButtonLoad ノードに pressed” シグナルを追加します。シグナル関数は以下のように記述します。

func _on_ButtonLoad_pressed():
	print("load")
	_load()

ButtonSave ノードにpressed” シグナルを追加します。シグナル関数は以下のように記述します。

func _on_ButtonSave_pressed():
	_save()
	print("save")

動作確認

F6(macOSはCmd+R)でシーン実行して CSV ファイルを読み書きできることを確認します。

“LOAD” ボタンを押すと “data.txt” の内容が読み込まれ、”SAVE”ボタンで編集した内容が “data.txt” に書き込まれればOKです。

これでCSVエディタのUIは実装完了となります。

拡張エディタに反映させる

“SimpleCsvEditor.gd” を開いて以下のように記述します。

tool
extends EditorPlugin

const CsvEditor = preload("res://addons/SimpleCsvEditor/CsvEditor.tscn")

# CsvEditorのインスタンス
var _csv_editor

func _enter_tree():
	# インスタンスを生成
	_csv_editor = CsvEditor.instance()
	
	# 下のパネルに追加する
	add_control_to_bottom_panel(_csv_editor, "CSVエディタ")

func _exit_tree():
	# パネルから削除
	remove_control_from_bottom_panel(_csv_editor)
	
	# インスタンスも消しておく
	_csv_editor.queue_free()

このシーンをエディタの下のパネルに追加するコードです。

エディタに反映させる

エディタに反映させるためにはプロジェクトの再起動が必要となります。メニューから「プロジェクト > 現在のプロジェクトをリロード」を選びます。

プロジェクトをリロードすると、画面下のパネルに「CSVエディタ」という項目が追加されます。これをクリックすると「LOAD」「SAVE」ボタンが表示されます。

ただ、ボタンを押しても反応がありません。これはスクリプトがツール用になっていないためです。

CsvEditor.gd を開いて、最初の行に「tool」という宣言を記述します。

tool # エディタで動作させるために必要
extends VBoxContainer

# LineEdit(入力テキストボックス)にはルートノードのテーマが適用されます

...

これでスクリプトが有効になったのですが、”onready” で読み込まれる 各種インスタンスが存在せずにエラーとなります。

load
 res://addons/SimpleCsvEditor/CsvEditor.gd:18 - Invalid call. Nonexistent function 'get_children' in base 'Nil'.

なのでもう一度だけプロジェクトをリロードします。

では再び「CSVエディタ」をクリックして動作確認をします。

完成プロジェクトファイル

今回の記事で作成したプロジェクトファイルを添付しておきます。

参考

今回は下部分のパネルに拡張機能を追加する方法を紹介しましたが、以下の記事はインスペクタを拡張する方法についての参考記事となります。

また公式ドキュメントでは、カスタムノードや左右のドックにパネルを追加する方法についての記事があります。