celeste地图制作指南(装饰,decals)
我是Mark萌萌哒
编辑于 2022年09月01日 06:07

    本文将基于ahorn,介绍一种添加装饰物(decals)的轻度编程的方法,需要读者有极其微量的代码知识。Celeste版本为1.4.0.0,ahorn为最新版,操作系统为windows11。

    如果你对文字材料感到陌生,或者需要借助例子来理解文本,可观看最底下的“样例示范”视频。

    问中内容均为自己探索结果,可能有不准确地方,欢迎指出错误。

cut-off

运行机制

    假设mod的文件夹命叫做name。那么,每次加载mod时,蔚蓝会读取当前目录下的Mods/name/Graphics/Atlases/Gameplay/decals/folders,其中folders代表了一系列的子目录,用于区分不同地图应该使用什么装饰物。

    例如,如果在当前目录下的Mods/name/Maps/nickname/campaign中有一个名叫test.bin(作者是nickname)的地图,那么一个比较合理的用于存放装饰物的文件夹应该是Mods/name/Graphics/Atlases/Gameplay/decals/nickname/campaign

    在读取装饰物时,如果遇到了单独的图片,那么蔚蓝会直接读取。如果遇到了连续的图片,例如pic_0.png,pic_1.png,pic_2.png(它们名称的后缀是连续的数字,并且必须从0开始),那么蔚蓝会认为这三张图片是一个装饰物的三帧动画,并将它们读取到同一个动画中。因此,如果要加入有动画的装饰物,一定要把它们存在一个特定的文件夹里,避免混淆。

    其实,在decals文件夹里放入图片后,就已经能够在ahorn里读取它们了(Placements/fgDecals或Placements/bgDecals,分别是前景装饰物(foreground)和背景装饰物(background))。每次在decals文件夹里加入图片后,重新用ahorn打开地图.bin,就能使用新的装饰物。但是更新动态的装饰物时可能要重启蔚蓝。

cut-off

DecalRegistry.xml

    蔚蓝在Mods/name文件夹下有一个DecalRegistry.xml的文件,能够方便地描述静态的装饰物的各种特点,但对动态的装饰物支持不是很够。它的格式大致如下:

代码块
HTML
自动换行
复制代码
<decals>
  <!-- 第一个装饰物 -->
	<decal path="...">
		<PROPERTY_1 />
		<PROPERTY_2 />
		...
		<PROPERTY_n />
	</decal>
	...
  <!-- 第n个装饰物 -->
	<decal path="...">
		<PROPERTY_1 />
		<PROPERTY_2 />
		...
		<PROPERTY_n />
	</decal>
</decals>
复制成功

    需要说明的是,<decals></decals>是必须放在开头、末尾的,不可缺少。

<PROPERTY>是各种属性,下面会具体地讲。

<decal path="...&#​34;>描述了该修改哪一个装饰物的文件,path里面的内容应该是Mods/name/Graphics/Atlases/Gameplay/decals/nickname/campaign/xxx.png红色标出来的部分(不要有.png,注意用'/&#​39;而不是'\&#​39;),描述完所有的属性后必须用一个</decal>来结束该装饰物的配置。

    注意文件夹用英文!否则会出现奇怪的结果。

cut-off

各种属性

<PROPERTY>描述了一个装饰物的属性,一个装饰物可以有多种属性。对于一种属性,它可以有若干参数,每个参数都需要有一个具体的值。

    例如,我们要让一个装饰物(路径为nickname/campaign/duck.png)有旗帜一样的效果,并且让它漂浮,因此我们可以使用属性<banner>和<floaty>。我们可以用<banner>的两个参数:speed(float)和sliceSize(int),那么一种可行的写法是:

代码块
HTML
自动换行
复制代码

<decals>
    <decal path="nickname/campaign/duck">
        <banner speed="1.0" sliceSize="1" />
        <floaty />
    </decal>
