How can I use a custom class as a dictionary key?

Godot Version

v4.6.2.stable.official [71f334935]

Question

I’ve created a class that I’d like to use as a key in dictionary. I want to use it as a lookup for hotkey keypresses. I can’t find any info on whether there needs to be any special handling for it - I know in languages like C++, you need to define a hash function for data structures used as keys.

Can I assume Godot will compare two keys based on their content, or do I have to do something extra like implementing things like hash and equals methods?

Eg, for this class:

@tool
extends Resource
class_name KeymapKeypress

@export var keycode:Key = KEY_NONE
@export var shift:bool = false
@export var ctrl:bool = false
@export var alt:bool = false
@export var meta:bool = false

Will this work (ie, print 6):

var map:Dictionary[KeymapKeypress, int]
var key1 = KeymapKeypress.new()
key1.keycode = KEY_A
var key2 = KeymapKeypress.new()
key2.keycode = KEY_A

map[key1] = 5
map[key2] = 6

print(map[key1])

Yes that works. Your example will print 5 though.

Theoretically Godot can hash by the memory address of any given object, I don’t think it is serializing the object as your two keys would produce the same serialized data, and that’s not what I would want from using my objects as keys anyways.

You can use objects as keys without any problems. Note that it’s not object’s value (data) that’s used as a key/hash but the reference itself, which will always be unique. Internally, Godot might in fact use pointers (i.e. object’s memory addresses) for that.

I don’t want to compare by the object reference. I want to be able to construct a key and then check if the dictionary already has that. Other languages like Python and Java let you do this by allowing you to define equals() and hashcode() methds that are part of their base object.

Do I have to create a hash manually and then use that as my key?

You want to hash by value?

Yes. Like in my example, I want to check if a user’s input keypress is already in my hash table. I could create string based on the input and then use that as my key, but it would be more elegant to use a custom object as my key (that I could look up by value). I’m kinda of surprised this isn’t in GDScript already.

In that case, calculate the key from the object data in whatever way suits your use case, and just use that. You can always implement the hashing function as a part of that object’s class.

You’re overthinking this and trying to future proof your code without actually trying it out. You’re also coding something that already exists. And I’d bet money this is another example of the XY Problem, where your actual question is “How do I remap keys?

So I’m going to point you to two plugins I’ve made which should either solve your problem or allow you to solve your problem without all that extra digging you’re doing.

The first is a plugin I use specifically for EditorPlugins for adding new actions to the InputMap. I just put it up today. It’s my Input Map Action Plugin.

The other is my Controller Plugin, which among other things, has functions for remapping keys.

  • rebind_action(action: String, event: InputEvent) -> void Sets the passed event for the given action in the InputMap and saves it to disk for loading the next time the game is loaded.
  • event_to_string(event: InputEvent) -> String Returns a string representation of the passed InputEvent. Returns a string of “Unknown” if the InputEvent was not listed here.

It also has a whole UI for remapping and a Dictionary that sorts with the action as the key, so it can show multiple options per action. Which also might solve the problem you’re trying to solve.


Either way, the answer to your question is, don’t use a custom Resource for whatever it is you’re doing. Use an InputEventKey. It stores all that stuff already, and you can compare them like you want.

The InputEvevtKey doesn’t allow you to specify modifiers. And even if I could use it for this particular case, in general it is very useful to be able to use custom classes as keys in Dictionaries.

What do you mean? What kind of modifiers are you looking for, if not alt_pressed, shift_pressed, etc.

I never ever needed to use object data as an associative array key. If you think you can’t do without that, you’re likely doing “something wrong” ™

If languages like Python, Java and C++ allow for it, I think there is pretty good justification for it. Sometimes you need to define a custom key object that is more than just a primitive. Just because you personally have never had to do it does not mean this is doing “something wrong”.

Also, from the docs InputEvevtKey does not seem to allow you to specify modifiers, so I don’t know how I would use that to distinguish Shift-A from regular A.

It inherits from InputEventWithModifiers. Which defines all these modifiers:

Is that not enough?

It’s not very performative though. Godot does support Objects as Dictionary keys. But again, we go back to have you tired it and it has failed?

Godot does allow for it.

InputEventKey inherits InputEventWithModifiers

Nothing stopping you from doing it GDScript. Either use your own key/hash generating function or serialize the object into a packed byte array and call hash() on it.

extends Node

var test: Dictionary[InputEventKey, String]


func _ready() -> void:
	var key := InputEventKey.new()
	key.keycode = KEY_1
	test[key] = "String 1"
	print(test)

	var key_2 := InputEventKey.new()
	key_2.keycode = KEY_2
	test[key_2] = "String 2"
	print(test)
	
	var key_3 := InputEventKey.new()
	key_3.keycode = KEY_1
	test[key_3] = "String 3"
	print(test)
	
	test[key] = "String 4"
	print(test)
	
	key.keycode = KEY_4
	print(test)

Output:

{ InputEventKey: keycode=49 (1), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 1” }
{ InputEventKey: keycode=49 (1), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 1”, InputEventKey: keycode=50 (2), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 2” }
{ InputEventKey: keycode=49 (1), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 1”, InputEventKey: keycode=50 (2), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 2”, InputEventKey: keycode=49 (1), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 3” }
{ InputEventKey: keycode=49 (1), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 4”, InputEventKey: keycode=50 (2), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 2”, InputEventKey: keycode=49 (1), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 3” }
{ InputEventKey: keycode=52 (4), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 4”, InputEventKey: keycode=50 (2), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 2”, InputEventKey: keycode=49 (1), mods=none, physical=false, location=unspecified, pressed=false, echo=false: “String 3” }

As you can see from my little test, Objects as Dictionary keys are supported by Godot. They are compared by reference. So two Objects with the same values, but different memory locations will evaluate as different.

If you don’t like that comparison, you can do it on your own as @normalized points out. But this feels more now like a question of why GDScript doesn’t allow you to do something that other languages do. If you really think it should, open up a feature request.