简体   繁体   English

运动时闪烁(不使用JPanel或Swing)

[英]Flicker on movement (Not using JPanel or Swing)

first post here so forgive me if this is the absolute wrong thing but I'm in a tiny bit of a pickle. 如果您的确是错的话,请原谅我,但我有点不高兴。 - My computer science class has us making our own game/program from scratch, but our teacher specifically ignored the existence of swing and said that it was out of his realm to teach it so from my readings it seems as if a game is almost out of the question, with what I've written so far my main sprite and debugging information flickers violently on key input. -我的计算机科学课上让我们从头开始制作了自己的游戏/程序,但是我们的老师特别忽略了挥杆的存在,并说这是他的境界来教球,所以从我的阅读中看来,好像一个游戏几乎就要用完了问题,到目前为止,我写的主要内容和调试信息在按键输入上剧烈闪烁。 Is there a way around this? 有没有解决的办法?

I know JPanel/JFrame/Swing would fix this, but we weren't taught it and all I've read so far is confusing me to no end, if someone would be able to help me convert my code to use it that would be amazing as I learn easier from dissecting it.. Which may seem a bit dodgy but it really is the easiest way to learn from. 我知道JPanel / JFrame / Swing可以解决此问题,但是我们还没有教过它,到目前为止,如果有人能够帮助我转换代码以使用它,那我将无所适从令人惊讶,因为我从解剖中学到的东西变得更容易。。这似乎有些不可靠,但这确实是最简单的学习方法。 My code so far is; 到目前为止,我的代码是;

public class GameThing extends Applet implements KeyListener {

    Image rightsprite, picture;
    Image leftsprite, picture2;
    Image spritedead, picture3;
    Image background, picture4;

    boolean left;
    boolean dead;
    boolean wall;
    boolean menu;

    int widthX, heightY;
    int bgx, bgy;
    String sr = " ";
    char ch = '*';
    Graphics bufferGraphics;
    Image offscreen;
    Dimension dim;

    @Override
    public void init() {
        rightsprite = getImage(getDocumentBase(), "SpriteRight.png");
        leftsprite = getImage(getDocumentBase(), "SpriteLeft.png");
        spritedead = getImage(getDocumentBase(), "Sprite.png");
        background = getImage(getDocumentBase(), "Background.png");
        requestFocus();
        addKeyListener(this);
        left = false;
        menu = true;
        setSize(600, 380);
        dim = getSize();
        offscreen = createImage(dim.width, dim.height);
        bufferGraphics = offscreen.getGraphics();
        bgx = 0;
        bgy = 0;
    }

    public void pause(final int delay) { // PAUSE COMMAND LIFTED FROM RAY
        // waits a while
        try {
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            return;
        }
    }

    public GameThing() { // SPRITE INITIAL SPAWN LOCATION
        widthX = 100;
        heightY = 300;
    }

    @Override
    public void paint(final Graphics g) { // DRAW MAIN CHARACTER SPRITE AND BACKGROUND
        bufferGraphics.setColor(Color.black); // DRAW GROUND (REPLACE WITH BLOCKS SOON)
        bufferGraphics.drawRect(0, 334, 500, 50);
        bufferGraphics.setColor(Color.green);
        bufferGraphics.fillRect(0, 335, 500, 50);
        g.drawImage(offscreen, 0, 0, this);
        if (menu == true) {
            pause(500);
            g.drawString("Side Scroller Test.", 180, 250);
            pause(1500);
            menu = false;
        }

        if (left == false) { // LEFT KEY MOVEMENT COMMANDS
            g.drawImage(rightsprite, widthX, heightY, this);
            g.drawString("Sprites Current X Location is: " + widthX, 20, 30);
            g.drawString("Sprites Current Y Location is: " + heightY, 20, 50);
            g.drawString("Background X location is: " + bgx, 20, 90);
            g.drawString("Currently: " + sr, 20, 70);

        }

        if (left == true) { // RIGHT KEY MOVEMENT COMMANDS
            g.drawImage(leftsprite, widthX, heightY, this);
            g.drawString("Sprites Current X Location is: " + widthX, 20, 30);
            g.drawString("Sprites Current Y Location is: " + heightY, 20, 50);
            g.drawString("Background X location is: " + bgx, 20, 90);
            g.drawString("Currently: " + sr, 20, 70);
        }

        if (dead == true) { // COMMANDS TO EXECUTE WHEN MAIN CHARACTER DIES
            widthX = 506;
            heightY = 314;
            g.drawImage(spritedead, widthX, heightY, this);
            pause(500);
            g.setColor(Color.white);
            g.drawRect(widthX, heightY, widthX, heightY);
            g.fillRect(widthX, heightY, widthX, heightY);
            pause(500);
            widthX = 100;
            heightY = 300;
            bgx = 0;
            left = true;
            dead = false;
        }

    }

