如何在Java中最好地实现战争迷雾?

3

你好,我是一名经验不足的程序员,这是我在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);
    }

在每次尝试中,我会将人物放在"可见"地形中部,也就是地图上没有雾的区域(我的雾图像有完全透明的像素的地方)。

尝试1:setRGB

首先,我找到了人物视野范围内的“新”坐标,如果它移动了的话。也就是说,并不是人物视野范围内的每个像素,而只是朝着移动方向的视野范围边缘的像素。这是通过一个for循环来完成的,最多会循环400个像素左右。

我将每个x和y坐标输入我的FogOfWar类。

我检查这些x、y坐标是否已经是可见的(如果是,我就不必再做什么工作以节省时间)。我通过维护一个Set of Lists来进行此检查。其中每个List包含两个元素:x值和y值。Set是一组独特的坐标列表。Set开始为空,我将添加x、y坐标来表示透明像素。我使用Set来保持集合的唯一性,因为我知道List.contains函数是进行此检查的快速方法。并且我将坐标存储在一个List中,以避免混淆x和y。

如果我的fogofwarBI上的给定x、y位置目前不可见,我使用.setRGB将其设置为透明,并将其添加到我的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),具体取决于我的游戏相机所在位置。

结果: 正常工作。 在穿过雾时帧速率为20-30,否则正常(250-300)。 由于.setRGB函数被运行了多达400次每次我的游戏“ticks”,所以这种方法很慢。

尝试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。

结果: 正常工作。 大约15帧/秒的恒定FPS。 我认为它一直如此缓慢(无论我的角色是否穿过雾),因为虽然操作像素数组很快,但光栅图一直在工作。

尝试3:更小的光栅图

这是尝试2的变化。

我在某个地方读到,使用10个输入版本的.drawImage不断调整BufferedImage的大小会很慢。我也认为为2160*1620的BufferedImage设置光栅图可能会很慢。

因此,我尝试将“雾层”只设置为我的视图大小(800*600),并根据当前像素是否应该是黑色或从我的标准transparentPoints集合中可见以及基于我的相机位置,使用for循环更新每个像素。

现在,我的editFog类只更新不可见像素的集合,我的render类如下:

    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大小,而是每次更新每个像素。

结果: 正常工作。 帧速率:恒定1 FPS-最差的结果! 我猜不调整fogofwarBI大小并使其更小的任何节省都被在栅格中更新800 * 600像素而不是大约400个的操作所抵消了。

我已经没有想法,我的互联网搜索也没有让我更进一步了解如何以更好的方式实现这一点。我认为肯定有一种有效地实现战争迷雾的方法,但也许我还不够熟悉Java或可用工具。

非常感谢能否提供关于当前尝试是否可以改进或者是否应该尝试其他事情的任何指导。

谢谢!

1个回答

1
这是一个好问题。我不熟悉awt/swing类型的渲染,所以我只能尝试解释一种可能的解决方案来解决这个问题。从性能角度来看,我认为将FOW分块/光栅化成更大的地图部分比使用基于像素的系统更好。这将减少每个时刻的检查量,并且更新它也需要更少的资源,因为只有一个窗口/地图的小部分需要更新。网格越大,检查量越少,但是你走得越远,视觉上的惩罚就越大。如果保留原样,FOW看起来会很方块/像素化,但这并不是无法解决的问题。对于玩家直接周围的区域,您可以添加一个以玩家为中心的圆形纹理。然后,您可以使用混合(我相信在awt/swing中的术语是composite)来“覆盖”圆形与FOW纹理重叠的alpha值。这样,基于像素的更新由渲染API完成,通常使用硬件增强方法来实现这些内容。 (对于自定义基于像素的渲染,如果渲染API支持,通常使用“着色器脚本”之类的东西)

如果你只需要暂时的视野范围(如果你不需要“记住”地图),那么这就足够了,你甚至不需要为FOW使用纹理网格,但我怀疑你想要“记住”地图。所以在这种情况下:

块状/像素化外观可以像网格地形一样修复。基本上,根据周围环境添加一些小的附加纹理/形状,使事物看起来更美观。下面的链接提供了良好的示例和详细的解释,说明如何进行所谓的“地形转换”。

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

我希望这能得到更好的结果。如果你无法得到更好的结果,我建议切换到像OpenGL这样的渲染引擎,因为它是用于游戏的,而awt/swing API主要用于UI/应用程序渲染。


1
非常感谢您提供的帮助性回复。我将尝试使用基于网格的系统和过渡来实现,并且我喜欢您建议的混合覆盖 alpha 的想法。但是正如您所说,我可能仍然需要尝试 OpenGL!了解到 awt/swing 主要用于 UI/应用程序也是很好的。 - Deanwace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接