ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

粒子生命模拟入门:用规则矩阵与力场打造涌现式人工生命

粒子生命模拟入门:用规则矩阵与力场打造涌现式人工生命

简介:《particle-life》是一个用JavaScript实现的粒子生命模拟项目,灵感来自经典“生命游戏”,但将网格细胞换成大量粒子,在纯前端画布上呈现自发组织与涌动的视觉效果。面向对元胞自动机、动画仿真与Web绘图感兴趣的开发者,适合想学习Canvas渲染与项目模块划分的初中级前端学习者。

压缩包共16个文件,核心为5个JavaScript源码模块,分别负责粒子更新、渲染、HSV转换与数组扩容等;另含package.json、README和配置文件等。资源仅13KB,结构紧凑。目前已有370人浏览学习,是粒子类可视化项目的轻量入门样本。

价值在于提供可直接运行的基础版本,并明确列出优化方向:测量Universe.step()性能、对比random-js与prob.js的随机数效率、改用固定大小数据结构、增加带缩放跟踪的摄像头等。读者可沿这些方向动手改造,同时记录有18-20 fps渲染瓶颈及快捷键缺失问题,有助于理解性能分析、热重载调试和构建发布流程,是一份实践性很强的参考代码。

1. 粒子生命:把“生命”压缩成几条规则的小游戏

一屏随机撒下的彩色小点,没有预设动画,没有个体决策,只有一条按颜色和距离决定“吸引还是排斥”的局部规则。运行几十帧之后,这些点会自己聚成旋臂、涡旋、团块,甚至出现互相追逐的类生物形态。这类现象就是标题里所说的粒子生命:一套用规则矩阵和力场曲线驱动的最小“人工生命”模拟,非常适合想低成本体验涌现行为的开发者与爱好者。它能帮你解决的问题很直接:理解“复杂秩序为什么能从简单规则里长出来”,以及如何用几百行代码亲手复现这个过程。无论你是刚接触模拟的新手,还是想把这类效果做成交互作品的熟手,这套方案都有一条能快速上手、可以独立掌控的落地路径。

2. 粒子生命的核心逻辑:规则矩阵与距离衰减才能出涌现

2.1 少数颜色种类反而更重要:把“生物”简化为可判定的角色

粒子生命的第一个设计决策是:粒子到底要分几种“物种”。市面上的经典 demo 一般取 3 到 8 种颜色,很少超过 10 种。这不是偷懒,而是规则数量决定了系统能否被理解和调控。若种类数为 C,规则矩阵就有 C×C 个相互作用值,每种颜色和其他所有颜色各有一个关系项。C=5 时是 25 个值,已经能表达捕食、共生、排斥、中立等多种关系;加到 12 种时就是 144 个值,绝大多数配对在视觉上无法区分,最后结果往往退化成一片看不出结构的噪声。

把“生物”简化为颜色的视觉标签,意味着你不再需要为单个粒子编写任何行为逻辑。绿色粒子吸红色粒子、排斥蓝色粒子,对应的规则矩阵里就是rule[green][red] = 1.2、rule[green][blue] = -0.8。每个粒子的身份完全由颜色决定,同种颜色所有粒子的行为完全一致。正因如此,系统才能从几对简单的相互作用中涌现出群体结构:不是程序画好了一段动画,而是每一对粒子在每一帧的合力碰撞下,自己走出一条条聚散路径。

我给初学者的建议是固定 5 种颜色起步。5 色系统正好能摆出一条“食物链”骨架:红吸黄、黄吸绿、绿斥蓝,再留一种颜色专门拆散过于聚集的团块。在这样的配置下,你能同时看到追逐、趋同、逃离三种行为,又不会因为规则太多而失去掌控感。等跑通了再逐步增减颜色,对比不同数量带来的形态差异。

2.2 规则矩阵:谁吸引谁、谁排斥谁

规则矩阵是整个粒子生命的“DNA”,同时也是你直接编辑的配置文件。我习惯用二维数组rule[C][C]表示:行是当前粒子所属种类,列是对方粒子所属种类,值的大小是作用强度,正负号代表方向。若rule[A][B] > 0,A 对 B 产生吸引;若小于 0,A 对 B 产生排斥;等于 0 则完全无视。

