在經歷了之前的《用 AI 做了幾個超炫酷的 Flutter 動畫》的抽象實現後,就有了想讓 AI 做點更炫酷又更具象化的東西,剛好前段時間對無限城篇裡“三哥”的破壞殺·羅針展開印象深刻,所以就決定用它試試水。
整個過程體驗下來,只能說,AI 是很強,但是用過的也都知道,某種程度上其實也沒有那麼強,因為你需要和它同頻,只有馴服它之後,才能真正做到你想要的效果,比如這個效果我就和 AI 前後摩擦了 50 多個版本才勉強可以接受:
首先,我找了一張陣法的原圖,然後交給 AI,嘗試讓它理解圖片裡的線條來實現相應的效果,可想而知,結果慘不忍睹:

所以一開始我得到的是這樣的東西,相信這時候已經開始勸退大多數人了,並覺得 AI 也就這樣,根本做不出我們想要的:

但是這對我來說是符合預期的,因為我們給出的提示詞其實太空泛了,並且 AI 也並不能真的完全靠自己理解我們給出的圖片畫面,所以我嘗試通過豐富提示詞來完成,然後在這個過程我就得到了類似下方這樣的結果:


這時候我開始意識到 AI 真的沒能直接理解圖片裡的畫面,所以我換了個思路,嘗試通過一些輔助手段讓 AI 理解整個構圖的形成:


在通過提示詞和構圖坐標理解後,AI 是真的開始理解圖片的構圖了,至少它是真的能通過紅圈明白自己需要做的事情,這也體現在代碼的極坐標實現上,然後我就得到了下方的效果:

看起來是不是還差很多?但是不怕,只要方向是對的就行,接著就是各種微調和糾錯工作,這個過程需要不停給出明確指令,並及時糾正 AI 在代碼上的錯誤:







當然,這個過程難免還是存在“心態爆炸”的時候,畢竟當你發現 AI 總是喜歡特立獨行,明明你已經糾正他了,但是他還是說:“是的,你的對的,但是我還是要保持我的做法”的時候,不罵兩句是真的意難平:

另外,由於會話長了之後,概率出現上下文丟失,就會出現一些奇奇怪怪的問題,比如本來就生氣了,這裡還直接將需求變成使用 Nano Banana pro 給我出圖而不是調整代碼,心態炸裂。
當然,在經歷多次細節調整後,終於還是畫出了我們大概希望的效果,然後就是各種實現細節的打磨,這個時候 AI 對於具體調整的理解就很到位了,並且當你用箭頭指出新花樣修改的位置時,它也能及時調整。
比如我光速告訴它,雪花分叉的末端細節應該有角度的,並且對齊枝幹,這時候它就能做出正確的修改:


有時候 AI 改起來太慢了,或者路線已經偏了的時候,就需要人工介入,通過人工進行微調,然後讓 AI 通過你的代碼再繼續:


最終我得到了我需要的陣圖效果,雖然還原的不是 100%,但是再稍微人工調整下,就是可用的狀態了:

之後就是讓陣法繞 X 軸渲染,當然 AI 在最後還是再給我來了一錘,看起來提示詞是細節是真的一點都不能省:

但是有時候 AI 又很貼心,因為它會發現旋轉 90 度並不合理,需要讓角度調整為 80% 來實現我們的效果:

同時一些實現過程中的 bug 問題,它也能夠及時理解改正,並說明原因,這個也是非常不錯的場景學習:

可以看到,AI 實際上是能理解我們給出的圖片的,只是它無法直接完整且具體的理解,所以需要我們給去具體詳細的步驟,甚至幫助它去結構目標效果的構成,從而幫助我們實現最終的效果,這其實也是馴服 AI 的過程。
所有妄圖在「現階段」讓 AI 一步到位完成複雜邏輯的想法,大多數時候都會以放棄收尾。
最後,我們來解讀代碼實現,其實整個實現上就是使用了 CustomPaint、AnimationController 和 Matrix4,在代碼實現結構上,主要有 “多階段動畫調度”、“數學繪圖原理”、“3D 空間變換” 和 “角色合成特效” 四個核心部分。
在動畫實現上, AI 並沒有使用單一的動畫控制器,而是定義了四個順序執行的控制器,模擬出從無到有的“展開”過程,具體實現邏輯是:
_drawController, 4秒): 陣法線條從中心向外生長_rotateController, 3秒): 陣法繪製完成後開始快速旋轉_tiltController, 1.5秒): 陣法從 2D 平面倒下變成 3D 地面視角,同時伴隨亮度爆發_characterController, 1秒): 角色配合光效浮現同時,利用 CurvedAnimation 和 Interval 將繪製階段細分為更自然的子步驟:
因為我提供了陣法的圓周對稱的示例,所以整個繪製邏輯都是圍繞圓形為基礎做佈局,其中主要就是以極坐標系為主:
陣法是圓周對稱的,為了確定 12 個方位的坐標,代碼大量使用了極坐標轉換公式:

