【问题标题】:Image flickering when call paint() method调用paint()方法时图像闪烁
【发布时间】:2015-09-24 04:10:15
【问题描述】:
package Game;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.*;

import Maps.*;

public class Window extends JFrame implements KeyListener
{
    private Insets insets;
    private int currentMapX;
    private int currentMapY;

    public Window()
    {
        super();
        setSize(new Dimension(1920, 1080));
        setLayout(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setUndecorated(true);
        setFocusable(true);
        setContentPane(new Container());
        setBackground(Color.BLACK);
        addKeyListener(this);
    }

    public void startGame()
    {
        insets = getInsets();
        currentMapX = 960 - (Game.level_01.getWidth() / 2); 
        currentMapY = 540 - (Game.level_01.getHeight() / 2);
        Game.level_01.setBounds(currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
        add(Game.level_01);
    }

    private void moveMapRight()
    {
        Game.level_01.setBounds(++currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
    }

    private void moveMapLeft()
    {
        Game.level_01.setBounds(--currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
    }

    public void paint(Graphics g)
    {
        super.paint(g);
        Graphics2D g2d = (Graphics2D)g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        Game.player.paint(g2d);
    }

    public void keyPressed(KeyEvent k) 
    {
        if(k.getKeyCode() == KeyEvent.VK_RIGHT) moveMapRight();
        if(k.getKeyCode() == KeyEvent.VK_LEFT) moveMapLeft();
    }
    public void keyReleased(KeyEvent k){}
    public void keyTyped(KeyEvent k){}
}

我有第一个扩展 JFrame 并包含以下类的类。

package Maps;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.io.*;
import java.util.*;

import javax.swing.*;

import Game.*;
import Tiles.*;

public class Level extends JPanel
{
    protected final File txt_MAP;
    protected final ArrayList<Tile> jLabel_MAP;
    protected final ArrayList<Integer> linesLength;
    protected Insets insets = Game.window.getInsets();
    protected int arrayIndex;
    protected int leftIndex;
    protected int topIndex;
    protected int width;
    protected int height;

    public Level(File f)
    {
        super();
        setLayout(null);
        setBackground(Color.BLACK);
        leftIndex = 0;
        topIndex = 0;
        txt_MAP = f;
        jLabel_MAP = new ArrayList<>();
        linesLength = new ArrayList<>();
        arrayIndex = 0;
        readTxt();
        width = linesLength.get(0) * Game.tileSize;
        height = linesLength.size() * Game.tileSize;
        addTiles();
    }

    private void readTxt()
    {
        BufferedReader br = null;
        try
        {
            br = new BufferedReader(new FileReader(txt_MAP));
        }
        catch(FileNotFoundException e){}
        try
        {
            String line = br.readLine();
            while(line != null)
            {
                String[] words = line.split(" ");
                for(int a = 0; a < words.length; a++)
                {
                    for(int b = 0; b < Game.tilesIcons.length; b++)
                    {
                        if(Game.tilesList[b].getName().equals(words[a] + ".gif"))
                        {
                            if(Game.tilesList[b].getName().contains(Game.grass_TYPE)) jLabel_MAP.add(arrayIndex, new Grass(Game.tilesIcons[b]));
                            if(Game.tilesList[b].getName().contains(Game.soil_TYPE)) jLabel_MAP.add(arrayIndex, new Soil(Game.tilesIcons[b]));
                            if(Game.tilesList[b].getName().contains(Game.sky_TYPE)) jLabel_MAP.add(arrayIndex, new Sky(Game.tilesIcons[b]));
                            arrayIndex++;
                        }
                    }
                }
                linesLength.add(words.length);
                line = br.readLine();
            }
        }
        catch(IOException e){}
    }

    private void addTiles()
    {
        for(int i = 0; i < jLabel_MAP.size(); i++)
        {
            jLabel_MAP.get(i).setBorder(null);
            jLabel_MAP.get(i).setBounds(insets.left + leftIndex, insets.top + topIndex, 64, 64);
            add(jLabel_MAP.get(i));
            if(leftIndex == width - Game.tileSize) 
            {
                leftIndex = 0;
                topIndex += 64;
            }
            else leftIndex += 64;
        }
    }

    public int getWidth()
    {
        return width;
    }

    public int getHeight()
    {
        return height;
    }
}

此类扩展 JPanel 并包含 Jlabels 的 arrayList。

package Player;

import java.awt.*;

import Game.*;

public class Player
{
    private int x;
    private int y;
    private int xa;
    private int ya;
    private Graphics2D g2d; 
    public Player(double x, double y)
    {
        super();
        this.x = (int)x;
        this.y = (int)y;
        xa = 0;
        ya = 1;
    }

    public void movePlayer()
    {
        x += xa;
        y += ya;
    }

    public void setDirection(int xa, int ya)
    {
        this.xa = xa;
        this.ya = ya;   
    }

    public void paint(Graphics g)
    {
        g2d = (Graphics2D)g;
        g2d.drawImage(Game.playerImages[6], x , y, Game.tileSize, Game.tileSize, null);
    }

    public int getX()
    {
        return x;
    }

    public int getY()
    {
        return y;
    }

    public int getXA()
    {
        return xa;
    }

    public int getYA()
    {
        return ya;
    }
}

最后这个类是播放器的类,即一个BufferedImage。我的问题是,当我启动程序时,播放器图像开始闪烁,因为当我调用 JFrame 类中的 paint() 方法时,这首先绘制了 jpanel,然后是播放器图像。如何解决图像闪烁? 提前致谢。

【问题讨论】:

  • 你可以打赌你可以修复闪烁的整个stackoverflow存档历史。可能有几件事实际上导致了这个问题......我想知道你的游戏循环代码是如何编程的,你如何将屏幕上的输入转换为游戏逻辑。
  • 1) 为了尽快获得更好的帮助,请发布MCVE(最小完整可验证示例)或SSCCE(简短、自包含、正确示例)。 2) 例如,获取图像的一种方法是热链接到在this Q&A 中看到的图像。

标签: java swing jframe paint bufferedimage


【解决方案1】:
  1. 不要在像JFrame 这样的顶级容器中覆盖paint(Graphics)(它不是双缓冲的)。而是在 JPanel 中进行自定义绘画(双缓冲的)。
  2. 此外,当覆盖任何绘制方法时,立即调用 super 方法,从而绘制背景并有效擦除之前的绘制。

【讨论】:

    【解决方案2】:

    正如其他人所说,您不应该在顶级组件中覆盖 paint(Graphics g),这是问题的一部分,但您还需要小心确保每次重绘只在屏幕上绘制一次:

    我的问题是当我启动程序时,播放器图像启动 闪烁,因为当我在 JFrame 中调用 paint() 方法时 类,这将首先绘制 jpanel,然后绘制播放器图像。

    现在发生的事情是,每次你在你的paint(Graphics screen)方法中调用一个修改Graphics对象的方法,它都是在直接修改屏幕内容,在你画完你真正想要的东西之前强制屏幕刷新到-Player。通过首先绘制到super,然后再次使用您的自定义渲染,您实际上至少在屏幕上绘制了两次,从而导致闪烁。您可以通过双缓冲解决此问题。

    双缓冲涉及首先渲染到图像,然后将该图像绘制到屏幕上。使用Component 类提供的内置方法(记住JPanel 扩展了Component),您可以获得组件可视区域的大小。创建一个相同大小的BufferedImage,然后调用bufferedImage.createGraphics() - 这将为您提供一个Graphics2D 对象,您可以使用该对象在您的bufferedImage 上进行绘制。完成对bufferedImage 的渲染后,请致电screen.drawImage(bufferedImage,0,0,null)。这使您可以根据需要多次修改bufferedImage,而无需实际进行屏幕刷新,然后在一次调用中将缓冲区的内容绘制到屏幕上。

    附加信息

    我早该指出,我对屏幕上的实际布局一无所知,因此您可能需要对播放器屏幕区域的边界以及左上角的位置进行一些额外的检查在对drawImage(Image,x,y,ImageObserver) 的调用中。还要注意 BufferedImage 中的透明度或缺乏透明度 - 如果您在屏幕上的其他重要内容上绘制不透明像素,您很容易迷失方向。

    试试这个(但不要保留它)

    同样,我不建议您在顶级组件中进行所有绘画,但您可以尝试在此期间执行以下操作:

    //This is in the Window class:
    public void paint(Graphics screen)
    {
        //Render everything first to a BufferedImage:
        BufferedImage bufferedImage = ... //Initialize based on Window's
                                          //size and bounds.
        Graphics2D buf = bufferedImage.createGraphics();
        super.paint(buf);
        buf.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                RenderingHints.VALUE_ANTIALIAS_ON);
        Game.player.paint(buf);
    
        //Now paint the buffer to screen:
        int x = ... //set to top-left x-coordinate, probably 0
        int y = ... //set to top-left y-coordinate, probably 0
        screen.drawImage(buf,x,y,null);
    }
    

    【讨论】:

    • 非常感谢!!我明白我必须做什么,但我没有找到我必须编写缺失代码的地方。您能否编辑我的代码或解释我将新代码放在哪里?
    • 您窗口的paint 方法首先调用super.paint(g),然后使用相同的Graphics 对象将绘画委托给Player 实例——这是屏幕的Graphics 对象。在Window.paint(Graphics screen) 的顶部,您需要创建一个BufferedImage 并使用它在那里完成所有绘图。然后,Window.paint(Graphics screen) 的最后一次调用将是调用screen.drawImage(bufferedImage,x,y,null) - 试一试,让我们知道会发生什么。
    • 对不起,我没听明白。我试过了,但我认为我做错了什么。你能编辑我的原始代码并给我看新的吗?
    • 我发布了一些不完整的示例代码来帮助您入门。应该让事情更清楚一点。
    • 我还有一个问题。我试图将paint方法放在jPanel类中,但是当我启动程序时,我看不到播放器。我应该改变什么才能让它工作?
    猜你喜欢
    • 1970-01-01
    • 2017-03-17
    • 2018-06-20
    • 2011-07-04
    • 2020-08-27
    • 2012-08-22
    • 2015-04-25
    • 2012-05-13
    • 2010-10-29
    相关资源
    最近更新 更多