初始化时有一个必须遵守的防呆设置:对角线全部置为负数,也就是同色粒子彼此排斥。否则同色粒子会挤成实心球,系统在几秒内就冻结成静止的团块。其余位置可以用均匀分布在 [-1, 1] 内随机取值,再把其中少数的绝对值手动调大,形成明显的强约束关系。

规则矩阵不要求对称,这是它比真实物理力场更有表达力的地方。rule[A][B]表示“A 对 B 的立场”,可以和rule[B][A]完全相反,于是很容易写出“红追绿、绿躲红”这种单向追逐关系,而实际的群体运动也因此出现绕圈、迂回和分离。下面是我常用的一个 4 色矩阵参考模板:

种类红绿蓝黄
红-0.90.60.2-0.3
绿-0.4-0.70.80.1
蓝0.0-0.5-0.60.7
黄0.50.3-0.2-0.8

这个矩阵的意图是:红绿之间既有吸引也有排斥,会拉出螺旋状的追逐尾迹;蓝推开绿,黄又拉走红,整盘图形不会朝单一方向堆积。实际调节时不需要逐个分辨 25 个值,优先看绝对值大于 0.5 的“强项”,它们主导整体形态。

2.3 力场曲线:让吸引力在远段衰减,让斥力守住近距底盘

有了矩阵还不够,矩阵里的值只是“势力强度”,还要有一套机制把它换算成粒子间实际受到的力。常见做法是定义两个半径:r_min和r_max。对每一对粒子,先算距离 d:

  • 若d < r_min:进入近距离区间,强制排斥,防止粒子重叠;
  • 若r_min <= d < r_max:中距离区间,按规则值决定吸引或排斥,强度随距离线性衰减;
  • 若d >= r_max:超距,作用力归零。

对应公式我经常写成:

f = w * (d / r_min - 1) # d < r_min f = w * (1 - d / r_max) # r_min <= d < r_max f = 0 # d >= r_max

当 w 为正时,中距区间得到正向吸引力,而近距区间因为(d/r_min - 1)是负数,实际表现为斥力,所以“正规则”的含义是“近处推开、远处拉近”,整体上容易形成动态聚合;负规则相反,远处推开、近处反而靠近。两个区间叠加后,每个粒子处在一个非对称力场中,局部邻居决定了它下一秒往哪走。

如果只靠这两条公式,粒子将保持匀速直线运动,所以还要加阻尼与边界约束,这部分在实现章节展开。观察已完成的作品时,你可以根据形态反推参数:看到大片静止的浓密圆斑,通常是r_min太小或damp太低;看到系统像布朗运动一样毫无结构,则是r_max太短或规则矩阵大多数项接近 0。记住一句话:粒子生命中真正起决定性的是“局部势力范围”,而不是全局牵引力。

3. 用 Python 写一个 1200 粒子的最小粒子生命

原理说清楚后,就可以动手了。这里给出一个不依赖游戏引擎的最小实现,用 Python 加 NumPy 完成,核心链路是:计算成对距离、套用力场公式、更新速度与位置、边界反弹、逐帧渲染。整套代码大约 150 行,能观察到完整的涌现效果,也方便你逐行修改参数看差异。

3.1 数据结构和初始化:N、C 与状态数组

粒子生命的最小状态只有三个数组:位置pos[N, 2]、速度vel[N, 2]、种类标签color[N]。先用参数区统一声明规模与物理常量,具体数值可直接照抄:

import numpy as np N = 1200 # 粒子总数,太小难看到涌现群体 C = 5 # 颜色种类数 W, H = 800, 800 # 模拟区域宽高 dt = 0.12 # 时间步长 damp = 0.45 # 速度阻尼,每次更新后保留的速度比例 r_min = 8.0 # 近程排斥半径 r_max = 28.0 # 作用半径上限 rng = np.random.default_rng(42) # 规则矩阵:对角线禁止同色黏连,其余随机取 [-1, 1] rule = rng.uniform(-1.0, 1.0, size=(C, C)) np.fill_diagonal(rule, -1.0) # 初始位置随机铺满画布,速度从零开始 pos = rng.random((N, 2)) * np.array([W, H]) vel = np.zeros((N, 2)) color = rng.integers(0, C, size=N)

