Splatoon 2 自带了一个简单的像素涂鸦工具,大触们可以一个一个像素点绘制出类似于下面这样的作品,然后把它顶在广场自己的小乌贼头上。

然而对于我这种手残而言,真要自己一个一个像素点画幅画是不可能的,这辈子都不可能的。但是又想要好看的画怎么办,Switch-Fightstick 这个项目就是来拯救你我这种手残的。

Switch-Fightstick 主要有两个功能,一个是将你的图片转换为像素图(和对应的数组),另一个是将像素数组通过单片机模拟 Pokken Tournament Pro Pad 手柄,把一个一个像素给点进去。

比如一张图片(320 x 120):

转换为像素图:

编译,烧入单片机。给 Switch 插上单片机,然后它就一点一点开始画了,速度很慢:

最终成果:


Switch-Fightstick 在 macOS 的使用过程相对简单,按照 GitHub 上的 Guide 一步一步走,安装 AVR CrossPack,下载 LUFA,编辑 makefile,pip 安装 pillow,执行 Python 脚本,make,Tensy Loader 写入 hex 二进制文件即可。

虽然 Switch-Fightstick 文档写的支持 Tensy 2.0++,但买错板子的 我实测 Tensy 2.0 也是完全没问题的,不过在执行 make 之前,需要编辑一下 makefile,把

MCU = at90usb1286

修改成

MCU = atmega32u4

这样才能保证 Teensy Loader 能够正确地将 hex 二进制文件写入到单片机中。


浅读了一下 Switch-Fightstick 的源码,大概理解了一下几个源代码的作用。

png2c.py 的作用是生成 image.c 用于后期编译,输入为一张 340 * 120 的 png 文件,转换为黑白图,逐像素读取得到一个二进制数组(1 表示涂色,0 表示不涂),再把这个二进制数每八个构造一个 Byte,写到 image.c 中。

JoyStick.c 比较重要,从源码来看,与主机交互的过程放到一个无限 Loop 中,每次循环读取一次主机传递过来的输入,再把控制指令传给主机,而每次具体的指令内容则由一个状态机来维护,有四个状态用于控制手柄左摇杆的运动和打点(按 A),控制运动的顺序是这样的:

emmm…蛇形走位虽然很简单但是…效率似乎并不是特别高,尤其是对于留白比较多的图片而言,实际上很多步骤移动都是没有必要的。一种比较简单的思路是酱紫的:

思路很简单,当前行移动的最远点,是根据当前行最右(或最左,根据方向决定)的涂色点(值为 1),和下一行最右(最左)的涂色点共同决定,每次都走到两者之间最远的点,然后立即走到下一行,而不是必须走到最末的点。对于留白多的图,时间会远远优于原始的算法。

稍微修改了一下 png2c.py,除了生成图形点的数据之外,还额外生成一个数组存储每行最左和最右的黑点。

 for i in range(0, 120):
   sublist = data[i * 320:(i + 1) * 320 ]
   indices = [j for j,x in enumerate(sublist) if x == 1]
   if (len(indices) == 0):
     edges.append(0)
     edges.append(319)
   elif (len(indices) == 1):
     edges.append(indices[0])
     edges.append(indices[0])
   else:
     edges.append(indices[0])
     edges.append(indices[-1])

JoyStick.c 这边主要是每次走下一行之间,获取下一行走的最远的点,然后左右移动时候控制在最远的点之内的范围。

 if ((ypos % 2 && xpos > minLeftMost) || (!(ypos % 2) && xpos < maxRightMost)) {
   state = STOP_X;
 } else {
   state = STOP_Y;
 }

新的源码放在了我的 GitHub 上。

好吧折腾到此为止,原理上可以用 Tensy 模拟所有的手柄操作,比如看到 一个项目 基于 Switch-Fightstick 修改来实现异度神剑 2 自动购买食物和喂食异刃。以后有啥好的思路再继续折腾吧。