After making games in processing for a while, I and many of you have realized that processing’s keyboard input is not that great for games, especially games when remapping controls is a desired feature. I made a class that can be plunked into any sketch and can be used for input with any key on the keyboard except for a few keys. You can use this with the three main mouse buttons as well.
This can be used for multiplayer by making multiple ControlManager objects, or you could use a single object to control every player at once. It depends on how you implement it.
I’m releasing this code here because 1. I don’t know how to make a library and 2. this is only one class, do I need a library for it?
The instructions for use are in the comment at the top. You don’t have to credit me if you use this in your sketches. If there are bugs, please let me know. There are some criticisms you could make about this system’s efficiency because I am iterating through lists a LOT when changing controls, but it’s not that bad.
/*
Control Manager
by Yehoshua Halle
This is a class that manages keyboard input and mouse button input with the intent that they will be used in a game.
Quick Start
1. Create a Control Manager Object
2. Use setMapping(String name, char input) to add a new mapping or change an old one
3. Call the method "updatePressed(keyCode)" method at the beginning of the keyPressed() method
4. Call the method "updateReleased(keyCode)" method at the beginning of the keyReleased() method
5. Call the method "updateMousePressed(mouseButton)" at the beginning of the mousePressed() method
6. Call the method "updateMouseReleased(mouseButton)" at the begining of the mouseReleased() method
7. Use getControl(String name) to get the current state of a mapping
Mapping controls
letter characters and all typed characters can be input directly into the char argument in setMapping, like 'a' for the A key or ' ' for the space bar
non-typed keys have these options: UP, DOWN, LEFT, RIGHT, SHIFT, CONTROL, BACKSPACE, TAB, RETURN, ENTER, and DELETE
For mouse buttons, use the constants LEFTMOUSE, MIDDLEMOUSE, and RIGHTMOUSE for the char argument
if you want to allow the player to input a button in void keyPressed, use 'keyCode' for input
More Methods and Info
void setMapping(String name, char input)
- input should be a character as it appears with the 'keyCode' reserved word: 'a' for the A key or LEFT for left arrow, etc.
- if allowConflicts is false (which is its default value), then attempting to set the button of a mapping to a button that is already mapped to something else will
swap the two buttons if the target mapping already has a button, or set the old mapping to null
- Be sure to check for null in your code with the hasNull() method
void updatePressed(keyCode) and updateReleased(keyCode)
- the argument in these methods should be the 'keyCode' reserved word, but 'key' can be used if you don't want to use non-type buttons like shift and the arrow keys
void updateMousePressed(mouseButton) and updateMouseReleased(mouseButton)
- the argument must be mouseButton
boolean getControl()
- if the mapped button is not mapped (null), then this function will always return false
void setDefault()
- saves the current mappings temporarily
void restoreDefault()
- restores the saved default mapping if one exists
boolean hasNull()
- returns true when at least one mapping has no button mapped to it. This can result when two mappings conflict and allowConflicts is false.
String getMappingName(String name)
- returns a readable version of the button mapped to a mapping if the mapping exists.
- for example, a mapping called "jump" is mapped to ' ', which is the space bar, but it's not easy to read. getMappingName("jump") would return "Space" instead of ' '
String getMappingNameDirectly(keyCode)
- returns a readable version of the key passed in the argument
- for this to work well, pass this method keyCode or an equivalent value like SHIFT or UP
- this is useful when you want to tell a player how the program interprets their direct input without mapping it
void changeMappingName(String oldName, String newName)
- changes the name of a mapping to something else
void removeMapping(String name)
- removes a mapping that matches the given name
void clearMappings()
- removes all mappings
Limitations:
- For keyboard input and mouse buttons ONLY, no mouse movement or scroll wheel and no gamepad input.
- Windows, Alt, Escape, and Function keys are not usable!
- Only intended for use in processing, not java in general
*/
public final static int LEFTMOUSE = '{';
public final static int MIDDLEMOUSE = '}';
public final static int RIGHTMOUSE = '|';
public class ControlManager {
private HashMap<String, Character> mappings;
private HashMap<String, Character> defaults;
private HashMap<String, Boolean> controls;
public boolean allowConflicts = false;
public ControlManager() {
mappings = new HashMap<String, Character>();
controls = new HashMap<String, Boolean>();
defaults = null;
}
public ControlManager(int numMappings) {
mappings = new HashMap<String, Character>(numMappings);
controls = new HashMap<String, Boolean>(numMappings);
defaults = null;
}
//IMPORTANT METHODS (the ones you must use in your program)
public void setMapping(String name, int raw) {
char fixed = keyFix(raw);
if (!allowConflicts) { //assumes 1:1 mapping is not violated already (if it was, you have a problem and should call resetMappings() to reset your controls)
if (mappings.containsValue(fixed)) { //if value is already used, there is a conflict
for (HashMap.Entry<String, Character> entry : mappings.entrySet()) { //find which mapping conflicts
if (entry.getValue().equals(fixed)) { //if this entry matches the conflicting value, use its key
if (mappings.containsKey(name)) { //if controls already has a mapping using this name
mappings.replace(entry.getKey(), mappings.get(name)); //swap controls
} else mappings.replace(entry.getKey(), null); //otherwise, clear the old value and create the new one
}
}
}
}
mappings.put(name, fixed);
finalizeControls();
}
public void updatePressed(int raw) {
char fixed = keyFix(raw);
for (HashMap.Entry<String, Character> entry : mappings.entrySet()) {
if (entry.getValue() == null) continue;
if (entry.getValue().equals(fixed)) {
controls.replace(entry.getKey(), true);
}
}
}
public void updateReleased(int raw) {
char fixed = keyFix(raw);
for (HashMap.Entry<String, Character> entry : mappings.entrySet()) {
if (entry.getValue() == null) continue;
if (entry.getValue().equals(fixed)) {
controls.replace(entry.getKey(), false);
}
}
}
public void updateMousePressed(int button) {
if (button == LEFT) {
updatePressed(LEFTMOUSE);
}
if (button == CENTER) {
updatePressed(MIDDLEMOUSE);
}
if (button == RIGHT) {
updatePressed(RIGHTMOUSE);
}
}
public void updateMouseReleased(int button) {
if (button == LEFT) {
updateReleased(LEFTMOUSE);
}
if (button == CENTER) {
updateReleased(MIDDLEMOUSE);
}
if (button == RIGHT) {
updateReleased(RIGHTMOUSE);
}
}
public boolean getControl(String name) {
if (controls.keySet().contains(name))
return controls.get(name);
else
return false;
}
public void setDefault() {
defaults = new HashMap<String, Character>(mappings.size());
for (HashMap.Entry<String, Character> entry : mappings.entrySet()) {
defaults.put(entry.getKey(), entry.getValue());
}
}
public void restoreDefault() {
if (defaults == null) return;
mappings = new HashMap<String, Character>(defaults.size());
for (HashMap.Entry<String, Character> entry : defaults.entrySet()) {
mappings.put(entry.getKey(), entry.getValue());
}
finalizeControls();
}
public boolean hasNull() {
return mappings.values().contains(null);
}
//OTHER METHODS (optional to use, but useful when using a GUI to set controls)
public void changeMappingName(String oldName, String newName) {
if (mappings.keySet().contains(oldName)) {
mappings.put(newName, mappings.get(oldName));
mappings.remove(oldName);
finalizeControls();
}
}
public void removeMapping(String name) {
if (mappings.keySet().contains(name)) {
mappings.remove(name);
finalizeControls();
}
}
public void clearMappings() {
mappings.clear();
controls.clear();
}
public String getMappingName(String name) {
//Gets a readable name of the control mapped to this name
if (controls.keySet().contains(name))
return getKeyName(mappings.get(name));
else
return "None";
}
public String getMappingNameDirectly(int raw) {
//Gets a readable name of the control that this character would result in
return(getKeyName(keyFix(raw)));
}
public HashMap<String, String> getMappings() {
HashMap<String, String> map = new HashMap<String, String>();
for (String k : mappings.keySet()) {
map.put(k, getMappingName(k));
}
return map;
}
//UNDER THE HOOD METHODS (you can't use these, sorry)
private void finalizeControls() {
//keeps controls in sync with mappings, ABSOLUTELY NECESSARY FOR CORE FUNCTIONALITY
//automatically called at the end of every method that changes mappings
//it couldn't hurt to call this yourself
controls.clear();
for (String name : mappings.keySet()) {
controls.put(name, false);
}
}
private String getKeyName(Character in) {
//Gets a readable name of the given "fixed" character
//Returns strings in Title Case for most diverse manipulation
if (in == null) { //if conflicts are not allowed, there is a chance 'in' could be null
return "Empty";
}
switch(in) {
case 'U':
return "Up Arrow";
case 'D':
return "Down Arrow";
case 'L':
return "Left Arrow";
case 'R':
return "Right Arrow";
case 'S':
return "Shift";
case 'C':
return "Ctrl";
case ' ':
return "Space";
case '{':
return "Left Mouse";
case '}':
return "Middle Mouse";
case '|':
return "Right Mouse";
case 'T':
return "Tab";
case 'E':
return "Enter";
case 'B':
return "Backspace";
case 'Q':
return "Delete";
default:
return str(in).toUpperCase();
}
}
private Character keyFix(int raw) {
if (raw == UP) {
return 'U';
}
if (raw == DOWN) {
return 'D';
}
if (raw == LEFT) {
return 'L';
}
if (raw == RIGHT) {
return 'R';
}
if (raw == SHIFT) {
return 'S';
}
if (raw == CONTROL) {
return 'C';
}
if (raw == TAB) {
return 'T';
}
if (raw == ENTER) {
return 'E';
}
if (raw == RETURN) {
return 'E';
}
if (raw == BACKSPACE) {
return 'B';
}
if (raw == DELETE) {
return 'Q';
}
if (raw == LEFTMOUSE) {
return '{';
}
if (raw == MIDDLEMOUSE) {
return '}';
}
if (raw == RIGHTMOUSE) {
return '|';
}
char pkey = str((char) raw).toLowerCase().charAt(0);
switch(pkey) {
case '~':
pkey = '`';
break;
case '!':
pkey = '1';
break;
case '@':
pkey = '2';
break;
case '#':
pkey = '3';
break;
case '$':
pkey = '4';
break;
case '%':
pkey = '5';
break;
case '^':
pkey = '6';
break;
case '&':
pkey = '7';
break;
case '*':
pkey = '8';
break;
case '(':
pkey = '9';
break;
case ')':
pkey = '0';
break;
case '_':
pkey = '-';
break;
case '+':
pkey = '=';
break;
case '{':
pkey = '[';
break;
case '}':
pkey = ']';
break;
case '|':
pkey = '\\';
break;
case ':':
pkey = ';';
break;
case '"':
pkey = '\'';
break;
case '<':
pkey = ',';
break;
case '>':
pkey = '.';
break;
case '?':
pkey = '/';
break;
default:
break;
}
return pkey;
}
}