2

我正在编写一个java rpg游戏,并且已经陷入僵局。我的代码目前有精灵动画,带有柏林噪声和碰撞检测的随机地图生成。该地图是平铺的,所以我目前正在尝试将柏林噪声转换为平铺。perlin 函数生成一个数组,并将该数组的每个数字输入到一个 tile png。这就是问题所在:RUNTIME ERROR: Java.Lang.NullPointerException。问题是我的编译器(netbeans)没有显示错误发生的位置,而是只给了我这个错误代码。通过排除过程,我设法找到了错误,该错误发生在第 364 行。如果此站点不支持行,则它位于 loadTile() 方法中,位于“if(perlinIsland[x][y] <= 0.05)blockImg [x][y] = 瓷砖 [0];"。我相信所有变量都已正确初始化,但我无法找到解决方案。请原谅长代码,但为了提供信息,我包含了所有内容。提前感谢您的帮助!

package java4k;

import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import java.math.*;
import java.util.*;

/**
 *
 * @author Christophe
 */
public class Main extends JFrame implements Runnable{

    public Image dbImage;
    public Graphics dbGraphics;

    //Image + Array size
    final static int listWidth = 500, listHeight = 500;


    //Move Variables
    int playerX = 320, playerY = 240, xDirection, yDirection;

    //Sprites
    BufferedImage spriteSheet;

    //Lists for sprite sheet: 1 = STILL; 2 = MOVING_1; 3 = MOVING_2
    BufferedImage[] ARCHER_NORTH = new BufferedImage[4];
    BufferedImage[] ARCHER_SOUTH = new BufferedImage[4];
    BufferedImage[] ARCHER_EAST = new BufferedImage[4];
    BufferedImage[] ARCHER_WEST = new BufferedImage[4];

    Image[] TILE = new Image[8];

    //Animation Variables
    int currentFrame = 0, framePeriod = 150;
    long frameTicker = 0l;
    Boolean still = true;
    Boolean MOVING_NORTH = false, MOVING_SOUTH = false, MOVING_EAST = false, MOVING_WEST = false;

    BufferedImage player = ARCHER_SOUTH[0];

    //World Tile Variables
    //20 X 15 = 300 tiles 
    Rectangle[][] blocks = new Rectangle[listWidth][listHeight];
    Image[][] blockImg = new Image[listWidth][listHeight];
    Image[][] blockImgTrans = new Image[listWidth][listHeight];
    Boolean[][] isSolid = new Boolean[listWidth][listHeight];
    int tileX = 0, tileY = 0;
    Random r = new Random();

    Rectangle playerRect = new Rectangle(playerX + 4,playerY+20,32,20);
    //Map Navigation
    static final byte PAN_UP = 0, PAN_DOWN  = 1, PAN_LEFT = 2, PAN_RIGHT = 3;

    //Perlin noise variables:
    Color test = new Color(0, 0, 0);
    static float[][] perlinNoise = new float[listWidth][listHeight];
    static float[][] gradiantNoise = new float[listWidth][listHeight];
    static float[][] perlinIsland = new float[listWidth][listHeight];
    static float[][] biome = new float[listWidth][listHeight];
    //Saved as png
    static BufferedImage perlinImage;



    public Main(){

        this.setTitle("JAVA4K");
        this.setSize(640,505);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);

        addKeyListener(new AL());

        TILE[0] = new ImageIcon("C:/Users/Christophe/Documents/NetBeansProjects/Java4k/src/java4k/TILE_GRASS_1.png").getImage();
        TILE[1] = new ImageIcon("C:/Users/Christophe/Documents/NetBeansProjects/Java4k/src/java4k/TILE_GRASS_2.png").getImage();
        TILE[2] = new ImageIcon("C:/Users/Christophe/Documents/NetBeansProjects/Java4k/src/java4k/TILE_GRASS_3.png").getImage();
        TILE[3] = new ImageIcon("C:/Users/Christophe/Documents/NetBeansProjects/Java4k/src/java4k/TILE_WATER_1.png").getImage();
        TILE[4] = new ImageIcon("C:/Users/Christophe/Documents/NetBeansProjects/Java4k/src/java4k/TILE_TREE_1_BOT.png").getImage();
        TILE[5] = new ImageIcon("C:/Users/Christophe/Documents/NetBeansProjects/Java4k/src/java4k/TILE_TREE_1_TOP.png").getImage();
        TILE[6] = new ImageIcon("C:/Users/Christophe/Documents/NetBeansProjects/Java4k/src/java4k/TILE_TREE_2_BOT.png").getImage();
        TILE[7] = new ImageIcon("C:/Users/Christophe/Documents/NetBeansProjects/Java4k/src/java4k/TILE_TREE_2_TOP.png").getImage();

