为什么我不能擦除我的雪碧

时间:2017-05-10 16:07:46

标签: java jframe sprite bufferedimage

我已经尝试了一段时间来使用Java工作游戏,在对其他人的痛苦之后我做了自己的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());

  }
}

1 个答案:

答案 0 :(得分:1)

代码中存在一些问题,这些问题相结合,可以实现这一目标。首先,你的擦除方法会删除错误的部分。尝试在擦除中仅写入白色像素,然后您将看到。发生的是,对于spawn方法,您提供坐标,但对于擦除方法,您不提供坐标。 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坐标属性。毕竟,精灵有一个位置,必须保持这些信息。从面向对象的角度来看,将它保存在精灵对象之外是没有意义的。

所以像这样调整你的课程:

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和erase方法中使用精灵坐标。

// 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(sprite_y和stepper_y从0到高度,sprite_x和stepper_x从0到width),并调整世界图像相对于精灵的基本坐标(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. 现在只有帧渲染实际完成,调整后的背景。
  6. 你可以通过在主代码中在框架代码和获取射手精灵之间放置一个睡眠来测试这个,如下所示:

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

    现在,在精灵可以调整背景之前,帧会有时间渲染。结果你不会看到它的变化。

    以下是另一种测试方法。再次移除上述5秒睡眠,现在在写入精灵的一半时添加一个短暂的睡眠:

    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();
    

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

    在2D渲染中通常做的是你有图层,这些图层按照给定的顺序渲染:一个或多个背景图层,然后是顶部的精灵。可以使用一些模拟器来为SNES或MegaDrive等老式控制台,或者用于NeoGeo和CPS-2等系统的街机模拟器(例如MAME或Kawaks)。您通常可以禁用特定图层并查看事物的呈现方式。

    对于一个非常简单的游戏,必须显示大部分静态内容,比如棋盘,渲染背景,然后精灵在顶部将主要起作用。但是对于移动速度更快且不断更新帧的内容,根据输出到屏幕时渲染阶段的位置,您可能会丢失精灵或闪烁。

    通常的解决方案是使用一些帧缓冲区:帧被渲染成一些背景缓冲区图像,只有一旦准备就可以在屏幕上显示。像这样:

    render loop

    虽然你可以在Swing(和AWT)中做到这一点,但它并不是一种非常高效的方式。无论如何,您都希望使用更基本的组件而不是标签和图标,这些组件旨在构成图形用户界面。如果你不想使用现有的精灵库而不是自己做,那么最好是研究硬件渲染的接口,比如OpenGL。可以使用Java的绑定。

    另请参阅游戏开发堆栈交换:https://gamedev.stackexchange.com/