開始進行此作業後,找了找適合的卡通角色,本來嘗試做長髮公主、珍珠美人魚…但都因為圖形太複雜,失敗告終。最後選擇了哆啦A夢,因為他圓滾滾的,感覺比較好實現。
製作過程
一開始,我先請 AI 幫我分析哆啦A夢的視覺結構,並生成了一段基礎的 SVG 代碼。AI 很快就抓到了頭部比例、眼睛位置等大框架。接著,我再請它將這些邏輯轉化為 SwiftUI 的 Circle 和 Path。
這部分讓我最滿意的是,AI 幫我省去了手算座標的繁瑣過程,尤其是那些對稱的計算(像是左右手的擺放位置),幾乎是瞬間完成,這讓我有更多時間專注在微調細節。不過還是有許多地方需要人工調整!
AI 產出的初版雖然像,但總覺得少了點靈魂。以下是我花最多時間的地方: 眼神的細節:AI 給的眼睛比例有點太過機械化。我手動微調了 Ellipse 的 rx 和 ry,並重新定位了瞳孔中的高光,讓它看起來更有神。 紅鼻子的部分: 我特別用了 RadialGradient。AI 給出的漸層色階太跳了,我微調了 startRadius 和顏色比例,才做出了那種像乒乓球一樣、圓潤又有光澤的立體效果。
這次的製作讓我體會到,AI 是一個極強的輔助工具,它能處理掉 70% 的重複性勞動(如基礎座標、語法結構),但剩下的 30% ,關於審美、細節與情感,還是需要人類開發者的修正微調。
最終成果

