【问题标题】:Why can't I erase my Sprite为什么我不能擦除我的 Sprite
【发布时间】:2017-05-10 16:07:45
【问题描述】:

一段时间以来,我一直在尝试让游戏在 Java 中运行,在对其他人的 Sprite 函数感到非常痛苦之后,我自己制作了自己的 Sprite 函数,但不明白为什么我不能删除它。我知道它正在改变背景的像素以显示我的弓箭手精灵,因为它出现了,但无论出于何种原因,我都无法将像素改回原来的样子。有谁知道为什么会这样或我该如何解决? 链接到带有图像的谷歌文档: https://docs.google.com/document/d/1eU6faW1d7valq1yE_Bo09IPMbXuuZ6ZgqUu3BesaJUw/edit?usp=sharing

import javax.swing.*;
import javax.imageio.*;
import java.io.*;
import java.awt.image.BufferedImage;

public class Sprite {
BufferedImage image;
public Sprite(BufferedImage image) throws IOException{
this.image = image;
}
public BufferedImage getSprite(){
return this.image;
}
public int getX(){
return this.image.getMinX();
}
public int getY(){
return this.image.getMinY();
}

//to spawn a sprite on top of another image.
public void spawn(JFrame frame, BufferedImage world,int x, int y) throws 
IOException{
int orig_x = x;
for (int sprite_y = 0; sprite_y < this.image.getHeight(); sprite_y++){
  for (int sprite_x = 0; sprite_x < this.image.getWidth(); sprite_x++){
    int sprite_pixel = this.image.getRGB(sprite_x,sprite_y);
    int sprite_alpha = (sprite_pixel>>24) & 0xff;
    int sprite_red   = (sprite_pixel>>16) & 0xff;
    int sprite_green = (sprite_pixel>>8 ) & 0xff;
    int sprite_blue  =  sprite_pixel      & 0xff;
    int pixel = (sprite_alpha<<24) | (sprite_red<<16) | (sprite_green<<8) | 
    sprite_blue;
    world.setRGB(x,y,pixel);
    x++;
    }
    y++;
    x = orig_x;
    }
    }

    public void erase(JFrame frame,BufferedImage world, BufferedImage 
    orig_world) throws IOException{
    int sprite_x = this.image.getMinX();
    int sprite_y = this.image.getMinY();
    int orig_sprite_x = sprite_x;
    for (int stepper_y = this.image.getMinY(); stepper_y < 
    this.image.getHeight(); stepper_y++){
      for (int stepper_x = this.image.getMinX(); stepper_x < 
      this.image.getWidth(); stepper_x++){
         int sprite_pixel =  orig_world.getRGB(sprite_x,sprite_y);
         //get pixel from orginal sprite
         int sprite_alpha = (sprite_pixel>>24) & 0xff;
         //get alpha value from original sprite
         int sprite_red   = (sprite_pixel>>16) & 0xff;
         //get red   value from original sprite
         int sprite_green = (sprite_pixel>>8 ) & 0xff;
         //get green value from original sprite
         int sprite_blue  =  sprite_pixel      & 0xff;
         //get blue  value from original sprite

         int pixel = (sprite_alpha<<24) | (sprite_red<<16) | 
         (sprite_green<<8) | sprite_blue;
         //set the pixel equal to the old values
         world.setRGB(sprite_x,sprite_y,pixel);
         //place the pixel
         sprite_x++;
         }
    sprite_x = orig_sprite_x;
    // setting equal to original is so that at the end of each row it resets 
    to the farthest left pixel.
    sprite_y++;
   }
 }

 public static void main(String[] args) throws IOException{

  Sprite orig_world = new Sprite(ImageIO.read(new 
  File("C:/Users/sfiel42/Documents/game/castles.png")));
  Sprite world      = new Sprite(ImageIO.read(new 
  File("C:/Users/sfiel42/Documents/game/castles.png")));

  JLabel label      = new JLabel(); 
  label.setLocation(0,0);
  label.setIcon(new ImageIcon(world.getSprite()));
  label.setVisible(true);   

  JFrame frame      = new JFrame();
  frame.setVisible(true);
  frame.setSize(783,615);
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  frame.add(label);

  Sprite archer      = new Sprite(ImageIO.read(new 
  File("C:/Users/sfiel42/Documents/game/archer.png")));
  archer.spawn(frame,world.getSprite(),250,400);
  archer.erase(frame,world.getSprite(),orig_world.getSprite());

  }
}

【问题讨论】:

    标签: java jframe sprite bufferedimage


    【解决方案1】:

    代码存在几个问题,共同促成了这一点。首先是您的擦除方法擦除了错误的部分。尝试在擦除中只写白色像素,你会看到。发生的情况是,对于 spawn 方法,您提供坐标,但对于 erase 方法,您没有。 getMinX()getMinY() 方法不会给你精灵坐标,而是图像本身的最小 X 和 Y 坐标。对于缓冲图像,它始终为零,因为图像没有隐含的位置;像标签一样的东西。这是一个正确的版本:

    public void erase(JFrame frame, BufferedImage world, BufferedImage orig_world, int x, int y) throws IOException {
        for (int stepper_y = 0; stepper_y < this.image.getHeight(); stepper_y++) {
            for (int stepper_x = 0; stepper_x < this.image.getWidth(); stepper_x++) {
                int sprite_pixel = orig_world.getRGB(x + stepper_x, y + stepper_y);
                // get pixel from orginal sprite
                int sprite_alpha = (sprite_pixel >> 24) & 0xff;
                // get alpha value from original sprite
                int sprite_red = (sprite_pixel >> 16) & 0xff;
                // get red value from original sprite
                int sprite_green = (sprite_pixel >> 8) & 0xff;
                // get green value from original sprite
                int sprite_blue = sprite_pixel & 0xff;
                // get blue value from original sprite
    
                int pixel = (sprite_alpha << 24) | (sprite_red << 16) | (sprite_green << 8) | sprite_blue;
                // set the pixel equal to the old values
                world.setRGB(x + stepper_x, y + stepper_y, pixel);
                // place the pixel
            }
        }
    }
    

    更好的是使Sprite 的x 和y 坐标属性。毕竟,精灵有一个位置,并且必须维护这个信息。从面向对象的角度来看,将其保留在 sprite 对象之外是没有意义的。

    所以像这样调整你的班级:

    int x, y;
    BufferedImage image;
    
    public Sprite(BufferedImage image, int x, int y) throws IOException {
        this.image = image;
        this.x = x;
        this.y = y;
    }
    
    public BufferedImage getSprite() {
        return this.image;
    }
    
    public int getX() {
        return x;
    }
    
    public int getY() {
        return y;
    }
    

    然后在其 swpan 和擦除方法中使用精灵的坐标。

    // to spawn a sprite on top of another image.
    public void spawn(JFrame frame, BufferedImage world) throws IOException, InterruptedException {
        for (int sprite_y = 0; sprite_y < this.image.getHeight(); sprite_y++) {
            for (int sprite_x = 0; sprite_x < this.image.getWidth(); sprite_x++) {
                int sprite_pixel = this.image.getRGB(sprite_x, sprite_y);
                int sprite_alpha = (sprite_pixel >> 24) & 0xff;
                int sprite_red = (sprite_pixel >> 16) & 0xff;
                int sprite_green = (sprite_pixel >> 8) & 0xff;
                int sprite_blue = sprite_pixel & 0xff;
                int pixel = (sprite_alpha << 24) | (sprite_red << 16) | (sprite_green << 8) | sprite_blue;
                world.setRGB(x + sprite_x, y + sprite_y, pixel);
            }
        }
    }
    
    public void erase(JFrame frame, BufferedImage world, BufferedImage orig_world) throws IOException {
        for (int stepper_y = 0; stepper_y < this.image.getHeight(); stepper_y++) {
            for (int stepper_x = 0; stepper_x < this.image.getWidth(); stepper_x++) {
                int sprite_pixel = orig_world.getRGB(x + stepper_x, y + stepper_y);
                // get pixel from orginal sprite
                int sprite_alpha = (sprite_pixel >> 24) & 0xff;
                // get alpha value from original sprite
                int sprite_red = (sprite_pixel >> 16) & 0xff;
                // get red value from original sprite
                int sprite_green = (sprite_pixel >> 8) & 0xff;
                // get green value from original sprite
                int sprite_blue = sprite_pixel & 0xff;
                // get blue value from original sprite
    
                int pixel = (sprite_alpha << 24) | (sprite_red << 16) | (sprite_green << 8) | sprite_blue;
                // set the pixel equal to the old values
                world.setRGB(x + stepper_x, y + stepper_y, pixel);
                // place the pixel
            }
        }
    }
    

    for 循环中的变量是相对于精灵的(sprite_y 和 stepper_y 从 0 到高度,sprite_x 和 stepper_x 从 0 到宽度),并相对于精灵的基础坐标(x 和 y)调整世界图像。


    进入第二期。

    在您当前的代码中实际发生的是,您从未真正渲染过背景,然后再将精灵渲染到它。这可能听起来很奇怪,因为您正在看到它,对吧?但正在发生的是一个竞争条件。 Java Swing 使用单独的线程进行渲染,这意味着当您使某些内容可见时,您不能保证在您的代码继续之前它实际上已经被渲染。

    这里是这个部分:

    JLabel label      = new JLabel(); 
    label.setLocation(0,0);
    label.setIcon(new ImageIcon(world.getSprite()));
    label.setVisible(true);   
    
    JFrame frame      = new JFrame();
    frame.setVisible(true);
    frame.setSize(783,615);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(label);
    
    Sprite archer = new Sprite(ImageIO.read(new File("C:/Users/sfiel42/Documents/game/archer.png")));
    archer.spawn(frame,world.getSprite(),250,400);
    archer.erase(frame,world.getSprite(),orig_world.getSprite());
    

    事情发生的顺序其实是这样的:

    1. 使用背景图像(世界精灵)创建标签并将其设置为可见。它还没有上下文,所以你还没有真正看到它。
    2. 创建框架,将其设置为可见,设置其大小并添加标签。 此时帧的渲染将由 Swing 后台线程处理。您的代码现在继续。但该帧尚未渲染。
    3. 阅读弓箭手精灵。
    4. 生成弓箭手精灵,这意味着它会覆盖作为标签图标的世界图像的一些像素。
    5. 直到现在帧渲染才真正完成,调整了背景。

    您可以通过在框架代码和获取弓箭手精灵之间的主线程中设置睡眠来测试这一点,如下所示:

    frame.add(label);
    
    Thread.sleep(5000);
    
    Sprite archer = new Sprite(ImageIO.read(new File("C:/Users/sfiel42/Documents/game/archer.png")));
    

    现在,在精灵可以调整背景之前,帧有时间渲染。因此,您最终不会看到它的变化。

    这是另一种测试方法。再次去掉上面的 5 秒 sleep,现在在 sprite 写入一半的时候添加一个 short sleep:

    public void spawn(JFrame frame, BufferedImage world, int x, int y) throws IOException, InterruptedException {
        int orig_x = x;
        for (int sprite_y = 0; sprite_y < this.image.getHeight(); sprite_y++) {
            if (sprite_y == this.image.getHeight() / 2) {
                Thread.sleep(100);
            }
    

    您可能会看到一半的精灵而另一半不见了。所有这一切都将取决于渲染线程的时间、计算机的速度和其他方面,因此结果可能无法预测。如果读取弓箭手精灵文件的速度较慢,您可能永远看不到您的精灵。

    当您将某些内容更改为世界图像时,框架和图标不会自动更新;您正在直接写入一些缓冲图像,因此使用它的组件不知道发生了什么变化,它们应该改变它们在屏幕上的表示。渲染后调用更新:

    Sprite archer = new Sprite(ImageIO.read(new File("C:/Users/sfiel42/Documents/game/archer.png")));
    archer.spawn(frame, world.getSprite());
    frame.repaint();
    Thread.sleep(2000);
    System.out.println("Erasing");
    archer.erase(frame, world.getSprite(), orig_world.getSprite());
    frame.repaint();
    

    那么最后一个问题是这里采用的渲染方法。如果要删除精灵,则通过保留背景副本然后用副本的区域显式替换精灵区域来擦除精灵。一旦您获得多个精灵或试图移动它们,这将使事情变得困难。例如,如果精灵在 spawn 和erase 调用之间移动,您不会完全擦除它。

    在 2D 渲染中通常会做一些图层,这些图层按给定的顺序渲染:一个或多个背景图层,然后是顶部的精灵。使用一些适用于 SNES 或 MegaDrive 等旧游戏机的模拟器,或适用于 NeoGeo 和 CPS-2 等系统的街机模拟器(例如 MAME 或 Kawaks)玩一下。您通常可以禁用特定图层并查看事物是如何呈现的。

    对于必须显示大部分静态内容(例如棋盘)的非常简单的游戏,渲染背景和顶部的精灵大部分都可以工作。但是对于移动得更快并且不断更新帧的东西,您可能会丢失精灵或闪烁,具体取决于您在输出到屏幕时处于渲染阶段的位置。

    通常的解决方案是使用一些帧缓冲区:帧被渲染到一些背景缓冲区图像中,只有当它准备好后才允许在屏幕上显示。像这样:

    虽然您可以在 Swing(和 AWT)中执行此操作,但这并不是一种非常高效的方式。无论如何,您都希望使用更基本的组件,而不是用于组成图形用户界面的标签和图标。如果您不想使用现有的精灵库而是自己做事,最好还是研究一下硬件渲染的接口,例如 OpenGL。有可用的 Java 绑定。

    还可以查看游戏开发堆栈交换:https://gamedev.stackexchange.com/

    【讨论】:

    • @ScottField 如果这个或任何答案解决了您的问题,请考虑accepting it by clicking the check-mark。这向更广泛的社区表明您已经找到了解决方案,并为回答者和您自己提供了一些声誉。没有义务这样做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-27
    • 2011-07-23
    • 1970-01-01
    相关资源
    最近更新 更多