# (Slick) Steuerung - mehrere Belegungen



## staxx6 (26. Jun 2012)

hi!, 

ich programmiere eine Zeit lang schon mit Slick und hab jetzt nen kleinen Markel,
wenn man das mal so ausdrücken darf.
Mich stört seit längerem die Methode um ein bestimmten Tastendruck zu ermitteln:

if(input.isKeyDown(Input.KEY_A)) {} 

Bei mehreren Belegungen müsste ich ne paar && ... anhängen was bei mehreren Aktionen
recht unübersichtlich wird und chaotisch, besonders wenn ich dran denke die Steuerung später
in eine Datei zu speichern (unterschiedliche mengen an .isKeyDown(...)) 

Da wollte ich euch mal fragen was ihr für Ideen dazu habt. Eine Idee von mir ist es 
"Belegungen" in ein Array zu stecken. ->    jumpList - UP, SPACE, W und alle andere Aktionen.
Würde so ca. so aussehen (hier mit nur einem Array)


```
private boolean isKeyUp(Input input)
	{
		boolean isKeyPressed = false;
		
		ArrayList<Integer> keys = new ArrayList<Integer>();
		keys.add(new Integer(0xC8)); // TODO: load from file

		for(int i = 0; i < keys.size(); i++)
		{
			if(input.isKeyDown(keys.get(i).intValue()))
			{
				isKeyPressed = true;
				break;
			}
		}
		return isKeyPressed;
	}
```

Bin mir nicht so recht sicher ob es da nicht vielleicht noch bessere Ideen gibt.


----------



## Marco13 (27. Jun 2012)

Ich kenne mich mit Slick im speziellen nicht aus, aber ... meinst du den Fall, dass eine Aktion durch mehrere verschiedene Tasten ausgelöst werden kann? Das mit der Liste ist wohl eine Option, aber ob man sich da (für das Laden und Speichern, oder die Abfrage) noch mehr Infrastruktur drumrumbauen müßte (GROB im Sinne von InputMap/ActionMap, wie bei How to Use Key Bindings (The Java™ Tutorials > Creating a GUI With JFC/Swing > Using Other Swing Features) , auch wenn das nicht direkt übertragbar ist) kann ich gerade nicht einschätzen.


----------



## Spacerat (27. Jun 2012)

Ja, ich finde diese Tastaturabfragen mit Int-Zeigern in ein Boolean-Array auch sehr sinnig... deswegen findet man sie wohl auch überall :lol:.
Ne, im Ernst. Als ich ähnliche Probleme mit diesem Aufbau bekam, realisierte ich mir ähnliche Abfragen mit der Klasse BitSet. Mit "get(int index)" funktioniert die Abfrage einzelner Tasten weiterhin wie gewohnt und mit "and(BitSet x)" die Multi-Key-Abfragen. Multi-Keys können natürlich auch als BitSet-Konstanten definiert werden.
[EDIT]Wenn's aber um die Belegung freidefinierbarer Tastaturbelegungen in einem Spiel geht, ist die Idee mit der Liste gar nicht mal schlecht. Ich würde aber die Listindices wieder mit Konstanten benennen und die Abfrage wie dann gewohnt gestalten, sonst verliert man am Ende nur wieder den Überblick.[/EDIT]


----------



## staxx6 (27. Jun 2012)

hi!,

danke für die Antworten, sie haben mich zum nachdenken angeregt und auf eine noch andere Lösung gebracht. 

Ich hab eine Control Klasse mit einem Array, welches KeyMaps Klassen hält.
Ein KeyMap hat drei einfache Werte Name, Key1, Key2. 

So lass ich einfach die Control klasse nach dem richtigen KeyMap suchen und dieser gibt wiederum an ob der input passt.

In der Game-Logik würde das dann auch einfach so aussehen:

```
if(control.isAction("Jump"))
{
     machWas();
}
```

Die Control Klasse welche später auch aus der Datei liest (das Drumherum gekürzt):

```
public class Control
{
	private Control()
	{
		keyMapList = new ArrayList<KeyMap>();
		addKeyMaps();
	}
	
	private void addKeyMaps()
	{
		// hier von der Datei laden
		keyMapList.add(new KeyMap("Jump", Input.KEY_UP, Input.KEY_W));
		keyMapList.add(new KeyMap("STRG", Input.KEY_LCONTROL, Input.KEY_LCONTROL));
	}
	
	public boolean isAction(final String actionName)
	{
		boolean isAction = false;
		
		for(KeyMap keyMap : keyMapList)
		{
			if(keyMap.getName().equals(actionName))
			{
				if(keyMap.checkEquality(INPUT))
				{
					isAction = true;
				}
			}
		}
		return isAction;
	}
}
```

Und die KeyMap Klasse:

```
public class KeyMap
{
	private final String name;
	private int key1 = 0, key2 = 0;
	
	public KeyMap(final String name, int key1, int key2)
	{
		this.name = name;
		this.key1 = key1;
		this.key2 = key2;
	}
	
	public boolean checkEquality(final Input input)
	{
		boolean isEqual = false;

		if(input.isKeyPressed(key1) || input.isKeyPressed(key2))
		{
			isEqual = true;
		}
		return isEqual;
	}
	
	public String getName()
	{
		return name;
	}
}
```

