Como utilizar duas teclas ao mesmo tempo no Java

Galera, é o seguinte…
Eu sou novo na programação de games e estou aprendendo java, então me desculpem se eu parecer muito leigo no assunto mas eu sou de fato iniciante.
Eu estou desenvolvendo um game estilo zelda onde o personagem se move em todas as direções (direita, esquerda, cima, baixo) e já está tudo funcionando bonitinho mas eu quero implementar uma função que tem no jogo Tibia, onde você pode pressionar a tecla CTRL e apertar uma seta para o personagem virar na direção da seta mas sem se mover, só mudar o sprite.

Exemplo: meu personagem estava andando pra cima mas eu quero que ele vire para a direita mas sem se mover para a direita, apenas trocar a animação, ai eu aperto CTRL + seta DIREITA.

Tentei algumas formas mas nada funcionou e eu quero muito fazer funcionar, tem alguma dica?

O meu código está assim atualmente.

}
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
//Andar para direita.
player.right = true;
}else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
//Andar para esquerda
player.left = true;
}

	if(e.getKeyCode() == KeyEvent.VK_UP) {
		//Andar para cima.
			  player.up = true;
	}else if(e.getKeyCode() == KeyEvent.VK_DOWN) {
		//Andar para Baixo.
			  player.down = true;
	}
	
}
@Override
public void keyReleased(KeyEvent e) {
	if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
		player.right = false;
	}else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
		player.left = false;
	}
	
	if(e.getKeyCode() == KeyEvent.VK_UP) {
		//Andar para cima.
		player.up = false;
	}else if(e.getKeyCode() == KeyEvent.VK_DOWN) {
		//Andar para Baixo.
		player.down = false;
	}
	
}

E na classe Player ele está assim:

public class Player extends Entity{

public boolean right,left,up,down;
public int right_dir = 0, left_dir = 1, up_dir = 2, down_dir = 3;
public int dir = right_dir;
public double speed = 0.6;

private int frames = 0, maxFrames = 6, index = 0, maxIndex = 3;
private boolean moved = false;
private BufferedImage[] rightPlayer;
private BufferedImage[] leftPlayer;
private BufferedImage[] upPlayer;
private BufferedImage[] downPlayer;

public Player(int x, int y, int width, int height, BufferedImage sprite) {
	super(x, y, width, height, sprite);
			
	rightPlayer = new BufferedImage[3];
	leftPlayer = new BufferedImage[3];
	upPlayer = new BufferedImage[3];
	downPlayer = new BufferedImage[3];
	
	for(int i = 0; i < 3; i++) {
		rightPlayer[i] = Game.spritesheet.getSprite(442, 0 + (i*17), 16, 16);			
	}
	for(int i = 0; i < 3; i++) {
		leftPlayer[i] = Game.spritesheet.getSprite(391, 0 + (i*17), 16, 16);			
	}
	for(int i = 0; i < 3; i++) {
		upPlayer[i] = Game.spritesheet.getSprite(425 , 0 + (i*17), 16, 16);			
	}
	for(int i = 0; i < 3; i++) {
		downPlayer[i] = Game.spritesheet.getSprite(408 , 0 + (i*17), 16, 16);			
	}
	
}

public void update() {
	moved = false;
	if(right) {
		moved = true;
		dir = right_dir;
		x+=speed;
	}if(left) {
		moved = true;
		dir = left_dir;
		x-=speed;
	}if(up) {
		moved = true;
		dir = up_dir;
		y-=speed;
	}if(down) {
		moved = true;
		dir = down_dir;
		y+=speed;
	}
	
	if(moved) {
		frames ++;
		if(frames == maxFrames) {
			frames = 0;
			index++;
			if(index == maxIndex)
				index = 0;
		}
	}else {
		index = 0;
	}
	
	Camera.x = this.getX() - (Game.WIDTH / 2);
	Camera.y = this.getY() - (Game.HEIGHT / 2);
}

public void render(Graphics g) {
	if(dir == right_dir) {
		g.drawImage(rightPlayer[index],this.getX() - Camera.x,this.getY() - Camera.y,null);
	}else if(dir == left_dir) {
		g.drawImage(leftPlayer[index],this.getX() - Camera.x,this.getY() - Camera.y,null);
	}else if(dir == up_dir) {
		g.drawImage(upPlayer[index],this.getX() - Camera.x,this.getY() - Camera.y,null);
	}else if(dir == down_dir) {
		g.drawImage(downPlayer[index],this.getX() - Camera.x,this.getY() - Camera.y,null);
	}
}

}

Se e é um KeyEvent:

if ( e.isControlDown() ) {
    // a tecla control está pressionada.
}

Para a tecla Shift: e.isShiftDown()
Para a tecla Alt: e.isAltDown()

Sim, mas como eu faço no caso para utilizar o Control juntamente de uma das setas?

Eu criaria um Enum com as direções:

public enum Direction {
    
    NONE,
    RIGHT,
    LEFT,
    UP,
    DOWN;
    