逻辑说明:规则矩阵用rng.uniform生成随机初始值,强制把对角线设为 -1,这是为了避免同色粒子互相吸引成团;位置在画布上均匀随机铺开,速度初始为零。初始速度为 0 是个有意选择:如果随机给速度,系统一开始的“沸腾”会掩盖规则本身产生的运动,遇到粒子乱飞时难以判断是参数问题还是初始条件问题。

参数说明:dt=0.12是半隐式欧拉格式下比较稳的值,超过 0.2 容易数值爆炸;damp=0.45表示每帧保留 45% 速度,能量持续消耗,系统才有机会从混乱收敛到有序纹理。r_min=8.0、r_max=28.0对应 800 宽画布的 1% 和 3.5%,换了大画布记得按比例缩放。

3.2 核心力场计算:一次算出全部成对距离

最小实现不引入任何空间分区,直接算 N×N 距离矩阵。1200 粒子约 144 万个距离条目,单次计算大约几十毫秒,用于调参完全能接受。完整矩阵的好处是逻辑非常直观,不必在近邻数据结构上分心:

def compute_force(pos, color, rule): # diff[i][j] 表示从粒子 i 指向粒子 j 的向量 diff = pos[:, None, :] - pos[None, :, :] dist = np.sqrt(np.sum(diff * diff, axis=2)) np.fill_diagonal(dist, 1e6) # 排除自身,避免对角项产生作用力 f_mat = np.zeros((N, N)) # 标量力矩阵 for ci in range(C): for cj in range(C): w = rule[ci, cj] if abs(w) < 1e-9: continue # 取出所有“当前粒子是 ci、对方粒子是 cj”的配对 row_idx = np.ix_(color == ci, color == cj) d = dist[row_idx] f = np.zeros_like(d) near = d < r_min mid = (d >= r_min) & (d < r_max) f[near] = w * (d[near] / r_min - 1.0) f[mid] = w * (1.0 - d[mid] / r_max) f_mat[row_idx] = f # 标量力乘单位方向向量,再按行求和得到每个粒子的合外力 direction = diff / np.maximum(dist, 1e-6)[:, :, None] force = (f_mat[:, :, None] * direction).sum(axis=1) return force

逻辑说明:dist是成对欧氏距离矩阵,对角线填成 1e6 后,自身对自动落在“超距无作用力”区间,省去单独掩码。两层颜色循环只有 C×C 次,每次用布尔索引一次性处理所有同色配对,避免写 N×N 的 Python 循环。中距区间的(1 - d/r_max)是线性衰减,距离越近拉力越大,这保证群落的凝聚力来自局部而非全局。

参数说明:np.fill_diagonal(dist, 1e6)是性能安全的写法,如果去掉,对角线距离为 0 会落在d < r_min区间,让每个粒子对自己施加一个无意义的排斥力。near和mid的阈值完全由r_min、r_max控制,它们直接决定团块的尺度。

3.3 更新位置:半隐式欧拉、阻尼与边界反弹

力场算完只给出了加速度来源,真正让画面“活”起来的是积分更新和边界处理。这里用最常见的半隐式欧拉:先用新合力更新速度,再用新速度更新位置,最后处理撞墙反弹:

def step(pos, vel, color, rule, dt, damp, W, H): force = compute_force(pos, color, rule) # 半隐式欧拉:速度更新 -> 坐标更新 vel = (vel + force * dt) * damp pos = pos + vel * dt # 边界反弹:撞墙时反转对应速度分量并拉回场内 hit_left = pos[:, 0] < 0 hit_right = pos[:, 0] >= W vel[hit_left, 0] = np.abs(vel[hit_left, 0]) * 0.6 vel[hit_right, 0] = -np.abs(vel[hit_right, 0]) * 0.6 pos[:, 0] = np.clip(pos[:, 0], 0, W - 1) hit_top = pos[:, 1] < 0 hit_bottom = pos[:, 1] >= H vel[hit_top, 1] = np.abs(vel[hit_top, 1]) * 0.6 vel[hit_bottom, 1] = -np.abs(vel[hit_bottom, 1]) * 0.6 pos[:, 1] = np.clip(pos[:, 1], 0, H - 1) return pos, vel

逻辑说明:force * dt是一次加速度冲量,damp相当于空气阻力,这是粒子从“随机噪点”走向“有序纹理”的最关键物理项。反弹系数取 0.6,表示撞墙后能量折损 40%;如果取 1.0,高能量会让粒子长期挤压在墙角,如果取 0.1,边界附近会堆一圈近乎静止的粒子“尸体”。

