基于Arduino与CircuitPython的交互式灯光艺术装置制作全流程
1. 项目概述复刻一场沉浸式光影对话几年前在纽约古根海姆博物馆我第一次看到詹姆斯·特瑞尔James Turrell的《Aten Reign》。那不是一个挂在墙上的“画”而是一个你走进去的“空间”。光线从头顶的天窗倾泻而下经过精密的滤光与反射填满了整个圆形大厅色彩在空气中缓慢地流动、呼吸仿佛光线本身有了质量和生命。那种体验很难用语言形容——你不是在看一件艺术品而是被包裹在光构成的实体中。从那时起我就琢磨着能不能把这种宏大而静谧的“光之体验”微缩成一个可以放在桌面上、并能与人互动的实体装置。这个想法就是今天这个项目的起点一个基于Arduino与CircuitPython的交互式灯光艺术装置。它不仅仅是一个会发光的盒子更是一个微缩的、可对话的光影剧场。装置的核心是一套嵌入式系统Arduino Nano RP2040作为大脑一个飞行时间ToF距离传感器充当“眼睛”一个电位器作为“调色盘”再加上一条可编程的NeoPixel RGB灯带作为“画笔”。当人靠近时灯光由暗渐明如同被唤醒远离时又缓缓暗下归于宁静。旋转电位器则能在预设的几种光色氛围如晨曦的蓝粉、深海的幽蓝、落日的暖橙间平滑切换。背景中还有一段舒缓的环境音效循环播放进一步强化沉浸感。整个制作流程融合了数字制造、基础木工、电子电路与创意编程。从用激光切割机精准制作木结构到焊接电路、编写将传感器数据映射为光效的代码每一步都需要动手与动脑的结合。无论你是对交互艺术感兴趣的创作者还是想深入学习Arduino和CircuitPython的硬件爱好者甚至是STEAM教育领域的老师寻找一个综合性的项目这个教程都希望能为你提供一条清晰的路径。它涉及的技能点很广但门槛并不高关键在于理解“输入传感器→ 处理微控制器→ 输出灯光/声音”这一核心的物理计算逻辑。接下来我们就从设计思路开始一步步拆解这个光影盒子的诞生记。2. 核心设计思路与材料选型解析2.1 艺术概念与技术实现的融合詹姆斯·特瑞尔的作品核心在于“感知光而非看见物体”。在复刻时我的目标不是做一个外观一模一样的模型而是捕捉其交互的精髓用光来响应人的存在与动作创造一种沉浸的、冥想式的体验。因此技术方案必须服务于这个艺术目标。装置的核心交互逻辑设计如下存在感知Presence Sensing采用ToF距离传感器而非普通的红外或超声波传感器。ToF传感器通过计算光线飞行时间得到距离精度高毫米级、响应快且不受环境光干扰。这确保了当观众缓慢走近时灯光亮度的变化是平滑、精准且可靠的避免了普通传感器因环境干扰而产生的跳变破坏了静谧感。色彩控制Color Control使用一个旋转电位器作为唯一的物理交互接口。电位器模拟电压值的变化经微控制器模数转换ADC读取后映射到不同的色彩模式。这种模拟式的、无限旋转的输入方式比按钮更符合“调节氛围”的直觉转动时轻微的阻尼感也增添了操作的仪式感。视觉呈现Visual Presentation采用多层镂空结构shadow box来营造景深。光线从背部的LED灯带发出穿过层层镂空的隔板在空间中形成柔和、弥散的光晕而非直接看到点状光源。这模仿了特瑞尔作品中光线仿佛悬浮在空中的效果。多感官叠加Multi-sensory Overlay加入一个微型扬声器循环播放抽象的环境音效如空灵的音符、轻微的白噪音。声音与灯光变化节奏同步从听觉维度强化视觉体验形成更完整的沉浸场。2.2 关键物料清单与选型依据制作这个装置你需要准备以下几大类材料。我会详细说明为什么选这些以及是否有可替代方案。2.2.1 主体结构材料木材项目使用了1/4英寸约6.35mm和1/8英寸约3.175mm厚的桦木板。选型依据桦木质地均匀激光切割时边缘光滑、焦痕浅易于后期打磨和上漆。1/4英寸用于外框保证强度1/8英寸用于内部镂空层兼顾轻薄与透光性。替代方案椴木板、亚克力板可透光但质感不同或高质量的中密度纤维板MDF。粘合剂太棒胶Titebond。选型依据木工专用粘接力强干燥后透明不影响后续上漆。表面处理Mod Podge一种胶性密封剂、白色哑光丙烯酸漆、砂纸180目至400目。选型依据Mod Podge作为底漆可以密封木材毛孔防止面漆被木材吸收导致不均匀。多层白色哑光漆旨在实现完全的“感官剥夺”效果消除木材纹理等视觉干扰让注意力完全集中在光上。2.2.2 电子核心组件微控制器Arduino Nano RP2040 Connect。选型依据这是本项目的“大脑”。选择它而非经典款Arduino Nano主要因为三点第一它搭载了RP2040双核处理器性能更强能流畅处理传感器数据、驱动LED和音频第二它原生支持CircuitPython和MicroPython开发体验比传统Arduino C更友好代码可读性极高适合快速迭代第三它集成了Wi-Fi/蓝牙模块本项目虽未使用但为未来联网升级留有余地。替代方案Adafruit Feather RP2040、Seeed Studio XIAO RP2040或使用传统Arduino Uno/Nano配合NeoPixel库和ADC功能也可实现基础效果。灯光系统WS2812B NeoPixel RGB LED灯带每米60灯或144灯。选型依据NeoPixel是智能寻址LED每个灯珠可独立编程控制颜色和亮度只需一根数据线串联控制极大地简化了布线。选择高密度144灯/米的灯带可以使光线过渡更平滑避免出现明显的光点。传感器与输入VL53L0X或VL53L1X ToF距离传感器测量范围约几十厘米到数米精度高I2C接口占用引脚少。10kΩ旋转电位器带旋钮经典模拟输入元件线性变化成本低廉。输出与供电微型扬声器或蜂鸣器模块用于播放简单的WAV格式音频或合成音效。选择带功放芯片的模块如MAX98357可以直接驱动。移动电源充电宝作为装置电源。选择输出为5V/2A或以上的型号确保能同时为Arduino和LED灯带全亮时功耗较大提供充足电流。辅助材料面包板、杜邦线公对公、公对母、焊锡、电工胶带、吸管用于理线、白色卡纸用于隐藏底部走线。注意电源安全LED灯带在全白高亮状态下电流可能超过2A。务必确保你的移动电源和连接线能承受此电流。建议在Arduino和灯带电源正极之间串联一个5A的自恢复保险丝以防短路。3. 结构制作与硬件装配详解3.1 激光切割设计与木结构组装木结构是整个装置的骨架也是光影效果的载体。其核心是一个五面封闭、一面可开启的“深景深盒子”内部嵌入三层镂空面板。3.1.1 设计图纸与切割要点确定尺寸我的外盒尺寸为正面板15.5英寸 x 18英寸高x宽侧板6英寸深x 18英寸高顶/底板15.5英寸 x 6英寸深。这个比例看起来比较协调深度足够营造层次感。你可以根据手头材料调整但建议深度不要小于4英寸。设计镂空图案使用Adobe Illustrator、Fusion 360或免费的Inkscape、LaserCAD等软件绘制。我为三层内板设计了同心但图案不同的“圆厅”式镂空Rotunda Pattern。图案灵感来源于建筑穹顶线条以曲线和几何重复为主确保激光切割后结构依然牢固。设计技巧线条宽度建议不小于3mm否则木板易断。相邻镂空间距也需保持一定距离。将设计文件导出为DXF或SVG格式供激光切割机使用。激光切割操作材料准备确保桦木板平整、无翘曲。用湿布清洁表面灰尘。参数设置这是关键。对于1/4英寸桦木我的参数是功率85%速度8mm/s频率1000Hz进行1-2次切割。对于1/8英寸桦木功率65%速度15mm/s。务必先在边角料上进行测试不同品牌机器、甚至不同批次的木材最佳参数都可能不同。目标是切透且焦痕较浅。切割顺序先切内层精细图案再切外层大框架。切割时用 masking tape美纹纸胶带覆盖切割区域能显著减少背面焦痕。传感器开孔别忘了在正面板下方预先设计一个直径约6mm的圆孔用于安装ToF传感器使其能“看见”外部。3.1.2 打磨、粘合与上漆精细打磨切割后的木板边缘会有激光灼烧留下的碳化物和细微毛刺。使用180目砂纸初步打磨再用400目砂纸精细打磨至光滑。对于内层镂空板的边缘可以将其卷在砂纸上小心打磨确保能平滑放入外盒。组装外盒在接合面均匀涂抹太棒胶。先将底板与正面板粘合用直角夹或自制木工夹确保90度角。然后依次粘合两侧板。关键顶板不要粘死它应该是可拆卸的方便后续电路安装和维护。我是在顶板内侧和侧板顶端粘贴了强磁铁如钕铁硼磁铁实现隐形且牢固的磁吸闭合。用夹子固定所有接缝静置至少24小时待胶水完全固化。多层上漆实现“白化”第一层Mod Podge密封。用宽刷将Mod Podge均匀涂刷在所有木表面包括内部。它会渗入木材形成密封层防止后续油漆被吸收。干燥约1小时。第二层及以后白色哑光丙烯酸漆。使用质量较好的哑光漆薄涂多层我涂了4-5层每层干燥后再涂下一层。方向要交叉例如第一层横刷第二层竖刷确保覆盖均匀。目标是达到完全均匀、不反光的纯白表面遮盖所有木材纹理。内部和背面同样要涂刷防止光线从木纹缝隙中漏出杂色。3.2 电路系统焊接与集成这是项目的“神经系统”需要耐心和细心。3.2.1 NeoPixel灯带的安装与焊接裁剪与测试根据每层内板的周长裁剪三段NeoPixel灯带。务必在灯带上标明的“剪刀”图标处裁剪。裁剪前先用Arduino和一段示例代码测试灯带是否完好。粘贴与走线规划撕掉灯带背面的胶带将其粘贴在内板背面的边缘围绕镂空图案。确保灯珠朝向木板光线通过木板漫反射数据流向通常有箭头标记一致。规划好每段灯带电源5V、地线GND和数据线DIN的汇合路径。焊接连接电源并联将所有灯带的正极5V焊接到一根较粗的导线上所有负极GND焊接到另一根粗导线上。粗线很重要以减少大电流下的压降和发热。数据串联将第一层灯带的数据输出DOUT焊接到第二层的数据输入DIN第二层的DOUT焊接到第三层的DIN。最终第一层的DIN将连接到Arduino的某个数字引脚如D5。焊接技巧使用尖头烙铁温度控制在350°C左右。先给焊盘和线头上锡然后快速焊接。焊完后用万用表通断档检查是否有短路或虚焊。最后用电工胶带或热缩管仔细包裹每个焊点防止短路。3.2.2 主控板与传感器的连接将Arduino Nano RP2040插在小型面包板上方便连接。电源接入将移动电源的USB线剪断或使用USB breakout模块引出正极红色5V和负极黑色GND连接到面包板的电源轨。再将面包板的电源正负极连接到Arduino的VIN或5V和GND。注意Arduino Nano RP2040的VIN引脚可以接受5V输入。NeoPixel连接5V- 灯带正极总线。GND- 灯带负极总线。D5或其他数字引脚- 第一段灯带的DIN。务必在Arduino的5V输出和灯带5V输入之间串联一个300-500欧姆的电阻以保护第一个LED的数据引脚。同时在灯带电源正负极之间并联一个1000µF的电解电容以缓冲瞬时电流冲击防止上电瞬间损坏LED。传感器与元件连接VL53L0X ToF传感器VIN- 面包板3.3V注意该传感器通常用3.3V逻辑电平GND- 面包板GNDSCL- ArduinoSCL物理引脚D13/GPIO27SDA- ArduinoSDA物理引脚D12/GPIO26电位器两侧引脚分别接面包板3.3V和GND。中间引脚滑动端接 Arduino 模拟输入引脚A0。扬声器模块以MAX98357 I2S模块为例VIN- 面包板5VGND- 面包板GNDDIN- ArduinoD9I2S数据BCLK- ArduinoD10位时钟LRC- ArduinoD11左右声道时钟内部理线与固定用扎带或胶带将电线捆扎整齐。我用了剪开的吸管套在一束束电线上既绝缘又整齐。用双面泡棉胶或纳米胶将Arduino面包板、移动电源固定在盒子底部角落。将ToF传感器从正面板预先开好的孔中伸出并固定。将电位器安装在侧面或背面旋钮朝外。最后用白色卡纸裁剪成底板大小覆盖在底部所有线材和设备上让内部看起来整洁。4. CircuitPython程序设计与交互逻辑实现CircuitPython让代码变得非常直观。我们将程序分解为几个功能模块。4.1 开发环境搭建与库管理安装CircuitPython固件访问 CircuitPython官网 下载对应Arduino Nano RP2040 Connect的最新UF2固件文件。用USB线连接板子到电脑。快速双击板子上的复位按钮RST此时电脑会出现一个名为RPI-RP2的可移动磁盘。将下载的UF2文件拖入该磁盘。完成后板子会自动重启并出现一个名为CIRCUITPY的新磁盘。代码编辑器可以使用Mu Editor、VS Code with CircuitPython插件或任何文本编辑器。代码文件将直接保存在CIRCUITPY磁盘上保存后自动运行。安装必要的库将以下库文件.mpy或.py复制到CIRCUITPY磁盘的lib文件夹中adafruit_vl53l0x.mpy用于ToF传感器。neopixel.mpy用于控制NeoPixel灯带。adafruit_ticks.mpy用于时间管理非必须但好用。如果使用I2S音频还需要adafruit_audiobusio和adafruit_wave用于播放WAV文件。4.2 核心代码分步解析创建一个名为code.py的文件保存在CIRCUITPY磁盘根目录。这是主板启动后自动运行的主程序。# 导入必要的库 import board import busio import time import analogio import neopixel import audiobusio import audiocore import adafruit_vl53l0x # 1. 初始化硬件 print(初始化交互光影装置...) # 初始化I2C总线用于ToF传感器 i2c busio.I2C(board.SCL, board.SDA) # 初始化ToF传感器 vl53 adafruit_vl53l0x.VL53L0X(i2c) # 设置测量模式平衡精度和速度 vl53.measurement_timing_budget 33000 # 微秒 # 初始化电位器模拟输入 potentiometer analogio.AnalogIn(board.A0) # 初始化NeoPixel灯带 # 假设三层灯带分别有30, 25, 20个灯珠总计75个 NUM_PIXELS 75 PIXEL_PIN board.D5 pixels neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, brightness0.3, auto_writeFalse) # 初始亮度设为0.3防止全亮太刺眼。auto_writeFalse意味着修改颜色后需要调用.show()才会更新。 # 初始化I2S音频输出以MAX98357为例 audio audiobusio.I2SOut(board.D9, board.D10, board.D11) # 加载一个WAV文件需要预先转换好格式并放在CIRCUITPY磁盘上 try: wave_file open(ambient.wav, rb) wave audiocore.WaveFile(wave_file) print(音频文件加载成功。) except OSError: print(未找到音频文件将静音运行。) wave None # 2. 定义全局变量与参数 # 亮度映射参数传感器测量范围毫米与目标亮度0.0-1.0的映射 DISTANCE_MIN 200 # 最近距离mm亮度最大 DISTANCE_MAX 1200 # 最远距离mm亮度最小非零 TARGET_BRIGHTNESS_MAX 0.8 # 最大亮度留有余地保护LED TARGET_BRIGHTNESS_MIN 0.1 # 最小亮度保持可见 # 色彩模式定义用RGB元组列表表示 COLOR_PALETTES [ [(255, 100, 150), (100, 200, 255)], # 模式0粉蓝渐变 [(10, 20, 80), (0, 255, 200)], # 模式1深海到青绿 [(255, 150, 50), (255, 50, 10)], # 模式2日落暖橙 [(200, 50, 255), (80, 10, 150)], # 模式3紫罗兰 [(255, 255, 200), (100, 255, 255)], # 模式4日光白到冰蓝 ] current_palette_index 0 num_palettes len(COLOR_PALETTES) # 3. 核心功能函数 def map_value(value, in_min, in_max, out_min, out_max): 将value从输入范围线性映射到输出范围。 return (value - in_min) * (out_max - out_min) / (in_max - in_min) out_min def get_smoothed_distance(): 读取ToF传感器数据并进行简单平滑滤波减少跳动。 readings [] for _ in range(5): # 快速采样5次 reading vl53.range if reading 30000: # 过滤超量程错误值 readings.append(reading) time.sleep(0.01) if readings: return sum(readings) / len(readings) # 返回平均值 else: return DISTANCE_MAX # 无有效数据时返回最远值 def set_gradient_color(palette): 根据当前选定的调色板为所有LED设置渐变颜色。 palette是一个包含两个RGB元组的列表表示渐变起点和终点色。 start_color, end_color palette for i in range(NUM_PIXELS): # 计算每个LED在渐变中的比例 ratio i / (NUM_PIXELS - 1) if NUM_PIXELS 1 else 0 # 线性插值计算RGB值 r int(start_color[0] (end_color[0] - start_color[0]) * ratio) g int(start_color[1] (end_color[1] - start_color[1]) * ratio) b int(start_color[2] (end_color[2] - start_color[2]) * ratio) pixels[i] (r, g, b) def update_brightness_from_distance(): 根据平滑后的距离值更新全局亮度。 distance get_smoothed_distance() # 将距离限制在定义范围内 distance_clamped max(DISTANCE_MIN, min(distance, DISTANCE_MAX)) # 映射距离到亮度距离越近亮度越高 new_brightness map_value(distance_clamped, DISTANCE_MIN, DISTANCE_MAX, TARGET_BRIGHTNESS_MAX, TARGET_BRIGHTNESS_MIN) # 为了更平滑的过渡可以加入一个简单的低通滤波 pixels.brightness new_brightness pixels.show() # 可选打印调试信息 # print(fDistance: {distance:.0f}mm, Brightness: {new_brightness:.2f}) # 4. 主循环 print(装置启动完成。开始交互循环...) if wave: audio.play(wave, loopTrue) # 循环播放背景音效 last_pot_value potentiometer.value POT_DEAD_ZONE 1000 # 电位器读数死区防止微小抖动触发模式切换 while True: # 4.1 检查电位器是否被大幅度旋转以切换色彩模式 pot_value potentiometer.value # 范围: 0-65535 pot_diff abs(pot_value - last_pot_value) if pot_diff POT_DEAD_ZONE: # 根据电位器值的变化方向决定索引增减 if pot_value last_pot_value: current_palette_index (current_palette_index 1) % num_palettes else: current_palette_index (current_palette_index - 1) % num_palettes print(f切换色彩模式至: {current_palette_index}) last_pot_value pot_value # 应用新调色板 set_gradient_color(COLOR_PALETTES[current_palette_index]) pixels.show() # 4.2 根据距离更新亮度 update_brightness_from_distance() # 4.3 控制循环速度 time.sleep(0.05) # 约20Hz的更新率响应流畅且不过度消耗CPU代码逻辑解读与关键点平滑滤波get_smoothed_distance()函数通过多次采样取平均有效滤除了传感器噪声使亮度变化丝滑避免了因单次读数跳动导致的灯光闪烁。亮度映射map_value函数是核心它将真实的物理距离200-1200mm线性映射到亮度值0.1-0.8。你可以调整DISTANCE_MIN和DISTANCE_MAX来改变装置的“感应区域”。色彩渐变算法set_gradient_color函数实现了从调色板起始色到结束色的线性插值为每个LED分配一个渐变的颜色值形成了平滑的色彩过渡带。电位器防抖通过POT_DEAD_ZONE死区设置只有当电位器读数变化超过一定阈值时才触发模式切换避免了因手部轻微抖动或电位器噪声造成的误切换。非阻塞音频使用audiobusio的play()函数并设置loopTrue后音频播放将在后台进行不会阻塞主循环确保灯光交互的实时性。5. 调试、优化与问题排查实录即使按照教程一步步来实际组装和编程中也可能遇到各种问题。下面是我在制作和后续改进中遇到的一些典型情况及解决方法。5.1 硬件连接与电源问题问题1上电后部分或全部NeoPixel灯珠不亮、颜色错乱或闪烁。排查步骤检查电源首先用万用表测量连接到灯带正负极的电压是否为稳定的5V。在全白高亮时电压不应低于4.7V否则供电不足。检查数据线方向确认数据流向DIN→DOUT是否正确第一颗LED的DIN是否接到了Arduino的引脚。检查接地确保Arduino的GND和灯带的GND共地即连接到了同一个电源的负极。这是最常见的问题之一。检查数据引脚确认代码中PIXEL_PIN的定义与实际连接的Arduino引脚一致。检查电阻和电容确认数据线上串联了300-500欧姆的电阻以及电源正负极间并联了1000µF电容。解决方案99%的NeoPixel问题源于电源或接地。尝试用一台输出能力更强的5V/3A电源适配器单独给灯带供电同时需将灯带GND与Arduino GND连接如果问题解决说明原移动电源或供电线路内阻过大。问题2ToF传感器读数不稳定或为0。排查步骤检查接线确认传感器的VCC接的是3.3V不是5VSDA和SCL没有接反。检查I2C地址在代码中加入扫描I2C地址的语句确认传感器被正确识别。i2c busio.I2C(board.SCL, board.SDA) while not i2c.try_lock(): pass print(I2C addresses found:, [hex(device_address) for device_address in i2c.scan()]) i2c.unlock()检查前方物体确保传感器前方没有全黑或强吸光材料且测量距离在其有效量程内VL53L0X约2米。解决方案调整measurement_timing_budget参数如从33000改为20000可以改变测量速度和精度平衡。在传感器镜头前加一小片干净的透明亚克力可以防止灰尘影响。5.2 软件与交互逻辑调试问题3灯光亮度变化不跟手有延迟或跳跃。原因分析可能是主循环time.sleep时间太长或传感器采样率太低或亮度映射函数计算开销大。优化方案减少主循环的time.sleep(0.05)到0.02或更短提高刷新率。在update_brightness_from_distance()函数中不要每次调用都执行set_gradient_color它遍历所有LED计算量大。只在颜色模式改变时调用它。亮度调整只修改pixels.brightness这是一个全局属性修改开销极小。引入更高级的滤波算法如一阶低通滤波指数平滑让亮度变化更“粘滞”避免因距离微小波动导致的亮度抖动。filtered_brightness 0.0 # 初始化 SMOOTHING_FACTOR 0.1 # 平滑系数 (0-1)越小越平滑 def update_brightness_smoothly(): global filtered_brightness distance get_smoothed_distance() distance_clamped max(DISTANCE_MIN, min(distance, DISTANCE_MAX)) target_brightness map_value(...) # 计算目标亮度 # 指数平滑滤波 filtered_brightness (SMOOTHING_FACTOR * target_brightness) ((1 - SMOOTHING_FACTOR) * filtered_brightness) pixels.brightness filtered_brightness pixels.show()问题4电位器切换色彩模式太灵敏或太迟钝。调整方法修改代码中的POT_DEAD_ZONE值。电位器读数范围是0-65535。如果太灵敏将此值从1000提高到3000或5000如果太迟钝则降低到500。你也可以改为根据电位器的绝对位置而非变化量来选择模式这样每个位置对应一个固定模式。5.3 艺术效果优化建议光线柔化如果觉得LED点光源太明显可以在灯带和镂空木板之间加一层扩散板。我用的是文具店买的白色硫酸纸描图纸裁剪后贴在木板背面效果极佳光线变得非常柔和均匀。色彩与动态扩展当前的色彩渐变是静态的。你可以修改代码让颜色随时间缓慢流动。例如在set_gradient_color函数中为起始色和结束色加入基于时间的偏移量创造出类似极光或呼吸的效果。音频互动不只是循环播放。可以让音频的音量或播放速度也随距离变化。例如人越近环境音音量越大或音调越高创造更强的沉浸感。这需要用到音频库的更多功能。这个项目从构思到实现最深的体会是硬件是骨骼代码是肌肉而艺术直觉才是灵魂。技术参数可以调试到完美但最终打动人的是光与影在空间中那种微妙的、与人呼应的节奏感。我花了大量时间在暗室里反复调整DISTANCE_MIN/MAX和亮度映射曲线只为找到那个“刚刚好”的响应阈值——既不会对微小动作过度反应又能在人真正靠近时给予明确而优雅的反馈。同样色彩模式的选择也并非随意每种配色都试图唤起一种情绪宁静、深邃、温暖或神秘。最后一个小技巧在正式固定所有内部组件前用一根长的USB线把Arduino连接到电脑在Mu Editor的串行监视器中实时打印传感器读数和亮度值。然后你拿着装置在房间里走动观察数值变化实时调整代码参数。这种“实时调参”的方式比反复烧录代码、测试要高效得多能帮你快速找到最佳的交互感觉。希望这个盒子也能成为你探索光、空间与代码之间无限可能性的一个起点。