When I run the code, the added Example1 class to the JOptionPane (in Frame) should get keyInput and then change the y value of the player instance (in example1), but it doesn't work. Also, how would I be able to rotate the ship on its axis and then move in the direction its facing? Currently it moves in the direction its rotated towards, but it rotates on what seems the coordinates 0,0.
Frame
import javax.swing.*;
import java.awt.*;
/**
* Created by griffin on 12/7/2015.
*/
public class Frame extends JFrame {
public Frame() {
initUI();
}
private void initUI() {
JTabbedPane jtp = new JTabbedPane();
Example1 e1 = new Example1();
Example2 e2 = new Example2();
getContentPane().add(jtp);
jtp.add(e1);
jtp.add(e2);
jtp.addTab("Example 1", e1);
jtp.addTab("Example 2", e2);
setResizable(false);
pack();
setTitle("NLTP");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new Frame();
frame.setVisible(true);
}
});
}
}
Example1
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
/**
* Created by griffin on 12/7/2015.
*/
public class Example1 extends JPanel implements Runnable {
private final int B_WIDTH = 640;
private final int B_HEIGHT = 480;
private int DELAY = 25;
private Thread thread;
Ship player = new Ship(320, 240);
public Example1() {
initScreen();
}
private void initScreen() {
JButton explanation = new JButton("Explanation");
explanation.setBounds(0, 0, 150, 30);
this.setLayout(null);
add(explanation);
addKeyListener(new keyInput());
setFocusable(true);
setBackground(Color.BLACK);
setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
setDoubleBuffered(true);
}
@Override
public void addNotify() {
super.addNotify();
thread = new Thread(this);
thread.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
AffineTransform old = g2d.getTransform();
g2d.rotate(Math.toRadians(player.angle));
g2d.drawImage(player.ship, player.x, player.y, null);
g2d.setTransform(old);
Toolkit.getDefaultToolkit().sync();
}
public void cycle() {
player.y += player.vY;
}
@Override
public void run() {
long beforeTime, timeDiff, sleep;
beforeTime = System.currentTimeMillis();
while (true) {
cycle();
repaint();
timeDiff = System.currentTimeMillis() - beforeTime;
sleep = DELAY - timeDiff;
if (sleep < 0) {
sleep = 2;
}
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
System.out.println("Interrupted: " + e.getMessage());
}
beforeTime = System.currentTimeMillis();
}
}
public class keyInput extends KeyAdapter {
@Override
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
if(key == KeyEvent.VK_W) {
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_W) {
player.vY -= 1;
}
if(key == KeyEvent.VK_A) {
player.angle++;
}
if(key == KeyEvent.VK_D) {
player.angle--;
}
}
}
}
KeyListener
is well known for having focus related issues. Not only does the component need to be focusable, but it has to have keyboard focus before it will register key events. This is an issue as it's all to easy for your component to lose focus for any number of reasons.The solution is to use the key bindings API which was designed to help solve this issue. See How to Use Key Bindings for more details.
Swing uses a passive rendering approach. That is, updates occur when ever the API decides something needs to be updated. See Painting in AWT and Swing for more details
What you really need is some kind of active rendering approach, where updates are made on a regular bases. This can be accomplished in a verity of ways, the simplest would be to use a Swing
Timer
, as it's safe to update the UI (or values that the UI relies on) without the risk of introducing race conditions. See How to use Swing Timers for more detailsSo, this example basically abstracts the input into four basic movements, rotate left/right and up/down. The code doesn't care how those inputs are generated, only that they can be.
It then uses the key bindings API to register actions for the inputs. This is all done with a single
Action
class, which just adds theInput
to aSet
, which theTimer
uses to determine what actions should be applied before requesting the UI be repainted.The rotation is accomplished through the use of a
AffineTransform
. First we translate the origin to the sprites current x/y position, then rotate it around the center of the sprite. This reduces a lot of the complexity/issues related to rotating things (me simple)Just beware, a
Graphics
context is a shared resource, before making these type of changes, you need to make a copy of it (and dispose of it when you are finished)Okay, what about movement? You could have a look at How do I make an entity move in a direction? and How can I move a sprite in the direction it is facing? for more ideas on how you might be able to achieve that