I created a program which should display a spaceship (essentially two rectangles) and shoot a bullet from it every time I press the space button. It doesn't work, though because apparently, my program is passing in a null Ship reference into one of my other classes, the MoveAction class. I have absolutely no clue why it's like this because in my Main class, I instantiate my Ship object before my Shoot class (calling the constructor that uses the ship object). I'm sorry if this question is a little bit silly as I'm still a novice programmer who doesn't have a clue about what he's doing most of the time :) Here's my code:
我创建了一个程序,它应该显示一个太空飞船(基本上是两个矩形),并在每次按下空格键时从中射出子弹。它不起作用,但是因为很明显,我的程序将一个null的Ship引用传递给我的其他类之一,即MoveAction类。我完全不知道为什么会这样,因为在我的Main类中,我在我的Shoot类之前实例化我的Ship对象(调用使用ship对象的构造函数)。我很抱歉,如果这个问题有点愚蠢,因为我仍然是一个新手程序员,他不知道他大部分时间在做什么:)这是我的代码:
public enum Direction {
LEFT, RIGHT, SPACE
}
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame frame;
Ship s1;
Shoot shoot;
// Set the frame up
frame = new JFrame();
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setVisible(true);
// Get some more necessary objects
s1 = new Ship();
shoot = new Shoot(s1);
frame.getContentPane().add(shoot);
s1.setShoot(shoot);
// Threads
Thread ship = new Thread(s1);
ship.start();
}
}
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class Shoot extends JPanel {
Ship s1;
public Shoot(Ship s1) {
this.s1 = s1;
addKeyBinding(KeyEvent.VK_LEFT, "left.pressed", new MoveAction(true, s1, Direction.LEFT), true);
addKeyBinding(KeyEvent.VK_LEFT, "left.released", new MoveAction(false, s1, Direction.LEFT), false);
addKeyBinding(KeyEvent.VK_RIGHT, "right.pressed", new MoveAction(true, s1, Direction.RIGHT), true);
addKeyBinding(KeyEvent.VK_RIGHT, "right.released", new MoveAction(false, s1, Direction.RIGHT), false);
addKeyBinding(KeyEvent.VK_SPACE, "space.pressed", new MoveAction(true, s1, Direction.SPACE), true);
addKeyBinding(KeyEvent.VK_SPACE, "space.released", new MoveAction(false, s1, Direction.SPACE), false);
setDoubleBuffered(true);
}
@Override
public void paintComponent(Graphics g) {
// Draw the ship
super.paintComponent(g);
s1.draw(g);
g.fill3DRect(40, 50, 10, 10, false);
}
protected void addKeyBinding(int keyCode, String name, Action action, boolean keyPressed) {
if (keyPressed) {
addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, false), name, action);
} else {
addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, true), name, action);
}
}
protected void addKeyBinding(KeyStroke keyStroke, String name, Action action) {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(keyStroke, name);
actionMap.put(name, action);
}
}
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
public class Ship implements Runnable {
int x, y, xDirection, bx, by;
boolean readyToFire, shooting = false;
Rectangle bullet;
Shoot shoot;
public Ship() {
x = 175;
y = 275;
bullet = new Rectangle(0, 0, 3, 5);
}
public void draw(Graphics g) {
// System.out.println("draw() called");
g.setColor(Color.BLUE);
g.fillRect(x, y, 40, 10);
g.fillRect(x + 18, y - 7, 4, 7);
if (shooting) {
g.setColor(Color.RED);
g.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
}
shoot.repaint();
}
public void move() {
x += xDirection;
if (x <= 0)
x = 0;
if (x >= 360)
x = 360;
shoot.repaint();
}
public void shoot() {
if (shooting) {
bullet.y--;
shoot.repaint();
}
}
public void setXDirection(int xdir) {
xDirection = xdir;
}
public void setShoot(Shoot shoot) {
this.shoot = shoot;
}
@Override
public void run() {
try {
while (true) {
shoot();
move();
Thread.sleep(5);
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.HashSet;
import javax.swing.AbstractAction;
public class MoveAction extends AbstractAction {
boolean pressed;
Ship s1;
Direction dir;
private HashSet<Direction> movement;
public MoveAction(boolean pressed, Ship s1, Direction dir) {
System.out.println("moveaction class");
this.pressed = pressed;
this.s1 = s1;
this.dir = dir;
}
@Override
public void actionPerformed(ActionEvent e) {
try {
if (movement.contains(Direction.LEFT)) {
if (pressed) {
s1.setXDirection(-1);
} else {
s1.setXDirection(0);
}
} else if (movement.contains(Direction.RIGHT)) {
if (pressed) {
s1.setXDirection(1);
} else {
s1.setXDirection(0);
}
} else if (movement.contains(Direction.SPACE)) {
if (pressed) {
if (s1.bullet == null)
s1.readyToFire = true;
if (s1.readyToFire) {
s1.bullet.x = s1.x + 18;
s1.bullet.y = s1.y - 7;
s1.shooting = true;
}
} else {
s1.readyToFire = false;
if (s1.bullet.y <= -7) {
s1.bullet = null;
s1.shooting = false;
s1.bullet = null;
s1.bullet = new Rectangle(0, 0, 0, 0);
s1.readyToFire = true;
}
}
}
} catch (NullPointerException ex) {
System.out.println("NullPointerException");
}
}
Stack Trace :)
堆栈跟踪 :)
at MoveAction.actionPerformed(MoveAction.java:24)
at javax.swing.SwingUtilities.notifyAction(Unknown Source)
at javax.swing.JComponent.processKeyBinding(Unknown Source)
at javax.swing.KeyboardManager.fireBinding(Unknown Source)
at javax.swing.KeyboardManager.fireKeyboardAction(Unknown Source)
at javax.swing.JComponent.processKeyBindingsForAllComponents(Unknown Source)
at javax.swing.SwingUtilities.processKeyBindings(Unknown Source)
at javax.swing.UIManager$2.postProcessKeyEvent(Unknown Source)
at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(Unknown Source)
at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(Unknown Source)
at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Window.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$500(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue$4.run(Unknown Source)
at java.awt.EventQueue$4.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
2 个解决方案
#1
1
You never initialize your member variable HashSet<Direction> movement
, that's why you always get a NPE.
您永远不会初始化您的成员变量HashSet
Try this
private Set<Direction> movement = new HashSet<>();
By the way, declaring an implementation class HashSet
as a variable type is a bad practice. You should use an interface Set
instead.
顺便说一下,将实现类HashSet声明为变量类型是一种不好的做法。您应该使用接口Set。
#2
-1
Inside your public Shoot(Ship s1)
constructor you instantiate the new MoveAction(true, s1, Direction.LEFT), true);
, making use of s1
, which also is the name of the field.
在你的公共Shoot(Ship s1)构造函数中,你实例化新的MoveAction(true,s1,Direction.LEFT),true);,使用s1,它也是字段的名称。
The thing is that you are not allowed to reference fields until the constructor finishes it's run. The behavior in such cases in undefined. Probably your compiler thinks the s1
you refer to is the field. It's not the case in my compiler, but I don't know which compiler you are using.
问题是,在构造函数完成运行之前,不允许引用字段。这种情况下的行为是未定义的。可能你的编译器认为你引用的s1是字段。在我的编译器中并非如此,但我不知道您使用的是哪个编译器。
In any case, try to give different names to the argument of the constructor and the field - s1
, and pass to the MoveAction(..)
constructor the argument.
在任何情况下,尝试为构造函数和字段的参数赋予不同的名称 - s1,并将参数传递给MoveAction(..)构造函数。
#1
1
You never initialize your member variable HashSet<Direction> movement
, that's why you always get a NPE.
您永远不会初始化您的成员变量HashSet
Try this
private Set<Direction> movement = new HashSet<>();
By the way, declaring an implementation class HashSet
as a variable type is a bad practice. You should use an interface Set
instead.
顺便说一下,将实现类HashSet声明为变量类型是一种不好的做法。您应该使用接口Set。
#2
-1
Inside your public Shoot(Ship s1)
constructor you instantiate the new MoveAction(true, s1, Direction.LEFT), true);
, making use of s1
, which also is the name of the field.
在你的公共Shoot(Ship s1)构造函数中,你实例化新的MoveAction(true,s1,Direction.LEFT),true);,使用s1,它也是字段的名称。
The thing is that you are not allowed to reference fields until the constructor finishes it's run. The behavior in such cases in undefined. Probably your compiler thinks the s1
you refer to is the field. It's not the case in my compiler, but I don't know which compiler you are using.
问题是,在构造函数完成运行之前,不允许引用字段。这种情况下的行为是未定义的。可能你的编译器认为你引用的s1是字段。在我的编译器中并非如此,但我不知道您使用的是哪个编译器。
In any case, try to give different names to the argument of the constructor and the field - s1
, and pass to the MoveAction(..)
constructor the argument.
在任何情况下,尝试为构造函数和字段的参数赋予不同的名称 - s1,并将参数传递给MoveAction(..)构造函数。