本帖最后由 LocusAzzurro 于 2020-7-19 21:36 编辑
前言 - 什么是pixel-perfect算法
这个算法可以让我们在绘制像素曲线时保持1像素的宽度,在绘制的同时实时移除L形状位置上的像素。
下图左侧是正常的铅笔工具绘制的像素曲线,右侧是经过算法加持形成的曲线,中间标记出了移除的像素。
function实现
tick.json
复制代码
draw.mcfunction
复制代码
iterate.mcfunction
复制代码
实现说明
循环function:
检测手持粘液球的玩家并运行算法,一个考虑到的点是只有玩家移动了超过一格距离才会进行一次迭代计算。另外加了一些粒子标记当前计算的位置。
迭代function:
第一部分主要是跟随玩家移动生成盔甲架,并且对齐到整数坐标便于计算。行进方式类似于贪吃蛇,用p1,p2,p3三个tag分辨生成的先后顺序。
接下来是检测三个盔甲架是否形成了一个直角,这里用的方法是在p2的盔甲架位置周围四个对角的位置分别生成盔甲架,然后检测1.5格范围内的盔甲架数量(1.5格刚好可以检测到旁边和对角,直线里比较远的则不在范围内),然后分别写入计分板备用。如果是直线,四个数值就都是2,如果是直角,会有两个2,一个1,一个3。
然后是条件检测,先默认p2位置的盔甲架属于直角,因此不绘制(nodraw)。然后检测三个盔甲架是否直线,如果是则移除tag。
后面的一个检测是在行进路线呈S型时进行的判断,因为刚才的行进规则,p2上的nodraw会在下一次迭代里继承到p1上,因为这个算法是保留S型里后绘制的那个像素,因此检测之前的p1上是不是已经有了nodraw,如果有就保留现在的p2位置的像素,移除p1。如果只是简单的直角,则保留nodraw,不绘制像素。
不同情况的处理方式见下图:
黄色=移除检测;红色=确认移除;绿色=确认保留
最后就是实际生成方块,是在每次周期开始的时候进行,如果p1最后确认为要绘制的像素,就在盔甲架下方一格位置生成方块。

后话
前几天RMG群里有人说到Aseprite里的这个工具,有点感兴趣,于是研究了一下算法。应该只能单机用,没做多人优化,算个prototype/proof of concept吧。版本兼容的话因为1.16刚出的就顺手用了,1.15/1.14应该也可以用。(记得放进去之后先跑一下 pixel:init 创建计分板)
pixel.zip
(2.9 KB, 下载次数: 0)
前言 - 什么是pixel-perfect算法
这个算法可以让我们在绘制像素曲线时保持1像素的宽度,在绘制的同时实时移除L形状位置上的像素。
下图左侧是正常的铅笔工具绘制的像素曲线,右侧是经过算法加持形成的曲线,中间标记出了移除的像素。

