1

I am currently developing a simple 2D game that is like Minecraft in Eclipse Java. One of the features I am currently trying to debug is saving a generated 2D noise map into a source folder separate from the root folder called resources and then using that same file that was saved to generate the map that will be displayed when the game runs. The issue that I am having is that my program does not display the recently generated map that was saved, it will display the previous map (from other test runs).

The different times the program was able to display the recently generated map is when:

  1. I run the program, close it, hit refresh in Eclipse, then run the program again.
  2. Find the project file, go into the bin folder, and delete the previous generated map that Java compiled.
  3. Comment out the code that displays the map, run the program with the code that generates the map, then run the program again with the code that displays the map.
  4. Open the file that contains the map data in Eclipse, have it update, then run the program.
  5. Run the program many times until it decides to update.

I can confirm that the program successfully generates the map, and saves it with each run so I do not think there are any bugs in that sense. My assumption is that the program can save the map data to the resources source folder but for some reason is not available during run time? Which is weird because the program looks in the same place that the map data is stored to display to the player. If anyone can point me in the right direction or at least notice if I am doing something wrong it will be greatly appreciated.

Below are screenshots and code:

[Structure of Files][1]

Main construction where objects are called:

public GamePanel() {
    this.setPreferredSize(new Dimension(screen_width,screen_height));
    this.setBackground(Color.gray);
    this.setDoubleBuffered(true);
    
    key_handler = new KeyEvents();
    this.addKeyListener(key_handler);
    this.setFocusable(true);
        
    player = new Player(this,key_handler);
    
    //generate map and save map data
    map_generator = new TileMap(this); 
    
    //load saved map data to be displayed   
    tile_manager = new TileManager(this);
}

Function of the map_generator object that saves map data:

void saveMap(String map) {
        Path resource_path = Paths.get("resources","map_files");
        String map_path = resource_path.toFile().getAbsolutePath() + "\\" + map;
        
        try {
            OutputStream map_stream = new FileOutputStream(map_path);
            for (int r=0; r<map_height; r++) {
                String map_row = "";
                for (int c=0; c<map_width; c++) {
                    map_row = map_row + map_array[r][c] + " ";
                }
                byte[] bytes = map_row.getBytes();
                map_stream.write(bytes);
                if (r!=map_height-1)
                    map_stream.write(10);
            }
            map_stream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Function of the tile_manager object that loads map data to be displayed to player

public void loadMap(String map) {
        try {
            InputStream map_stream = getClass().getResourceAsStream(map);
            BufferedReader map_reader = new BufferedReader
                    (new InputStreamReader(map_stream));
            
            int col = 0;
            int row = 0;
            while (col < game_panel.col_size && row < game_panel.row_size) {
                String map_line = map_reader.readLine();
                
                while (col < game_panel.col_size) {
                    String numbers[] = map_line.split(" ");
                    int num = Integer.parseInt(numbers[col]);
                    map_file[col][row] = num;
                    col++;
                }
                if (col == game_panel.col_size) {
                    col = 0;
                    row++;
                }
            }
            map_reader.close();
            
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
Chris
  • 11
  • 3
  • Well done making a thorough explanation! I do not see the screenshots attached, however. – Scollier Apr 27 '22 at 01:40
  • @Scollier apologies, the website was thinking the image links was a piece of code, took me a while to format it correctly. Thank you for your compliment! – Chris Apr 27 '22 at 01:48
  • 1
    Edit your question and include your code as text, not as images. Not only are images hard to read, they cannot be copied, cannot be searched, and are useless to sight impaired readers. – VGR Apr 27 '22 at 01:49
  • @VGR Thank you for the advice, There was only 1 image I could not do as text, it was the structure of my files in my project (if people find it useful) – Chris Apr 27 '22 at 02:05
  • 1
    `Paths.get("resources","map_files")` represents a *relative path.* A relative path’s actual location depends on the *current working directory* of the process, which generally is something a program cannot control. Store files in a known location, such as a subdirectory of the user’s home directory. See https://en.wikipedia.org/wiki/Working_directory for an explanation of working directories and relative paths. (They are a concept that was around long before Java.) – VGR Apr 27 '22 at 02:42
  • @VGR I see and that makes sense. The reason I used Paths was because I was having a hard time accessing the resources source folder where I keep all of the programs pngs and sprites. Do you know of any way I would be able to access that source folder without having to use the users home directory? – Chris Apr 27 '22 at 03:36
  • It’s fine to use Paths to access a directory. However, if you are ever going to distribute the game as a .jar file, your resources will be read-only. Programs should not attempt to modify themselves. It’s fine to have *default data* in your program as an application resource, but writes should be done to a known location. This is why Windows has %AppData%, Mac has ~/Library, and Linux has ~/.local and ~/.config. Why wouldn’t you want to make use of the user’s home directory? Doesn’t saved game data belong to the user? – VGR Apr 27 '22 at 10:57
  • @VGR Thank you for the great tip and I managed to learn allot more than I anticipated! – Chris Apr 28 '22 at 02:53

1 Answers1

0

I was able to successfully solve my problem, and now there are no issues with my program using the map data it generated and displaying it to the player while running. The solution was to use VGA's tip on using a known source (in my case Windows %APPDATA% folder) instead of having to read and write data into the programs files (specially considering that I would like to make this a jar file to be exported and played by others in the future).

The changes to the code are as follows: Top section of the saveMap function:

String working_directory = System.getenv("AppData") + "\\" + map;
        
//Path resource_path = Paths.get("resources","map_files");
//String map_path = resource_path.toFile().getAbsolutePath() + "\\" + map;

Top section of the loadMap function:

String working_directory = System.getenv("AppData") + "\\" + map;
        
        try {
            //InputStream map_stream = getClass().getResourceAsStream(map);
            InputStream map_stream = new FileInputStream(working_directory);

The top line in both functions, System.getenv("AppData"), allows Java to get the path of the AppData folder which is marked as a environment variable in computers running recent versions of the Windows OS. The AppData folder, by default, is hidden and contains three folders; Local, LocalLow, and Roaming. When testing, the map data was saved in Roaming, I believe this is default, but I could be wrong.

While researching this I did find out that this method is fine, but can backfire in unique edge cases where a user is running an older version of the Windows OS/Computer or if they have manually changed the %APPDATA% environment variables in their system. In those cases it is recommended to use Windows Native API calls which will find the folder for you even if its not default. There is a stack overflow post giving details on how you can implement Native API calls through java on the link below: Is there a Java library to access the native Windows API?

Since I am making a game and planning to export it I would love to show how to do this as well, but that will be something to implement later down the road once the program has been fleshed out and polished. Thank you everyone for your help and tips!

Chris
  • 11
  • 3