</decals> 
复制成功

    一二五六行是一个基本格式,上面提到了。第三行中,第二个单词speed指明了一个参数,并通过赋值(等于号)将其赋值为1.0(一个浮点数),第三个单词sliceSize指明了另一个参数,并通过赋值将其赋值为1。同时,第四行描述了这个装饰物有"floaty&#​34;(漂浮)的属性。最后用“/>”(一定要有!)来结束这个属性的描述。

    保存之后,大部分情况下蔚蓝都会直接更新。但如果语法错误,保存之后不会更新,甚至崩溃。

    下面是参数的若干类型:

代码块
HTML
自动换行
复制代码
int:整数,范围在-2147483648 - 2147483647。
float:浮点数,或者是有理数,不要写太离谱的数字即可。
bool:布尔,true代表1,false代表0。
string:字符串,一串字母(不要用中文,可能会出现奇怪的结果)。
frames:帧格式,下面会讲到。
复制成功

    接下来是各种属性及其参数:

代码块
HTML
自动换行
复制代码

<banner>:让插图像旗帜一样在水平方向晃动,并且以正弦波的方式晃动。具体来讲,插图会被切成一行一行的像素,在每一行上施加一个正弦波,实现插图像旗帜一样晃动的效果。
	speed(float):旗帜摇晃的速度,数值越大摇晃地越快,相当于决定了正弦波的频率。初始值为1.0。
	amplitude(float):旗帜在水平方向上摇晃的最远的距离,数值越大距离越远,相当于决定了正弦波的频率。初始值为1.0。
	sliceSize(int):每几个像素将插图切一行,数值越小切得越细,旗帜摇晃地更加丝滑。初始值为1。
	sliceSinIncrement(float):相邻两行距离相差多少,数值越小旗帜摇晃地更加丝滑,相当于决定了相邻两个正弦波初相位的差值。初始值为1.0。
	easeDown(bool):设定了旗帜是被固定在底下还是固定在上方。如果为true,固定在上方,否则为下方。初始值为false。
	onlyIfWindy(bool):如果为true,那么只有有风的时候才会摇动,否则任何时候都会摇动。初始值为false。
复制成功

banner效果

代码块
HTML
自动换行
复制代码
<floaty>:让插图像9A中的月岩一样漂浮,没有其他参数。
复制成功

代码块
HTML
自动换行
复制代码
<smoke>:冒烟。可以多次添加。
	offsetX(float):设定冒烟的位置的x坐标。如果是正的,那么会偏右。如果是负的,那么会偏左。初始值为1.0。
	offsetY(float):设定冒烟的位置的y坐标。如果是正的,那么会偏下。如果是负的,那么会偏上。初始值为1.0。
	inbg(bool):如果为true,那么烟会从后面冒出来,否则会从前面冒出来。初始值为false。
复制成功

smoke效果

代码块
HTML
自动换行
复制代码
<parallax>:视差,描述了一个插图距离屏幕有多远。
	amount(float):具体数值具体分析,难以描述。
复制成功

代码块
HTML
自动换行
复制代码
<bloom>:加一个荧光光源,可以多次添加。这个光源对其他插图没有效果,但对场景有效果。
	offsetX(float):光源在x方向上的偏移量。如果是正的,那么会偏右。如果是负的,那么会偏左。初始值为0。
	offsetY(float):光源在y方向上的偏移量。如果是正的,那么会偏下。如果是负的,那么会偏上。初始值为0。
	alpha(float):设定光源与其他图像混合的alpha值,范围在0~1之间。如果越大,那么越亮。否则越暗。初始值为1.0。
	radius(float):荧光的范围,初始值为1。(由于是像素范围,所以几乎看不出来!)
复制成功

代码块
HTML
自动换行
复制代码
<light>:加一个光源,可以多次添加。对所有东西都有效果。
	offsetX(float):光源在x方向上的偏移量。如果是正的,那么会偏右。如果是负的,那么会偏左。初始值为0。
	offsetY(float):光源在y方向上的偏移量。如果是正的,那么会偏下。如果是负的,那么会偏上。初始值为0。
	alpha(float):设定光源与其他图像混合的alpha值,范围在0~1之间。如果越大,那么越亮。否则越暗。初始值为1.0。
	startFade(int):光源开始减弱的距离,如果越大,那么光源能照亮的东西范围越大。初始值为16。
	endFade(int):光源强度减弱到0的最远的距离。初始值为24。同时,为了确保光源不会出现看起来奇怪的问题(光照强度瞬间降为0),它的值通常会大于startFade的值。