Nach längere Überlegung bin ich zum Entschluss gekommen, dass zwei Belegungen reichen pro "Aktion". 
Weitere Funktionen wie die Tasten ändern in der Laufzeit sollte auch kein Problem darstellen und Taste gedrückt halten, etc.


----------



## Spacerat (28. Jun 2012)

Bei dir geht's definitiv doch um verschiedene Aktions-Belegungen, die womöglich auch noch zusammen benutzt werden sollen. Na herzlichen Dank für diese Inspiration... deswegen sieht meine Keyabfrage nun so aus:

```
public final class KeySet {
	private static final BitSet downKeys = new BitSet(256);

	private static final KeyListener listener = new KeyListener() {
		
		@Override
		public void keyTyped(KeyEvent e) {
			// nothing
		}
		
		@Override
		public void keyReleased(KeyEvent e) {
			int key = e.getKeyCode();
			if(key > 0 && key < 256) {
				downKeys.clear(key);
			}
		}
		
		@Override
		public void keyPressed(KeyEvent e) {
			int key = e.getKeyCode();
			if(key > 0 && key < 256) {
				downKeys.set(key);
			}
		}
	};

	private static Component activeComponent;

	public static boolean isKeyDown(int key) {
		if(key < 1 || key > 255) {
			throw new IllegalArgumentException("key out of range " + key);
		}
		return downKeys.get(key);
	}

	public static boolean isKeySetDown(KeySet action) {
		return action.and(downKeys);
	}

	public static boolean isAction(KeySet action) {
		return action.or(downKeys);
	}

	public static Component register(Component comp) {
		synchronized(KeySet.class) {
			Component rc = activeComponent;
			if(rc != null) {
				rc.removeKeyListener(listener);
			}
			activeComponent = comp;
			if(comp != null) {
				comp.addKeyListener(listener);
			}
			return rc;
		}
	}

	private final int[] keys;

	public KeySet(int ... keys) {
		for(int n = 0; n < keys.length; n++) {
			if(keys[n] < 1 || keys[n] > 255) {
				throw new IllegalArgumentException("key out of range " + keys[n]);
			}
		}
		this.keys = keys;
	}

	private boolean and(BitSet bs) {
		for(int n = 0; n < keys.length; n++) {
			if(!bs.get(keys[n])) {
				return false;
			}
		}
		return true;
	}

	private boolean or(BitSet bs) {
		for(int n = 0; n < keys.length; n++) {
			if(bs.get(keys[n])) {
				return true;
			}
		}
		return false;
	}
}
```
Evtl. kannst du ja auch etwas damit anfangen. Leider kann sie aber immer noch nicht zwischen linken und rechten STRG-, ALT- und/oder Windows-Tasten unterscheiden, aber immerhin schon mal Mehrfachbelegungen und Multikeys. Deine Control-Klasse könnte sie nun so verwenden:

```
class Control {
	public static final KeySet JUMP = new KeySet(VK_UP, VK_W);
	public static final KeySet STRG = new KeySet(VK_CONTROL);
	//... usw.
}
```
Im Prinzip kannst su so auch mehr als nur zwei Belegungen definieren. Das müsste aber auch bei deiner Methode klappen, wenn du wie ich den Array-Parameter (...) verwendest.


----------



## staxx6 (28. Jun 2012)

In meiner ersten Version hatte ich in der KeyMap Klasse auch ein Array für die Belegung,
allerdings fand ich das doch übertrieben, da ich keine Situation kenne wo man mehr als zwei Tastenbelegung bräuchte. 
Für Tastenkombos hab mir einfach eine isKeyDown() Methode ausgedacht, die dann mit der isKeyPressed() true liefert. Slick bietet da ja schon passende Methoden.