参数说明:pos[:, 0] = np.clip(pos[:, 0], 0, W - 1)是安全网,防止浮点误差把坐标推出画布后,下一帧距离计算出现 NaN 进而导致整个循环崩溃。若粒子出现异常的高速直线运动或持续抖动,优先检查这一函数中对damp与rt的取值。

3.4 渲染循环:Matplotlib 动画的最小组合

有了状态更新函数,还需要一个可视化的逐帧渲染循环。我推荐 Matplotlib 的FuncAnimation,优点是零额外依赖,适合参数探索阶段;缺点是大规模渲染会卡,性能优化放到第 6 章再处理:

import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation fig, ax = plt.subplots(figsize=(6, 6)) ax.set_xlim(0, W) ax.set_ylim(0, H) ax.set_aspect("equal") ax.axis("off") scat = ax.scatter(pos[:, 0], pos[:, 1], c=color, cmap="tab10", s=3) def animate(frame): global pos, vel pos, vel = step(pos, vel, color, rule, dt, damp, W, H) scat.set_offsets(pos) return scat, ani = FuncAnimation(fig, animate, frames=600, interval=30, blit=True) plt.show()

逻辑说明:scat.set_offsets(pos)直接替换散点坐标,比每帧重建scatter对象省内存得多;颜色标签传color数组,通过cmap="tab10"映射成 5 种固定色调。interval=30表示动画帧间隔 30 毫秒,若算力跟不上,可以适当加大这个值,但模拟耗时也会同步拉长。

参数说明:你的电脑如果内存吃紧,先别急着换算法,把N降到 600,再观察每帧计算时间是否低于 0.1 秒。调参的一个重要原则是:先把计算帧率稳定住,再谈调节效果,否则参数与卡顿混合在一起时很难定位问题。

4. 把粒子生命调出“活物感”:3 个必调参数与血泪调试顺序

很多人拿到一个粒子生命 demo 后,第一件事就是去改规则矩阵,结果发现无论怎么调,图形要么开场就炸成满屏雪花,要么跑几十帧变成一坨静止的球。这里有个“调参玄学”:大多数情况下,稳定性和活力不是由规则矩阵决定的,而是由几个物理参数统治的。改矩阵前,先确保这套参数处在合理区间。

4.1 时间步长 dt 与阻尼 damp:决定系统是沸腾还是死寂

dt 与 damp 是粒子生命中最被低估的一对。dt 太大会让每次速度增量过大,近距离斥力形成正反馈,粒子飞速互相弹开;damp 太接近 1 意味着能量几乎不耗散,系统永远不会自然刹停,最终表现为持续混乱。下面是我针对 800×800 画布、N=1200 场景的经验取值表:

调试阶段dtdamp预期效果备注
初始起步0.120.45稳定收敛,有少量流动推荐从这里开始
想要急促追逐感0.080.55结构松脆,个体脉冲明显减小步长避免爆炸,靠阻尼保运动
想要缓慢大块纹理0.150.60大片聚集,流速慢大 dt 会破坏小尺度结构
典型错误配置0.300.90立即爆炸或全部黏墙高步长加高阻尼等于发狂

为什么 dt=0.3 会爆炸?看力场公式:近距离斥力近似于-w,乘以 dt 叠加到速度上,重叠粒子每帧获得一个很大的分离速度,下一帧距离更大、斥力更大,形成正反馈。damp=0.9 又几乎不消耗能量,速度只增不减,最终所有粒子以极高速度在画面里互相弹射。

我的血泪调试顺序是:先把规则矩阵全部清零,只保留对角线负一,然后单独调 dt/damp,等系统不再炸、也不再死,再把规则矩阵逐对放开。这个顺序能让你分清“炸”是物理积分的锅还是规则的锅。我曾在一组强吸引加强排斥的规则下,用 dt=0.25 跑出全屏雪花,还花很久去改规则,最后发现只是步长过大。

4.2 作用半径 r_min / r_max:决定你看到的是“团块”还是“雪花”

如果说 dt/damp 决定系统能不能跑,那么 r_min/r_max 决定跑出来像什么。r_max 是作用力的极限距离,直接决定纹理团块的直径;r_min 是近距排斥半径,决定粒子的最小间距,也就是高密度区的稀疏程度。