复制成功

    不过,如果startFade或endFade太大,似乎光源会出现问题。

正常的光源,值为100,100

范围设定太大的光源,值为200,200

代码块
HTML
自动换行
复制代码
<depth>:插图的深度。由于蔚蓝是2D游戏,因此要做出具有纵深效果的图像,就得按照一定的顺序将图像画在频幕上。具体来说,每个物品会有一个设定的深度值,我们将其称为depth,那么,绘制的时候将按照depth从大到小的顺序进行绘制。所以,depth大的图像会被depth小的图像覆盖。
	value(int):设定深度值。
复制成功

代码块
HTML
自动换行
复制代码
<lightOcclude>:添加一个能挡住光源的看不见的能通过的矩形。
	x(int):矩形的左上角在x方向上的偏移量。如果是正的,那么会偏右。如果是负的,那么会偏左。初始值为0。
	y(int):矩形的左上角在y方向上的偏移量。如果是正的,那么会偏下。如果是负的,那么会偏上。初始值为0。
	width(int):矩形的宽度。初始值为16。
	height(int):矩形的高度。初始值为16。
	alpha(float):透明度,0是完全透明。1是完全不透明。初始值为1.0。
注意,如果这个矩形挡住了光源,那么将没有效果。
复制成功

    对了,如果偏移量为0,那么这个矩形的左上角会在正中间,即恰好在行数为一半像素数、列数为一半像素数的那个像素的右下角的那个像素的位置。

8x8的图片,红色方块是矩形左上角的像素

代码块
HTML
自动换行
复制代码
<solid>:添加一个不能看见的但会阻碍通行的矩形。
	x(int):矩形的左上角在x方向上的偏移量。如果是正的,那么会偏右。如果是负的,那么会偏左。初始值为0。
	y(int):矩形的左上角在y方向上的偏移量。如果是正的,那么会偏下。如果是负的,那么会偏上。初始值为0。
	width(int):矩形的宽度。初始值为16。
	height(int):矩形的高度。初始值为16。
	index(int):踩上去的声音,初始值为14(度假村楼顶的声音)。具体见下表。
	blockWaterfalls(bool):是否会阻挡瀑布。初始值为true。
	safe(bool):是否是能够收集草莓的安全的平面。初始值为true。
复制成功

代码块
HTML
自动换行
复制代码
<coreSwap>:第8章的环境有两种状态,一种是“hot”,另一种是“cold”,这个属性能够设置两种状态下不同的材质,就像弹球、岩浆块和冰块、冰墙和电梯墙。
	coldPath(string):设定“cold”环境的材质。
	hotPath(string):设定“hot”环境的材质。
复制成功

代码块
HTML
自动换行
复制代码
<mirror>:增加一个镜面的效果。
	keepOffsetClose(bool):是否决定镜面反射看起来与玩家更近一点,初始值为false。
	不过为了正确地使用这个属性,需要在Mods/name/Graphics/Atlases/Gameplay/mirrormasks/folders下建立名称相同的掩图。
	我们知道,我们常使用的png图片是一个n行m列小格子组成的矩形,并且在每一个格子里有三个基础的颜色:红色(R),绿色(G),蓝色(B),它们的范围是0-255的整数,并且组成了一个能够在显示屏上显示的颜色。
	为了正确地描述装饰物在哪些位置需要照出人像,哪些位置不需要找出人像,蔚蓝需要在mirrormasks/floders找到一个行数、列数和原始图片相同的png图片。对于每一个小格子,如果是透明的,那么这个位置就不是镜子(不会照出人像)。否则,它会检测三个RGB值:
	
	对于R,数值越大代表在水平方向上离玩家越近。如果越小,可以理解为照出的人像会偏右。
	对于G,数值越大代表在竖直方向上离玩家越近。如果越小,可以理解为照出的人像会偏下。
	对于B,没有什么作用。
复制成功

cut-off

和动态装饰物有关的属性

    

    我喜欢的一个做法是,把所有的图片都放在同一个文件夹里,然后依次给它标号。

    图片的名称以此为name0,name1,name2,...。

    几个注意点:

  1. 更新图片时,可能要重启蔚蓝才能有用。

  2. 图片后缀的数字一定严格从0开始,严格地递增,前面的name一定相同。

  3. 更新DecalRegistry.xml时可能要重启蔚蓝。

代码块
HTML
自动换行
复制代码
<animationSpeed>:设置切图的速度。
	value(int):一秒里有多少帧,数值越大切图越快。初始值为12。填负数会崩溃。
复制成功

代码块
HTML
自动换行
复制代码
<randomizeFrame>:设置一个随机的开始帧。要求Everest版本在3442及其以上。
复制成功

代码块
HTML
自动换行
复制代码
<scared>:设定一个“受惊”的动画。见下面参数。
	hideRange(int):设定一个值,如果玩家距离装饰物小于这个值,那么播放“隐藏”动画。
	showRange(int):设定一个值,如果玩家距离装饰物大于这个值,且装饰物处于“隐藏”状态,那么播放“出现”动画。
	idleFrames(frames):装饰物既不在“隐藏”,又不在“出现”时播放的动画,初始值为“0”。
	hideFrames(frames):装饰物“隐藏”的动画。初始值为0。
	hiddenFrames(frames):装饰物隐藏之后播放的动画。初始值为0。
	showFrames(frames):装饰物“出现”的动画。初始值为0。
	其中,frames是一个字符串,描述了哪些帧(图片)应该被播放。如果写"0,1,2,5,7",那么pic_0,pic_1,pic_2,pic_5,pic_7会按照设定的速度(但很可惜,不是animationSpeed)依次播放。同样,它能写成"0-2,5,7-7"。
复制成功

    DecalRegistry对动图的支持度很不好,不仅属性少,还会给文件夹下的所有图片赋值同样的属性!

    而且path的规则也很古怪,现在发现用folders/name/这样是最好用的,其中name是存放动图的文件夹。

    还有些属性(sound,overlay,flagSwap,staticMover,scale)感觉不太常用,或者难度不适合这篇文章,所以没有列出,可以看https://github.com/EverestAPI/Resources/wiki/Decal-Registry。

    这是我目前所发现的,蔚蓝源码没好好看,没什么讨论,discord也狂卡。如果有不对或更好的办法欢迎指出。

cut-off

附表

代码块
HTML
自动换行
复制代码
原版游戏声音的代码

None = 0
Asphalt = 1
Car = 2
Dirt = 3
Snow = 4
Wood = 5
Bridge = 6
Girder = 7
Brick = 8
Zip Mover = 9
Space Jam (Inactive) = 11
Space Jam (Active) = 12
Resort Wood = 13
Resort Roof = 14
Resort Platform = 15
Resort Basement = 16
Resort Laundry = 17
Resort Boxes = 18
Resort Books = 19
Resort Forcefield = 20
Resort Clutterswitch = 21
Resort Elevator = 22
Cliffside Snow = 23
Cliffside Grass = 25
Cliffside Whiteblock = 27
Gondola = 28
Glass = 32
Grass = 33
Cassette Block = 35
Core Ice = 36
Core Rock = 37
Glitch = 40
Internet Café = 42
Cloud = 43
Moon = 44
复制成功
cut-off

常见错误

Q:为什么我更新了没效果?

A:没保存或者代码写错了。

Q:为什么更新后蔚蓝崩溃了?

A:可能是代码中双引号里的类型写错了,例如int里写了一些字符。

Q:动态装饰物不会动?

A:标号前面名称可能不一致、可能要重启蔚蓝。

cut-off

样例

视频:celeste地图制作指南(装饰,decals)

相应的包:https://pan.baidu.com/s/1JGMZ3QA5QSVqEvWEW2jVjA?pwd=hjyd

码:hjyd

cut-off

参考:Decal Registry · EverestAPI/Resources Wiki · GitHub