    @Override
    public void update(final Graphics g) { // KEEPS BACKGROUND STATIC
        paint(g);
    }

    public void keyPressed(final KeyEvent e) {
        sr = "blank!";
        ch = '1';
        if (e.getKeyCode() == 39) {
            sr = "Moving Right!";
            widthX = widthX + 7;

            if (widthX <= 121) {
                bgx = bgx;
            } else {
                bgx = bgx - 7;
            }

            left = false;

            if ((widthX > 490) && (heightY > 300)) { // FALL DEATH

                sr = "You Died!";
                widthX = 900;
                heightY = 900;
                dead = true;
            }

            if (widthX == 499) {
                heightY = heightY + 7;
            }
        }

        if (widthX == 2) {
            wall = true;
        }

        if (e.getKeyCode() == 37) {

            if (wall == true) {
                sr = "Wall!";
                if (widthX > 2) {
                    wall = false;
                }
            }
            if (wall == false) {
                sr = "Moving Left!";
                widthX = widthX - 7;
                if (bgx >= 0) {
                    bgx = bgx;
                } else {
                    bgx = bgx + 7;
                }
                left = true;
            }
        }

        if (e.getKeyCode() == 38) {
            sr = "Jumping!";
        }
        repaint();
    }

    public void keyTyped(final KeyEvent e) {
        ch = e.getKeyChar();
        repaint();
    }

    public void keyReleased(final KeyEvent e) {
    } // key released
}

ps: Jumping with the up key has been a problem too, I can't simply add to the Y variable and then subtract as it just goes so fast it's as if it never moved.. The basics of a sidescroller are a lot more than I ever expected.. ps:使用向上键跳也是一个问题,我不能简单地添加到Y变量,然后减去它,因为它是如此之快,就好像它从未移动过。我没想到

I edited your code: 我编辑了您的代码:

public class GameThing extends Applet {

    // External resources
    BufferedImage rightSprite, leftSprite, spriteDead, backgroundImg;

    // Game data
    int state = 1; //0 = left, 1 = right, 2 = dead

    // Geometry
    int locX = 100, locY = 300;
    int bgX = 0, bgY = 0;
    int groundX = 0, groundY = 334, groundW = 500, groundH = 50;
    int appletW = 600, appletH = 480;
    int wallW = 20, wallH = 40, wallX = 20, wallY = groundY - wallH;
    final int STEP_SIZE = 7;

    // Information
    final String X_LOC_STR = "Sprites Current X Location is: ";
    final String Y_LOC_STR = "Sprites Current Y Location is: ";
    final String STATE_STR = "Currently: ";
    final String X_BG_STR = "Background X location is: ";
    String stateString = "";

    // Double buffering
    Image offscreen;
    Graphics bufferGraphics;
    Dimension dim;

    // GUI components
    Panel gamePanel;
    Panel statusPanel = new Panel();
    Label xLocLabel = new Label();
    Label yLocLabel = new Label();
    Label stateLabel = new Label();
    Label xBGLocLabel = new Label();

    @Override
    public void init() {

        // Load images
        try {
            rightSprite = ImageIO.read(new File(getClass().getResource("SpriteRIght.png").getPath()));
            leftSprite = ImageIO.read(new File(getClass().getResource("SpriteLeft.png").getPath()));
            spriteDead = ImageIO.read(new File(getClass().getResource("SpriteDead.png").getPath()));
            backgroundImg = ImageIO.read(new File(getClass().getResource("Background.png").getPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Set the panel that displays data
        statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.Y_AXIS));
        statusPanel.add(xLocLabel);
        statusPanel.add(yLocLabel);
        statusPanel.add(stateLabel);
        statusPanel.add(xBGLocLabel);

        // Create the panel that draws the game and specify its behavior
        gamePanel = new Panel() {

            // Reduces flickering
            @Override
            public void update(Graphics g) {

                paint(g);
            }

            // DRAW MAIN CHARACTER SPRITE AND BACKGROUND
            @Override
            public void paint(Graphics g) {

                bufferGraphics.clearRect(0, 0, dim.width, dim.width);
                bufferGraphics.drawImage(backgroundImg, bgX, bgY, this);
                bufferGraphics.setColor(Color.BLACK); // DRAW GROUND (REPLACE WITH BLOCKS SOON)
                bufferGraphics.drawRect(groundX, groundY, groundW, groundH);
                bufferGraphics.setColor(Color.GREEN);
                bufferGraphics.fillRect(groundX, groundY + 1, groundW, groundH);
                bufferGraphics.setColor(Color.RED);
                bufferGraphics.fillRect(wallX, wallY, wallW, wallH);

                switch (state) {
                    case 0: bufferGraphics.drawImage(leftSprite, locX, locY, this);
                            break;
                    case 1: bufferGraphics.drawImage(rightSprite, locX, locY, this);
                            break;
                    case 2: bufferGraphics.drawImage(spriteDead, locX, locY, this);
                            // After death wait a bit and reset the game
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {

                                    pause(2000);
                                    reset();
                                }
                            });
                            break;
                }
                g.drawImage(offscreen, 0, 0, this);
            }
        };

        // Set the applet window
        setLayout(new BorderLayout());
        add(statusPanel, BorderLayout.PAGE_START);
        add(gamePanel);

        // Set double buffering
        setSize(appletW, appletH);
        dim = getSize();
        offscreen = createImage(dim.width, dim.height);
        bufferGraphics = offscreen.getGraphics();

        // Set the panel that draws the game
        gamePanel.addKeyListener(new Controls());
        gamePanel.requestFocusInWindow();

