【问题标题】:BufferedImage not being cleared before each rendering每次渲染前未清除 BufferedImage
【发布时间】:2016-01-24 21:00:29
【问题描述】:

我正在尝试通过我正在观看的教程来学习如何构建一个简单的游戏。到目前为止一切都很好,但是当我移动图像时,前一个图像不会被删除或处置。我不确定到底出了什么问题,或者为什么会这样。我有 3 个类,一个主类、一个播放器类和一个 bufferimageloader 类。

主类:

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JFrame;

public class Main extends Canvas implements Runnable {
private boolean running = false;
private Thread thread;
private BufferedImage player;
private Player p;

public void init(){ // load and initiliaze
    BufferedImageLoader loader = new BufferedImageLoader();

    try {
        player = loader.loadImage("/player_shotgun2.png");
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    p = new Player(100, 100, this);

}

private synchronized void start(){
if(running)
    return;
running = true;
thread = new Thread(this);
thread.start();
}

private synchronized void stop(){
    if(!running)
        return;
    running = false;
    try {
        thread.join();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.exit(1);
}

public void run() {
    init();
    long lastTime = System.nanoTime();
    final double amountOfTicks = 60.0;
    double ns = 1000000000 / amountOfTicks;// 1 second divided by 60, run 60 times per second
    double delta = 0;
    int updates = 0;
    int frames = 0;
    long timer = System.currentTimeMillis();
    System.out.println("hi");
    while(running){
        long now = System.nanoTime();
        delta += (now - lastTime) / ns;
        lastTime = now;
        if(delta >= 1){// delta = 1 = 1 second
            tick();
            updates++;
            delta--;
        }
        render();
        frames++;
        if(System.currentTimeMillis() - timer > 1000){
            timer+= 1000;
            System.out.println(updates + " Ticks, Fps " + frames);
            updates = 0;
            frames = 0;
        }
    }
    stop();
}

// Everything thats is updated in the game
private void tick(){
    p.tick();
}

// Everything that is rendered in the game
private void render(){
    BufferStrategy bs = this.getBufferStrategy();
    if(bs == null){
        createBufferStrategy(3);
        return;
    }
    Graphics g = bs.getDrawGraphics();
    //////////////////////////////
    p.render(g);

    //////////////////////////////
    g.dispose();
    bs.show();
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    Main main = new Main();
    JFrame window = new JFrame();
    window.setSize(500,600);
    window.setTitle("Zombie Game");
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.setVisible(true);
    window.add(main);
    main.start();
}

public BufferedImage getPlayerImage(){
    return player;
}

}

播放器类:

import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class Player extends JPanel {

    private double x;
    private double y;
    public BufferedImage player;

    public Player(double x, double y, Main main){
        this.x = x;
        this.y = y;

        player = main.getPlayerImage();
    }

    public void tick(){
        x++;
    }

    public void render(Graphics g){
        super.paintComponent(g);
        g.drawImage(player, (int)x, (int)y, null);
        g.dispose();
    }
}

Bufferedimageloader 类:

import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;

public class BufferedImageLoader {

    private BufferedImage image;

    public BufferedImage loadImage(String path) throws IOException{
        image = ImageIO.read(getClass().getResource(path));
        return image;
    }
}

这是我启动并且图像移动时得到的输出:

【问题讨论】:

    标签: java bufferedimage


    【解决方案1】:

    这是一个名为 Moving Eyes 的简单 Swing 应用程序。当您在 GUI 的绘图区域中移动光标时,GUI 中的眼球会跟随鼠标光标。

    我意识到它没有做你想做的事。我提供此代码以便您了解如何制作简单的 Swing 动画。您可以以此代码为基础制作自己的动画。

    这是 Swing GUI。

    我在创建这个 Swing GUI 时使用了model / view / controller model。这意味着:

    1. 视图可以从模型中读取值。
    2. 视图可能不会更新模型。
    3. 控制器将更新模型。
    4. 控制器将重新绘制/重新验证视图。

    基本上,模型不知道视图和控制器。这允许您将视图和控制器从 Swing 更改为网站或 Android 应用程序。

    模型/视图/控制器模式允许您一次专注于 Swing GUI 的一个部分。通常,您将首先创建模型,然后是视图,最后是控制器。您必须返回并为模型添加字段。

    这是代码:

    package com.ggl.testing;
    
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Point;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseMotionAdapter;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class MovingEyes implements Runnable {
    
        private static final int drawingWidth = 400;
        private static final int drawingHeight = 400;
        private static final int eyeballHeight = 150;
        private static final int eyeballWidthMargin = 125;
    
        private DrawingPanel drawingPanel;
    
        private Eye[] eyes;
    
        private JFrame frame;
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new MovingEyes());
        }
    
        public MovingEyes() {
            this.eyes = new Eye[2];
            this.eyes[0] = new Eye(new Point(eyeballWidthMargin, eyeballHeight));
            this.eyes[1] = new Eye(new Point(drawingWidth - eyeballWidthMargin,
                    eyeballHeight));
        }
    
        @Override
        public void run() {
            frame = new JFrame("Moving Eyes");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            drawingPanel = new DrawingPanel(drawingWidth, drawingHeight);
            frame.add(drawingPanel);
    
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        }
    
        public class DrawingPanel extends JPanel {
    
            private static final long serialVersionUID = -2977860217912678180L;
    
            private static final int eyeballOuterRadius = 50;
            private static final int eyeballInnerRadius = 20;
    
            public DrawingPanel(int width, int height) {
                this.addMouseMotionListener(new EyeballListener(this,
                        eyeballOuterRadius - eyeballInnerRadius - 5));
                this.setBackground(Color.WHITE);
                this.setPreferredSize(new Dimension(width, height));
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
    
                g.setColor(Color.BLACK);
    
                for (Eye eye : eyes) {
                    drawCircle(g, eye.getOrigin(), eyeballOuterRadius);
                    fillCircle(g, eye.getEyeballOrigin(), eyeballInnerRadius);
                }
            }
    
            private void drawCircle(Graphics g, Point origin, int radius) {
                g.drawOval(origin.x - radius, origin.y - radius, radius + radius,
                        radius + radius);
            }
    
            private void fillCircle(Graphics g, Point origin, int radius) {
                g.fillOval(origin.x - radius, origin.y - radius, radius + radius,
                        radius + radius);
            }
    
        }
    
        public class Eye {
            private final Point origin;
            private Point eyeballOrigin;
    
            public Eye(Point origin) {
                this.origin = origin;
                this.eyeballOrigin = origin;
            }
    
            public Point getEyeballOrigin() {
                return eyeballOrigin;
            }
    
            public void setEyeballOrigin(Point eyeballOrigin) {
                this.eyeballOrigin = eyeballOrigin;
            }
    
            public Point getOrigin() {
                return origin;
            }
    
        }
    
        public class EyeballListener extends MouseMotionAdapter {
    
            private final double eyeballDistance;
    
            private final DrawingPanel drawingPanel;
    
            public EyeballListener(DrawingPanel drawingPanel, double eyeballDistance) {
                this.drawingPanel = drawingPanel;
                this.eyeballDistance = eyeballDistance;
            }
    
            @Override
            public void mouseMoved(MouseEvent event) {
                Point p = event.getPoint();
                for (Eye eye : eyes) {
                    Point origin = eye.getOrigin();
                    double theta = Math.atan2((double) (p.y - origin.y),
                            (double) (p.x - origin.x));
                    int x = (int) Math.round(Math.cos(theta) * eyeballDistance)
                            + origin.x;
                    int y = (int) Math.round(Math.sin(theta) * eyeballDistance)
                            + origin.y;
                    eye.setEyeballOrigin(new Point(x, y));
                }
    
                drawingPanel.repaint();
            }
    
        }
    
    }
    

    型号

    Eye 类是一个 Java 对象,它保存了眼睛(圆)的原点和眼球的原点。 Eye 类就是这个简单示例中的模型。

    查看

    MovingEyes 类是定义 JFrame 的类。 MovingEyes 类是视图的一部分。该类的main方法调用SwingUtilities的invokeLater方法,确保Swing组件在Event Dispatch线程上定义和修改。

    我们使用 JFrame。我们不扩展 JFrame。扩展 Swing 组件或任何 Java 类的唯一时间是当您想要覆盖其中一个类方法时。当我谈到绘图面板时,我们会看到这一点。

    MovingEyes 类的构造函数定义了 Eye 类的 2 个实例。 run 方法定义了 JFrame。所有 Swing GUI 的 run 方法中的代码都是相似的。

    DrawingPanel 类构成了视图的其余部分。 DrawingPanel 类扩展了 JPanel,因为我们想要覆盖 paintComponent 方法。 DrawingPanel 类的构造函数设置绘图区域的首选大小,并添加鼠标运动侦听器。鼠标运动监听器是这个 Swing GUI 的控制器。

    DrawingPanel 类的paintComponent 方法首先调用superpaintComponent 方法。这维护了 Swing 绘制链,并且应该始终是被覆盖的 paintComponent 方法的第一条语句。

    DrawingPanel 类的paintComponent 方法中的其余代码用于绘制眼睛。我们在paintComponent 方法中只有绘制(绘画)代码。控制代码属于控制器。

    控制器

    EyeballListener 类是控制器类。在更复杂的 Swing GUI 中可以有多个控制器类。

    EyeballListener 类扩展了 MouseMotionAdapter。您可以实现 MouseMotionListener。我重写了一种方法,所以当我扩展 MouseMotionAdapter 时代码会更短。

    EyeballListener 类的 mouseMoved 方法会在鼠标移动时触发 MouseEvent。我们通过找到从眼睛中心到鼠标位置的θ角来计算眼球中心的新位置。 θ角用于计算眼球的新中心。

    每个 Eye 实例在 for 循环中单独更新。更新双眼后,重新绘制绘图面板。这发生得如此之快,以至于不需要在单独的线程中进行动画循环。

    动画循环更新模型、绘制视图并等待指定的时间段。您将为动画循环使用单独的线程,以便事件调度线程上的 GUI 保持响应。如果您的 GUI 没有响应,您可能在 Event Dispatch 线程上做了太多工作。

    【讨论】:

    • 非常好!由于 OP 似乎对此一无所知,因此可能会放弃设置抗锯齿功能,这会对您的结果产生积极影响。
    【解决方案2】:

    您看过 BufferStrategy 的示例代码吗? https://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferStrategy.html

    您只需要在程序开始时创建一个 BufferStrategy 对象,而不是每一帧。但是您的旧图像没有被删除的原因是您从不删除它。你可以调用 fillRect 来做到这一点。

    【讨论】:

    • 您能解释一下 fillRect 以及如何删除它吗?此外,bufferstrategy 只会被初始化一次,因为它只会在它为 null 时被初始化。
    • 在您的 Main 类中,就在 p.render(g); 行之前,您可以使用 g.setColor(Color.white); g.fillRect(0, 0, getWidth(), getHeight()); 清除屏幕
    • 谢谢,解决了。但是,没有办法完全避免这种情况的发生吗?因为这样做意味着我不能在游戏中添加其他东西。每次我添加敌人或结构之类的东西时,它都会在玩家移动时被删除。
    • 除非你使用一些 java 库来为你清除绘图表面,否则你将不得不手动完成。
    • 您只需要重绘每一帧中的所有内容。不仅仅是玩家。
    猜你喜欢
    • 1970-01-01
    • 2014-08-09
    • 2019-08-06
    • 2019-10-30
    • 1970-01-01
    • 1970-01-01
    • 2014-11-16
    • 2020-02-24
    • 1970-01-01
    相关资源
    最近更新 更多