Java游戏2D中使用Swing实现重叠阴影

7
我正在使用Swing作为我的主要绘图组件开发一个2D Java游戏。每个物体都有一个阴影(BufferedImage),但是每个阴影都会重叠其他阴影。是否可能只让阴影不彼此重叠?因为如果物体在玩家下面,我仍然希望阴影绘制在玩家上方,而如果物体在玩家上面,则不需要绘制在玩家上方。这里有一张图片以便更清楚地理解:

Screenshot

我已经看过了alpha合成,我想我需要Source Out吗?我也考虑过将所有阴影(没有透明度)绘制在一个图层上,然后用透明度绘制它,但那样就无法像之前那样绘制在玩家和其他物体上。

我有一个Draw对象,它是一个JPanel并覆盖了paintComponent方法。在这个方法中,我首先绘制当前房间的地板,然后迭代当前房间的物体列表,并调用每个物体的draw方法来绘制所有东西。

物体绘制方法:

public void draw(Graphics g) {
    if (visible && checkInScreen()) {

        // The required drawing location
        int drawLocationX = getX() - globalCameraX;
        int drawLocationY = getY() - globalCameraY;

        if (shadow) {
            g.drawImage(shadowImages.get(imageIndex),
                    drawLocationX + shadowOffset.x + (getImageWidth()/2),
                    drawLocationY + shadowOffset.y, null);
        }
        g.drawImage(images.get(imageIndex), drawLocationX, drawLocationY, null);

        //Collisionbox
        if (SHOW_COLLISION_BOXES){
            g.setColor(Color.WHITE);
            g.drawRect(drawLocationX + getCollBoxX(), drawLocationY + getCollBoxY(), getCollBoxW() - getCollBoxX(), getCollBoxH() - getCollBoxY());
        }
    }
}

抱歉如果这个问题已经被问过了,但我找不到类似的东西。

将所有阴影绘制在一个图层上是个好主意。如果你最后才绘制阴影,为什么它们不会出现在玩家和物体上呢? - eldo
但是,那么阴影不会落在“投射阴影”的物体上吗?因为阴影部分在物体后面。 - Patrick Swijgman
如果你的阴影只是当前可见部分,那就不是真正的阴影。因此,树的阴影永远不会覆盖树本身。 - eldo
但是如果它不应该覆盖其他三个,它也会覆盖它们。唔… - eldo
怎么做呢?截取所有阴影的某种屏幕截图并使用透明度进行绘制? - Patrick Swijgman
2个回答

1

我承认我不熟悉Swing,所以我不确定在该特定界面中是否可能,但以下解决方案可以在各种2D图形引擎中使用。

您需要一个与屏幕尺寸匹配的离屏“阴影层”来绘制。将阴影层初始化为纯白色。

对于每个从后往前(y排序)绘制的对象,按顺序执行以下操作:

  • 使用单一的实心深灰色将对象的阴影形状绘制到阴影层中

  • 将对象本身作为纯白色精灵(即对象位图中所有非透明像素都为白色)绘制到阴影层中

当然,也要将对象本身绘制到屏幕上。

然后,在所有对象都已绘制到屏幕和阴影层后,使用乘法混合将阴影层绘制到屏幕上。乘法混合保证阴影会使其覆盖的任何内容变暗(与alpha混合不同,对于非常浅的阴影,它有可能实际上会使其覆盖的颜色变亮)。它还会使图层中的纯白部分什么也不做,这正是您想要的。

以上步骤意味着每个对象绘制阴影后,当它以白色绘制到阴影层时,会擦除任何在其下面的阴影,在最终场景中,因此它不会对自身产生阴影,而且对象不会在技术上位于其前面的其他对象上投射阴影。
正如所需的那样,物体仍然会向它们后面的其他物体投射阴影,因为任何未被重叠对象擦除的阴影部分仍将应用(或者如果它们被擦除,则可能由稍后的对象重新绘制)。并且,由于您将阴影绘制为单个非半透明颜色到阴影层,多个重叠的阴影也不会互相影响,这当然是主要的目的。
您可以根据可用的内容修改此技术。例如,您可以使用完全透明的阴影层和“擦除”混合模式[(src * 0) + (dst * (1-srcAlpha))]来绘制擦除其下阴影的对象。然后,您可以使用alpha而不是multiply blend如果您更喜欢将阴影层绘制到屏幕上。

1
我会尝试解决这个问题,我的建议是使用一个阴影层位图。我的意思是: 将阴影纹理保存为2D布尔值数组(表示阴影像素的位置)。
您可以通过逻辑或运算将阴影映射合并在一起,创建单个图层,然后将其放置在树纹理后面以创建阴影。
您可能希望将布尔值更改为浮点数以表示阴影的颜色/强度,然后进行更大的计算来合并阴影。
下面的ShadowMap类用于存储每个阴影的数据:
class ShadowMap {

    public int xPos, yPos;
    public boolean[][] array;

    public ShadowMap(int xPos, int yPos, boolean[][] array) {
        this.xPos = xPos;
        this.yPos = yPos;
        this.array = array;
    }

}

ShadowLayer类创建一个2D数组,用于整个屏幕,包含每个像素是否存在阴影:

class ShadowLayer {

    public static boolean[][] array = new boolean[SCREEN_WIDTH][SCREEN_HEIGHT];

    public static makeNew(ShadowMap[] shadows) {
        for (int x = 0; x < SCREEN_WIDTH; x++) {
            for (int y = 0; y < SCREEN_HEIGHT; y++) {
                array[x][y] = false;
            }
        }
        for (ShadowMap map : shadows) {
            for (int i = 0; i < SCREEN_WIDTH; i++) {
                for (int j = 0; j < SCREEN_HEIGHT; j++) {
                    // Logical or such that the pixel at (x, y) has a shadow 
                    // if any shadow map also has a shadow at pixel (x, y)
                    array[i + map.xPos][j + map.yPos] |= map.array[i][j];
                }
            }
        }
    }

}

使用这个ShadowLayer类,如果ShadowMap在同一像素上有阴影,你只需要让屏幕上的每个像素变暗:
public static Color ajustPixelForShadows(int x, int y, Color pixel) {
    return ShadowMap.array[x][y] ? pixel.darken() : pixel;
}

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