        updateLabels();
    }   

    // PAUSE COMMAND LIFTED FROM RAY (who is Ray?)
    // waits a while
    public void pause(int delay) {

        try {
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // Set to start parameters
    public void reset() {

        locX = 100; locY = 300;
        bgX = 0;
        state = 1;
        gamePanel.repaint(); // These 2 lines automatically restart the game
        updateLabels();      // Remove if you want to stay on the death screen until next press
    }

    public void updateLabels() {

        xLocLabel.setText(X_LOC_STR + locX);
        yLocLabel.setText(Y_LOC_STR + locY);
        stateLabel.setText(STATE_STR + stateString);
        xBGLocLabel.setText(X_BG_STR + bgX);
    }

    private class Controls extends KeyAdapter {

        public void keyPressed(KeyEvent e) {

            switch (e.getKeyCode()) {

                case KeyEvent.VK_RIGHT:
                    stateString = "Moving Right!";
                    locX += STEP_SIZE;
                    state = 1;
                    if (locX > 121)
                        bgX -= STEP_SIZE;
                    // FALL DEATH
                    if ((locX > 490) && (locY > 300)) {
                        stateString = "You Died!";
                        state = 2;
                    }
                    // Start fall
                    if (locX == 499)
                        locY += STEP_SIZE;
                    // Check wall collision
                    if (locX >= wallX && locX <= wallX + wallW) {
                        locX -= STEP_SIZE;
                        stateString = "Wall!";
                    }
                    break;

                case KeyEvent.VK_LEFT:
                    stateString = "Moving Left!";
                    locX -= STEP_SIZE;
                    state = 0;

                    if (bgX < 0) 
                        bgX += STEP_SIZE;
                    // Check wall collision
                    if (locX >= wallX && locX <= wallX + wallW) {
                        locX += STEP_SIZE;
                        stateString = "Wall!";
                    }
                    break;

                case KeyEvent.VK_UP:
                    // Implement jumping
                    stateString = "Jumping!";
                    break;
            }

            // Repaint the game panel and update the data panel
            gamePanel.repaint();
            updateLabels();
        }
    }
}

Start by testing and understanding the changes : 首先测试并了解更改

  • I created a separate panel for the current data display and one for the game (which gets repainted). 我为当前数据显示创建了一个单独的面板,为游戏创建了一个面板(将其重新绘制)。
  • I added a few help functions. 我添加了一些帮助功能。
  • I made a lot of syntax changes (eg multiple if else replaced by switch , VK_UP instead of 37 and such). 我做了很多的语法的变化(例如多个if else替换switchVK_UP 37和这样的代替)。
  • Better to have all values in one place and read them from there instead of spreading them in various places (still needs a bit of work but it will change when advancing with the code). 最好将所有值放在一个位置并从那里读取它们,而不是将它们分散在各个位置(仍然需要一些工作,但是随着代码的前进它将改变)。
  • Make constant values (values that don't change) final . 将常量值(不变的值)定为final
  • In Java conventions, constants are named with uppercase and underscore (_) (so Color.RED , not Color.red ). 在Java约定中,常量使用大写字母和下划线(_)命名(因此Color.RED而不是Color.red )。
  • Better to use @Override when overriding methods even when the compiler understands it. 最好在重写方法时使用@Override ,即使编译器理解了它也是如此。
  • Do not block the event dispatch thread (EDT) with sleep or expensive computations. 不要用sleep或昂贵的计算来阻塞事件分发线程(EDT)。 Methods like paint key events are meant to execute quickly. 诸如paint键事件之类的方法旨在快速执行。 I used invokeLater to pause only after the EDT finished processing all queued events. 我仅在EDT处理完所有排队事件后才使用invokeLater暂停。 (I understand that this threading subject comes as a bomb from nowhere, just bear with me). (我知道这个线程主题无处不在,只要忍受我吧)。

Looking forward for the current state of the code (no new features): 期待代码的当前状态(无新功能):

  • state should probably be an enum or something close to it instead of random numbers. state应该是一个enum或接近它的东西,而不是随机数。
  • Using Shape for drawn objects instead of specifying their parameters during painting. 在绘制对象时使用Shape而不是指定其参数。

Double buffering implementation notes: 双缓冲实现说明:

You create an off-screen Image object offscreen and a Graphics object to draw on it - bufferGraphics . 您将在屏幕offscreen创建一个屏幕offscreen Image对象,并在其上绘制一个Graphics对象bufferGraphics Inside paint , you use only bufferGraphics for the drawings. paint内部,您仅对 bufferGraphics使用bufferGraphics You in fact draw on offscreen even though the paint belongs to gamePanel (or any other object and its paint ), slightly confusing. 实际上,即使paint属于gamePanel (或任何其他对象及其paint ),您实际上还是在offscreen paint ,这有些令人困惑。 When you finish drawing everything off-screen, you throw it all in one go onto the screen with g.drawImage(offscreen...) which uses gamePanel 's Graphics object g . 当您在屏幕外完成绘制所有内容时,您可以使用g.drawImage(offscreen...)将其全部放入屏幕,后者使用gamePanelGraphics对象g

The idea is like drawing everything on a paper and then putting it in front of the camera instead of drawing bit by bit on the camera, or something like that... 这个想法就像将所有东西都画在纸上然后放到相机的前面,而不是一点一点地在相机上画,或者类似的东西...

Currently, the off-screen image is the size of the whole applet and not just the game panel, but it makes little difference. 当前,屏幕外图像是整个applet的大小,而不仅仅是游戏面板的大小,但差别不大。

Relevant differences from a Swing approach: Swing方法相关的差异:

  • Replace Label with JLabel , Panel with JPanel etc. JLabel替换Label ,用JPanel替换Panel等。
  • Swing components automatically implement double buffering (see JComponent 's setDoubleBuffered and isDoubleBuffered ). Swing组件自动实现双重缓冲(请参见JComponentsetDoubleBufferedisDoubleBuffered )。 Remove all of those off-screen shenanigans and draw directly with Graphics g . 删除所有这些屏幕外的恶作剧,并直接使用Graphics g绘制。 No need to override update also. 也无需覆盖update
  • Painting in Swing is done in paintComponent and not in paint . Swing中的paint是在paintComponent完成的,而不是在paint
  • Swing runs from its own thread and not from the EDT (not automatically, but easily done). Swing从自己的线程运行,而不是从EDT运行(不是自动运行,但很容易完成)。 There are some benefits to this I won't get into right now. 我暂时不会对此有一些好处。
  • Key events are better handled by key bindings (Swing only) rather than the key listener. 通过键绑定(仅限于Swing)而不是通过键侦听器,可以更好地处理键事件。 This manifests in responsiveness, native environment independence and more (eg concurrent key events). 这表现为响应能力,本机环境独立性以及更多(例如,并发键事件)。

@user1803551 Okay, so it's all sorted back to how it was again. @ user1803551好的,一切都又回到了原来的样子。 - The scrolling background is in (Just a couple things around the place needed some tweaking from your post), and I understand that the final line "gamePanel.repaint(); is what's refreshing everything on the screen, since the background needs to be updated with every key input, it shouldn't be limited to the tiny square from your code, right? This now refreshes the whole screen, but I assume this isn't what is wanted because it just results in the flickering, the buffergraphics of the green rectangle at the bottom however don't flicker. So my assumption is to convert -滚动的背景在其中(仅需要从您的帖子中进行一些调整),我知道最后一行“ gamePanel.repaint();”会刷新屏幕上的所有内容,因为背景需要更新每个按键输入,它不应该局限于代码中的微小方块,对吗?现在可以刷新整个屏幕,但是我认为这不是所需要的,因为它只会导致闪烁,底部的绿色矩形不会闪烁,所以我的假设是转换

g.drawImage(backgroundImg, bgx, bgy,this); 

into buffergraphics? 进入缓冲图形?

-EDIT- Moved the main Sprite into bufferGraphics aswell as the background image. -编辑-将主Sprite和背景图像移到bufferGraphics中。 Flickering is now almost gone, but after holding down an arrow key for a little bit the screen will flicker just for a moment and then return to normal. 现在,闪烁几乎消失了,但是按住箭头键几秒钟后,屏幕将闪烁片刻,然后恢复正常。

public class GameTest extends Applet {
/**
 * 
 */
private static final long serialVersionUID = -1533028791433336859L;
Image rightsprite, leftsprite, spritedead, backgroundImg, offscreen;
boolean left = false, dead = false, wall = false, menu = true;
int locX = 100, locY = 300, width, height, bgx = 0, bgy = 0;
final int STEP_SIZE = 7;
final String X_LOC_STR= "Sprites Current X Location is: ";
final String Y_LOC_STR= "Sprites Current Y Location is: ";
final String STATE_STR= "Currently: ";
final String X_BG_STR= "Background X location is: ";
String sr = " ";
Graphics bufferGraphics;
Dimension dim;
Panel gamePanel;
Panel statusPanel = new Panel();
Label xLoc = new Label(X_LOC_STR);
Label yLoc = new Label(Y_LOC_STR);
Label state = new Label(STATE_STR);
Label xBGLoc = new Label(X_BG_STR);
public void init() {
    try {
        rightsprite = ImageIO.read(new File(getClass().getResource("SpriteRight.png").getPath()));
        leftsprite = ImageIO.read(new File(getClass().getResource("SpriteLeft.png").getPath()));
        spritedead = ImageIO.read(new File(getClass().getResource("SpriteDead.png").getPath()));
        backgroundImg = ImageIO.read(new File(getClass().getResource("Background.png").getPath()));
    } catch (IOException e) {
        e.printStackTrace();
    }
    height = rightsprite.getHeight(this);
    width = rightsprite.getWidth(this);
    statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.Y_AXIS));
    statusPanel.add(xLoc);
    statusPanel.add(yLoc);
    statusPanel.add(state);
    statusPanel.add(xBGLoc);
    gamePanel = new Panel() {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        // DRAW MAIN CHARACTER SPRITE AND BACKGROUND
        @Override
        public void paint(Graphics g) {
            bufferGraphics.drawImage(backgroundImg,bgx, bgy, this);
            bufferGraphics.setColor(Color.black); // DRAW GROUND (REPLACE WITH BLOCKS SOON)
            bufferGraphics.drawRect(0, 334, 500, 50);
            bufferGraphics.setColor(Color.green);
            bufferGraphics.fillRect(0, 335, 500, 50);




            if (menu == true) {
                pause(500);
                g.drawString("Side Scroller Test.", 180, 250);
                pause(1500);
                menu = false;
            }
            // LEFT KEY MOVEMENT COMMANDS
            if (left == false)
                bufferGraphics.drawImage(rightsprite, locX, locY, this);
            // RIGHT KEY MOVEMENT COMMANDS
            else
                bufferGraphics.drawImage(leftsprite, locX, locY, this);
            // COMMANDS TO EXECUTE WHEN MAIN CHARACTER DIES
            if (dead == true) {
                locX = 506;
                locY = 314;
                bufferGraphics.drawImage(spritedead, locX, locY, this);
                pause(500);
                bufferGraphics.setColor(Color.white);
                bufferGraphics.drawRect(locX, locY, width, height);
                bufferGraphics.fillRect(locX, locY, width, height);
                pause(500);
                locX = 100;
                locY = 300;
                bgx = 0;
                bgy = 0;
                bufferGraphics.clearRect(locX, locY, width, height);
                left = true;
                dead = false;
            }
            g.drawImage(offscreen, 0, 0, this);
            super.paint(g);

        }
    };
    setLayout(new BorderLayout());
    add(statusPanel, BorderLayout.PAGE_START);
    add(gamePanel);
    setSize(600, 480);
    dim = getSize();
    offscreen = createImage(dim.width, dim.height);
    bufferGraphics = offscreen.getGraphics();
    gamePanel.addKeyListener(new Controls());
    gamePanel.requestFocusInWindow();
}
// waits a while
public void pause(int delay) {
    try {
        Thread.sleep(delay);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
private class Controls extends KeyAdapter {
    public void keyPressed(KeyEvent e) {
        sr = "blank!";
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            sr = "Moving Right!";
            locX += STEP_SIZE;
            if (locX > 121)
                bgx = bgx - STEP_SIZE;

                left = false;
            // FALL DEATH
            if ((locX > 490) && (locY > 300)) {
                sr = "You Died!";
                locX = 900;
                locY = 900;
                dead = true;
                gamePanel.repaint();
            }
            if (locX == 499) {
                locY += STEP_SIZE;
            }
        }
        if (locX == 2) {
            wall = true;
        }
        else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            if (wall == true) {
                sr = "Wall!";
                if (locX > 2) {
                    wall = false;
                }
            }
            else {
                sr = "Moving Left!";
                locX -= STEP_SIZE;
                if (bgx < 0) 
                    bgx += STEP_SIZE;
                left = true;
            }
        }
        if (e.getKeyCode() == KeyEvent.VK_UP) {
            sr = "Jumping!";
        }
        xLoc.setText(X_LOC_STR + locX);
        yLoc.setText(Y_LOC_STR + locY);
        state.setText(STATE_STR + sr);
        xBGLoc.setText(X_BG_STR + bgx);
        gamePanel.repaint();
    }
}

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM