为什么我不能删除我的雪碧 [英] Why can't I erase my Sprite

查看:84
本文介绍了为什么我不能删除我的雪碧的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很久以来一直在尝试用Java开发游戏,在与其他人苦恼之后,我开发了自己的Sprite函数,却不明白为什么我不能删除它。我知道它改变了背景的像素以显示我的弓箭手精灵,因为它已经显示了,但是出于任何原因,我都无法将像素恢复为以前的水平。有谁知道这是为什么,或者我怎么解决?
链接到带有图像的Google文档:



虽然您可以在Swing中做到这一点(和AWT),这不是一种非常有效的方法。无论如何,您都希望使用更基本的组件,而不是用来组成图形用户界面的标签和图标。如果您不想使用现有的Sprite库,而是自己去做,那么最好还是去研究用于硬件渲染的接口,例如OpenGL。



还可以查看Game Development Stack Exchange: https:/ /gamedev.stackexchange.com/


I've been trying for a while now to get a game working in Java, after much anguish over other peoples Sprite functions I made my own and don't understand why I can't erase it. I know it's changing the pixels of the background to display my archer sprite since it shows up but for whatever reason I can't change the pixels back to what they were before. Does anyone have an idea why that is or how I can fix it? Link to google doc with images: 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());

  }
}

解决方案

There's a couple of issues with the code that combine to make this happen. The first is that your erase method erases the wrong section. Try writing only white pixels in the erase and you'll see. What's happening is that for the spawn method you supply coordinates, but for the erase method you do not. The getMinX() and getMinY() methods don't give you the sprite coordinates, but the minimum X and Y coordinates of the image itself. For a buffered image this is always zero, because an image does not implicitly have a position; something like a label does. Here's a version that would be correct:

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
        }
    }
}

Better yet would be to make the x and y coordinates properties of a Sprite. After all, a sprite has a position and must maintain this information. Keeping it outside of a sprite object makes no sense from an object-oriented point of view.

So adjust your class like this:

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;
}

Then use the sprite's coordinates in its swpan and erase methods.

// 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
        }
    }
}

The variables in the for loops are relative to the sprite (sprite_y and stepper_y from 0 to height, sprite_x and stepper_x from 0 to width), and adjust the world image relative to the sprite's base coordinates (x and y).


Onto the second issue.

What's actually really happening in your current code is that you never really rendered the background and then rendered the sprite to it. That may sound weird because you're seeing it, right? But what's going on is a race condition. Java Swing works with separate threading for its rendering, meaning that when you make something visible, you're not guaranteed that it will actually have been rendered before your code continues.

It's this part here:

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

The order that things are happening is actually this:

  1. Create label with the background image (the world sprite) and set it to be visible. It doesn't have a context yet so you're not actually seeing it yet.
  2. Create the frame, set it to be visible, set its size and add the label. At this point rendering of the frame will be handled by the Swing background thread. Your code now continues. But the frame hasn't been rendered yet.
  3. Read the archer sprite.
  4. Have the archer sprite spawned, meaning it overwrites some pixels of the world image that is the label's icon.
  5. Only now is the frame rendering actually completing, with the adjusted background.

You can test this by putting a sleep in your main thread between the frame code and getting the archer sprite, like this:

frame.add(label);

Thread.sleep(5000);

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

Now the frame is given time to render before the sprite can adjust the background. And as a result you don't end up seeing its change.

Here's another way you can test this. Remove the above 5 second sleep again, and now add a short sleep when half the sprite has been written:

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

You'll likely see half the sprite with the other half missing. All of this is gonna depend on timing of the rendering thread, your computer's speed and other aspects, so the results can be unpredictable. There was a chance that you would never see your sprite to begin with, if reading the archer sprite file had been slower.

The frame and icon don't automatically update when you change something to the world image; you're writing directly to some buffered image, so the components using it have no idea something has changed and they should change their representation on screen. Call an update after the rendering:

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


Then the final issue is the approach to rendering taken here. A sprite is erased by keeping a copy of the background and then explicitly replacing the sprite region with that of the copy if you want to remove the sprite. This is going to make things difficult once you're getting multiple sprites or trying to move them. For example, if the sprite moves between the spawn and erase calls, you won't entirely erase it.

What's usually done in 2D rendering is that you have layers, which are rendered in some given order: one or more background layers, then the sprites on top. Play around a bit with some emulators for older consoles like the SNES or MegaDrive, or arcade emulators for systems like the NeoGeo and CPS-2 (for example MAME or Kawaks). You can often disable specific layers and see how things are rendered.

For a very simple game that has to show mostly static content like, say, a chess board, rendering the background and then the sprites on top will mostly work. But for something moving faster and constantly updating frames, you could get missing sprites or flicker depending on where you are in the rendering phase when you output to the screen.

The usual solution is the use of some frame buffer: frames are rendered into some background buffer image, and only once it's ready is it allowed to be displayed on screen. Like so:

While you can do this in Swing (and AWT), it's not a very performant way. At any rate you'll want to use components that are a bit more basic instead of labels and icons, which are intended to compose graphical user interfaces. If you don't want to use existing sprite libraries and instead do things yourself, it would probably still be best to look into interfaces for hardware rendering, such as OpenGL. There's bindings for Java available.

Also check out the Game Development Stack Exchange: https://gamedev.stackexchange.com/

这篇关于为什么我不能删除我的雪碧的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