[LibGDX] One Controller class, multiple controllers

I first created a basic pong game some weeks ago, terribly down and learned alot.

I have started fresh and I am trying to figure out how to do this.

I have one controller class called PaddleControl, you can guess what it does.

In here we have the following:

	enum Control {

		UP, DOWN
	}

	static Map<Control, Boolean> keys = new HashMap<PaddleControl.Control, Boolean>();
	static {

		keys.put(Control.UP, false);
		keys.put(Control.DOWN, false);

	};

I also have a bunch of methods that are self explanatory as such:

	public void upPressed() {
		keys.get(keys.put(Control.UP, true));
	}
	public boolean keyDown(int keycode) {
		switch (keycode) {
		case Keys.W:
		case Keys.UP:
			upPressed();
			break;
		case Keys.S:
		case Keys.DOWN:
			downPressed();
			break;
		default:
			assert false;
		}
		return false;
	}

Now the problem persists in this part here, I want to NOT do this as it makes the object useless since it would be constraint to only 1 paddle being controlled despite it being a 2 player game:

	public void processInput(){
		
		if(keys.get(Control.UP)){
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, 10f);
			System.out.print("Am I moving?");
		}else if(keys.get(Control.DOWN)){
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, -10f);
		}else if(!keys.get(Control.UP) && !keys.get(Control.DOWN)){
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, 0);
		}
	}

So this is in my update method and that update method is within the GameScreen, the code is pretty straight forward, you press up, booleans take it from there and the if statements decide what to do, however as you can see I am having to access the GameScreen.p1Paddle in order to get that specific body. The way I want it is body.getBody, since I have the field declared for it.

Whenever I do this with just a single controller controller the p1 paddle, I get a null exception on the following line in bold ([b]):

		}else if(!keys.get(Control.UP) && !keys.get(Control.DOWN)){
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, 0);[/b]
		}

Ideally what I want to do is create 2 controllers, then use the multiplexer to handle both seperately. However I see some issues, how on earth can it tell which paddle should move to which key, as you can see I have it setup to be controlled with either W/S and Up/Down.

Am I going about this wrong?

EDIT: Fixed the null exception problem, now I just need a way for each controllers to control specific paddles without getting mixed up

Pardon me if I skipped something, but what’s wrong with InputProcessor/Gdx.input.isKeyPressed()?

I done it like this last time is all.

Also that would have the same problem would it not? If I am using Paddle.getBody, how would it tell apart the paddles?

The idea behind it is to stop the controller being constraint to 2 controllers, later I will be added p1 vs cpu, p2 vs cpu and cpu vs cpu. Therfore i need seperate controllers for each and be able to create them without having to go into the PaddleControl class and add GameScreen.p1Paddle etc etc.

You need 2 mappings:

  • input -> action
  • input -> body

This means that You need to be able to understand to which body and to which action the current input goes.
Your currently have only the input -> action part. (if Keys.W doPressUp() )

How You implement the mappings is largely up to You but You need to know which key to goes where. Here is small something to get an idea.
Lets say, P1 has the controls of W and S and P2 has I and K (2 players on 1 keyboard)
You can do something like that


switch(inputKey)
{
    case S:
    case W: this.body = P1.paddle.body;break;
    case I:
    case K: this.body = P2.paddle.body;break
}

switch ( inputKey)
{
    case W:
    case I: performUp(this.body);break;
    case S:
    case K: performDown(this.body);break;
}

Basically 2 selects. This is quick and dirty, apply abstraction and design according to own desire.

I uh… I don’t entirely understand why you would ever structure an input handler like that. For one, just use LibGDX’s built in one, don’t reinvent the wheel. Two, your structure is bad. Don’t use hashmaps, use an array of booleans. Loop through, set them all the false each frame and then get a key code and change the corresponding boolean to true.

Well my Java skills are pretty weak and I am still learning proper design and use.

Can you post a rough idea of what you mean?

So basically create a boolean array that holds a key status, true or false. Loop through the array in my render method and adjust movement depending on the values.

I understand all that but how would it work if I am setting them to false every frame?, if I continue to hold the key down will setting a false boolean to override my key? as if I lifted my finger?

You are not going to loop through the array. As a rough idea, most of the keys that exist on a standard ps/2 keyboard with 106 keys have keycode values which are less than 603. So, creating an array of 603 elements will suffice. So


private static boolean[] KEYS = new boolean[603];

Now in with the KeyListener methods, this array can be populated by


public void keyPressed(KeyEvent e)
{
    KEYS[e.getKeyCode()] = true;
}

