ぬーぶのメモ帳

フリーゲームエンジン「Godot」でプログラム経験のない素人の備忘録 最近は色々な動作を考えて試すのが好き。

【Godot】ゲーム内のキーコンフィングやデータ保存に挑戦

ゲームでは当たり前の機能



見出しの通りゲームで当たり前になっているキー設定の変更、
当たり前でも実装しようとすると思った以上に大変なんて事がよくあります。

とは言えどう作るか疑問に思ってしまうと調べたくなる性分、幸いにも今回は動画が見つかりました。
コードは以下の動画を参考にして作成しました。


キーコンフィングの解説動画
www.youtube.com


ファイルの保存方法(リソース)
www.youtube.com


今回は操作キーの設定切り替えとそのデータのファイル保存に挑戦してみました。
良ければお付き合いください。



今回は機能を作るだけなので出来るだけ簡素に仕上げるつもりです。

構成ノードとアクションボタンの設定

まずは必要なノードを作成
 親になるキャンバスレイヤー
 現在押してるキーを表示するラベル
 ボタン表示と押したキーが一致しているかの判定ラベル
 現在のキー表示、ボタンをクリックでキー設定になるボタン

ボタンノードは別のシーンで作ることで複数での運用が考えられています。



キー入力の登録方法をご存じの方は読み飛ばして大丈夫です
実験用のアクションキーを登録したいと思います。
「プロジェクト → プロジェクト設定 → インプットマップ」まで開きます。

任意のアクション名をつけて追加をクリック

項目の一番下にアクション名が追加される

次に追加したアクションに対応するキーを登録します。
アクション名の右はじに「+」があるのでクリック、キーボード入力のキーを選択

キーを入力するように指示がでるので任意のキーを登録、今回はキーボードの A を使用。

OK を押すとアクション名の下に登録キーが追加される。

以上で入力アクションの登録ができました。
アクション登録することでジャンプ、攻撃、ショット、のような分かりやすい名前で、自分の理想とするボタン配置などもデザインすることが可能になります。



リソース作成

自分も全てを理解できていませんが GDScript で Resource を定義することでカスタムリソースを作成することができるようです。
定義する事で様々データを格納、やり取りするのに便利であるのは確かなようです。

そして最初に紹介した動画の一つでそのカスタムリソースを使いデータを保存する方法が解説されています。
またこちらの動画もカスタムリソースへの理解が進むと思います。
www.youtube.com



リソース用のコードになります

extends Resource
class_name key_option

export var key_dic := {
	"test": 65,
}

これ自体は Dictionary型上で登録したボタンのアクション名と キーボードのキーリストに対応した数字のシンプルなものになります。
なおこのスクリプトカスタムリソースの素で、別途リソースを作成する必要があります。


キーボード等の入力機器にはカテゴリー内で数値を返す事でどのキーを押したか判断しています。
例えばキーボードのAキーに対応した数字は65 となります。

仮に別のキーも設定に追加したくなったら、"アクション名"と対応するキー番号 を追加すれば対応できます。


入力キーのカテゴリと対応した数値の一覧がこちらになるので参考にしてください。
KeyList の項目がキーボードの各対応表になっています。
docs.godotengine.org



ではリソースを作成します。
ファイルシステム上で右クリックして新規リソースを選択

一覧の一番下に定義したカスタムリソースがあると思います

選択したら任意の名前を付けて 2つ 作成します。
デフォルト用のキーリソース と キー変更保存用のリソース の2つになります。

リソースファイルは拡張子が「tres」になっている


ボタンノード作成

ボタンノードは別シーンで作成して量産を視野に入れます。

今回は Enabled Focus の項目を None に設定します。
こうすることでマウスでボタンをクリックした際、ボタンノードに対象が残らないようにします。
対象が残り続けると、該当ボタンノードで決定キーの登録をすると「キーの変更→キーの登録開始」のループが起こります。

またこの方法の問題点はマウスだけでスイッチが起動するので、コントローラーやキーボードで操作を完結させたい場合ボタンノードにフォーカスを向けさせるなど他の方法を模索する必要があります。


今回のコード

extends Button

# ボタンを複製した時 別のアクション名を入力出来るように
export(String) var action_name := ""

# これが true の時にキー変更ができる
var do_set := false

# デフォルトのキーデータ と キー変更の保存先 2種のリソース
var default_key := preload("res://data/default_key.tres")
var customized_key := preload("res://data/customized_key.tres")

# 画面上のラベル2種
onready var push_button: Label = $"../push_button"
onready var return_status: Label = $"../return_status"



func _ready() -> void:
#	print("def: ", default_key.key_dic)
#	print("cus: ", customized_key.key_dic)
	key_load_test()


# ボタンを押すとキー登録が true に
func _pressed() -> void:
	text = "any key"
	do_set = true


