Minecraft Modding: KeyBinding Tutorial

Background


It is often useful to add functionality where a key press will cause some action in your mod.  This is often related to interaction with a GUI, but can really be anything you want (like spawn something when certain key is pressed).

Over the various versions of Forge, the events related to key input have changed drastically.  There is now a single event called KeyInputEvent on the FML event bus. (See my tutorial on events for more background on how events are registered and handled).  Unfortunately this event is only fired on the initial press of a key, and not when a key is let go (i.e. there is no KeyUp type event like previous Forge versions had).  Also, unlike other events there are not many useful fields passed by the event parameter-- instead you need to combine it with a KeyBinding instance to get more information on which key was pressed.

A KeyBinding basically maps a key with a state (i.e. is it pressed) and a description and is automatically updated by the input handler.

Key point: You don't want to process the Keyboard KeyEvent directly as it (a) doesn't help manage user-mappings of keys, (b) it will interfere with the rest of Minecraft class' handling of keyboard input (because it will pop the events from the queue before Minecraft class gets to handle them).  So always use KeyBinding for any custom key input.

Since in many mods you might have several KeyBinding instances, it is useful to put them in an array (and possibly index them with "enumerated" constants representing the intended functionality).

Lastly, user input is a client side functionality and in fact the KeyBinding class is only loaded on client side.  Whenever you see client-side classes there are two things that should immediately come to mind:
  • You should use the class only in your ClientProxy or other client-side only methods.
  • If you want the server side to respond to the key press then you will likely need to send a custom packet.
Anyway, putting all the above together the steps you need are:
  1. In your ClientProxy define and register your KeyBinding instances.
  2. In your FML event bus handler you need a method that handles the KeyInputEvent and checks all the KeyBinding instances to determine what actual input event happened.

Create And Register KeyBinding Instances


Since KeyBinding is a client side only class, you should create and register the instances in your ClientProxy class, in the "init" method (that handles the FMLInitializationEvent).

First, in your ClientProxy you need to declare the array to keep your KeyBinding instances. This should be public (or probably better make a public getter method) since you'll need to access it from your FML event handler class.

For example declare following in your ClientProxy:
public static KeyBinding[] keyBindings;

Then create and register your KeyBinding instances in your "init" method, for example:

// declare an array of key bindings
keyBindings = new KeyBinding[2]; 
  
// instantiate the key bindings
keyBindings[0] = new KeyBinding("key.structure.desc", Keyboard.KEY_P, "key.magicbeans.category");
keyBindings[1] = new KeyBinding("key.hud.desc", Keyboard.KEY_H, "key.magicbeans.category");
  
// register all the key bindings
for (int i = 0; i < keyBindings.length; ++i) 
{
    ClientRegistry.registerKeyBinding(keyBindings[i]);
}

Important: The Keyboard class should be from the org.lwjgl.input import. There are Keyboard classes in other common libraries so be careful when accepting recommendations from Eclipse on what to import.

In the example above I've created two bindings, the "P" key will be used to create a structure and the "H" key will be used to bring up a HUD.

The description and category are useful to allow users to re-map the functionality and they are in a form that can be localized using a lang file.

Tip: Don't forget to register the key bindings!

Create Handler For KeyInputEvent


It is possible to poll the key bindings in some tick event handler or some tick method.  That is probably preferable for keys that are only active in very specific situations.  For example if you had custom sword that could do a special attack with a key press then it may be better to check the key binding only when holding that sword item.

Further, if you need to detect a "key up" event (which is no longer available as built-in event in latest versions of Forge) you should use a tick event or method to check the pressed state and fire a custom event when the pressed state goes from pressed to not pressed.

However, generally if you're looking to detect key press events you can handle them in the KeyInputEvent.

So in your event handler class (registered to the FML event bus with the FMLCommonHandler.instance().bus().register() method) you should have something like this:

@SideOnly(Side.CLIENT)
@SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
public void onEvent(KeyInputEvent event)
{
    // DEBUG
    System.out.println("Key Input Event");

    // make local copy of key binding array
    KeyBinding[] keyBindings = ClientProxy.keyBindings;
   
    // check each enumerated key binding type for pressed and take appropriate action
    if (keyBindings[0].isPressed()) 
    {
        // DEBUG
        System.out.println("Key binding ="+keyBindings[0].getKeyDescription());
            
        // do stuff for this key binding here
        // remember you may need to send packet to server
    }
    if (keyBindings[1].isPressed()) 
    {
        // DEBUG
        System.out.println("Key binding ="+keyBindings[1].getKeyDescription());
            
        // do stuff for this key binding here
        // remember you may need to send packet to server
    }
}

I've put in some console statements to help trace the execution and prove that the key bindings are working.  With the above, if you run your mod and press various keys you should get console output showing that any key will print out "Key Input Event" but pressing the "P" or "H" key will additionally print out to indicate which key binding was activated.

Key Point: You probably need to send a packet to the server for each of the "do stuff for key binding" in the above code.

Tip: Don't use else if statements because it is possible (although rare) that you could press multiple keys in same tick and using else if would miss the additional presses.

Tip: The @SideOnly annotation does not seem to be strictly necessary since the event is only fired on the client side; however it is good practice to annotate any code that references code (in this case the KeyBinding class) that is @SideOnly.  This is because Java is somewhat unpredictable on whether it will consider it an error when unexecuted code contains unloaded classes.

Now all you have to do is put your code for your mod in the relevant section.  Have fun!

Conclusion And Further Thoughts


There are many alternative ways to use KeyBinding, but the above example should give you a solid approach that is useful in most situations.  It is annoying that there are not better events for key input built in, I can only hope these get added back.

As always please feel free to write me with suggestions on improvements and corrections.

No comments:

Post a Comment