在這個實現裡,極坐標是繪製整個圓形陣法(羅針)的數學核心,簡單來說,就是螢幕繪圖使用的是直角坐標系 (x, y)(向右為 x,向下為 y),而圓形陣法的設計邏輯是基於圓心、角度和距離的,極坐標的作用就是將“角度+距離”翻譯成螢幕能理解的“x+y”。
因為在平面幾何中,確定一個點的位置有兩種方式:
對於像“破壞殺·羅針”這種由 12 個重複元素組成的圓周對稱圖形,使用極坐標是最自然和高效的方法,所以 _polarToOffset 實現了上述的算法,它的作用就類似“翻譯官”:
// center: 圓心位置 (比如螢幕正中間)
// radius: 半徑 (距離圓心多遠)
// angle: 角度 (弧度制)
Offset _polarToOffset(Offset center, double radius, double angle) {
return Offset(
center.dx + radius * math.cos(angle), // x = r * cos(θ)
center.dy + radius * math.sin(angle), // y = r * sin(θ)
);
}
math.cos(angle) 計算出水平方向的分量math.sin(angle) 計算出垂直方向的分量center.dx 和 center.dy :把坐標原點從螢幕左上角移動到畫布中心而陣法有 12 個角,圓周是 360,所以每個角間隔 360 / 12 = 30:
// i 從 0 到 11
// i * 30: 每隔30度一個
// - 90: 數學上的0度通常在"3點鐘"方向,減90度是為了讓它從"12點鐘"方向開始
// * (math.pi / 180): 計算機只認弧度,不認角度,所以要轉換
final double angle = (i * 30 - 90) * (math.pi / 180);
同時陣法不是一個正圓,而是像雪花一樣長短交替的:
final bool isLongBranch = (i % 2 == 0); // 判斷是否為長軸
// 如果是長軸,半徑用 rLongHex (大半徑)
// 如果是短軸,半徑用 rShortHex (小半徑)
final double currentHexRadius = isLongBranch ? rLongHex : rShortHex;
有了 角度 (angle) 和 半徑 (currentHexRadius),代碼就開始調用 _polarToOffset 算出具體的螢幕坐標點,用來畫線、畫六邊形,代碼定義了幾個關鍵點(從圓心向外):
// 舉例:計算六邊形中心的位置
Offset hexCenter = _polarToOffset(center, currentHexRadius, angle);
另外,文字也需要角度,當你畫位於“3點鐘”方向的文字“參”時,文字應該是正的,但當你畫“6點鐘”方向的文字時,如果直接畫,文字可能會歪,所以代碼在繪製六邊形和文字時,用到了 canvas.rotate,這也是基於極坐標思想的延伸:
canvas.save();
canvas.translate(hexCenter.dx, hexCenter.dy); // 1. 先把畫筆移動到六邊形的位置
canvas.rotate(angle + math.pi / 2); // 2. 旋轉畫筆,讓它對準圓心方向
// ... 開始畫六邊形 ...
canvas.restore();
循環 12 次繪製雪花瓣,代碼通過 i % 2 == 0 判斷當前是長軸還是短軸,從而實現錯落有致的視覺效果(類似於雪花結構)
for (int i = 0; i < 12; i++) {
final bool isLongBranch = (i % 2 == 0); // 偶數為長軸,奇數為短軸
// 根據長短軸決定繪製半徑 rLongHex 或 rShortHex
}
Flutter 的 Canvas 其實並沒有“發光”屬性,所以 AI 代碼通過 “雙重繪製” 模擬發光:
MaskFilter.blur (高斯模糊) + 較粗的線條// 1. 畫光暈
canvas.drawLine(p1, p2, Paint()
..color = glowColor.withValues(alpha: glowOpacity)
..strokeWidth = width * 5 * bloomWidth // 筆觸加寬
..maskFilter = MaskFilter.blur(BlurStyle.normal, 12 * bloomWidth)); // 高斯模糊
// 2. 畫高亮實線
canvas.drawLine(p1, p2, Paint()
..color = brightColor // 純亮色
..strokeWidth = width);
陣法一開始是正對著螢幕的(2D),後來倒在地上變成(3D),這是通過 Transform 組件配合 Matrix4 實現的,具體邏輯是:
(3, 2) 元素(即 wz 因子),產生近大遠小的透視感。Matrix4 transformMatrix = Matrix4.identity();
transformMatrix.setEntry(3, 2, 0.002); // 關鍵:開啟透視,數值越小透視越弱
if (_tiltController.value > 0) {
transformMatrix.rotateX(_tiltAnim.value); // 這裡的 value 是 0 到 -80度
// 隨著倒下,稍微縮小一點比例以適應螢幕
double scale = 1.0 - (_tiltController.value * 0.2);
transformMatrix.scale(scale);
}
最後一步是將人物 akaza.png 放置在陣法中心,由於人物圖片可能是全身像,中心點在腰部,但陣法中心應該對齊“腳底”,所以代碼使用了 Matrix4.translationValues 進行了微調:
// 向上偏移高度的 42%,讓腳底對準陣法中心
transform: Matrix4.translationValues(0, -targetHeight * 0.42, 0),
同時為了讓 2D 圖片看起來融入發光的陣法,代碼對人物圖片做了特殊的堆疊處理:
ColorFiltered 變成純青色剪影 -> 使用 ImageFiltered 進行強高斯模糊,這創造了一個與人物輪廓完全一致的“背光”Stack(
children: [
// 1. 發光層 (Blur + ColorFilter)
ImageFiltered(
imageFilter: ui.ImageFilter.blur(sigmaX: 20.0, sigmaY: 20.0),
child: ColorFiltered(
colorFilter: const ColorFilter.mode(charGlowColor, BlendMode.srcIn),
child: Image.asset(...), // 變成發光的剪影
),
),
// 2. 原始圖層
Image.asset(...),
],
)
同時陣法在倒地之後,也加強了光暈效果,配合角色光暈,這就形成了人物和陣法光暈的融合,也增加了立體感:
最後,還是想說,AI 真的在變強,而且變強的速度越來越快。