    public static Direction forKeyEvent(KeyEvent ke) {
        switch (ke.getKeyCode()) {
            case KeyEvent.VK_RIGHT :
                return RIGHT;
            case KeyEvent.VK_LEFT :
                return LEFT;
            case KeyEvent.VK_UP :
                return UP;
            case KeyEvent.VK_DOWN :
                return DOWN;
            default :
                return NONE;
        }            
    }
}

Em seguida criaria dois métodos na classe Player: turn e move que recebem como parâmetro a direção.

class Player {
    
    public void turn(Direction direction) {
        // aqui você vira para a direção desejada
    }
    
    public void move(Direction direction) {
        // aqui você move para a direção desejada
    }
}

Aí o meus métodos de tratamento das teclas ficariam assim:

@Override
public void keyPressed(KeyEvent e) {
    Direction direcao = Direction.forKeyEvent(e);
    if (e.isControlDown()) {
        player.turn(direcao);
    } else {
        player.move(direcao);
    }
}

Gente, eu fiquei muito confuso com as respostas que obtive aqui então eu mesmo corri atrás de informações e fui fazendo testes e mais testes, depois de duas semanas eu finalmente consegui fazer um código 100% funcional e estou super satisfeito.

O que eu fiz foi o seguinte, eu criei quatro variáveis booleanas que fazem referência as direções direita, esquerda, cima, baixo, todas são = false.
Depois eu fui ao método KeyPressed e atribui as teclas que itia utilizar para cada uma dessas variáveis como seta direita + control por exemplo.
Depois eu fui a minha classe do player e utilizei um if a mais nos métodos que eu já havia criado para o personagem andar, mas dessa vez eu adicionei um “not” (!) antes das variáveis, e no else eu pus o que eu queria que ele fizesse.

O que deu mais trabalho em tudo isso foi o fato de que a tecla control funciona diferente das outras teclas, a gente chama o método com .isControlDown() e o mesmo já é igual a true e quando você o solta ele passa a ser falso, ou seja, você não precisa dizer depois que ele é falso pois ele mesmo entende que é falso quando você deixa de pressionar.

Segue o código.

public boolean cRight = false, cLeft = false, cUp = false, cDown = false;

@Override
public void keyTyped(KeyEvent e) {
	
}
@Override
public void keyPressed(KeyEvent e) {
	if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
		//Andar para direita.
			  player.right = true;
	}else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
		//Andar para esquerda
			  player.left = true;
	}
	
	if(e.getKeyCode() == KeyEvent.VK_UP) {
		//Andar para cima.
			  player.up = true;
	}else if(e.getKeyCode() == KeyEvent.VK_DOWN) {
		//Andar para Baixo.
			  player.down = true;
	}
	
	if(e.getKeyCode() == KeyEvent.VK_RIGHT && e.isControlDown()) {
		player.cRight = true;
	}else if(e.getKeyCode() == KeyEvent.VK_LEFT && e.isControlDown()) {
		player.cLeft = true;
	}else if (e.getKeyCode() == KeyEvent.VK_UP && e.isControlDown()) {
		player.cUp = true;
	}else if(e.getKeyCode() == KeyEvent.VK_DOWN && e.isControlDown()) {
		player.cDown = true;
	}


@Override
public void keyReleased(KeyEvent e) {
	if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
		player.right = false;
	}else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
		player.left = false;
	}
	
	if(e.getKeyCode() == KeyEvent.VK_UP) {
		//Andar para cima.
		player.up = false;
	}else if(e.getKeyCode() == KeyEvent.VK_DOWN) {
		//Andar para Baixo.
		player.down = false;
	}
	
	if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
		player.cRight = false;
	}else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
		player.cLeft = false;
	}else if (e.getKeyCode() == KeyEvent.VK_UP) {
		player.cUp = false;
	}else if(e.getKeyCode() == KeyEvent.VK_DOWN) {
		player.cDown = false;
	}

public void update() {
moved = false;

	if(!cRight) {
	if(right && World.isFree((int)(x+speed),this.getY())) {
		moved = true;
		dir = right_dir;
		x+=speed;
	}
	}else {
		moved = false;
		dir = right_dir;
			index = 0;
	}

	if(!cLeft) {
	if(left && World.isFree((int)(x-speed),this.getY())) {
		moved = true;
		dir = left_dir;
		x-=speed;
	}
	}else {
		moved = false;
		dir = left_dir;
			index = 0;
	}

	if(!cUp) {
	if(up && World.isFree(this.getX(),(int)(y-speed))) {
		moved = true;
		dir = up_dir;
		y-=speed;
	}
	}else {
		moved = false;
		dir = up_dir;
			index = 0;
	}

	if(!cDown) {
	if(down && World.isFree(this.getX(),(int)(y+speed))) {
		moved = true;
		dir = down_dir;
		y+=speed;
	}
	}else {
		moved = false;
		dir = down_dir;
			index = 0;
	}