操作方法如下:r_max 初始设为画布短边的 3% 到 5%。以 800 为例,就是 24 到 40 像素。取大了,粒子会感受到太多远端邻居,系统趋向于全局吸引,所有颜色最终融合成一个密球;取小了,大量粒子进入“无交互”状态,画面像无规则布朗运动。r_min 初始取 r_max 的 1/8 到 1/4,也就是 3 到 10 像素左右。r_min 太小会导致粒子在团块内堆成密不透风的一团;r_min 太大则团块的边界被硬撑开,失去柔软流动感。

还有一个经验:两者比例决定层次感。1/8 的比值偏向细碎纹理,1/4 的比值偏向大块群落与长拖尾。想要更小的纹理颗粒,可以把两个参数等比例缩小,但不要降到 3 像素以下,否则数值离散会导致粒子在网格位置上颤抖,失去流畅的观感。

4.3 规则矩阵的标定:先把“捕食-猎物”基团排好

规则矩阵看起来自由,实际很难通过逐个试符号来调好。单一规则的符号在整体组合里会被淹没,例如把某两色的关系从正改成负,视觉上可能毫无变化。所以我建议把矩阵拆成“基团”分步配置:

  • 基团一:对角线全部为负,这是防呆底线;
  • 基团二:设置一对强正吸引,加一对强负排斥,形成捕食关系,制造追逐与绕行;
  • 基团三:其余位置填入 ±0.2 左右的弱扰动,只提供变化种子,不主导结构;
  • 基团四:最后再逐对微调,每调完一对跑 200 帧看结构是否被破坏。

一个有效的检查方法是:跑 200 帧后暂停,看图上是否同时存在至少三种不同颜色的团块。如果只剩一种颜色霸占全场,说明你的强吸引太多或强排斥不足;如果三种以上颜色均匀混合成灰色噪声,说明规则强度整体太弱,需要把弱扰动位的幅度从 ±0.2 提至 ±0.4。

5. 粒子生命常见问题与避坑:逃不开的 5 个真实陷阱

这一章整理我实际调试中反复踩过的坑。每条都按“现象 → 原因 → 解决”展开,你可以把它当作一份排错清单直接对照。

5.1 粒子一开场就全屏乱飞或瞬间飞出边界

现象:动画第一帧还算正常,第 3 到第 5 帧粒子开始拉出长尾,迅速铺满整个画面,大量粒子被甩到边界并不断反弹,最终变成一片高速闪烁的噪声。

原因:这不是规则写错,而是数值积分爆炸。常见诱因有两个:dt 超过 0.2,导致近距离斥力在相邻帧之间累积成巨大速度;damp 大于 0.8,能量无法耗散,速度只增不减。部分时候也来自同一行规则里同时存在强吸引与强排斥,粒子在高频振荡中被持续泵入能量。

解决:按顺序执行。先把 dt 改为 0.1,damp 改为 0.4,重跑一次;若稳定,再逐步回到原参数找平衡点。若仍爆炸,检查规则矩阵,将该行内所有绝对值大于 0.8 的值限制到 0.5 以内,而不是整行清零。记住“先物理后规则”这条顺序。

5.2 跑 300 帧后系统完全静止,变成一滩死水

现象:前 50 帧能形成漂亮的漩涡和追赶结构,之后漩涡慢慢收缩,最后所有粒子聚成几个静止圆斑,不再有流动与追逐。

原因:damp 低于 0.3 时能量消耗过快,系统迅速跌入局部能量最低点,一旦到达稳态,剩余速度不足以克服阻力,所有运动停止。常见背景是调试早期为了让系统快速稳定而把 damp 调低,之后忘记调回来。

解决:把 damp 提到 0.5 以上,或者给系统加一个最小随机扰动:每 200 帧把 2% 的粒子速度重置为随机方向,破坏绝对死锁。另一个做法是把对角线值从 -1 改成 -0.7,缩小同色排斥力,给原本凝固的结构一个继续移动的余地。如果你就是想观察静态结构,也可以把它当“雕塑”直接导出,不必强行维持动态。

5.3 全部粒子黏成一个大球,看不出任何中尺度结构

现象:无论规则矩阵怎么换,最后都是所有粒子聚成一个致密球体,在画布角落或中心缓慢滚动,没有多个团块共存。