function实现
tick.json
- {
- "values": [
- "pixel:draw"
- ]
- }
draw.mcfunction
- #pixel-imperfect-comparison
- tag @a remove ip_drawing
- execute as @p[nbt={SelectedItem:{id:"minecraft:clay_ball"}}] run tag @s add ip_drawing
- execute at @a[tag=ip_drawing] align xzy run setblock ~ ~-1 ~ white_wool
- #pixel-perfect
- tag @a remove drawing
- execute as @p[nbt={SelectedItem:{id:"minecraft:slime_ball"}}] run tag @s add drawing
- execute at @e[tag=p2] if entity @a[tag=drawing,distance=..5] run particle minecraft:end_rod ~ ~0.5 ~ 0.1 0 0.1 0.005 1 force
- execute at @a[tag=drawing] align xzy unless entity @e[tag=pixel,distance=0] run function pixel:iterate
iterate.mcfunction
- #drawing @ p1
- execute at @e[tag=p1,tag=!nodraw] run setblock ~ ~-1 ~ white_wool
- #trace iteration
- kill @e[tag=p1]
- tag @e[tag=p2] add p1
- tag @e[tag=p1] remove p2
- tag @e[tag=p3] add p2
- tag @e[tag=p2] remove p3
- execute at @p align xzy run summon armor_stand ~ ~ ~ {NoGravity:1b,Invulnerable:1b,Invisible:1b,Tags:["p3","pixel"]}
- #corner pixel check
- execute at @e[tag=p2] run summon armor_stand ~1 ~ ~1 {NoGravity:1b,Invulnerable:1b,Invisible:1b,Tags:["anchor1"]}
- execute store result score __cornerTest1 corner run execute as @e[tag=anchor1] at @e[tag=anchor1] if entity @e[distance=..1.5,tag=pixel]
- kill @e[tag=anchor1]
- execute at @e[tag=p2] run summon armor_stand ~1 ~ ~-1 {NoGravity:1b,Invulnerable:1b,Invisible:1b,Tags:["anchor2"]}
- execute store result score __cornerTest2 corner run execute as @e[tag=anchor2] at @e[tag=anchor2] if entity @e[distance=..1.5,tag=pixel]
- kill @e[tag=anchor2]
- execute at @e[tag=p2] run summon armor_stand ~-1 ~ ~1 {NoGravity:1b,Invulnerable:1b,Invisible:1b,Tags:["anchor3"]}
- execute store result score __cornerTest3 corner run execute as @e[tag=anchor3] at @e[tag=anchor3] if entity @e[distance=..1.5,tag=pixel]
- kill @e[tag=anchor3]
- execute at @e[tag=p2] run summon armor_stand ~-1 ~ ~-1 {NoGravity:1b,Invulnerable:1b,Invisible:1b,Tags:["anchor4"]}
- execute store result score __cornerTest4 corner run execute as @e[tag=anchor4] at @e[tag=anchor4] if entity @e[distance=..1.5,tag=pixel]
- kill @e[tag=anchor4]
- #condition check
- tag @e[tag=p2] add nodraw
- execute if score __cornerTest1 corner matches 2 if score __cornerTest2 corner matches 2 if score __cornerTest3 corner matches 2 if score __cornerTest4 corner matches 2 run tag @e[tag=p2] remove nodraw
- execute as @e[tag=p2,tag=nodraw] at @e[tag=p2,tag=nodraw] if entity @e[tag=nodraw,tag=p1,distance=..1] run tag @e[tag=p2] remove nodraw
实现说明
循环function:
检测手持粘液球的玩家并运行算法,一个考虑到的点是只有玩家移动了超过一格距离才会进行一次迭代计算。另外加了一些粒子标记当前计算的位置。
迭代function:
第一部分主要是跟随玩家移动生成盔甲架,并且对齐到整数坐标便于计算。行进方式类似于贪吃蛇,用p1,p2,p3三个tag分辨生成的先后顺序。

接下来是检测三个盔甲架是否形成了一个直角,这里用的方法是在p2的盔甲架位置周围四个对角的位置分别生成盔甲架,然后检测1.5格范围内的盔甲架数量(1.5格刚好可以检测到旁边和对角,直线里比较远的则不在范围内),然后分别写入计分板备用。如果是直线,四个数值就都是2,如果是直角,会有两个2,一个1,一个3。

然后是条件检测,先默认p2位置的盔甲架属于直角,因此不绘制(nodraw)。然后检测三个盔甲架是否直线,如果是则移除tag。
后面的一个检测是在行进路线呈S型时进行的判断,因为刚才的行进规则,p2上的nodraw会在下一次迭代里继承到p1上,因为这个算法是保留S型里后绘制的那个像素,因此检测之前的p1上是不是已经有了nodraw,如果有就保留现在的p2位置的像素,移除p1。如果只是简单的直角,则保留nodraw,不绘制像素。
不同情况的处理方式见下图:
黄色=移除检测;红色=确认移除;绿色=确认保留

最后就是实际生成方块,是在每次周期开始的时候进行,如果p1最后确认为要绘制的像素,就在盔甲架下方一格位置生成方块。

后话
前几天RMG群里有人说到Aseprite里的这个工具,有点感兴趣,于是研究了一下算法。应该只能单机用,没做多人优化,算个prototype/proof of concept吧。版本兼容的话因为1.16刚出的就顺手用了,1.15/1.14应该也可以用。(记得放进去之后先跑一下 pixel:init 创建计分板)

66666666666
补充一下:
对于更大半径的画笔 可以使用同样的方式进行补偿
如果绘制路径发现直角,那就将直角位置的整个画笔都删掉
对于更大半径的画笔 可以使用同样的方式进行补偿
如果绘制路径发现直角,那就将直角位置的整个画笔都删掉
通俗易懂的编码,爱了
mcbbs有你更精彩
通俗易懂的编码,爱了
我一直以为pixel是神奇宝贝有关的名字XD,原来是这个意思吗
厉害还可以这样玩