Thats definitely a useful tip thanks 
I think I get it, and now I understand why it can be so benefitial to to decouple visual representation with game logic, that way I could theoretically even switch the whole graphics engine that is being used for rendering, because my game data and logic is independent from the visualization. And the controls act like a bridge between the model and the view (like u said MVC), so they keep the data in sync with the visualization. Wow lots of things became clear now 
I think my problem is that I mostly understand the theoretical part but I am always unsure which way to go in code. Everything comes with a drawback, some things having a bigger one than others but I find it hard to evaluate what“s better.
Nevertheless, for that project it“s unfortunately too late to use your entity component system (which I am really looking forward to trying out). I would simply have to rewrite too much I guess. But I try to make the best out of the existing code so I“ll post one of my problems.
Lets say I have first person player in my game who can move and look around as well as drop items, pick up items, attack certain objects with the items, like chopping wood with an axe, mining rocks with a pickaxe or attacking animals with a sword.
Looking at the MonkeyZone example, I really like the idea of having a generic MovementControl which offers some common methods for movement (steerX(), moveX(), ā¦). So for the player I would create a FirstPersonMovementControl which implements the above control and manages movement via a BetterCharacterControl. Now there are two possible ways:
Should I create a FirstPersonInputControl which has a reference to the FirstPersonMovementControl and listens for key and mouse input in order to control the players movement.
public class FirstPersonInputControl extends AbstractControl implements ActionListener, AnalogListener {
private final String MOVE_LEFT = "FPS_Move_Left";
private final String MOVE_RIGHT = "FPS_Move_Right";
private final String MOVE_BACKWARD = "FPS_Move_Backward";
private final String MOVE_FORWARD = "FPS_Move_Forward";
private final String TOGGLE_RUNNING = "FPS_Toggle_Running";
private final String JUMP = "FPS_Jump";
private final String DUCK = "FPS_Duck";
private final String ROTATE_LEFT = "FPS_Rotate_Left";
private final String ROTATE_RIGHT = "FPS_Rotate_Right";
private final String LOOK_UP = "FPS_Look_Up";
private final String LOOK_DOWN = "FPS_Look_Down";
private InputManager inputManager;
private FirstPersonControl firstPersonControl;
//Default Movement Keys
private int left = KeyInput.KEY_A;
private int right = KeyInput.KEY_D;
private int backward = KeyInput.KEY_S;
private int forward = KeyInput.KEY_W;
private int toggleRunning = KeyInput.KEY_R;
private int jump = KeyInput.KEY_SPACE;
private int duck = KeyInput.KEY_LSHIFT;
private int rotateLeft = MouseInput.AXIS_X;
private int rotateRight = MouseInput.AXIS_X;
private int lookUp = MouseInput.AXIS_Y;
private int lookDown = MouseInput.AXIS_Y;
/**
* Creates a new FirstPersonControl with default key mappings (W, A, S, D, etc.).
* @param inputManager
*/
public FirstPersonInputControl(InputManager inputManager) {
this.inputManager = inputManager;
initKeyMappings();
listenForKeys();
}
/**
* Creates a new FirstPersonControl with the specified Keys for controlling movement.
* @param inputManager
* @param left
* @param right
* @param backward
* @param forward
* @param toggleRunning
* @param jump
* @param duck
* @param rotateLeft
* @param rotateRight
* @param lookUp
* @param lookDown
*/
public FirstPersonInputControl(InputManager inputManager, int left, int right, int backward, int forward, int toggleRunning, int jump, int duck, int rotateLeft, int rotateRight, int lookUp, int lookDown) {
this.inputManager = inputManager;
this.left = left;
this.right = right;
this.backward = backward;
this.forward = forward;
this.toggleRunning = toggleRunning;
this.jump = jump;
this.duck = duck;
this.rotateLeft = rotateLeft;
this.rotateRight = rotateRight;
this.lookUp = lookUp;
this.lookDown = lookDown;
initKeyMappings();
listenForKeys();
}
private void initKeyMappings() {
inputManager.addMapping(MOVE_LEFT, new KeyTrigger(left));
inputManager.addMapping(MOVE_RIGHT, new KeyTrigger(right));
inputManager.addMapping(MOVE_BACKWARD, new KeyTrigger(backward));
inputManager.addMapping(MOVE_FORWARD, new KeyTrigger(forward));
inputManager.addMapping(TOGGLE_RUNNING, new KeyTrigger(toggleRunning));
inputManager.addMapping(JUMP, new KeyTrigger(jump));
inputManager.addMapping(DUCK, new KeyTrigger(duck));
inputManager.addMapping(ROTATE_LEFT, new MouseAxisTrigger(rotateLeft, false));
inputManager.addMapping(ROTATE_RIGHT, new MouseAxisTrigger(rotateRight, true));
inputManager.addMapping(LOOK_DOWN, new MouseAxisTrigger(lookDown, false));
inputManager.addMapping(LOOK_UP, new MouseAxisTrigger(lookUp, true));
}
private void listenForKeys() {
inputManager.addListener(this, MOVE_LEFT,
MOVE_RIGHT,
MOVE_FORWARD,
MOVE_FORWARD,
TOGGLE_RUNNING,
JUMP,
DUCK,
ROTATE_LEFT,
ROTATE_RIGHT,
LOOK_DOWN,
LOOK_UP);
}
@Override
public void setSpatial(Spatial spatial) {
super.setSpatial(spatial);
if(spatial == null) return;
this.firstPersonControl = spatial.getControl(FirstPersonControl.class);
if(firstPersonControl == null) {
throw new IllegalStateException("Cannot add FirstPersonInputControl to a Spatial without a FirstPersonControl!");
}
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if(enabled) {
//Listen for keys
listenForKeys();
} else {
//Stop listening for keys
inputManager.removeListener(this);
}
}
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) { }
public void onAction(String name, boolean isPressed, float tpf) {
if(name.equals(MOVE_LEFT)){
firstPersonControl.setMovingLeft(isPressed);
}
if(name.equals(MOVE_RIGHT)){
firstPersonControl.setMovingRight(isPressed);
}
if(name.equals(MOVE_FORWARD)){
firstPersonControl.setMovingForward(isPressed);
}
if(name.equals(MOVE_BACKWARD)){
firstPersonControl.setMovingBackward(isPressed);
}
if(name.equals(JUMP) && isPressed){
firstPersonControl.jump();
}
if(name.equals(DUCK)){
firstPersonControl.setDucking(isPressed);
}
if(name.equals(TOGGLE_RUNNING) && isPressed){
firstPersonControl.toggleRunning();
}
}
public void onAnalog(String name, float value, float tpf) {
if(name.equals(ROTATE_LEFT)){
firstPersonControl.rotateBody(-value, tpf);
}
if(name.equals(ROTATE_RIGHT)){
firstPersonControl.rotateBody(value, tpf);
}
if(name.equals(LOOK_UP)){
firstPersonControl.lookUpDownHead(value, tpf);
}
if(name.equals(LOOK_DOWN)){
firstPersonControl.lookUpDownHead(-value, tpf);
}
}
What I really like about that way is that it can be reused a lot. I just have to attach a FirstPersonInputControl every time and finished. No need for writting game dependent code, first person movement is always the same.
Or would I create an AppState, say PlayerInputState, which listens for input and then calls the FirstPersonMovementControls methods. That way input would be in a central place and it“s easier to deactivate/activate player input all at once. At the moment I have both ways in code (one is used), but I am not sure about this.
For the second question: Lets say the player can hold an item in his hands (like in minecraft ;)), would I use a control here or an AppState. In my opinion, it“s a property or a behaviour of the player so a control would make more sense. This control would then have a way of setting the current item in the players hand and animate it when the player is walking. So it would only be responsible for the graphic visualization. AppState or control and why?
Finally, I have a question about graphical representation of inventories, but as this post got quite long I“ll post it later 
Maybe I“ll see that light too one day 