if(control.actionHold("LSTRG") && control.action("JUMP")) {//mach was }


----------



## staxx6 (2. Jul 2012)

Ich hab mal dein Rat mit 
	
	
	
	





```
public static final KeySet JUMP = new KeySet(VK_UP, VK_W);
```
 beherzigt und das daraus gemacht:



```
public final class Control
{	
	/* Static list for the KeyMap */
	
	/* Player actions */
	public static KeyMap MOVE_JUMP = new KeyMap("moveJump", Input.KEY_UP, Input.KEY_W);;
	public static KeyMap MOVE_LEFT = new KeyMap("moveLeft", Input.KEY_A, Input.KEY_LEFT);
	public static KeyMap MOVE_RIGHT = new KeyMap("moveRight", Input.KEY_D, Input.KEY_RIGHT);
	
	public static KeyMap RESET_POSITION = new KeyMap("ResetPosition", Input.KEY_R, Input.KEY_F5);
	
	/* Additional keys */
	public static KeyMap L_CONTROL  = new KeyMap("LSTRG", Input.KEY_LCONTROL, Input.KEY_LCONTROL);
	
	private Control()
	{
		logger.info("loading controls");
	}
	
	public static boolean isAction(final KeyMap keyMap, final boolean holdingKey)
	{
		if(holdingKey == true) {
			if(keyMap.checkEqualityHolding(INPUT))
			{
				return true;
			}
		} else if(keyMap.checkEquality(INPUT))
			{
				return true;
			}
		return false;
	}
}
```


```
public class KeyMap
{
	private final String name;
	private int key1 = 0, key2 = 0;
	
	public KeyMap(final String name, int key1, int key2)
	{
		this.name = name;
		this.key1 = key1;
		this.key2 = key2;

                //TODO: Von Datei Laden
	}
	
	public boolean checkEquality(final Input input)
	{
		if(input.isKeyPressed(key1) || input.isKeyPressed(key2))
		{
			return true;
		} else { return false; }
	}
	
	public boolean checkEqualityHolding(final Input input)
	{
		if(input.isKeyDown(key1) || input.isKeyDown(key2))
		{
			return true;
		} else { return false; }
	}
	
	public String getName()
	{
		return name;
	}
}
```

So kann ich sogar total auf ein Array verzichten. Ich schätze mal das ich das jetzt so lasse, wenn nicht was noch besseres kommt.


----------



## Spacerat (2. Jul 2012)

Was besseres wäre, wenn meine Implementation noch die Location unterscheiden könnte - man arbeitet dran, allerdings ohne Slick, für eigene Zwecke.
Was dich betrifft... wozu benötigst du nun noch die Strings in KeyMap?
Das Array wäre allerdings noch 'ne Möglichkeit einige Belegungen mehr inkl. einer benutzerdefinierten anzubieten, wie man es von diversen Spielen schon kennt.


----------



## staxx6 (2. Jul 2012)

Was meinst du genau mit Location?
Die Strings halte ich, damit ich die später in den Optionen anzeigen kann und damit man die von der Datei lesen kann.

Kommt jetzt drauf an was du noch mehr an Benutzerdefinierte Testen haben möchtest. Kenne da nur das höhste in WoW z.B., Laufen: Belegung1; Belegung2 
Für Springen z.B. kannst du ja diese einfach ändern Ingame.  (steht oben noch nicht im Code..) Doch das sollte ja kein Problem sein, sprich:   
	
	
	
	





```
Control.MOVE_JUMP.changeKey("key1", Input.KEY_X);
```
 etc.

Wenn du solche eigen definierte Belegung wie z.B. STRG+W meinst, kann ich die KeyMap erweitern um zwei "Keys".


----------



## Spacerat (2. Jul 2012)

staxx6 hat gesagt.:


> Was meinst du genau mit Location?


Unterscheiden ob STRG-Links oder STRG-Rechts gedrückt wurde. Bei KeyEvents ist das die Location.



staxx6 hat gesagt.:


> Kommt jetzt drauf an was du noch mehr an Benutzerdefinierte Testen haben möchtest. Kenne da nur das höhste in WoW z.B., Laufen: Belegung1; Belegung2
> Für Springen z.B. kannst du ja diese einfach ändern Ingame.  (steht oben noch nicht im Code..) Doch das sollte ja kein Problem sein, sprich:
> 
> 
> ...


Das hört sich nach vorbelegten KeySets an aus denen man auswählen kann. Ich meinte eher eine benutzerdefinierte Belegung ala The Elder Scrolls oder Need for Speed, wo man einer Aktion einzelne Tasten bzw. Tastenkombis zuweisen kann.


----------



## staxx6 (2. Jul 2012)

Aso, Slick hat schon die Links-Rechts Unterscheidung.

Ich bin mir immer noch nicht so recht sicher was du meinst. 
Die Aktionen sind klar vordefiniert "JUMP". Die Belegungen der Aktionen sind vorgeladen, sagen wir mal
"start-settings", welche man später dann durch eigene Tasten verändern kann.

Input.KEY_X war nur Exemplarisch, welche Taste genau gedruckt wurde, ist klar zu ermitteln oder halt Tasten (strg..).


----------



## Spacerat (2. Jul 2012)

Bei einigen Spielen hat man diverse Belegungsvorgaben, zwischen denen man wählen kann. Von diesen bieten widerum einige die Möglichkeit einer individuellen "pro Spielerprofil"-Belegung.
Also eigentlich das was du geschrieben hast. Weil du deine Control-Klasse so fest verdrahtest hattest, bin ich davon ausgegangen, dass du nur verschiedene Belegungsvorgaben anbieten wolltest.


----------



## staxx6 (2. Jul 2012)

Ach-so meinst du das. Das was du als Belegungsvorgaben siehst soll nur die Standard Konfig. sein.
Wie ich Post drüber schrieb sollte auf jeden Fall noch eine Methode folgen, um die Belegungen zu ändern.
Sowie diese zu speichern und laden. Ggf. auch in einem Spieler Profil speichern für ganze unterschiedliche Profile.


----------

