1

我已经在这个游戏上工作了一段时间,它实际上内置了不同的游戏模式。起初,我一直exiting the program在用户丢失或想要退出之后处理执行。因为不仅必须重新打开程序很烦人,而且当用户不明白它为什么关闭时,我有理由在丢失或想要退出后返回主菜单。

此时出现的问题是我swing在游戏设计的主要部分中使用。这不仅包括主菜单,还包括其他菜单,甚至是游戏的一部分。Swing用于按钮和其他主要功能的交互性。所以现在我要切换到返回主菜单和所有内容,我不得不基本上重写整个窗口的基础rendering和在窗口之间切换。

由于我正在重写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)

我理解的唯一部分是我在使用 AWT 做一些完全错误的事情,它处理诸如Graphics和之类的事情Canvas。仅仅用 try..catch 方法吞没错误可能是一个可怕的想法,但话又说回来,我真的不知道它是在哪里引起的。

要详细了解我如何切换,这里是从我的主菜单到实际游戏模式:

private void initListeners() {
    btnRegular.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            setGameState(GameState.REGULAR);
            renderer.switchDisplay(RegularMode.class);
        }
    });
    // more code is here, but useless for now
}

My rendereris myStateRenderer旨在处理游戏的所有渲染,当您实际上位于具有该Canvas元素的屏幕上并由GameState. 现在我将向您展示该renderer.switchDisplay(Class 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();
}

这可能有点低效,但所有这些似乎都工作得很好。现在深入了解当一切都切换回引发所有错误的主菜单时!

这是直接导致错误的运行代码:

public static void switchDisplay() {
    setGameState(GameState.MAIN_MENU); // Switched the game state, should stop looping.
    frame.setContentPane(panel);
    frame.validate();
    frame.repaint();
    frame.pack();
}

当然,这给了我一个完整的感觉,即错误存在于我的StateRenderer班级的某个地方,更具体地说,是任何与该run()方法相关的东西。我已经处理了有关渲染的循环的部分。

总结
因此,当我从具有Canvas组件的面板切换时遇到问题,该组件实现Runnable. 在我的代码中,我已经处理了当它没有可见时渲染的问题Canvas,或者当GameState它不是供游戏渲染的时候。但是,当从当前正在渲染和更新的画布切换到没有这样做的菜单时,会导致 NullPointerException。

我想继续感谢任何人和每个人的帮助,因为这个问题真的让我很难过。

编辑
通过在寻求帮助时我总是决定进行的进一步测试,我发现问题出现在CellDefender.switchDisplay()该行的方法中frame.validate()。我不明白为什么这会导致问题。

4

1 回答 1

3

根据评论中的讨论,该问题很可能与违反 Swing“单线程规则”有关:

一旦实现了一个 Swing 组件,所有可能影响或依赖于该组件状态的代码都应该在事件调度线程中执行。

违反此规则的结果可能是任意奇怪的,但NullPointerException来自 Swing 管理基础架构内部的 s 是更常见的结果。

在很多情况下,这个问题可以通过务实的模式来解决:假设你有一个修改 Swing 组件的方法:

void modifySwingComponents()
{
    someComponent.add(someOtherComponent);
    someComponent.remove(somethingElse);
    someTextComponent.setText("Text");
    ...
}

然后您可以通过插入类似的内容轻松检查是否违反单线程规则

System.out.println(Thread.currentThread());

在这个方法中。它应该总是打印Thread[AWT-EventQueue-0,6,main](或类似的,表明该方法是在事件调度线程上执行的)。或者,您可以直接查询

System.out.println(SwingUtilities.isEventDispatchThread());

如果从不是事件调度线程 (EDT)的线程调用该方法,则可以将此方法“包装”到放入事件队列的 Runnable 中:

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");
    ...
}

但请注意,虽然这看起来很简单,并且似乎很容易解决某些问题,但它并不能免除您勤奋检查和记录在哪个线程上执行哪个方法的责任。

于 2014-03-18T19:49:21.840 回答