【问题标题】:Is there an efficient way to load textures as .PNG images for a simple 2D game有没有一种有效的方法将纹理加载为简单的 2D 游戏的 .PNG 图像
【发布时间】:2020-02-12 11:40:31
【问题描述】:

我正在编写一个简单的基于 2D 块的游戏(类似于 Minecraft)来开始使用 Java。我有很多游戏机制,比如世界生成工作,但加载纹理体积庞大且效率低下。基本上,我使用加载函数单独加载了所有纹理(纹理是 16x16 .PNG 文件)。游戏中的每个块都有一个名称(例如“石头”、“泥土”),当游戏渲染图块时,它会从 textureManager 类加载纹理。这可行,但我必须对每个纹理加载和返回进行硬编码。我想做的是让纹理自动加载,但仍然有它们的字符串索引。例如,我希望游戏加载纹理文件夹中的所有 .PNG 文件,但保留它们的名称,以便我可以说类似 loadTexture("stone") 的内容。

这是我当前的纹理管理器代码。它在游戏开始时简单地加载每个文件,并为背景图块变暗。添加新纹理很痛苦,而且效率不高。

package graphics;

import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;

public class textureManager {
    private static final BufferedImage darken(BufferedImage image) {
        return new RescaleOp(.7f, 0, null).filter(image, null);
    }

    private static final String path = "/assets/textures/";
    // textures 
    public static final BufferedImage unknown   = renderer.loadImage(path + "unknown.png");
    private static final BufferedImage stone    = renderer.loadImage(path + "stone.png");
    private static final BufferedImage grass    = renderer.loadImage(path + "grass.png");
    private static final BufferedImage dirt     = renderer.loadImage(path + "dirt.png");
    private static final BufferedImage iron_ore = renderer.loadImage(path + "iron_ore.png");
    private static final BufferedImage coal_ore = renderer.loadImage(path + "coal_ore.png");
    private static final BufferedImage diamond_ore = renderer.loadImage(path + "diamond_ore.png");
    private static final BufferedImage bedrock  = renderer.loadImage(path + "bedrock.png");
    private static final BufferedImage andesite = renderer.loadImage(path + "andesite.png");
    private static final BufferedImage granite  = renderer.loadImage(path + "granite.png");
    private static final BufferedImage marble   = renderer.loadImage(path + "marble.png");

    // darkened textures
    private static final BufferedImage dark_stone = darken(stone);
    private static final BufferedImage dark_grass = darken(grass);
    private static final BufferedImage dark_dirt = darken(dirt);
    private static final BufferedImage dark_iron_ore = darken(iron_ore);
    private static final BufferedImage dark_coal_ore = darken(coal_ore);
    private static final BufferedImage dark_diamond_ore = darken(diamond_ore);
    private static final BufferedImage dark_andesite = darken(andesite);
    private static final BufferedImage dark_granite = darken(granite);
    private static final BufferedImage dark_marble = darken(marble);

    // animations
    private static Animation player   = new Animation("player");
    private static Animation lava     = new Animation("lava");
    private static Animation plant_01 = new Animation("plant_01");

    /** returns the textures of the name provided */
    public static BufferedImage getTexture(String name, boolean darken) {
        if(name.equals("air")) return null;
        if(name.equals("stone"))    if(!darken) return stone;           else return dark_stone;
        if(name.equals("grass"))    if(!darken) return grass;           else return dark_grass;
        if(name.equals("dirt"))     if(!darken) return dirt;            else return dark_dirt;
        if(name.equals("iron_ore")) if(!darken) return iron_ore;        else return dark_iron_ore;
        if(name.equals("coal_ore")) if(!darken) return coal_ore;        else return dark_coal_ore;
        if(name.equals("diamond_ore")) if(!darken) return diamond_ore;  else return dark_diamond_ore;
        if(name.equals("bedrock"))  return bedrock;
        if(name.equals("andesite")) if(!darken) return andesite;        else return dark_andesite;
        if(name.equals("granite"))  if(!darken) return granite;         else return dark_granite;
        if(name.equals("marble"))   if(!darken) return marble;          else return dark_marble;

        return unknown;
    }

    /** returns the animation of the name provided */
    public static Animation getAnimation(String name) {
        if(name.equals("player"))   return player;
        if(name.equals("lava"))     return lava;
        if(name.equals("plant_01")) return plant_01;

        return null;
    }
}

虽然此代码有效,但我想添加一种更有效的方法,但我不知道如何。我正在考虑使用数组列表来存储图像,但它不会接受字符串索引。也许是一个可以管理数组列表和名称索引的自定义类?