public void keyReleased(KeyEvent e)
{
    KEYS[e.getKeyCode()] = false;
}

And then you can easily query them every frame with


if (KEYS[KeyEvent.VK_LEFT))
{
    // Left arrow has been pressed
}

This basically works with all the keycodes are defined as integers. This is actually for Java2D but there should be an identical one for LibGdx.

For libgdx

Gdx.input.isKeyPressed(Input.Key.W)

Ah right this makes sense. I have tried to implement this and came up with this for my class:

package com.gibbo.pongv2.controller;

import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputAdapter;
import com.gibbo.pongv2.screens.GameScreen;

public class PaddleControl extends InputAdapter {
	
	private boolean[] KEYS = new boolean[603];

	@Override
	public boolean keyDown(int keycode) {
		KEYS[keycode] = true;
		return true;
	}

	@Override
	public boolean keyUp(int keycode) {
		KEYS[keycode] = false;
		return true;
	}

	@Override
	public boolean scrolled(int amount) {
		// TODO Auto-generated method stub
		return false;
	}
	
	public void processInput(){
		if(KEYS[Keys.W]){	
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, 10);
		}else{
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, 0);
		}
		
	}
	
	public void update(float delta){
		processInput();
	}

}

I now have this, which is MUCH less code than before and seems to do the same thing.

Now I still have the problem of moving each paddle seperate.
Even if I use GameScreen.p1Paddle it still moves p2 paddle and vice versa, it can’t tell the different between them.

So now all I want to do is have 2 paddles, 1 controller. I have to head out for dinner so will have a good bash at it later, thanks everyone!

I dont understand the problem, just have seperate variables that you increase/decrease in the input handler and then call something like paddle1.move(x1, y1) and paddle2.move(x2, y2). I still would recommend using the libgdx way though. You’re reinventing the circle here.

As xsvenson said, LibGdx had this ability. It already manages what I’ve said underneath. So you can now change the class code to


package com.gibbo.pongv2.controller;

import com.badlogic.gdx.Input.Keys;
import com.gibbo.pongv2.screens.GameScreen;

public class PaddleControl
{

    public void processInput()
    {
        if (Gdx.input.isKeyPressed(Input.Key.W))
        {
            GameScreen.p1Paddle.getBody().setLinearVelocity(0, 10);
        }
        else
        {
            GameScreen.p1Paddle.getBody().setLinearVelocity(0, 0);
        }
    }

    public void update(float delta)
    {
        processInput();
    }

}

This is much less code to than your previous ones.

Yeah I am really making this more complex than it needs to be haha.

OK I will use the gdx.input.isKeyPressed method, at least I know how to do it without that at least :slight_smile:

I can not get this to work lol, it seems that only 1 paddle actually gets recognized at any given time, normally the most recently created.

I have this for my controller class:

public class PaddleControl {
	
	
	
	public void processInput(){
		// Player 1 controls
		if(Gdx.input.isKeyPressed(Keys.W)){	
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, 10);
			System.out.println("Test");
		}else if(Gdx.input.isKeyPressed(Keys.S)){
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, -10);
			System.out.println("Test");
		}else{
			GameScreen.p1Paddle.getBody().setLinearVelocity(0, 0);
			
		}
		
		// Player 2 controls
		if(Gdx.input.isKeyPressed(Keys.UP)){
			GameScreen.p2Paddle.getBody().setLinearVelocity(0, 10);
		}else if(Gdx.input.isKeyPressed(Keys.DOWN)){
			GameScreen.p2Paddle.getBody().setLinearVelocity(0, -10);
		}else{
			GameScreen.p2Paddle.getBody().setLinearVelocity(0, 0);
		}
		
	}
	
	
	
	public void update(float delta){
		processInput();
	}

}

As suggested by everyone here, much cleaner code and does the same thing.

This is my gamescreen:

public class GameScreen extends InputAdapter implements Screen {
	
	private World world;
	private Box2DDebugRenderer debugRender;
	private OrthographicCamera cam;
	
	// Scene2D animation stuff
	TweenManager tweenManager;
	
	// For drawing images
	private SpriteBatch batch;
	private Sprite pongTableSprite;	
	private Array<Body> tmpBodies = new Array<Body>();	

	// Paddle stuff
	public static Paddle p1Paddle;
	public static Paddle p2Paddle;
	
	// Paddle control
	private PaddleControl paddleControl;
	
	private static final float STEP_TIME = 1f / 60f;

