简介:这是一份用Python标准库turtle绘制动漫人物柯南的趣味编程源码,压缩包内仅1个.py文件,大小约2KB,非常适合Python初学者、图形编程爱好者及少儿编程教学场景使用。案例以柯南形象为绘制目标,完整展示从导入turtle库、创建画笔对象,到逐步绘制头部、眼睛、衣服等部位的全过程,代码按绘制顺序排列,可直接运行并查看逐步呈现的图形效果。代码中综合运用了forward、left、right等移动指令,goto进行坐标定位,penup/pendown控制抬笔落笔,circle与ellipse绘制圆形、椭圆,配合begin_fill/end_fill、fillcolor、pencolor实现色彩填充,最后以turtle.done()保持画布显示。通过调整坐标、半径与颜色参数,还能举一反三创作其他角色或图案。目前已有8139人学习浏览,适合作为图形编程入门练手项目,也可作为课堂教学中的示例代码,帮助学习者直观理解turtle绘图的坐标体系与绘图逻辑。
1. 为什么用 turtle 画柯南?先解决“看得见”的编程练习
第一次接触 Python 的人,往往卡在print输出一堆文字之后不知道该干嘛。turtle 库把代码执行变成了画笔运动,一行forward(100)就能看到乌龟拖着线往前走,这种即时反馈是入门阶段最需要的。用 turtle 画柯南不是要做一张高清海报,而是通过拆解角色的脸型、眼镜、衣服和颜色填充,把坐标系、循环、函数封装这些基础知识都过一遍。源码看起来是画图,实际上练的是“先规划坐标,再控制状态,最后组合复用”的编程思路。这个项目适合刚学完 Python 基础语法的新手,也适合需要带人入门的老手——它比打印九九乘法表更有趣,又比写爬虫简单,半小时内就能跑出结果。更重要的是,turtle 作为 Python 自带的标准库,不需要额外安装,只要你把 Python 环境配好,就能直接运行,这也是它常年出现在 python 入门教程里的原因。
2. turtle 库的坐标系与画笔控制:先画准,再画像
2.1 画布与坐标系:所有图形都是坐标游戏
turtle 的默认画布大小是屏幕的 800x800 左右,原点 (0,0) 在画布中心,向右是 x 正方向,向上是 y 正方向。这与数学坐标系一致,所以画人物时要先在脑子里建一个直角坐标系。比如画柯南的头部,圆心放在 (0,0),画一个半径为 100 的圆,那么头顶在 (0,100),下巴在 (0,-100)。如果用goto()直接移动,需要清楚当前画笔箭头指向的方向;但更稳妥的做法是先用penup()抬笔,goto()到目标点,再pendown()落笔,这样就不受当前朝向干扰。
很多初学者会问“为什么我的图形是反的”或者“画笔移动到了奇怪的位置”,通常就是没有理解left()和right()是相对当前朝向旋转,而不是绝对角度。比如left(90)是向左转 90 度,而不是转到画布左上角。如果想让画笔从当前位置移动到某个坐标,直接用goto(x, y)最省心。turtle 的坐标单位是像素,在 1000x800 的画布上,(0,0) 到 (500,0) 正好是画布右边缘的一半,所以规划人物时不要把身体画到 ±400 以外,否则会出画。
2.2 画笔状态与运动控制:forward、penup、goto 的配合
turtle 对象的常用方法分三类:运动控制、状态控制和属性设置。运动控制包括forward(distance)/fd()、backward()、left(angle)、right(angle)、goto(x, y)、circle(radius, extent);状态控制包括penup()/up()、pendown()/down();属性设置包括pencolor()、fillcolor()、pensize()、speed()。这里的distance单位是像素,angle单位是度。
下面的代码演示了如何用三种方式画同一条直线:
import turtle t = turtle.Turtle() t.speed(3) # 方式1:forward前进100像素 t.forward(100) # 方式2:goto绝对定位 t.penup() t.goto(-50, 0) t.pendown() t.setheading(0) # 设置朝向为正东 t.forward(100) # 方式3:右转向后再前进,模拟相对运动 t.penup() t.goto(50, 50) t.pendown() t.right(90) t.forward(100) turtle.done()setheading(0)是很多人忽略的细节,它把画笔方向重置为水平向右。如果不重置,上一个动作留下的朝向会影响后面的forward()方向,导致画出斜线。goto()则不存在这个问题,因为它用的是绝对坐标。方式 1 最简单,但只能沿当前方向移动;方式 2 适合跳转到指定起点;方式 3 用于模拟“从某个点出发,沿某个角度前进”的相对运动。在画柯南的四肢时,方式 3 更好用,因为身体部件之间有夹角,直接用left/right转向更直观。
对于需要精确控制起点的场景,我一般这样搭配:penup() -> goto(x, y) -> setheading(0) -> pendown()。这个固定节奏能避免大部分定位混乱的问题。另外,pensize()默认是 1,画轮廓线时改成 2 或 3 会更有手绘感;speed(0)表示最快,但调试时建议用speed(5),能看清绘制顺序。
2.3 常用绘制方法速查表
| 方法 | 参数 | 作用 | 常用值 |
|---|---|---|---|
forward() | distance | 沿当前方向前进 | 100 |
left()/right() | angle | 左转/右转角度 | 90, 45 |
goto() | x, y | 移动至绝对坐标 | (0, -100) |
penup()/pendown() | 无 | 抬笔/落笔 | - |
pencolor() | color | 线条颜色 | "black" |
fillcolor() | color | 填充颜色 | "lightblue" |
begin_fill()/end_fill() | 无 | 填充区域的起止标记 | - |
circle() | radius, extent | 画圆或弧,extent 为度数 | radius=100, extent=360 |
speed() | 0-10 | 画笔速度,0 最快 | 3 |
这张表基本覆盖了画柯南需要的全部方法。注意circle(radius, extent)中 extent 默认是 360 度,想画半圆就传 180。turtle 没有提供直接画椭圆的 API,源码里如果用了ellipse(),那是自定义封装,不是标准库方法。我们在下一章会看到如何手动实现椭圆。另外,fillcolor()在调用begin_fill()之前设置有效,如果先开始填充再设置颜色,颜色可能不会生效。这是初学者经常犯的次序错误。
3. 拆解柯南:从头部到眼镜的绘制流程
3.1 头部轮廓:用 circle 画圆脸和填充底色
柯南的头部接近圆形,但下巴略尖。为了上手,先用正圆代替脸型。圆心放在原点,半径设为 120,填充肤色。turtle 中填充是这样工作的:先begin_fill(),再画封闭图形,最后end_fill()。注意begin_fill()之前画笔要落笔,否则即使有闭合图形也填不上色。
import turtle kornan = turtle.Turtle() kornan.speed(3) kornan.penup() kornan.goto(0, -120) kornan.pendown() kornan.pencolor("black") kornan.fillcolor("#FDDCB5") # 肤色 kornan.begin_fill() kornan.circle(120) kornan.end_fill()这里goto(0, -120)是圆的底部,然后circle(120)逆时针画圆,最终回到起点,形成一个封闭圆。fillcolor的颜色值可以用十六进制字符串,也可以直接用 "orange" 这类内置颜色名。肤色用#FDDCB5比内置"bisque"更贴近动漫角色的亮肤色。
要画尖下巴,就需要在圆圈基础上调整。常见做法是circle(120, 180)画上半圆,然后用goto()连接下半部分,或者用pensize()加粗下颌线。初学者先保持正圆也能看出是人物头部,后续再叠加上刘海和眼镜,轮廓感自然就来了。如果你用的是 Python 3.10 以上的环境,turtle 的circle()曲线会更平滑,低版本时线段感明显,可以适当调大画布尺寸来缓解。
3.2 眼睛和眼镜框:用椭圆与弧线定位关键特征
眼睛是柯南最具辨识度的特征,典型结构是大眼眶、蓝色虹膜和高光。标准 turtle 画不出椭圆,需要自己写一个ellipse函数,基于circle绘制弧线拼接:
def ellipse(pen, radius_x, radius_y): pen.begin_fill() for _ in range(2): pen.circle(radius_x, 90) pen.circle(radius_y, 90) pen.end_fill()这个函数的原理是:用两段半径不同的圆弧交替拼接四次,形成一个近似的椭圆。radius_x控制横向半径,radius_y控制纵向半径。比如要画一个宽 40、高 60 的眼眶,调用ellipse(kornan, 20, 30)即可。
接下来用绝对坐标把两个眼睛放在脸部中线的左右两侧。脸的中心是 (0,0),所以左眼中心设在 (-30, -10),右眼中心设在 (30, -10)。ellipse的起点是椭圆最左侧的端点,因此左眼要从(-55, -10)开始,右眼从(5, -10)开始,相差正好一个横向直径 50。
# 左眼眶,中心(-30, -10) kornan.penup() kornan.goto(-55, -10) # 左眼椭圆最左侧起点 kornan.pendown() ellipse(kornan, 25, 35) # 右眼眶,中心(30, -10) kornan.penup() kornan.goto(5, -10) kornan.pendown() ellipse(kornan, 25, 35)这里的goto坐标是椭圆最左侧端点,因为ellipse函数内部从当前点开始画弧。如果画出来的椭圆位置不对,先计算椭圆最左点坐标:中心 x 减去横向半径,再对齐 y 坐标。比如中心 (-30, -10),横向半径 25,则起点 x = -30 - 25 = -55。把这个公式记清楚,后续调整位置时就不会靠猜。
3.3 头发、嘴巴与身体:用直线和多边形搭出轮廓
头部上方要加刘海。柯南的刘海是一簇尖角向下的形状,可以用forward()和left()组合画出锯齿。更简单的方式是直接用goto()依次连接几个固定点,再填充深灰色:
kornan.penup() kornan.goto(-70, 80) kornan.pendown() kornan.fillcolor("#333333") kornan.begin_fill() kornan.goto(-40, 30) kornan.goto(-10, 70) kornan.goto(20, 20) kornan.goto(50, 70) kornan.goto(70, 80) kornan.goto(50, 110) kornan.goto(0, 90) kornan.goto(-50, 110) kornan.goto(-70, 80) kornan.end_fill()这段代码画了一个带尖角的发帘,覆盖在圆形的上半部分。注意点序列的走向是从左到右,再从右边绕回左边,形成闭合区域。填充色#333333是深灰色,避免纯黑死板。嘴巴可以简化成一条直线加上下弯,用penup()和goto()画短线段即可,这里不再展开。
身体部分用梯形表示西装前襟,再叠两个三角形蝴蝶结:
# 西装主体 kornan.penup() kornan.goto(-60, -120) kornan.pendown() kornan.fillcolor("#4A90D9") kornan.begin_fill() kornan.goto(60, -120) kornan.goto(70, -220) kornan.goto(-70, -220) kornan.goto(-60, -120) kornan.end_fill() # 蝴蝶结 for side in [-1, 1]: kornan.penup() kornan.goto(0, -150) kornan.pendown() kornan.fillcolor("red") kornan.begin_fill() kornan.goto(side * 30, -140) kornan.goto(side * 20, -155) kornan.end_fill()西装梯形的上边两个点 (-60, -120) 和 (60, -120) 正好与头部下缘衔接,下边更宽,形成肩部。goto顺序决定了图形闭合路径,不要求回到起点,end_fill()会自动闭合。蝴蝶结用side取 -1 和 1,分别画左侧和右侧的结,避免复制两份几乎相同的代码。
3.4 完整绘制代码:一个可以直接运行的脚本
把上面的片段组合起来,再加上眼镜框和刘海,就是一个完整脚本。实际源码文件用Python的turtle库画柯南.py里更细致,这里给出一个可运行的精简版:
import turtle def ellipse(pen, rx, ry): pen.begin_fill() for _ in range(2): pen.circle(rx, 90) pen.circle(ry, 90) pen.end_fill() kornan = turtle.Turtle() kornan.speed(3) # 头部 kornan.penup() kornan.goto(0, -120) kornan.pendown() kornan.fillcolor("#FDDCB5") kornan.begin_fill() kornan.circle(120) kornan.end_fill() # 左眼,中心(-30, -10) kornan.penup() kornan.goto(-55, -10) kornan.pendown() ellipse(kornan, 25, 35) # 右眼,中心(30, -10) kornan.penup() kornan.goto(5, -10) kornan.pendown() ellipse(kornan, 25, 35) # 西装 kornan.penup() kornan.goto(-60, -120) kornan.pendown() kornan.fillcolor("#4A90D9") kornan.begin_fill() kornan.goto(60, -120) kornan.goto(70, -220) kornan.goto(-70, -220) kornan.goto(-60, -120) kornan.end_fill() turtle.done()这个脚本运行后能看到基本的柯南轮廓:圆脸、两个大眼睛和蓝色西装块。注意ellipse函数里pen.begin_fill()和end_fill()放在一起,如果中间有抬笔或者跳转,会导致填充区域不闭合。建议在椭圆函数内部不调用penup(),保证填充路径连续。在此基础上,你可以继续加刘海、眼镜框、蝴蝶结和嘴巴,慢慢把细节补全。
4. 颜色填充、坐标微调与函数封装:让代码可维护
4.1 用 fillcolor 和 pencolor 控制颜色层次
turtle 的颜色系统支持英文颜色名、十六进制 RGB 值和 RGB 元组。画人物时,需要一份颜色配置表,把肤色、头发、西装、领结的颜色统一管理。比如柯南的头发不是纯黑,而是深灰色#333333;西装是藏蓝色#2E4E7E;蝴蝶结是红色#E60000。直接在代码里重复写十六进制串容易出错,也难调整,建议先定义成变量。
SKIN = "#FDDCB5" HAIR = "#333333" SUIT = "#2E4E7E" TIE = "#E60000"然后在使用时写成kornan.fillcolor(SKIN)。这样想换配色时,只需要改顶部常量。对于大量相同形状的部件,比如左右眼镜框,可以先指定填充色和边框色,再调用绘图函数。pencolor()默认是黑色,如果线条和填充色对比强,可以关掉边框,即kornan.pencolor(SKIN)让轮廓线融入填充色,让图形边缘更干净。很多成品代码会刻意让线条颜色和填充颜色一致,模拟无描边的扁平风格。
另外,turtle 支持 RGB 元组,比如kornan.pencolor((0.2, 0.3, 0.4)),三个分量取值 0~1。这在需要渐变或者精确匹配图片颜色时很有用。对于初学者,十六进制字符串最直观,在浏览器里取色后直接拷贝即可。
4.2 坐标相对化:用 goto 和方向增量避免硬编码
上面示例中的坐标都是手工硬编码的,比如goto(-55, -10)。这种代码能跑,但调整位置时每次都要改多个数字。更好的做法是用相对坐标:以头部中心为基点,定义眼睛、嘴巴、衣服到基点的偏移量。比如设置head_x, head_y = 0, 0,左眼中心表示为(head_x - 30, head_y - 10)。这样移动头部时,眼睛会跟着动。
我一般会写一个辅助函数,专门画椭圆,并且传入中心坐标:
def draw_eye(pen, cx, cy, rx, ry, fill_color): pen.penup() pen.goto(cx - rx, cy) # 从左侧起点开始 pen.pendown() pen.fillcolor(fill_color) pen.begin_fill() for _ in range(2): pen.circle(rx, 90) pen.circle(ry, 90) pen.end_fill() pen.penup() pen.goto(cx, cy)cx - rx保证了从椭圆最左侧开始,cy为垂直中心。这样一来,调用draw_eye(kornan, -30, -10, 25, 35, "white")时不需要关心起点怎么算。封装后,重复代码从每次 10 行降为 1 行,也更容易批量修改左右眼的间距和大小。
在调试坐标时,我习惯在关键位置打印画笔坐标:在绘制每个部件前,把pen.position()输出到控制台。比如print(kornan.pos()),这样可以快速确认goto()之后画笔是否到达预期位置。turtle 的返回值是(x, y)元组,可以直接和设计值对比。
4.3 将重复绘画封装成函数:画眼睛、画眼镜框
柯南的眼镜框是圆形,两个镜片中间连一条横梁。把“画一个眼镜框”封装成函数,可以同时画左镜框和右镜框:
def draw_glasses(pen, left_x, right_x, cy, radius): for cx in (left_x, right_x): pen.penup() pen.goto(cx - radius, cy) pen.pendown() pen.pensize(2) pen.pencolor("black") pen.fillcolor("") # 不填充,只画边框 pen.begin_fill() pen.circle(radius) pen.end_fill() # 横梁 pen.penup() pen.goto(left_x + radius, cy) pen.pendown() pen.goto(right_x - radius, cy)提示:
fillcolor("")并不是把颜色设为空,而是告诉 turtle 不执行填充。如果你直接在fillcolor()里传空字符串,部分版本会报错,更稳妥的做法是用pen.fill(False)替代。这里保留begin_fill()和end_fill()是为了让圆环的路径闭合,避免出现缺口。
pensize(2)加粗边框,让眼镜在脸上更醒目。radius是镜框半径,left_x和right_x是两个镜框中心 x 坐标。封装前后的对比可以看下表:
| 方式 | 代码量 | 修改镜框尺寸 | 修改位置 | 出错率 |
|---|---|---|---|---|
| 硬编码 | 每组 10 行 | 每个坐标都要改 | 每个坐标都要改 | 高 |
| 函数封装 | 每组 1 行调用 | 只改参数 | 只改参数 | 低 |
函数封装的另一好处是可以复用。画完柯南后,你还可以画另一个戴眼镜的角色,只需要改draw_glasses的调用参数。比如把left_x从 -35 改成 -50,两个镜框会同时外移,不需要动横梁代码。这就是 turtle 画图向“面向对象”思维的过渡。
5. 进阶:让柯南动起来,以及 turtle 绘图的三个常见掉坑点
5.1 用 turtle 做简单动画:平移和旋转
画完静态柯南后,可以让他眨眼或者走动。turtle 动画的基本方式是用turtle.tracer(0)关闭自动刷新,手动修改坐标后用turtle.update()刷新。比如实现一个上下移动的漂浮效果:
import turtle def float_move(pen, base_y, step=1): for i in range(50): pen.penup() pen.goto(0, base_y + i * step) pen.pendown() turtle.update() turtle.ontimer(lambda: None, 10)这里ontimer不是必须的,只是为了手动控制刷新频率。tracer(0)先关掉动画缓冲,update()再一次性渲染当前状态,这样移动看起来流畅,不会出现画笔残影。注意ontimer的回调需要函数对象,lambda: None只是占位,实际开发中可以用time.sleep(0.02)替代,但这样会阻塞事件循环,不适合复杂动画。
5.2 保存绘图结果:用 postscript 或屏幕截图
turtle 没有直接保存 PNG 的函数,但可以借助turtle.getcanvas().postscript()导出矢量格式,再通过其他工具转换。代码如下:
ts = turtle.getscreen() ts.getcanvas().postscript(file="conan.eps")这样会生成一个 EPS 文件,预览软件可以打开,也可以用 Ghostscript 或在线工具转成 PDF 或 PNG。如果你用的是 Windows,可以导出 EPS 后在画图软件里截图;Mac 用户可以直接用系统截图快捷键。对于博客配图,导出 EPS 后转高分辨率 PNG 会比直接截图清晰得多,尤其是线条边缘不会出现锯齿。
5.3 常见掉坑点:turtle 库用不了、窗口闪退、速度太快
第一个高频问题是import turtle后环境报错,通常是因为安装了多个 Python 版本,或者使用了一些精简的 IDE。建议在终端里执行python -m pip list | findstr turtle(Windows)或pip list | grep turtle(Linux/macOS)确认 turtle 是否可用。turtle 是 Python 标准库,正常情况下不需要安装,如果你用的是 Anaconda,注意不要和turtle-graphics第三方包混淆。在 VSCode 里运行前,要先确认右下角选择的 Python 解释器是同一个环境,否则会出现“在终端能跑,在 VSCode 里报 ModuleNotFoundError”的诡异问题。
第二个问题是窗口闪退。很多初学者把代码保存成 .py 文件后双击运行,窗口一出现就消失。解决方法是确保运行环境中没有语法错误,并且在脚本最后调用turtle.done()或turtle.mainloop(),让事件循环保持监听。另外,不要在 Jupyter Notebook 里直接运行,Notebook 的交互式环境和 turtle 的 GUI 循环冲突,建议在终端用python 用Python的turtle库画柯南.py运行。如果你确实想在 Notebook 里预览,可以加%matplotlib inline并使用IPython.display的Image来显示导出的图片,但这不是原生支持,不如直接跑脚本方便。
第三个问题是绘制速度。speed(0)是最快的,speed(10)是普通快,但画几百个线段时仍然慢。合理做法是先用speed(10)观察线段位置,确认无误后改成speed(0)跑最终效果。如果填充很慢,可以把tracer(0)和update()结合,一次性渲染所有图形。tracer(0)会暂停所有自动刷新,画笔移动时屏幕不更新,直到调用update()才统一显示,这比speed(0)快一个量级。注意tracer(0)之后,speed()设置会失效,所以要在关闭刷新前把速度调好。
本文还有配套的精品资源,点击获取