【问题标题】:How can I best implement fog of war with Java?我怎样才能最好地用 Java 实现战争迷雾?
【发布时间】:2020-10-04 06:04:33
【问题描述】:

您好,我是一个没有经验的程序员,这是我关于 Stack Overflow 的第一个问题!

我正在尝试在我的 Java 游戏中实现“战争迷雾”。这意味着我的大部分地图都是从黑色开始的,然后当我的一个角色在地图的某些部分移动时会显示出来。我搜索了包括这里在内的一些建议,并尝试自己调整它们。我的每种方法都有效,但是每种方法都遇到了严重的运行时问题。相比之下,在我尝试任何战争迷雾之前,我的帧率为 250-300 FPS。

这是我的基本方法:

  1. 在我的 JPanel 上渲染我的背景和所有对象
  2. 创建黑色 BufferedImage (fogofwarBI)
  3. 找出我的地图的哪些区域需要可见
  4. 将我的 fogofwarBI 上的相关像素设置为完全透明
  5. 渲染我的 fogofwarBI,从而用黑色和透明部分覆盖屏幕的某些部分,从而可以看到背景和对象。

为了初始化缓冲图像,我在 FogOfWar() 类中执行了以下操作:

    private BufferedImage blackBI =  loader.loadImage("/map_black_2160x1620.png");
    private BufferedImage fogofwarBI = new BufferedImage(blackBI.getWidth(), blackBI.getHeight(), BufferedImage.TYPE_INT_ARGB);

    public FogOfWar() {
        fogofwarBI.getGraphics().drawImage(blackBI,0,0,null);
    }

在我的每次尝试中,我都会在“可见”地形的中间开始角色,即。在我的地图中没有雾的部分(我的 fogofwarBI 将具有完全透明的像素)。

尝试 1:设置RGB

首先,如果角色移动了,我会在角色的视野中找到“新”坐标。 IE。不是角色视线范围内的每个像素,而只是在他移动方向上他的视线范围边缘的像素。这是通过 for 循环完成的,最多会经过 400 个左右的像素。

我将这些 x 和 y 坐标中的每一个都输入到我的 FogOfWar 类中。

我检查这些 x,y 坐标是否已经可见(在这种情况下,我不会费心对它们做任何事情以节省时间)。我通过维护一组列表来进行此检查。其中每个 List 包含两个元素:x 和 y 值。 Set 是坐标列表的唯一集合。 Set 开始为空,我将添加 x,y 坐标来表示透明像素。我使用 Set 来保持集合的唯一性,因为我了解 List.contains 函数是执行此检查的快速方法。我将坐标存储在一个列表中以避免混淆 x 和 y。

如果我的 fogofwarBI 上的给定 x,y 位置当前不可见,我添加使用 .setRGB 将 RBG 设置为透明,并将其添加到我的 transparentPoints Set 中,以便以后不会再次编辑坐标。

    Set<List<Integer>> transparentPoints = new HashSet<List<Integer>>();

    public void editFog(int x, int y) {

        if (transparentPoints.contains(Arrays.asList(x,y)) == false){
            fogofwarBI.setRGB(x,y,0); // 0 is transparent in ARGB
            transparentPoints.add(Arrays.asList(x,y));
        }
    }

然后我使用

渲染它
    public void render(Graphics g, Camera camera) {
        g.drawImage(fogofwarBI, 0, 0, Game.v_WIDTH, Game.v_HEIGHT, 
                    camera.getX()-Game.v_WIDTH/2, camera.getY()-Game.v_HEIGHT/2, 
                    camera.getX()+Game.v_WIDTH/2, camera.getY()+Game.v_HEIGHT/2, null);
    }

根据我的游戏相机的位置,我基本上将我的 fogofwarBI 的正确部分应用到我的 JPanel (800*600)。

结果: 工作正常。 在雾中移动时 FPS 为 20-30,否则正常 (250-300)。 由于 .setRGB 函数,此方法很慢,每次我的游戏“滴答”时运行多达 400 次。

尝试 2:光栅

在这次尝试中,我创建了我的 fogofwarBI 的栅格,以直接以数组格式处理像素。

    private BufferedImage blackBI =  loader.loadImage("/map_black_2160x1620.png");
    private BufferedImage fogofwarBI = new BufferedImage(blackBI.getWidth(), blackBI.getHeight(), BufferedImage.TYPE_INT_ARGB);

    WritableRaster raster = fogofwarBI.getRaster();
    DataBufferInt dataBuffer = (DataBufferInt)raster.getDataBuffer();
    int[] pixels = dataBuffer.getData();

    public FogOfWar() {
        fogofwarBI.getGraphics().drawImage(blackBI,0,0,null);
    }

我的 editFog 方法如下所示:

    public void editFog(int x, int y) {
        if (transparentPoints.contains(Arrays.asList(x,y)) == false){
            pixels[(x)+((y)*Game.m_WIDTH)] = 0; // 0 is transparent in ARGB
            transparentPoints.add(Arrays.asList(x,y));
        }
    }

我的理解是栅格与像素阵列处于(恒定?)通信中,因此我以与尝试 1 相同的方式渲染 BI。

结果: 工作正常。 恒定的 FPS 约为 15。 我相信它总是这么慢(不管我的角色是否在雾中移动),因为虽然操作像素阵列很快,但光栅一直在工作。

尝试 3:较小的栅格

这是尝试 2 的变体。

我在某处读到,使用 .drawImage 的 10 个输入版本不断调整 BufferedImage 的大小很慢。我还认为拥有 2160*1620 BufferedImage 的光栅可能会很慢。

因此,我尝试让我的“雾层”仅等于我的视图大小 (800*600),并使用 for 循环更新每个像素,具体取决于当前像素应该是黑色还是从我的标准透明点中可见设置并基于我的相机位置。

所以现在我的 editFog 类只更新了不可见像素集,我的渲染类看起来像这样:

    public void render(Graphics g, Camera camera) {

        int xOffset = camera.getX() - Game.v_WIDTH/2;
        int yOffset = camera.getY() - Game.v_HEIGHT/2;

        for (int i = 0; i<Game.v_WIDTH; i++) {
            for (int j = 0; j<Game.v_HEIGHT; j++) {
                if ( transparentPoints.contains(Arrays.asList(i+xOffset,j+yOffset)) ) {
                    pixels[i+j*Game.v_WIDTH] = 0;
                } else {
                    pixels[i+j*Game.v_WIDTH] = myBlackARGB;
                }
            }
        }

            g.drawImage(fogofwarBI, 0, 0, null);

    }

所以我不再动态调整我的fogofwarBI 的大小,而是每次都更新每个像素。

结果: 工作正常。 FPS:持续 1 FPS - 最糟糕的结果! 我猜想通过更新光栅中的 800*600 像素而不是大约 400 像素,可以大大超过不调整我的 fogofwarBI 大小并使其更小的任何节省。

我的想法已经用完了,我的互联网搜索都没有让我进一步尝试以更好的方式做到这一点。我认为必须有一种方法可以有效地制作战争迷雾,但也许我对 Java 或可用工具还不够熟悉。

非常感谢关于我目前的尝试是否可以改进或我是否应该完全尝试其他东西的指针。

谢谢!

【问题讨论】:

    标签: java render raster bufferedimage frame-rate


    【解决方案1】:

    这是个好问题。我对awt/swing类型的渲染不是很熟悉,所以只能尝试解释一下这个问题的可能解决方案。

    从性能的角度来看,我认为在地图的更大部分中对 FOW 进行分块/光栅化比使用基于像素的系统更好。这将减少每次滴答的检查量,并且更新它也将占用更少的资源,因为只有一小部分窗口/地图需要更新。网格越大,检查越少,但越走越有视觉损失。

    保持这样会使 FOW 看起来像块状/像素化,但这不是您无法修复的。

    对于玩家的直接周围,您可以添加一个以玩家为中心的圆形纹理。您可以使用混合(我相信 awt/swing 中的术语是composite)来“覆盖”圆圈与 FOW 纹理重叠的 alpha。这样,基于像素的更新由通常使用硬件增强方法来实现这些事情的渲染 API 完成。 (对于基于像素的自定义渲染,如果渲染 API 支持,通常会使用“着色器脚本”之类的东西)

    如果您只需要 FOW 中的临时视觉(如果您不需要“记住”地图),这已经足够了,您甚至不需要 FOW 的纹理网格,但我怀疑您确实想要“记住”地图。所以在那种情况下:

    可以像使用基于网格的地形一样修复块状/像素化外观。基本上根据周围环境添加一些额外的纹理/形状,以使事情看起来不错。下面的链接提供了很好的示例,并详细说明了如何进行所谓的“地形转换”。

    https://www.gamedev.net/articles/programming/general-and-gameplay-programming/tilemap-based-game-techniques-handling-terrai-r934/

    我希望这会带来更好的结果。如果您无法获得更好的结果,我建议您切换到类似 OpenGL 的渲染引擎,因为它适用于游戏,而 awt/swing API 主要用于 UI/应用程序渲染。

    【讨论】:

    • 感谢您提供非常有帮助的回复。我将尝试使用过渡实现基于网格的系统,并且我喜欢您建议的混合以覆盖 alpha 的想法。但是,正如您所说,我可能仍然需要尝试 OpenGL!很高兴知道 awt/swing 也主要用于 UI/应用程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-24
    • 2017-11-08
    相关资源
    最近更新 更多