话题
RPGMaker是我小时候很喜欢玩的游戏制作工具,其中最让我感到神奇的是绘制地图时地面纹理的拼接:
看起来不管我怎么画,图中【水】和【地面】边缘的衔接都是自然的。
不过,RPGMaker本身是一个轻量级的二维游戏制作工具,达成这个效果的方式一定只是简单的贴图拼接。我现在想要研究一下这个拼接的方式是什么。
研究
我在项目目录的\img\tilesets\
发现了所有地图tile的贴图,这种能自动拼接的贴图资源都是一个格式:
就称它为【模板】
吧,它的尺寸以tile为单位的话是2X3。
那么tile就是拼接的元素吗?为了解决这个疑问我对【模板】做了标记:
然后发现游戏中的情况就变成这样了:
从图中可以看出来:
1.圆圈颜色不一致。这表示拼接的元素并不是一个tile,而是一个1/4的tile。
2.圆圈线条是连接的。这表示“左上”的元素在【模板】的tile中也一定是“左上”。右上、左下、右下同理。
之后,我以数字为标记,标记出了【模板】里所有的拼接元素:
然后看游戏中的情况:
从图中可以看出,1~4其实没有被使用。1 、2 、3 、4 其实就是 9 、12 、21 、24。而 5 ~ 24这20个元素都被使用了。在之前已经确定了一个tile的四个部分不会混用(即“左上”部分不会使用【模板】中某个tile的“右下”部分),因此每个部分应该有 5种情况。
继续观察能发现,一个部分的元素选择实际上和它最相邻的三个tile有关,意思是:“左上”元素的选择只和 左
上
左上
三个相邻的tile有关。这样实际上是 2^3=8种情况,而他们实际上有5种表现,以“左上”部分为例:
从上至下的五种表现分别为:
1.左
上
左上
都有同样式的tile,取值19。
2.左
上
有同类型tile,但是左上
没有,取值5。
3.左
上
都没有,则不管左上
有没有,都取值9。
4.上
有且左
没有,则不管左上
有没有,都取值17。
5.左
有且上
没有,则不管左上
有没有,都取值11。
实践算法
完整的Unity工程见链接
工程的结构比较简单,在元素选取时的代码是这样的(以左上为例):
if(part==TilePart.LU)
{
bool up = NeighbourFilled(new Vector2Int(0, 1));
bool left = NeighbourFilled(new Vector2Int(-1, 0));
bool up_left = NeighbourFilled(new Vector2Int(-1, 1));
if (up && left)//上和左都有
{
if (up_left) //左上也有
return 19;
else //左上没有
return 5;
}
else if (!(up || left))//上和左都没有
return 9;
else //左和上有一者有
{
if (up) //上
return 17;
else //左
return 11;
}
}
算法上,我相信有更简洁的表示方法,不过那样的可读性可能会变差。
最终效果和原版一样:
资源制作流程
虽然拼接的算法搞明白了,但还有个重要的问题没有讨论:
【模板】怎么制作?
我的意思是,虽然我们能知道一个元素它潜在的邻居,例如:【5】上会接【13】,左会接【10】,右可能接【18】或【6】或【20】等等。。。但是这并不能成为美术的制作规则,你不能对美术说:“嗨,我需要一个有20个元素的贴图资源,其中【5】号元素上半部分需要和谁谁谁无缝衔接,而【6】号元素需要和谁谁谁衔接”。
因此,一套流程是必要,美术应该按一套步骤尽量简单,规则尽量简单的流程制作资源,同时流程能保证制作出的资源一定是满足我们算法的要求的。
关于RPGMaker官方是如何生产这些资源的,我没有找到资料,他们很可能创造了一些工具来配合流程。
这里,我猜测了一种流程:
1.首先制作一个tile尺寸的无缝衔接的贴图:
重点是保证上下左右都可以无缝衔接(即tiling)
他将会作为模板中的14 、15 、18 、19:
2.制作边缘:
延展上一步得到的贴图一个元素的长度(为tile尺寸的一半),例如“上”边缘:
重点是保证左右可以无缝衔接(即tiling)
以同样的方法制作出:
左边缘(保证上下可tilling)、下边缘(保证左右可tilling)、右边缘(保证上下可tilling)。
此时模板:
3.制作边缘角
以左上为例,在已有10,13,14的情况下画出左上边缘角,注意要保证无缝:
以同样的方式画出左下、右上、右下边缘角。
此时模板:(1 、2 、3 、4也已填充,因为他们实际就是9 、12 、21 、24)
4.制作拐角
以右下拐角为例,在已有10,13的情况下画出右下拐角,注意要保证无缝:
以同样方式画出剩下三个拐角。
至此模板完成:
尝试制作
我用上面的流程尝试制作了一个模板:
这是他的效果: