如何加快 JLabel 的巨大平铺网格的加载时间

How to speed up load times for a huge tile grid of JLabels

我正在尝试创建一个天空视角 RPG 游戏,但初始加载时间已成为一个问题。我对编程和 Java 还很陌生,所以我不太了解 Java 如何将东西加载到内存或任何东西中。 所以问题是,当我最初 运行 我的程序时,可能需要很长时间才能显示我的所有 JLabel(在我的网格中设置)。我希望制作一张非常大的地图,总共包含数十万个图块。在进行了一些计算和测试之后,事实证明加载 30 秒只会产生大约 6,724 个图块。这只是一张超过 8 个屏幕的地图,非常小,因为我的角色比 50x50 像素小一点。因此,对于我可能满意的大小的地图,每 运行 加载一次需要超过 6 分钟。那太疯狂了。而且我的地图创建器加载速度更慢。

每个图块为 50x50 像素。每个图块都设置为具有多层 JLabel。例如,第一个仅用于将添加其他标签的 JLabel,第二个用于地形(如草地),第三个用于交互式元素,例如门,这样门可能会出现在草

我所有的 Tile 对象都已创建并插入到二维 Tile 数组中。每个 Tile 实例都有:

2 个 JLabel。

图标至少添加到第一个 JLabel,通常只添加到第一个 JLabel。每次 tile 请求一个图标时,它都会从 MapTiles class 中获取它。

5 个字符串数组

7 个布尔值

4 个整数

该大小的层数组中有 4 个层实例

每个 Tile 实例还实现了 MouseListener

每个图层实例有:

1 个没有图标的 JLabel,除非它是 Grass。 (75% 的层实例不是草)

4 个布尔值

1 整数

1 个字符串

MapTiles class 将为事物创建一个 ImageIcon,例如 "Grass",如果尚未创建的话。如果它已经为它创建了一个 ImageIcon,那么它只是 returns 该 ImageIcon。通过阅读有关改进加载时间的文章,我了解到这可能有助于加快速度,而不是为每个 Tile 创建一个新的 ImageIcon。

在这里,我将为涉及 creating/loading 网格的 3 个主要 classes 中的每一个提供开始。开始时,我指的只是构造函数和对象的创建。每个class中还有一些其他的方法,但不是很多,并且none在初始加载时使用。

平铺 class:

package Tiles;
import Datas.*;
import Images.*;
import MapCreation.*;
import UniversalVariables.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.*;
import java.util.*;
public class Tile implements MouseListener{
    public Layer[] layers = new Layer[4];
    //MAKE SURE THAT THE FOLLOWING INTEGERS MATCH THE ARRAY IN MAPDATA
    int terrain = 0;
    int interactive = 1;
    int destroyable = 2;
    int obstruction = 3;

    public JLabel image = new JLabel();
    JLabel mouseDetector = new JLabel();
    boolean hasBarrier = false;
    boolean hasModifiedBarrier = false;
    boolean isInteractive = false;

    boolean leftClickPressed = false;
    boolean rightClickPressed = false;
    boolean scrollClickPressed = false;
    String[] layerTypes = MapData.uni.tileLayerTypes;
    String[] terrainTypes = MapData.uni.terrainTypes;
    String[] interactiveTypes = MapData.uni.interactiveTypes;
    String[] destroyableTypes = MapData.uni.destroyableTypes;
    String[] obstructionTypes = MapData.uni.obstructionTypes;
    int tileSize = MapData.uni.tilePixelSize;
    int row = 0;
    int col = 0;
    boolean mapCreatorOpen = UVars.uni.mapCreatorRunning;

    public Tile(int rowNum, int colNum){
        row = rowNum;
        col = colNum;
    setLayer("Terrain", "Grass");
    for(int c = 1; c < layers.length; c++){ //Sets all layers except terrain to have new layerName
        layers[c].layerName = MapData.uni.tileLayerTypes[c] + "NLT";
    }
    mouseDetector.addMouseListener(this);
    image.add(mouseDetector);
    mouseDetector.setBounds(0, 0, tileSize, tileSize);
    //image.setComponentZOrder(mouseDetector, 0);
    for(int c = 0; c < layers.length; c++){
        image.setComponentZOrder(layers[c].image, c);
    }
    image.setComponentZOrder(mouseDetector, 0);
}

}

图层class:

package Tiles;
import Datas.*;
import Images.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Layer{
    public JLabel image = new JLabel();
    int tileSize;
    public String layerName = "No Layer Name";
    boolean hasBarrier;
    boolean hasModifiedBarrier;
    boolean isInteractive;
    boolean affectsMovement;

    public Layer(Tile tile, String layerType){
        //For empty layers
        tile.add(image);
    }
    public Layer(Tile tile, String layerType, String name){
        if(layerType.equals("Terrain") && name.equals("No Layer Name")){
            name = "Grass";
        }
        layerName = name;
        tileSize = tile.tileSize;
        tile.add(image);
        image.setBounds(0, 0, tileSize, tileSize);
        image.setIcon(MapTiles.uni.getIcon(layerName));
        if(layerType.equals("Terrain")){
            Terrain terrain = new Terrain(layerName);
            terrain.exchangeValues(this, layerName);
        } else if(layerType.equals("Interactive")){
            Interactive interactive = new Interactive(layerName);
            interactive.exchangeValues(this, layerName);
        } else if(layerType.equals("Destroyable")){
            Destroyable destroyable = new Destroyable(layerName);
            destroyable.exchangeValues(this, layerName);
        } else if(layerType.equals("Obstruction")){
            Obstruction obstruction = new Obstruction(layerName);
            obstruction.exchangeValues(this, layerName);
        }
    }
}