	@Override
	public void render(float delta) {
		// TODO Test this, I am not clearing the screen since the main menu has the same background
//		Gdx.gl.glClearColor(0, 0, 0, 1);
//		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		
		debugRender.render(world, cam.combined);
		
		// update our animations
		tweenManager.update(delta);
		cam.update();

		// Start drawing shit
		batch.setProjectionMatrix(cam.combined);
		cam.update();
		batch.begin();
		pongTableSprite.draw(batch);
		
		
		//Iterate through bodies, get sprite
		world.getBodies(tmpBodies);
		for(Body body : tmpBodies){
			if(body.getUserData() instanceof Sprite){
				Sprite sprite = (Sprite) body.getUserData();
				sprite.setPosition(body.getPosition().x - sprite.getWidth() / 2, body.getPosition().y - sprite.getHeight() / 2);
				sprite.setRotation(body.getAngle() * MathUtils.radiansToDegrees);
				sprite.draw(batch);
			}
		}
		
		batch.end();		
				
		// TODO Remove later
		if(Gdx.input.isKeyPressed(Keys.ESCAPE)){
			((Game) Gdx.app.getApplicationListener()).setScreen(new MainMenuScreen());
		}
		
		paddleControl.update(delta);
		

		
		// Update all box2d related calculations
		world.step(STEP_TIME, 8, 5);

	}

	@Override
	public void resize(int width, int height) {


	}

	@Override
	public void show() {	
		// Setup tween manager
		tweenManager = new TweenManager();
		Tween.registerAccessor(Sprite.class, new SpriteAccessor());
		
		// Create world
		world = new World(new Vector2(0, 0), true);		
		// Create camera
		cam = new OrthographicCamera(30, 14);	
		cam.position.set(15, 7f, 0);
		// Create debug renderer
		debugRender = new Box2DDebugRenderer();		
		
		//Create sprites and textures
		batch = new SpriteBatch();
		pongTableSprite = new Sprite(new Texture("img/pongTable.png"));
		pongTableSprite.setSize(30, 14);
		
		// Create the paddles
		p1Paddle = new Paddle(world, 1f, 7f);
		p2Paddle = new Paddle(world, 28f, 7f);
		
		// Background animation
		// TODO test this, it is supposed to fade a the background in from nothing
		// instead it fades out the main menu text and writing by pasting a new image over
		Tween.set(pongTableSprite, SpriteAccessor.ALPHA).target(0).start(tweenManager);
		Tween.to(pongTableSprite, SpriteAccessor.ALPHA, 1.5f).target(1).start(tweenManager);
		// Fade in for paddles
		world.getBodies(tmpBodies);
		for(Body body : tmpBodies){
			if(body.getUserData() instanceof Sprite){
				Sprite sprite = (Sprite) body.getUserData();
				sprite.setPosition(body.getPosition().x - sprite.getWidth() / 2, body.getPosition().y - sprite.getHeight() / 2);
				sprite.setRotation(body.getAngle() * MathUtils.radiansToDegrees);
				Tween.set(sprite, SpriteAccessor.ALPHA).target(0).start(tweenManager);
				Tween.to(sprite, SpriteAccessor.ALPHA, 1.5f).target(1).start(tweenManager);
			}
		}
		
		// Create controller
		paddleControl = new PaddleControl();
		
	}

	@Override
	public void hide() {
		dispose();
	}

	@Override
	public void pause() {

	}

	@Override
	public void resume() {

	}

	@Override
	public void dispose() {
		batch.dispose();
		debugRender.dispose();
	}

}

I can’t quite figure out what is wrong here, this is a list of things I have tried:

  • Remove each paddle and test separately, both work 100% fine on there own without the other
  • Created an update method in the Paddle class to check if they are moving on the Y axis, only the most recent created paddle moves
  • Removed sprites to make sure it was not a sprite update problem
  • Remove graphics completely to make sure it was not a problem caused by the background[li]

Nothing seems to work, the input from the left paddle just gets completely ignored lol.

any ideas?

Heh, moral of the story…don’t use static when you don’t need to. Seems to have fixed it:


-- omitted for clarity --
public class Paddle {
	
	private Body body;
	private Fixture fixture;
	private float sizeX = 0.1f, sizeY = 1.15f;
	
	Sprite paddleSprite;
-- omitted for clarity --

I had the Body set to private static for some weird reason. Fixed now.

The Controller class is kind of unnecessary - why not just do the polling/movement directly in the paddle class?

I could yeah, I could even put it in the GameScreens render method but just have this thought that if the game was complex, with a lot of controls such as zooming, panning, keyboard shortcuts. May as well practice doing this stuff in a seperate class to keep it neat.

Or is that a bad idea? It’s not as if it is causing a problem imo, seems to work just fine.

What you think?

Cheers