注意:动画是一个自定义类,用于对从文件夹加载的纹理进行动画处理。这些可以忽略。

【问题讨论】:

    标签: java file textures


    【解决方案1】:

    我会这样做,而不是列出您要加载的所有文件。加载该路径中的所有文件。将它们放入映射Map<String, BufferedImage> textures = new HashMap<>(); 然后您从文件名创建密钥。然后,您可以生成并添加深色纹理,将“dark_”或“d”添加到Key。然后在获取纹理中你需要做的就是if (darken) textures.get('d'+name) else textures.get(name)
    一旦从这里的文件中加载了所有纹理,就可以获得深色版本

    textures.putAll(textures.entrySet().stream().collect(Collectors.toMap(entry->"d"+entry.getKey(),entry->darken(entry.getValue()))));
    

    【讨论】:

    • 我目前正在将文件加载到哈希映射中。我将结合您响应中的代码和之前的另一个代码,并制作纹理加载功能。谢谢!
    【解决方案2】:

    你想要Map

    Map 的一个常见(可能是最常见的)实现是HashMap

    private static final Map<String, Map<Boolean, BufferedImage>> textures;
    
    static {
        String[] baseNames = {
            "unknown",
            "stone",
            "grass",
            "dirt",
            "iron_ore",
            "coal_ore",
            "diamond_ore",
            "bedrock",
            "andesite",
            "granite",
            "marble",
        };
    
        Map<String, Map<Boolean, BufferedImage>> map = new HashMap<>();
        for (String baseName : baseNames) {
            BufferedImage regular =
                renderer.loadImage(path + baseName + ".png");
            BufferedImage dark = darken(regular);
    
            Map<Boolean, BufferedImage> regularAndDark = new HashMap<>(2);
            regularAndDark.put(false, regular);
            regularAndDark.put(true, dark);
    
            map.put(baseName, Collections.unmodifiableMap(regularAndDark));
        }
    
        textures = Collections.unmodifiableMap(map);
    }
    
    /** returns the textures of the name provided */
    public static BufferedImage getTexture(String name, boolean darken) {
        Map<Boolean, BufferedImage>> matches = textures.get(name);
        assert matches != null : "No textures for name \"" + name + "\"";
        return matches.get(darken);
    }
    

    但是……还有改进的余地:

    • 如您所见,我添加了一个assert 语句以确保没有提供无效的字符串参数。这可以通过使用 enum 值作为图像键而不是字符串来避免,从而消除拼写错误的可能性。
    • Map&lt;Boolean, BufferedImage&gt;&gt; 有点难以理解。我会做一个简单的内部类来容纳两者,所以结构更明显。

    考虑到这一点,它看起来像这样:

    public enum TextureType {
        UNKNOWN,
        STONE,
        GRASS,
        DIRT,
        IRON_ORE,
        COAL_ORE,
        DIAMOND_ORE,
        BEDROCK,
        ANDESITE,
        GRANITE,
        MARBLE,
    }
    
    private static class Textures {
        final BufferedImage regular;
        final BufferedImage dark;
    
        Textures(BufferedImage regular) {
            this.regular = Objects.requireNonNull(regular,
                "Image cannot be null");
            this.dark = darken(regular);
        }
    }
    
    private static final Map<TextureType, Textures> textures;
    
    static {
        Map<TextureType, Textures> map = new EnumMap(TextureType.class);
        for (TextureType type : TextureType.values()) {
            map.put(type,
                new Textures(renderer.loadImage(path + baseName + ".png")));
        }
    
        textures = Collections.unmodifiableMap(map);
    }
    
    /** returns the textures of the name provided */
    public static BufferedImage getTexture(TextureType type, boolean darken) {
        Objects.requireNonNull(type, "Texture type cannot be null");
        Textures match = textures.get(type);
        return darken ? match.dark : match.regular;
    }
    

    【讨论】:

    • 您的第二个示例运行良好。在渲染纹理时,我得到了要加载的枚举列表。唯一的问题是,如果没有找到块而不是返回未知值(只是空白纹理),枚举会使游戏崩溃。我有一些代码来检查一个值是否有效,但由于渲染必须每帧调用大约 2400 次 getTexture 函数,因此检查将帧速率降低到无法播放。
    • “未找到块”是什么意思?你是说纹理类型可以为空吗?如果不为null,那么根据定义,它必须是枚举常量之一,并且必须有对应的图像。
    猜你喜欢
    • 2020-01-16
    • 1970-01-01
    • 2010-11-18
    • 2014-01-22
    • 2017-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多