func _input(event: InputEvent) -> void:
	# do_set が true になると最優先で処理されるので他のインプットに行かなくなる
	if do_set:
		if event is InputEventKey:
			# デフォルトと自分が押したキーを格納する変数
			var def_key = InputEventKey.new()
			var cus_key = InputEventKey.new()
			
			# 作成したイベントキーにデフォルトと入力キーの数値を入れる
			def_key.scancode = int(default_key.key_dic[action_name])
			cus_key.scancode = int(event.scancode)
			
			# インプットキーから登録しているアクションを認識、削除と追加
			InputMap.action_erase_event(action_name, def_key)
			InputMap.action_add_event(action_name, cus_key)
			
			text = OS.get_scancode_string(cus_key.scancode)
			
			# 変更したキーを resource ファイルに保存
			key_save_test(action_name, cus_key.scancode)

			# 設定が終わったらインプット関数が機能するように戻す
			do_set = false
		return
	
	
	if event is InputEventKey:
		# 上のラベルが押したキーを表示する
		push_button.text = OS.get_scancode_string(event.scancode)
		# 設定したキーでアクションが実行されれば true 
		if event.is_action("test"):
			return_status.text = str(event.is_action("test"))
		else:
			return_status.text = str(event.is_action("test"))
			

func key_load_test()-> void:
	var def_key = InputEventKey.new()
	var cus_key = InputEventKey.new()
	
	def_key.scancode = int(default_key.key_dic[action_name])
	cus_key.scancode = int(customized_key.key_dic[action_name])
	
	InputMap.action_erase_event(action_name, def_key)
	InputMap.action_add_event(action_name, cus_key)
	
	text = OS.get_scancode_string(cus_key.scancode)


func key_save_test(act_name:String, key_num:int)-> void:
	customized_key.key_dic[act_name] = key_num
	ResourceSaver.save("res://data/customized_key.tres", customized_key)

実行したものがこちらになります

画面上では分かりにくいかもしれませんがキーボードの色々な所を押しています。
左のボタンノードに登録されている「A」以外のキーを押すと false と表示されています。

	if event is InputEventKey:
		# 上のラベルが押したキーを表示する
		push_button.text = OS.get_scancode_string(event.scancode)
		# 設定したキーでアクションが実行されれば true 
		if event.is_action("test"):
			return_status.text = str(event.is_action("test"))
		else:
			return_status.text = str(event.is_action("test"))


その後ボタンをクリックをすると「any key」とキー設定を受け付けるモードになります。

func _pressed() -> void:
	text = "any key"
	do_set = true


次に任意のキーを押します 今回は「K」キーを登録しました。
登録する時に前のキーを消しておきます、消さなかった場合 "test"アクション に対応キーが残り続けるので反応するキーが増え続けてしまいます。
(今回で言うと「A」と「K」の両方が True 判定になってしまう)

		# デフォルトと自分が押したキーを格納する変数
		var def_key = InputEventKey.new()
		var cus_key = InputEventKey.new()
			
		# 作成したイベントキーにデフォルトと入力キーの数値を入れる
		def_key.scancode = int(default_key.key_dic[action_name])
		cus_key.scancode = int(event.scancode)
			
		# インプットキーから登録しているアクションを認識、削除と追加
		InputMap.action_erase_event(action_name, def_key)
		InputMap.action_add_event(action_name, cus_key)


登録と同時にカスタム用のリソースを保存する命令を実行して "test"アクションに、Kキーの対応数字を書き換えています

func key_save_test(act_name:String, key_num:int)-> void:
	customized_key.key_dic[act_name] = key_num
	ResourceSaver.save("res://data/customized_key.tres", customized_key)

  ResourceSaver.save(PC上にセーブする場所, 保存するカスタムリソース)
このコマンドがリソースを保存する命令で指定したPCのアドレス上にデータがあれば上書きし、なければデータを作成します。
よくわからない場合は "res://リソースの名前.tres" でアドレス指定しておけば作成しているプロジェクトのフォルダに保存されるので変な所に保存されたり他データを上書きする心配はないと思います。


最後に再度実行すると _ready() 内でカスタム用リソースをロードし、初期ボタンが「A」から「K」に置き換わっています。

func _ready() -> void:
	key_load_test()


func key_load_test()-> void:
	var def_key = InputEventKey.new()
	var cus_key = InputEventKey.new()
	
	def_key.scancode = int(default_key.key_dic[action_name])
	cus_key.scancode = int(customized_key.key_dic[action_name])
	
	InputMap.action_erase_event(action_name, def_key)
	InputMap.action_add_event(action_name, cus_key)
	
	text = OS.get_scancode_string(cus_key.scancode)



まとめ
今回は一つのキーを登録し保存と読み込みを実装しました。
複数のキーやボタンで行う場合は繰り返し処理など改良が必要ですが一応機能するものになりました。

またデータ保存に関しても json ファイル以外の方式で比較的簡単に保存する方法が見つかった事もよかったです。
とは言えボタンノード一つに全ての機能を詰め込みすぎたので、専用の自動読み込みのスクリプトなどを作成した方がスマートだったかもしれません。

以上となります、ここまで読んでくださりありがとうございました。