Java:删除可运行画布组件
我已经为这个游戏工作了一段时间了,它实际上内置了不同的游戏模式。首先,在用户丢失或想要退出程序后,我一直通过退出程序来处理执行。因为不仅必须重新打开程序很烦人,而且当用户不明白为什么它会关闭时,我有理由在丢失或想要退出后,让他们返回主菜单 此时出现的问题是,我在游戏设计的主要部分使用了Java:删除可运行画布组件,java,swing,awt,runnable,java-canvas,Java,Swing,Awt,Runnable,Java Canvas,我已经为这个游戏工作了一段时间了,它实际上内置了不同的游戏模式。首先,在用户丢失或想要退出程序后,我一直通过退出程序来处理执行。因为不仅必须重新打开程序很烦人,而且当用户不明白为什么它会关闭时,我有理由在丢失或想要退出后,让他们返回主菜单 此时出现的问题是,我在游戏设计的主要部分使用了swing。这不仅包括主菜单,还包括其他菜单,甚至是游戏的一部分Swing用于按钮和其他主要功能的交互。因此,现在我切换到返回主菜单和所有内容,我必须重写渲染和在窗口之间切换的整个基础 因为我正在重写游戏的rend
swing
。这不仅包括主菜单,还包括其他菜单,甚至是游戏的一部分<代码>Swing用于按钮和其他主要功能的交互。因此,现在我切换到返回主菜单和所有内容,我必须重写渲染和在窗口之间切换的整个基础
因为我正在重写游戏的render
方法,所以我决定创建一个StateRenderer
类。由此,它将处理并决定当前是否需要处理。因此,在run()
方法中,我放置了一行代码,检查它是否需要在菜单状态下渲染
@Override
public void run() {
long lastTime = System.nanoTime();
long timer = System.currentTimeMillis();
final double ns = BILLION / UPDATE_RATE;
double delta = 0;
int updates = 0, frames = 0;
while (running) {
// right here I am checking the state for it
GameState state = CellDefender.getGameState();
if (state == GameState.MAIN_MENU || state == GameState.STORE_MENU || state == GameState.SETTINGS_MENU) continue;
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
update();
updates++;
delta--;
}
render();
frames++;
if (System.currentTimeMillis() - timer >= 1000) {
while (System.currentTimeMillis() - timer >= 1000) // while idling it builds up much, and makes it less annoying when debugging
timer += 1000;
System.out.println("UPS: " + updates + ", FPS: " + frames);
updates = 0;
frames = 0;
}
}
stop();
}
现在,当我决定从主菜单切换到实际的游戏模式时,这很好,但是如果我在模式上失败,或者想退出到主菜单,我会遇到这个严重的错误,我不知道如何修复它:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: component argument pData
at sun.java2d.windows.GDIWindowSurfaceData.initOps(Native Method)
at sun.java2d.windows.GDIWindowSurfaceData.<init>(Unknown Source)
at sun.java2d.windows.GDIWindowSurfaceData.createData(Unknown Source)
at sun.java2d.d3d.D3DScreenUpdateManager.getGdiSurface(Unknown Source)
at sun.java2d.d3d.D3DScreenUpdateManager.createGraphics(Unknown Source)
at sun.awt.windows.WComponentPeer.getGraphics(Unknown Source)
at java.awt.Component.getGraphics(Unknown Source)
at sun.awt.RepaintArea.paint(Unknown Source)
at sun.awt.windows.WComponentPeer.handleEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$200(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$1.doIntersectionPrivilege(Unknown Source)
at java.security.ProtectionDomain$1.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$1.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)
Myrenderer
是我的StateRenderer
,当您实际处于具有Canvas
元素且由GameState
跟踪的屏幕上时,它将用于处理游戏的所有渲染。现在我将向您展示renderer.switchDisplay(Class)
方法中抛出的内容
public void switchDisplay(Class<? extends GameMode> mode) {
if (mode == RegularMode.class) {
currentMode = new RegularMode(size);
setPreferredSize(size);
screen = new Screen(size.width, size.height);
panel.add(currentMode.getFunctionBar(), BorderLayout.NORTH);
panel.add(currentMode.getScoreBar(), BorderLayout.SOUTH);
// -- extra stuff that is similar --
} else return;
JFrame frame = CellDefender.getFrame();
frame.remove(CellDefender.getMainPanel());
panel.add(this, BorderLayout.CENTER);
frame.add(panel);
frame.validate();
frame.repaint();
frame.pack();
currentMode.initialize();
requestFocus();
}
当然,这让我完全感觉到错误在我的StateRenderer
类中的某个地方,更具体地说是与run()
方法相关的任何东西。我已经处理过的关于渲染的循环部分
摘要
因此,当从带有Canvas
组件的面板切换时,我遇到了一个问题,该组件实现了Runnable
。在我的代码中,我处理了当它没有可见的画布
,或者当游戏状态
不适合游戏渲染时的渲染问题。但是,当从当前正在渲染和更新的画布切换到未执行此操作的菜单时,会导致NullPointerException
我想继续感谢所有人和所有人的帮助,因为这个问题真的让我难堪
编辑
当我请求帮助时,我总是决定进行进一步的测试,我发现问题出现在celldender.switchDisplay()
方法的frame.validate()
行中。我不明白为什么会出现问题。根据评论中的讨论,问题很可能与违反Swing“单线程规则”有关:
一旦实现了Swing组件,所有可能影响或依赖于该组件状态的代码都应该在事件调度线程中执行
违反此规则的结果可能是任意奇怪的,但是Swing管理基础架构内部的NullPointerException
s是更常见的结果之一
在许多情况下,这个问题可以通过一个实用的模式来解决:假设您有一个修改Swing组件的方法:
void modifySwingComponents()
{
someComponent.add(someOtherComponent);
someComponent.remove(somethingElse);
someTextComponent.setText("Text");
...
}
然后,您可以通过插入以下内容轻松检查是否违反了单线程规则
System.out.println(Thread.currentThread());
用这种方法。它应该始终打印线程[AWT-EventQueue-0,6,main]
(或类似内容,表示该方法在事件调度线程上执行)。或者,您可以直接查询
System.out.println(SwingUtilities.isEventDispatchThread());
如果从非事件分派线程(EDT)的线程调用该方法,则可以将该方法“包装”到放置在事件队列上的可运行线程中:
void modifySwingComponents()
{
if (SwingUtilities.isEventDispatchThread())
{
// We're on the right thread - direcly call the method
modifySwingComponentsOnEDT();
}
else
{
// Put the task to execute the method in the event queue,
// so that it will be executed on the EDT as soon as possible
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
modifySwingComponentsOnEDT();
}
});
}
}
// Always called on the EDT!
void modifySwingComponentsOnEDT()
{
someComponent.add(someOtherComponent);
someComponent.remove(somethingElse);
someTextComponent.setText("Text");
...
}
但是请注意尽管这看起来很简单,似乎很容易解决某些问题,但它并不能免除您认真检查和记录在哪个线程上执行的方法的责任 我将赌注押在线程问题上,并且非常确定某个线程(不是事件分派线程)正在以某种方式操纵Swing组件。从目前发布的代码来看,很难判断这可能发生在哪里(但无论如何,这仍然只是一个猜测)。switchDisplay
方法是从actionPerformed
方法之外的任何其他地方调用的吗?不是,调用它的地方很少,而且只有从actionPerformed
点调用,以及当你输了游戏时调用。我确实用底部的编辑更新了我的帖子,正如我所说,错误首先在我的CellDefender.switchDisplay()
方法中的frame.validate()
抛出。它被称为“当你输掉比赛时”-在哪个线程上发生?在switchDisplay
方法的开头插入System.out.println(Thread.currentThread())
,查看它是否打印出一个不是AWT事件调度线程的线程的名称它从CellDefender
switchDisplay()方法打印出的线程不是AWT事件调度线程,相反,它是StateRenderer
线程。对于StateRenderer
的switchDisplay方法,反之亦然。
System.out.println(SwingUtilities.isEventDispatchThread());
void modifySwingComponents()
{
if (SwingUtilities.isEventDispatchThread())
{
// We're on the right thread - direcly call the method
modifySwingComponentsOnEDT();
}
else
{
// Put the task to execute the method in the event queue,
// so that it will be executed on the EDT as soon as possible
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
modifySwingComponentsOnEDT();
}
});
}
}
// Always called on the EDT!
void modifySwingComponentsOnEDT()
{
someComponent.add(someOtherComponent);
someComponent.remove(somethingElse);
someTextComponent.setText("Text");
...
}