程式碼
//
// ContentView.swift
// figure
//
// Created by 楊哲鈞Owen on 2026/3/17.
//
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
LinearGradient(
colors: [
Color(red: 0.78, green: 0.92, blue: 0.99),
Color(red: 0.95, green: 0.97, blue: 1.00),
Color(red: 0.83, green: 0.91, blue: 0.99)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
ScrollView {
VStack(spacing: 28) {
titleSection
doraemonSection
}
.padding(.horizontal, 24)
.padding(.vertical, 32)
}
}
}
private var titleSection: some View {
Text("Doraemon")
.font(.system(size: 40, weight: .bold, design: .rounded))
.foregroundStyle(Color(red: 0.08, green: 0.20, blue: 0.36))
}
private var doraemonSection: some View {
ZStack {
RoundedRectangle(cornerRadius: 32, style: .continuous)
.fill(Color.white.opacity(0.72))
.frame(height: 520)
.shadow(color: .blue.opacity(0.12), radius: 24, x: 0, y: 14)
DoraemonView()
}
}
}
// MARK: - 多啦A夢零件
struct DoraemonView: View {
var body: some View {
ZStack {
let blue = Color(red: 0.0, green: 0.545, blue: 0.89)
// 左手手臂
Capsule()
.fill(blue)
.frame(width: 18, height: 72)
.shadow(color: .black.opacity(0.10), radius: 3, x: 0, y: 2)
.overlay {
Capsule()
.stroke(.black, lineWidth: 1)
}
.rotationEffect(.degrees(-130))
.position(x: 60, y: 166)
// 左手手掌
Circle()
.fill(.white)
.frame(width: 25, height: 25)
.overlay {
Circle()
.stroke(.black, lineWidth: 1)
}
.position(x: 30, y: 191)
// 右手手臂
Capsule()
.fill(blue)
.frame(width: 18, height: 72)
.shadow(color: .black.opacity(0.10), radius: 3, x: 0, y: 2)
.overlay {
Capsule()
.stroke(.black, lineWidth: 1)
}
.rotationEffect(.degrees(130))
.position(x: 140, y: 166)
// 右手手掌
Circle()
.fill(.white)
.frame(width: 25, height: 25)
.overlay {
Circle()
.stroke(.black, lineWidth: 1)
}
.position(x: 168, y: 187)
// 左腳
Capsule()
.fill(blue)
.frame(width: 16, height: 34)
.shadow(color: .black.opacity(0.10), radius: 3, x: 0, y: 2)
.overlay {
Capsule()
.stroke(.black, lineWidth: 1)
}
.position(x: 84, y: 213)
// 左腳腳掌
Ellipse()
.fill(.white)
.frame(width: 34, height: 18)
.shadow(color: .black.opacity(0.08), radius: 3, x: 0, y: 2)
.overlay {
Ellipse()
.stroke(.black, lineWidth: 1)
}
.position(x: 79, y: 226)
// 右腳
Capsule()
.fill(blue)
.frame(width: 16, height: 34)
.shadow(color: .black.opacity(0.10), radius: 3, x: 0, y: 2)
.overlay {
Capsule()
.stroke(.black, lineWidth: 1)
}
.position(x: 116, y: 213)
// 右腳腳掌
Ellipse()
.fill(.white)
.frame(width: 34, height: 18)
.shadow(color: .black.opacity(0.08), radius: 3, x: 0, y: 2)
.overlay {
Ellipse()
.stroke(.black, lineWidth: 1)
}
.position(x: 121, y: 226)
// 身體外圈
Circle()
.fill(blue)
.frame(width: 90, height: 90)
.shadow(color: .black.opacity(0.12), radius: 8, x: 0, y: 5)
.overlay {
Circle()
.fill(
LinearGradient(
colors: [.white.opacity(0.18), .clear],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
}
.overlay {
Circle()
.stroke(.black, lineWidth: 1.5)
}
.position(x: 100, y: 180)
// 肚子
Circle()
.fill(.white)
.frame(width: 70, height: 70)
.shadow(color: .black.opacity(0.08), radius: 4, x: 0, y: 2)
.overlay {
Circle()
.stroke(.black, lineWidth: 1)
}
.position(x: 100, y: 180)
// 肚子口袋弧線
Path { path in
path.move(to: CGPoint(x: 75, y: 180))
path.addArc(
center: CGPoint(x: 100, y: 180),
radius: 25,
startAngle: .degrees(0),
endAngle: .degrees(180),
clockwise: false
)
path.closeSubpath()
}
.stroke(Color.gray.opacity(0.85), lineWidth: 1)
// 頭部外圈
Circle()
.fill(blue)
.frame(width: 150, height: 150)
.shadow(color: .black.opacity(0.14), radius: 12, x: 0, y: 7)
.overlay {
Circle()
.fill(
LinearGradient(
colors: [.white.opacity(0.22), .clear],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
}
.overlay {
Circle()
.stroke(.black, lineWidth: 1.5)
}
.position(x: 100, y: 90)
// 臉部白色區域
Circle()
.fill(.white)
.frame(width: 124, height: 124)
.shadow(color: .black.opacity(0.08), radius: 4, x: 0, y: 2)
.overlay {
Circle()
.stroke(.black, lineWidth: 1)
}
.position(x: 100, y: 100)
// 左眼外框
Ellipse()
.fill(.white)
.frame(width: 36, height: 44)
.overlay {
Ellipse()
.stroke(.black, lineWidth: 1.5)
}
.position(x: 82, y: 65)
// 右眼外框
Ellipse()
.fill(.white)
.frame(width: 36, height: 44)
.overlay {
Ellipse()
.stroke(.black, lineWidth: 1.5)
}
.position(x: 118, y: 65)
// 左眼瞳孔
Circle()
.fill(.black)
.frame(width: 8, height: 8)
.position(x: 88, y: 72)
// 右眼瞳孔
Circle()
.fill(.black)
.frame(width: 8, height: 8)
.position(x: 112, y: 72)
// 左眼高光
Circle()
.fill(.white.opacity(0.9))
.frame(width: 3, height: 3)
.position(x: 89, y: 70)
// 右眼高光
Circle()
.fill(.white.opacity(0.9))
.frame(width: 3, height: 3)
.position(x: 113, y: 70)
// 鼻子
Circle()
.fill(
RadialGradient(
colors: [Color(red: 1.0, green: 0.3, blue: 0.3), Color(red: 0.7, green: 0.0, blue: 0.0)],
center: .center,
startRadius: 2,
endRadius: 9
)
)
.frame(width: 18, height: 18)
.overlay {
Circle()
.stroke(.black, lineWidth: 0.5)
}
.position(x: 100, y: 90)
// 鼻子高光
Circle()
.fill(.white.opacity(0.6))
.frame(width: 5, height: 5)
.position(x: 97, y: 87)
// 鼻子到嘴巴的中線
Path { path in
path.move(to: CGPoint(x: 100, y: 99))
path.addLine(to: CGPoint(x: 100, y: 135))
}
.stroke(.black, lineWidth: 1.5)
// 嘴巴弧線
Path { path in
path.move(to: CGPoint(x: 60, y: 125))
path.addQuadCurve(to: CGPoint(x: 140, y: 125), control: CGPoint(x: 100, y: 160))
}
.stroke(.black, lineWidth: 1.5)
// 左右鬍鬚
DoraemonWhiskerSet()
// 紅色項圈
Path { path in
path.move(to: CGPoint(x: 63, y: 158))
path.addQuadCurve(to: CGPoint(x: 137, y: 158), control: CGPoint(x: 100, y: 175))
}
.stroke(
Color.red,
style: StrokeStyle(lineWidth: 8, lineCap: .round)
)
// 鈴鐺外圈
Circle()
.fill(
RadialGradient(
colors: [Color(red: 0.98, green: 0.89, blue: 0.55), Color(red: 0.83, green: 0.69, blue: 0.22)],
center: .center,
startRadius: 2,
endRadius: 10
)
)
.frame(width: 20, height: 20)
.shadow(color: .black.opacity(0.12), radius: 3, x: 0, y: 2)
.overlay {
Circle()
.stroke(.black, lineWidth: 1)
}
.position(x: 100, y: 170)
// 鈴鐺橫線
Path { path in
path.move(to: CGPoint(x: 91, y: 167))
path.addLine(to: CGPoint(x: 109, y: 167))
}
.stroke(.black, lineWidth: 1)
// 鈴鐺中心小孔
Circle()
.fill(Color.gray.opacity(0.9))
.frame(width: 5, height: 5)
.position(x: 100, y: 174)
// 鈴鐺垂直線
Path { path in
path.move(to: CGPoint(x: 100, y: 174))
path.addLine(to: CGPoint(x: 100, y: 180))
}
.stroke(.black, lineWidth: 1)
}
.frame(width: 200, height: 230)
.scaleEffect(1.48)
}
}
private struct DoraemonWhiskerSet: View {
var body: some View {
Path { path in
// 右上鬍鬚
path.move(to: CGPoint(x: 120, y: 105))
path.addLine(to: CGPoint(x: 160, y: 95))
// 右中鬍鬚
path.move(to: CGPoint(x: 120, y: 115))
path.addLine(to: CGPoint(x: 165, y: 115))
// 右下鬍鬚
path.move(to: CGPoint(x: 120, y: 125))
path.addLine(to: CGPoint(x: 160, y: 135))
// 左上鬍鬚
path.move(to: CGPoint(x: 80, y: 105))
path.addLine(to: CGPoint(x: 40, y: 95))
// 左中鬍鬚
path.move(to: CGPoint(x: 80, y: 115))
path.addLine(to: CGPoint(x: 35, y: 115))
// 左下鬍鬚
path.move(to: CGPoint(x: 80, y: 125))
path.addLine(to: CGPoint(x: 40, y: 135))
}
.stroke(.black, lineWidth: 1.5)
}
}
#Preview {
ContentView()
}