        loadTiles();

        init();
    }


    //Step 1: Generates array of random number 0 < n < 1
    public static float[][] GenerateWhiteNoise(int width, int height){

        Random r = new Random();
        Random random = new Random(r.nextInt(1000000000)); //Seed to 0 for testing
        float[][] noise = new float[width][height];

        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                noise[i][j] = (float)random.nextDouble() % 1;
            }
        }

        return noise;

    }

    //Step 2: Smooths out random numbers
    public static float[][] GenerateSmoothNoise(float[][] baseNoise, int octave){

        int width = baseNoise.length;
        int height = baseNoise.length;

        float[][] smoothNoise = new float[width][height];

        int samplePeriod = 1 << octave; // calculates 2 ^ k
        float sampleFrequency = 1.0f / samplePeriod;

        for (int i = 0; i < width; i++)
        {
            //calculate the horizontal sampling indices
            int sample_i0 = (i / samplePeriod) * samplePeriod;
            int sample_i1 = (sample_i0 + samplePeriod) % width; //wrap around
            float horizontal_blend = (i - sample_i0) * sampleFrequency;

            for (int j = 0; j < height; j++)
            {
                //calculate the vertical sampling indices
                int sample_j0 = (j / samplePeriod) * samplePeriod;
                int sample_j1 = (sample_j0 + samplePeriod) % height; //wrap around
                float vertical_blend = (j - sample_j0) * sampleFrequency;

                //blend the top two corners
                float top = Interpolate(baseNoise[sample_i0][sample_j0],
                    baseNoise[sample_i1][sample_j0], horizontal_blend);

                //blend the bottom two corners
                float bottom = Interpolate(baseNoise[sample_i0][sample_j1],
                    baseNoise[sample_i1][sample_j1], horizontal_blend);

                //final blend
                smoothNoise[i][j] = Interpolate(top, bottom, vertical_blend);
            }
        }

        return smoothNoise;
    }

    //Used in GeneratePerlinNoise() to derivate functions
    public static float Interpolate(float x0, float x1, float alpha)
    {
        float ft = alpha * 3.1415927f; 
        float f = (float) (1 - Math.cos(ft)) * .5f;
        return x0*(1-f) + x1*f;
    }

    //Step 3: Combines arrays together to generate final perlin noise
    public static float[][] GeneratePerlinNoise(float[][] baseNoise, int octaveCount)
    {
    int width = baseNoise.length;
    int height = baseNoise[0].length;

    float[][][] smoothNoise = new float[octaveCount][][]; //an array of 2D arrays containing

    float persistance = 0.5f;

    //generate smooth noise
    for (int i = 0; i < octaveCount; i++)
    {
        smoothNoise[i] = GenerateSmoothNoise(baseNoise, i);
    }

        float[][] perlinNoise = new float[width][height];
        float amplitude = 1.0f;
        float totalAmplitude = 0.0f;

        //blend noise together
        for (int octave = octaveCount - 1; octave >= 0; octave--)
        {
        amplitude *= persistance;
        totalAmplitude += amplitude;

        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
            }
        }
        }

    //normalisation
    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < height; j++)
        {
            perlinNoise[i][j] /= totalAmplitude;
        }
    }

    return perlinNoise;
    }

    //Step 4: Generate circular gradiant: center = 0, outside = 1
    public static float[][] GenerateCircularGradiant(float[][] base, int size, int centerX, int centerY){

        base = new float[size][size];

        for (int x = 0; x < base.length; x++) {
            for (int y = 0; y < base.length; y++) {

                //Simple squaring, you can use whatever math libraries are available to you to make this more readable
                //The cool thing about squaring is that it will always give you a positive distance! (-10 * -10 = 100)
                float distanceX = (centerX - x) * (centerX - x);
                float distanceY = (centerY - y) * (centerY - y);

                float distanceToCenter = (float) Math.sqrt(distanceX + distanceY);

                //Make sure this value ends up as a float and not an integer
                //If you're not outputting this to an image, get the correct 1.0 white on the furthest edges by dividing by half the map size, in this case 64. You will get higher than 1.0 values, so clamp them!
                float mapSize = base.length/2;
                //mapSize = 500;
                distanceToCenter = distanceToCenter / mapSize;

                base[x][y] = distanceToCenter - 0.2f;

            }
        }

        return base;
    }

    //step 5: Combine perlin noise with circular gradiant to create island
    public static float[][] GenerateIsland(float[][] baseCircle, float[][] baseNoise){

        float[][] baseIsland = new float[baseNoise.length][baseNoise.length];

        for(int x = 0; x < baseNoise.length; x++){
            for(int y = 0; y < baseNoise.length; y++){
                baseIsland[x][y] = baseNoise[x][y] - baseCircle[x][y];
            }
        }

        return baseIsland;
    }

    //Method for optional paramater = float[][] biome
    public static void GreyWriteImage(float[][] data, String filename){

        float[][] temp = null;
        GreyWriteImage(data, temp, filename);
    }

    //Converts array data to png image
    public static void GreyWriteImage(float[][] data, float[][] biome, String fileName){
        //this takes and array of doubles between 0 and 1 and generates a grey scale image from them

        BufferedImage image = new BufferedImage(data.length,data[0].length, BufferedImage.TYPE_INT_RGB);

        for (int y = 0; y < data[0].length; y++)
        {
          for (int x = 0; x < data.length; x++)
          {


              if (data[x][y]>1){
                data[x][y]=1;
            }
            if (data[x][y]<0){
                data[x][y]=0;
            }


            Color col;

            //Deep Water 0 - 0.05
            if(data[x][y] <= 0.05) col = new Color(0, 0, 255);
            //Shallow Water 0.05 - 0.08
            else if(data[x][y] <= 0.08) col = new Color(100, 100, 255);
            //Beach 0.08 - 0.2
            else if(data[x][y]<=0.15) col = new Color(255, 255, 0);
            //Forest 0.2 - 0.6 + 0 0 0.7
            else if(data[x][y]<=0.6 && biome != null && (biome[x][y] < 0.6 || biome[x][y] > 0.9)){
                //Forest
                if(biome[x][y] < 0.6) col = new Color(0, 150, 0);
                //Desert
                else col = new Color(200, 200, 0);
            }
            //Plains 0.2 - 0.6
            else if(data[x][y] <= 0.6)   col = new Color(0, 255, 0);
            //Rocky Mountains 0.6 - 0.8
            else if(data[x][y] <= 0.65) col = new Color(100, 100, 100);            
            //Snowy Mountains 0.6 - 1
            else col = new Color(255, 255, 255);

            image.setRGB(x, y, col.getRGB());
          }
        }

        try {
            // retrieve image
            File outputfile = new File(fileName);
            outputfile.createNewFile();

            ImageIO.write(image, "png", outputfile);
        } catch (IOException e) {
            System.out.println("GREY WRITE IMAGE ERROR 303: " + e);
        }
    }



    //First called to store image tiles in blockImg[][] and tile rectangles in blocks[][]
    private void loadTiles(){

        //Primary Perlin Noise Generation
        perlinNoise = GenerateWhiteNoise(listWidth, listHeight);
        GreyWriteImage(perlinNoise, "perlinNoise.png");
        perlinNoise = GenerateSmoothNoise(perlinNoise, 7);
        GreyWriteImage(perlinNoise, "smoothNoise.png");
        perlinNoise = GeneratePerlinNoise(perlinNoise, 5);
        GreyWriteImage(perlinNoise, "finalPerlin.png");
        gradiantNoise = GenerateCircularGradiant(gradiantNoise, listWidth, listWidth/2 - 1, listHeight/2 - 1);
        GreyWriteImage(gradiantNoise, "gradiantNoise.png");
        perlinIsland = GenerateIsland(gradiantNoise, perlinNoise);
        GreyWriteImage(perlinIsland, "perlinIsland.png");


        //Biome Perlin Noise Generation
        biome = GenerateWhiteNoise(listWidth, listHeight);
        biome = GenerateSmoothNoise(biome, 6);
        biome = GeneratePerlinNoise(biome, 5);
        GreyWriteImage(perlinIsland, biome, "biome.png");






        for(int y = 0; y < listHeight; y++){
            for(int x = 0; x < listWidth; x++){

                //Sets boundaries: 0 < perlinIsland[x][y] < 1 
                if (perlinIsland[x][y]>1) perlinIsland[x][y]=1;
                if (perlinIsland[x][y]<0) perlinIsland[x][y]=0;

                //Deep Water 0 - 0.05
                if(perlinIsland[x][y] <= 0.05)blockImg[x][y] = TILE[0];
                //Shallow Water 0.05 - 0.08
                else if(perlinIsland[x][y] <= 0.08) blockImg[x][y] = TILE[3];
                //Beach 0.08 - 0.2
                else if(perlinIsland[x][y]<=0.15) blockImg[x][y] = TILE[4];
                //Forest 0.2 - 0.6 + 0 0 0.7
                else if(perlinIsland[x][y]<=0.6 && biome != null && (biome[x][y] < 0.6 || biome[x][y] > 0.9)){
                    //Forest
                    if(biome[x][y] < 0.6) blockImg[x][y] = TILE[5];
                    //Desert
                    else blockImg[x][y] = TILE[3];
                }
                //Plains 0.2 - 0.6
                else if(perlinIsland[x][y] <= 0.6) blockImg[x][y] = TILE[2];   
                //Rocky Mountains 0.6 - 0.8
                else if(perlinIsland[x][y] <= 0.65) blockImg[x][y] = TILE[3];            
                //Snowy Mountains 0.6 - 1
                else blockImg[x][y] = TILE[1];

                blocks[x][y] = new Rectangle(x*32, y*32, 32, 32);

            }
        }

    }


    //collision detection
    public boolean collide(Rectangle in)
    {
        if(blocks[0][0] != null){
            for (int y = (int)((playerRect.y - blocks[0][0].y) / 32)-1; y <= (int)((playerRect.y+playerRect.height - blocks[0][0].y) / 32)+1; y++){
                for (int x = (int)((playerRect.x - blocks[0][0].x) / 32)-1; x <= (int)((playerRect.x+playerRect.width - blocks[0][0].x) / 32) + 1; x++){
                    if (x >= 0 && y >= 0 && x < 32 && y < 32){
                        if (blockImg[x][y] != null)
                        {
                                if (in.intersects(blocks[x][y]) && isSolid[x][y] == true){
                                {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }
      return false;
    }


    //Key Listener
    public class AL extends KeyAdapter{
        public void keyPressed(KeyEvent e){

            int keyInput = e.getKeyCode();
            still = false;
            if(keyInput == e.VK_LEFT){

                navigateMap(PAN_RIGHT);
                MOVING_WEST = true;

            }if(keyInput == e.VK_RIGHT){

                navigateMap(PAN_LEFT);
                MOVING_EAST = true;

            }if(keyInput == e.VK_UP){

                navigateMap(PAN_DOWN);
                MOVING_NORTH = true;

            }if(keyInput == e.VK_DOWN){

                navigateMap(PAN_UP);
                MOVING_SOUTH = true;
            }
        }
        public void keyReleased(KeyEvent e){

            int keyInput = e.getKeyCode();
            setYDirection(0);
            setXDirection(0);

            if(keyInput == e.VK_LEFT){

                MOVING_WEST = false;
                player = ARCHER_WEST[0];

            }if(keyInput == e.VK_RIGHT){

                MOVING_EAST = false;
                player = ARCHER_EAST[0];

            }if(keyInput == e.VK_UP){

                MOVING_NORTH = false;
                player = ARCHER_NORTH[0];

            }if(keyInput == e.VK_DOWN){

                MOVING_SOUTH = false;
                player = ARCHER_SOUTH[0];
            }
            if( MOVING_SOUTH == MOVING_NORTH == MOVING_EAST == MOVING_WEST == false){
                still = true;
            }
        }
    }


    public void moveMap(){


        for(int a = 0; a < 30; a++){
                for(int b = 0; b < 30; b++){
                    if(blocks[a][b] != null){
                        blocks[a][b].x += xDirection;
                        blocks[a][b].y += yDirection;
                    }
                }
            }

        if(collide(playerRect) && blocks[0][0]!= null){
            for(int a = 0; a < 30; a++){
                for(int b = 0; b < 30; b++){
                    blocks[a][b].x -= xDirection;
                    blocks[a][b].y -= yDirection;
                }
            }
        }
    }


    public void navigateMap(byte pan){
        switch(pan){
            default:
                System.out.println("Unrecognized pan!");
                break;
            case PAN_UP:
                setYDirection(-1);
                break;
            case PAN_DOWN:
                setYDirection(+1);
                break;
            case PAN_LEFT:
                setXDirection(-1);
                break;
            case PAN_RIGHT:
                setXDirection(+1);
                break;
        }
    }


    //Animation Update
    public void update(long gameTime) {

        if (gameTime > frameTicker + framePeriod) {
            frameTicker = gameTime;
            currentFrame++;
            if (currentFrame >= 4) {
                currentFrame = 0;
            }
        }
        if(MOVING_NORTH) player = ARCHER_NORTH[currentFrame];
        if(MOVING_SOUTH) player = ARCHER_SOUTH[currentFrame];
        if(MOVING_EAST) player = ARCHER_EAST[currentFrame];
        if(MOVING_WEST) player = ARCHER_WEST[currentFrame];
    }


    public void setXDirection(int xdir){

        xDirection = xdir;
    }


    public void setYDirection(int ydir){

        yDirection = ydir;
    }


    //Method to get sprites
    public BufferedImage grabSprite(int x, int y, int width, int height){
        BufferedImage sprite = spriteSheet.getSubimage(x, y, width, height);
        return sprite;
    }


    private void init(){    
        spriteSheet = null;
        try {
            spriteSheet = loadImage("ARCHER_SPRITESHEET.png");
        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
        for(int i = 0; i <= 3; i++){
            ARCHER_NORTH[i] = grabSprite(i*16, 16, 16,16);
            ARCHER_SOUTH[i] = grabSprite(i*16, 0, 16, 16);
            ARCHER_EAST[i] = grabSprite(i*16, 32, 16, 16);
            ARCHER_WEST[i] = grabSprite(i*16, 48, 16, 16);
        }
    }


    public BufferedImage loadImage(String pathRelativeToThis) throws IOException{

        URL url = this.getClass().getResource(pathRelativeToThis);
        BufferedImage img = ImageIO.read(url);
        return img;
    }


    public void paint(Graphics g){

        dbImage = createImage(getWidth(), getHeight());
        dbGraphics = dbImage.getGraphics();
        paintComponent(dbGraphics);
        g.drawImage(dbImage, 0, 25, this);

    }

    public void paintComponent(Graphics g){
        requestFocus();

        /**

        //Draws tiles and rectangular boundaries for debugging
        for(int a = 200; a < 230; a++){
            for(int b = 200; b < 230; b++){
                if(blockImg[a][b] != null && blocks[a][b] != null){
                    g.drawImage(blockImg[a][b], Math.round(blocks[a][b].x), Math.round(blocks[a][b].y), 32, 32, null);
                }
            }
        }  

        //Draw player and rectangular boundary for collision detection
        g.drawImage(player, playerX, playerY, 40, 40, null);
        repaint();

        //Draws transparent tiles
        for(int a = 0; a < 20; a++){
            for(int b = 0; b < 15; b++){
                if(blockImgTrans[a][b] != null && blocks[a][b] != null){
                    g.drawImage(blockImgTrans[a][b], blocks[a][b].x, blocks[a][b].y, 32, 32, null);
                }
            }
       }


        **/
    }

    public void run(){
        try{
            while(true){
                moveMap();
                if(!still) update(System.currentTimeMillis());
                Thread.sleep(13);
            }
        }catch(Exception e){
            System.out.println("RUNTIME ERROR: " + e);
        }    
    }


    public static void main(String[] args) {
        Main main = new Main();

        //Threads
        Thread thread1 = new Thread(main);
        thread1.start();
    }
}
4

1 回答 1

2

您有限的 catch 块代码会妨碍您找到空值的能力。

例如,这些代码行:

  try {
     while (true) {
        moveMap();
        if (!still)
           update(System.currentTimeMillis());
        Thread.sleep(13);
     }
  } catch (Exception e) {
     System.out.println("RUNTIME ERROR: " + e);
  }

只会打印

RUNTIME ERROR: java.lang.NullPointerException

当此代码运行到 NPE 时,没有行号或堆栈跟踪。

首先,您不应该为普通的异常而陷入困境,而应该为显式的异常。接下来,您应该使用信息更丰富的 catch 块,例如至少通过e.printStackTrace().

上面的块真的应该写成:

public void run() {
  while (true) {
     moveMap();
     if (!still)
        update(System.currentTimeMillis());
     try {
        Thread.sleep(13);
       // only catch the explicit exception and in localized code if possible.
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
  }
}

这样做,你会看到 NPE 发生在这里:

if (in.intersects(blocks[x][y]) && isSolid[x][y] == true) {

然后您可以在该行前面填充代码以查看导致问题的变量:

例如,

              if (blockImg[x][y] != null) {
                 System.out.println("in is null: " + (in == null));
                 System.out.println("blocks[x][y] is null: "
                       + (blocks[x][y] == null));
                 System.out.println("isSolid is null: "
                       + (isSolid == null));
                 System.out.println("isSolid[x][y] is null: "
                       + (isSolid[x][y] == null));

                 if (in.intersects(blocks[x][y]) && isSolid[x][y] == true) {
                    {
                       return true;
                    }
                 }
              }

你会看到问题isSolid[x][y]是空的:

in is null: false
blocks[x][y] is null: false
isSolid is null: false
isSolid[x][y] is null: true
in is null: false
blocks[x][y] is null: false
isSolid is null: false
isSolid[x][y] is null: true
in is null: false
blocks[x][y] is null: false
isSolid is null: false
isSolid[x][y] is null: true
Exception in thread "Thread-3" java.lang.NullPointerException
    at pkg.Main.collide(Main.java:465)
    at pkg.Main.moveMap(Main.java:557)
    at pkg.Main.run(Main.java:693)
    at java.lang.Thread.run(Unknown Source)
in is null: false
blocks[x][y] is null: false
isSolid is null: false
isSolid[x][y] is null: true
in is null: false
blocks[x][y] is null: false
isSolid is null: false
isSolid[x][y] is null: true
in is null: false
blocks[x][y] is null: false
isSolid is null: false
isSolid[x][y] is null: true

为什么是这样?它是一个布尔数组,而不是布尔数组,因此它没有初始化为 Boolean.FALSE,而是默认为 null。解决方案:使用 boolean[][] 数组或显式初始化您的数组。

最重要的是:使用信息丰富的 catch 块,不要捕获一般异常。


编辑请注意,顺便说一句,为了让您的代码运行,我不得不禁用您对图像和精灵表的使用,因为这些资源对我来说是不可用的。不过,这项努力应该是你的,因为你是未来的追求者。我要求将来,您将代码限制为我们可以测试和运行的最小代码,以演示您的问题,但没有与您的问题无关的代码,并且不依赖图像、数据库等外部资源等...,一个sscce

于 2013-07-27T13:53:21.207 回答