简介:一份基于 Python 内置 turtle 绘图库实现柯南角色绘制的完整源码,面向刚接触图形编程的 Python 初学者、想用代码绘制动漫形象的设计爱好者,以及希望把编程练习与兴趣结合的学习者。资源包为 zip 压缩格式,内含 1 个可直接运行的 py 脚本,整体仅 2KB,结构简洁、无额外依赖,适合直接打开代码阅读与二次修改。目前已有 8139 人学习下载,源码演示了 turtle 库的导入与画笔对象创建,并综合使用 forward、goto、circle、ellipse、penup/pendown、begin_fill/end_fill 等命令,逐步画出柯南的头部、眼睛和衣服轮廓。读者既能一键运行查看最终效果,也可以从坐标规划、颜色填充和图形组合中掌握海龟绘图的核心逻辑,并沿用同样思路尝试绘制其他动漫角色或自定义图案。这样的图形化编程练习能有效帮助初学者理解坐标系统与绘图顺序,是一份兼顾趣味性与教学性的入门范例。
1. 用 turtle 画柯南:把动漫脸拆成圆、弧线和坐标关系
用 Python 的 turtle 画柯南,难不在“画”,而在“拆”。你面对的不是一张位图,而是一个从零开始的坐标系:每一笔都要告诉海龟从哪出发、朝哪转、走多远。真正动手时你会发现,柯南的圆脸、大框眼镜、M 型刘海和红色领结,本质上就是几组圆形、弧线和折线的叠加。与其说是在画动漫角色,不如说是在练习“几何原语到视觉符号”的映射能力。
这个标题适合三种人:刚学 Python 循环和函数、想用短代码出图的入门者;带编程课、需要让学生有成就感的老师;以及想把“turtle 画图”这组套路沉淀成可复用源码的脚本爱好者。后续内容围绕一条主线展开:先定坐标系和画布,再用函数封装五官,最后按图层顺序组装成可运行的完整脚本,并给出排错和优化参数的经验。
2. 先用坐标系和基础图形把柯南头像骨架立起来
2.1 画柯南之前为什么必须固定画布参数
turtle 的默认画布大小是 800×600 像素,原点在画布中心,X 轴向右为正,Y 轴向上为正。初学者最容易踩的坑是把goto(x, y)里的坐标当成“左上角为原点的图像坐标”,导致画出来的头像跑到窗口外面。建议在脚本最前面调用Screen.setup(),把坐标系限定在一个可控范围内。
做一个能容纳头像和上半身的画布,比较顺手的参数是setup(520, 560)。这样画布左右约 260 像素、上下约 280 像素,头像放在 Y 轴正半区,衣服和领结放在 Y 轴负半区。配合bgcolor("#FDF6E3")设置米色背景,角色轮廓会更突出。
还有一个容易忽略的操作:如果不想看到海龟走得飞快或者画到一半窗口卡顿,先别急着speed(),直接用tracer(0)关闭逐帧刷新,等所有图形画完再调用update()一次性显示。对画柯南这种多笔画任务,这套组合能把绘制时间从十几秒压缩到一秒以内,也能避免画到一半窗口失去响应。
自检 turtle 环境是否正常,不一定要立刻跑完整代码,可以执行一条命令:
python -m turtle这条命令会弹出一个演示窗口,海龟会自动画几个图形。如果窗口正常弹出,说明 turtle 库可用;如果提示No module named turtle或者ModuleNotFoundError,通常是 Python 环境里没有 Tk 支持,换一个安装方式或解释器再试。网上很多“为什么 turtle 库用不了”的问题,最后都落在“系统 Python 缺 Tk”和“用 IDE 自带的虚拟解释器”这两个原因上。
2.2 把五官坐标列成表再动手
画复杂角色前,先在纸上把关键点坐标写出来,这一步能省下大量试错时间。以头像为例,可以用半径 80 的圆作为脸部基底,圆心放在(0, 10),脸的最低点在(0, -70)。在这个范围内,眼睛、镜框、嘴巴的位置相对固定。
| 部件 | 位置坐标(像素) | 尺寸/半径 | 绘制方式 |
|---|---|---|---|
| 脸部 | 圆心 (0, 10) | 半径 80 | circle(80) |
| 眼睛 | (±32, 25) | 眼白 dot 12 | 两层 dot 叠加 |
| 镜框 | 圆心 (±32, 12) | 半径 18 | circle(18, 360) |
| 刘海 | 头顶到额头 | 多段折线/弧线 | goto连线 |
| 嘴巴 | (−12, −30) 到 (12, −30) | 跨度 24 | 直线 |
| 领结 | 中心 (0, −88) | 菱形 | 四边形填充 |
这个表格的意义不是让你照抄,而是提供一个坐标映射思路。每加一个部件,先问三个问题:它相对脸部的偏移是多少?它是圆形、圆弧还是折线?它应该被之后画的哪个部件覆盖?把这三个问题在表里标注完,绘制代码基本就成型了。
2.3 辅助函数 jump 和头像轮廓的最小代码
turtle 最常见的手写代码是“抬笔、移动、落笔”三连,每次都写三行很啰嗦。我一般会定义一个jump辅助函数,把移动封装起来,后续所有部件定位都用它。
from turtle import Screen, Turtle def jump(t: Turtle, x: float, y: float) -> None: t.penup() t.goto(x, y) t.pendown()jump的名字来自“跳过去”,意思是笔尖不画线直接移动。参数t是海龟对象,x和y是目标坐标。这个函数在绘制柯南每个部件前都会用到,比如画脸部时先jump(t, 0, -70)再从最低点起笔,画眼睛时先jump(t, -32, 25)再点圆点。
有了辅助函数后,可以用最小代码画出脸部和头发基础轮廓:
def draw_base(t: Turtle) -> None: t.color("black", "#FFE0BD") # 边框黑色,内部肤色 jump(t, 0, -70) t.begin_fill() t.circle(80) # 逆时针画整圆,作为脸部基底 t.end_fill()这里circle(80)的半径是 80 像素,圆心在当前坐标正上方 80 像素处,也就是 (0, 10)。begin_fill()和end_fill()中间的所有封闭图形会填充#FFE0BD。脸部的左右边界约在x = -80和x = 80,这个范围决定了后续眼镜、刘海都不能超出太多。
3. 柯南的眼睛、眼镜和表情细节:绘制顺序与子函数拆分
3.1 为什么必须先画大面积皮肤,再画小部件
turtle 绘图没有图层概念,后画的图形会覆盖先画的图形。柯南的头像看起来复杂,但绘制逻辑可以简化为四层:最底层是衣服和脸部基底,第二层是五官和腮红,第三层是眼镜与头发,第四层才是领结这类凸出物体。每一层内部再按“先大面积后小面积”的顺序画。
如果顺序反过来,比如先把瞳孔画好,再去画皮肤色圆,瞳孔就会直接被覆盖。这个问题尤其在头发和脸部交界处会出现:先画脸,再用深色刘海压住额头上方,就能形成“头发挡在皮肤前”的视觉层次。所以每次添加新部件前,都要问一句:它应该遮住谁,还是应该被谁遮住。
画脸部基底时,填充色#FFE0BD属于偏白的肤色,适合表现动画角色的高亮度皮肤。若想调整肤色深浅,可以把颜色值改成#FCD7B8或#F8C89A,但要注意颜色越深,后续镜框和嘴的对比度就越低。
3.2 眼睛和镜框的绘制参数:dot 的半径差决定眼神
柯南最具辨识度的特征是圆形大框眼镜和小而聚拢的瞳孔。这种效果用dot比用circle更稳定,因为dot只画实心圆点,不依赖海龟当前的海拔方向。
def draw_eyes(t: Turtle) -> None: t.pencolor("black") for eye_x in (-32, 32): jump(t, eye_x, 25) t.dot(14, "#FFFFFF") # 白色眼白打底 jump(t, eye_x - 4, 23) # 瞳孔向左下偏移,模仿视角方向 t.dot(6, "#202020") # 深色瞳孔dot(14, "#FFFFFF")表示画一个直径 14 像素的白色实心圆,t.dot(6, "#202020")是在前一个白点内部再叠一个直径 6 像素的深色瞳孔。瞳孔向左偏移 4 像素,能让角色看起来有一点点视线偏向,这是让“画得死板”变成“有点神”的关键参数。如果不需要朝向感,把eye_x - 4改回eye_x就是正前方注视效果。
眼镜框要套住眼睛但不能贴太紧,镜片中心取在眼睛正下方 13 像素的位置,半径取 18。这样镜框的上沿大约在y = 30,下沿在y = -6,能框住大部分眼白和瞳孔。用pensize(3)加粗镜框线条,看起来会更接近动画里的黑框眼镜。
def draw_glasses(t: Turtle) -> None: t.pensize(3) t.pencolor("#3B3B3B") for glass_x in (-32, 32): jump(t, glass_x - 18, 12) t.circle(18, 360) # 画完整圆镜框 jump(t, -14, 5) # 镜框之间的鼻梁桥 t.goto(14, 5)值得注意circle(18, 360)和dot(36)的区别:前者画的是空心圆,线条宽度由pensize控制;后者画的是实心圆,没有线宽概念。画镜框只能用前者,画瞳孔只能用后者,两者不能互换。
3.3 刘海、嘴和腮红:用折线模拟尖角发梢
柯南的刘海不是整齐的半圆,而是带有尖角的 M 型走向。使用折线goto一套坐标点,可以让发梢既锐利又不对称,更吻合动画角色的手绘感。
def draw_hair(t: Turtle) -> None: t.pensize(10) t.pencolor("#2A2A2A") jump(t, -78, 18) t.setheading(15) t.goto(-40, 62) t.goto(-15, 25) t.goto(10, 60) t.goto(42, 64) t.goto(80, 20) t.pensize(1)这段代码的出发点是让海龟从左侧发际线向上斜切,形成三个锯齿:第一个锯齿指向(-40, 62),第二个锯齿在(-15, 25)收进去,第三个又抬到(10, 60)。setheading(15)规定了第一段线的角度约为 15 度,后续角度全由goto控制。由于笔宽为 10,线本身就有一定厚度,不需要额外填充就能形成深色头发轮廓。
嘴巴和腮红属于“增加生动度”的细节,画法非常简单:
def draw_mouth_and_blush(t: Turtle) -> None: t.pensize(3) t.pencolor("#5A3E36") jump(t, -12, -30) t.goto(12, -30) # 一条简短的微笑线 t.pensize(1) t.pencolor("#FF9E9E") for bx in (-54, 54): jump(t, bx, -12) t.dot(12, "#FF9E9E") # 淡粉色腮红嘴的横向跨度是 24 像素,刚好比瞳孔间距窄一截,符合“五官集中”的动漫脸比例。腮红距离脸部中心约 54 像素,接近脸圆半径 80 的 0.7 倍,这个比例在大多数脸型上都不会出错。
4. 从头像到上半身:领结、西装和配色表的整合绘制
4.1 整体图层顺序:领结应该压住西装还是压住下巴
画到上半身,绘制的部件变为衣领、领结和衬衫。许多人画到这里会乱,是因为不知道领结和衣领哪个先画。正确的顺序是先画西装衣领,再画脸部,最后画领结。
原因是柯南的红色领结位于下巴正下方,它的下半部分会叠在西装衣领前面,而上半部分接近下巴。如果先画领结再画衣领,衣领就会盖住领结的下半段,看起来像是领结被衣服吞掉了。反过来,先画衣领作为背景,再画领结,最后不管领结多宽都能保持完整轮廓。
def draw_collar(t: Turtle) -> None: t.color("#224488", "#2E5AA8") # 蓝色西装领 jump(t, -52, -118) t.begin_fill() for x, y in [(-52, -118), (52, -118), (34, -92), (0, -104), (-34, -92)]: t.goto(x, y) t.goto(-52, -118) t.end_fill()draw_collar其实是一个五边形闭合路径,从左肩点(-52, -118)走到右肩点,再经过领口左侧、中缝、领口右侧,最后回到起点。begin_fill()会把五条边围出的区域填成蓝色。
领结用一个旋转 45 度的菱形实现:
def draw_bowtie(t: Turtle) -> None: t.color("#CC2222", "#E8442A") jump(t, -22, -104) t.begin_fill() for x, y in [(-22, -104), (0, -88), (22, -104), (0, -120)]: t.goto(x, y) t.goto(-22, -104) t.end_fill()这个菱形的四个顶点分别是左右两角(-22, -104)、(22, -104),以及上下两个尖角。goto(0, -88)让领结顶部靠近下巴底部,goto(0, -120)让底部插入西装领口,视觉上形成“领结被夹在领子里”的效果。
4.2 用一个颜色常量表管理角色配色
柯南的经典配色可以抽象成六个颜色值。写代码时如果直接分散写十六进制字符串,后期调色会非常痛苦。推荐在源码顶部维护一个颜色常量区:
| 用途 | 颜色值 | 说明 |
|---|---|---|
| 肤色 | #FFE0BD | 面部填充 |
| 发色 | #2A2A2A | 头发描边及填充 |
| 西装色 | #224488 | 西装轮廓 |
| 西装亮色 | #2E5AA8 | 西装填充 |
| 领结色 | #E8442A | 领结填充 |
| 镜框色 | #3B3B3B | 眼镜边框 |
把这些值赋值给全大写变量,函数内只引用变量名。好处是调色时只需改一行,不必在十几个pencolor、fillcolor调用里翻找。这在源码组织里是一个简单但非常有效的习惯,尤其是以后想改成“灰原哀配色”“怪盗基德配色”时,只需换掉整张配色表。
4.3 把函数组织成完整的可运行源码
把所有函数和入口放到一个文件中,结构类似下面这样:
import turtle SKIN = "#FFE0BD" HAIR = "#2A2A2A" SUIT = "#224488" SUIT_LIGHT = "#2E5AA8" BOWTIE = "#E8442A" GLASS = "#3B3B3B" def jump(t, x, y): t.penup() t.goto(x, y) t.pendown() def draw_face(t): ... def draw_hair(t): ... def draw_eyes(t): ... def draw_glasses(t): ... def draw_mouth_and_blush(t): ... def draw_collar(t): ... def draw_bowtie(t): ... def main(): s = turtle.Screen() s.setup(520, 560) s.bgcolor("#FDF6E3") s.title("conan turtle demo") s.tracer(0) t = turtle.Turtle() t.speed(0) t.hideturtle() # 先画远处图层,再画近处图层 draw_collar(t) draw_face(t) draw_hair(t) draw_eyes(t) draw_glasses(t) draw_mouth_and_blush(t) draw_bowtie(t) s.update() # 配合 tracer(0),绘制结束后统一刷新 s.mainloop() if __name__ == "__main__": main()这个组织方式的优势是职责清楚:jump负责移动,各draw_*函数负责具体部件,main只负责初始化和图层顺序。将来想调整某个部位,直接改对应函数即可,不需要在几百行代码里找定位语句。
用turtle画柯南,本质上是在“有限图元”和“视觉目标”之间做映射:圆、弧、点、折线,再加上合适的颜色和图层顺序,就能还原出角色辨识度最高的几个特征。这也是这类绘图项目最值得保存为源码笔记的原因——代码本身不复杂,复杂的是拆解思路。
5. 验证画图效果和优化 turtle 绘图的三个参数
5.1 画完立即预览并保存成图片
代码能跑只是第一步,画出来是否“像柯南”需要在一个完整的窗口中检查。由于设置了tracer(0),画面会在update()之后一次性显示。如果看到某个部件位置不对,不要重启程序,只要调整对应函数里的坐标再运行一次即可。
turtle 不直接支持导出 PNG,但可以先导出 PostScript 文件再做转换:
s.update() canvas = s.getcanvas() canvas.postscript(file="conan.eps")这句代码把当前画布内容导出为 EPS 矢量文件。EPS 可以用 Photoshop、Ghostscript 或在线工具转成 PNG。考虑到部分预览工具不支持 EPS,更稳妥的做法是直接用截图工具截取 turtle 窗口,这样能看到真实渲染效果,也更方便对比修改前后的差别。
5.2 三个值得反复调节的参数
第一个参数是tracer(0, 0)。第二个参数是delay(),它控制每次绘制动作之间的毫秒延迟。画完后再设置turtle.delay(0),能让刷新更干脆。第三个参数是pensize,它不只是“线宽”,而是决定头发、镜框视觉重量感的工具。柯南的头发线条如果太细,看起来会像普通圆珠笔草图;调到 8 到 12 之间才会有粗线条的动漫描边质感。
这三个参数组合使用时有个经典调法:开发阶段开tracer(1)观察绘制过程,找出哪一步出现偏差;确认流程无误后改回tracer(0),再统一调节pensize让轮廓更扎实。保存最终成品前,把画面中距离最近的部件和背景色对比一下,若对比度不够,优先调整背景色而不是加重描边,这样能保留层次的同时减少脏感。
本文还有配套的精品资源,点击获取