MapTiles class:

package Images;
import Datas.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.*;
import java.util.*;
public class MapTiles{
    public static MapTiles uni;
    String tileLoc = "Tile Graphics\" + String.valueOf(MapData.uni.tilePixelSize) + "PX\";
    ArrayList<ImageIcon> icons = new ArrayList<ImageIcon>();
    ArrayList<String> iconList = new ArrayList<String>();
    public MapTiles(){
        uni = this;
    }
    public ImageIcon getIcon(String tileType){
        for(int c = 0; c < iconList.size(); c++){
            if(iconList.get(c).equals(tileType)){
                return icons.get(c);
            }
        }
        iconList.add(tileType);
        ImageIcon newIcon = new ImageIcon(tileLoc + tileType + ".png");
        icons.add(newIcon);
        return newIcon;
    }
}

那么,为什么我的网格需要这么长时间才能加载?它全部在默认 Java 线程中创建,但那是因为目前,我没有任何菜单或其他任何东西可以在进入地图之前分散用户的注意力。地图立即开始。出现的是框架只是一个空白面板。最终,一旦它最终在左上角创建了图块(用户视图的默认位置),您可以看到它创建了每个图块,用户甚至可以将鼠标悬停在一个图块上。 (将鼠标悬停在它上面会更改其上设置的图标)将鼠标悬停在它上面 运行 非常好。只有加载时间是问题。那么首先,你们有没有更好的想法来创建这样一个网格?其次,您是否可以想到更好的方法来完成我正在尝试做的事情?最后,有什么我做错的地方需要改进吗?我想我要问的主要问题是......是什么让 Java 需要这么长时间来加载新的 Tile 实例?顺便说一句,我有一台相当高端的个人电脑。这不是一些垃圾。例如,我可以 运行 大多数 2015 年之前的 PC 游戏最大图形设置,我有 16GB 的内存。我还有另一个问题,你们也许可以回答。我读到使用 GPU 加载东西而不是主要将它们加载到 RAM 运行s 中要快得多,但它使用 3D Java 或类似的东西。我能否使用我的 GPU 以某种方式加载许多图块并以这种方式缩短加载时间?

好吧,看起来您正在从磁盘多次加载相同的图像以获取图块。仅加载您需要的内容,然后在内存中共享相同的资源可能会有所帮助。

此外,您可能不应该尝试将整个地图保存在内存中,而是根据需要加载每个部分。

我建议您放弃 Swing,使用 LWJGL 或 LWJGL 支持的引擎。这将为您提供通常无法通过 Swing 获得的硬件加速渲染(除非传递某些运行时标志。)如果您担心质量,Minecraft 使用 LWJGL。如果您要使用 Java 进行游戏开发,这确实是正确的选择。

我特别推荐 LIBGDX 作为您的 LWJGL 支持的游戏框架。我已经广泛使用它,它是让您的游戏在多个 OS 移动设备上运行的好方法。

拥有无尽的地图并不难实现,主要是难以维护

诀窍是您不会一次加载所有图块,而是只加载可见的图块,因此称为视口。

public class ViewPort{
    private Tile[][] visibleTiles;

    public ViewPort(int width, int height, Factory factory){
        //create the Tiles and add them using a proper layout
        ....
    }

    public void setViewLocation(int x, int y){
    for(int dy = 0; dy < getHeight(); dy++){ //getHeight() with height from constructor
        for(int dx = 0; dx < getWidth(); dy++){ //getWidth() with width from constructor 
            visibleTiles[dx][dy] = factory.getTiles(dx+x, dy+y);
        } 
    }
}

如果你使用这个设计你可以跳到你地图上的任何位置,你只需要使用一个合适的工厂。

更难的问题是创建一个好的工厂:

public class Factory{
    public Tile getTile(int x, int y){
        if (x==0 && y==0) return getTile(TileType.Grass);
        ...
        return null;    
    }

    //speeding up using a lookup table, so we only create objects 
    //when we need new one / reUse old ones
    private Map<TileType, Tile> lookUpTable = ...;
    private Tile getTile(TileType type){
        Tile value = lookUpTable.get(type);
        if(value == null){
            value = new Tile(...);
            lookUpTable.put(type, value);
        }
        return value;
}

现在看起来容易的东西很难维护:

if (x==0 && y==0) return getTile(TileType.Grass);
if (x==1 && y==0) return getTile(TileType.Grass);

这段代码既不可维护又不高效,你应该使用switch/case

switch(x){
    case 0: 
    switch (y){
        case 0: return getTile(TileType.Grass);
        ...
    }
}

既不可维护也不可读...