原因:基本只有两种可能。一种是 r_max 过大,超过画布短边的 10%,导致远端粒子产生全局吸引,局部结构被吞并;另一种是规则矩阵中正吸引的数量和强度全面压倒负排斥,系统朝单一中心单向收缩。

解决:先把 r_max 压到 24 到 40 像素区间;再检查规则矩阵,确保每种颜色至少有一行含一个绝对值大于 0.5 的负值,也就是保留“拆散者”角色。如果仍黏球,降低粒子数量从 1200 到 800,低密度下局部势力范围更容易保留,宏观结构也就更容易出现。

5.4 复现不出网上看到的效果:改矩阵后仍然是噪点

现象:照着参考视频抄参数,跑出来的效果却完全不同,别人那里是飞舞的虫群,自己这里是一团扩散的烟雾。

原因:粒子生命对初始条件高度敏感。参考视频通常来自快进后的结果,且不同项目对作用半径和画布尺寸的定义不一样,更重要的是初始粒子分布方式不同。哪怕是同样参数、同样随机种子,换一个分布方式也可能走向完全不同的结构。

解决:我的习惯是每改一次规则矩阵都固定随机种子,并且用“网格初始化”代替随机均匀分布:先把粒子按等距网格排布,再叠加一个很小的随机抖动。网格初始化能明显提高复现不同规则矩阵时的确定性,更容易看到干净的团簇边界。等进入正式创作阶段再换回随机初始位置,寻找更自然的无序感。

5.5 跑大场景卡到没法玩:N 超过 3000

现象:N 从 1200 提高到 3000 后,帧率骤降到每秒几帧,整个模拟像放幻灯片。

原因:最小实现采用 O(N²) 全量距离矩阵,N=3000 时约有 900 万条距离项,每次步进计算量超过 0.2 秒,这是结构天花板,不是代码效率问题。

解决:入门调参阶段先把 N 压回 1000 以内。如果一定要跑大场景,进入第 6 章的网格分区优化,把复杂度从 O(N²) 降到近似 O(Nk),k 是平均邻居数。优化前先把逻辑验证正确,再用网格结构提速,不要一开始就上分块近邻搜索,否则调试复杂度会显著增加。

6. 进阶:从玩具到引擎的三个升级方向

当你能稳定跑出想要的形态,下一步就是把这套简单规则扩展到更大规模、更强交互和更可验证的创作环境中。

6.1 网格分区:先从“全量计算”切换到“局部搜索”

N 超过 3000 时,最直接的优化是把画布切成边长为 r_max 的网格。每个粒子只与自身所处格子及周围 8 个格子里的粒子计算力,距离超过 r_max 的配对直接忽略。这样单帧计算量从 N² 量级降到 N×平均邻居数,N=5000 时也能保持流畅。实现时用字典或二维数组按格子编号存储粒子索引,循环更新时只遍历邻近格子即可。

6.2 交互玩法:鼠标扰动与动态调参

交互是让粒子生命从“观赏”变成“可玩”的关键。常见做法是维护一个“鼠标斥力”:当鼠标按下时,对鼠标位置周围半径内所有粒子施加一个与距离成反比的排斥力,破坏局部平衡,原本凝固的结构被重新打散,随后自然重组。这个“破坏—重组”循环是粒子生命最有观赏价值的部分。只需在计算合力后叠加一项鼠标力,不影响全局,就能获得很强的操控感。

6.3 实验可验证的习惯:固定种子、量化活性

最后分享一个实验习惯:每次调试固定三样东西——粒子初始位置种子、规则矩阵文件、dt/damp 快照。这样你可以放心地单独改任何参数,前后对比才可信。想要进一步量化,可以用“粒子平均速度”或“颜色团块密度”绘制成曲线,比用肉眼判断“像不像生命”更有依据。

过去我调试这类模拟时吃过一次大亏:连续几天改规则矩阵,越改越复杂,效果却越来越平庸,最后发现只是 damp 从 0.45 被误改成了 0.25,规则根本没动过。从此我把所有物理参数固定在文件头部,绝不中途手改画布参数。粒子生命最大的魅力就在于它的简单——从随机点开始,靠几条局部规则就能重现无限接近生命感的行为;最怕的是在参数海里迷失方向。希望这些经验能帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进