尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python海龟绘图实战:用turtle库绘制Among Us角色

Python海龟绘图实战:用turtle库绘制Among Us角色 1. 项目概述用Python的“画笔”画出太空狼人如果你刚开始学Python或者想找个有趣的项目来练手那么用代码画一个《Among Us》里的宇航员小人绝对是个能让你成就感爆棚的选择。这个项目听起来有点“不务正业”但它能帮你把Python里一个非常经典但常被忽略的库——turtle海龟绘图库——玩得明明白白。你不用懂复杂的图形学也不用安装庞大的第三方库Python自带的turtle就能让你像指挥一支画笔一样在画布上绘制出任何你想要的图形。《Among Us》这个游戏的角色造型简单又极具辨识度一个倒置的鸡蛋形身体背着一个圆筒状的氧气背包再加上面罩和护目镜。这种由基本几何图形圆形、椭圆形、矩形组合而成的风格恰恰是turtle库最擅长表现的。通过这个项目你不仅能学会如何用代码控制“画笔”移动、转向、改变颜色和粗细更能深入理解计算机绘图中最核心的概念坐标系和相对路径。你会知道画一个圆不是魔法而是让“海龟”沿着一个正多边形路径走出来的画一条曲线可能是由许多极短的直线段模拟而成的。当你最终运行代码看着画笔自动勾勒出那个熟悉的小人轮廓时你会对“编程是创造力的延伸”这句话有更深的体会。这个项目适合所有阶段的Python学习者。对于新手它是绝佳的入门实践将枯燥的语法循环、函数转化为可视化的成果对于有经验者它可以是一次对算法思维和代码结构优化的有趣挑战比如如何用最简洁的指令、最优雅的函数来组织绘图逻辑。接下来我们就从零开始一步步拆解如何用Python的turtle库把这个太空小人生动地画出来。2. 核心思路与工具选型解析2.1 为什么选择Turtle库在Python的绘图世界里可选方案很多比如更强大的Pygame、工业级的Matplotlib或者底层一些的PIL。但对于我们这个“画个卡通小人”的目标turtle库有三大不可替代的优势零依赖开箱即用turtle是Python标准库的一部分只要你安装了Python无论是3.8还是更早版本就可以直接import turtle使用无需任何额外的pip install步骤。这避免了初学者在环境配置上可能遇到的各种坑让注意力完全集中在编码逻辑本身。符合直觉的“画笔”模型turtle的编程范式非常直观。你想象自己控制着一支画笔在库中体现为一个三角形的“海龟”图标你可以命令它前进forward、后退backward、左转left、右转right、抬笔penup和落笔pendown。这种模型与我们在纸上画画的思维过程高度一致极大地降低了学习门槛。教育性与趣味性结合turtle库最初就是为了向儿童介绍编程概念而设计的。它通过即时可视化的反馈让抽象的代码逻辑变得具体可见。画《Among Us》角色这种带有游戏和流行文化色彩的项目能极大地激发学习兴趣让编程练习不再枯燥。注意虽然turtle易于上手但它并不适合绘制复杂、高性能的动画或图形界面应用。它的主要优势在于教学和简单的矢量图形创作。对于这个项目它绰绰有余。2.2 角色造型的几何拆解在动笔码之前我们需要像雕塑家观察原石一样把目标角色分解成最基本的几何形状。这是将视觉目标转化为编程指令的关键一步。观察一个标准的《Among Us》红色船员Red Crewmate身体主体是一个横向的椭圆或者可以近似看作一个被压扁的圆形。在turtle中我们可以通过画一个圆的同时进行横向拉伸或者用多边形模拟来实现。背包身体背部的一个竖立的椭圆或圆角矩形代表氧气背包。面罩身体上半部分的一个透明区域通常用一个与身体同背景色但略浅的椭圆或圆形来表示并在其中画出护目镜。护目镜面罩上的两个圆形镜片。腿部身体底部的两个矩形或梯形短柱。细节背包上的条纹、身体上的标志等可以用矩形和线段来补充。通过这样的拆解一个复杂的卡通形象就变成了一系列turtle可以轻松绘制的circle(),forward()left()组合用于画矩形等基本命令的集合。我们的编程任务就变成了安排这些基本图形以正确的顺序、在正确的位置上被绘制出来。3. 环境准备与基础绘图技巧3.1 初始化你的画布与画笔万事开头难但初始化画布和画笔却很简单。我们首先导入库并设置好绘图环境。import turtle # 1. 创建画布和画笔 screen turtle.Screen() screen.title(Among Us Crewmate - Drawn by Python Turtle) screen.bgcolor(white) # 设置背景为白色 pen turtle.Turtle() pen.speed(0) # 设置最快的绘图速度0是最高速 pen.hideturtle() # 隐藏画笔的三角形图标我们只关心画出的线条这里有几个关键点turtle.Screen()创建了一个绘图窗口。你可以通过它设置窗口标题、背景色、大小等。pen.speed(0)将画笔速度设为最快。在调试阶段你可以设为较慢速度如3或6来观察绘图过程但最终成品为了快速显示建议设为0。pen.hideturtle()这个命令非常重要。默认情况下画笔是一个小三角形箭头它会随着绘图移动。隐藏它之后画布上就只留下我们绘制的图形画面更干净。3.2 必须掌握的Turtle核心指令在开始画复杂图形前务必熟练掌握这几个最核心的命令它们是你的“画笔基本功”移动与转向pen.forward(distance)/pen.fd(distance)向画笔当前方向前进指定像素距离。pen.backward(distance)/pen.bd(distance)向画笔当前方向的反方向后退。pen.left(angle)/pen.lt(angle)画笔原地左转一定角度单位是度。pen.right(angle)/pen.rt(angle)画笔原地右转一定角度。笔的控制pen.penup()/pen.pu()抬起笔。移动时不会留下痕迹。用于移动到新的起始点而不画线。pen.pendown()/pen.pd()落下笔。移动时会画线。pen.pensize(width)设置画笔线条的粗细。pen.pencolor(color_string)设置画笔颜色。颜色可以用字符串如red,#FF0000表示。填充控制pen.fillcolor(color_string)设置图形填充颜色。pen.begin_fill()在绘制一个封闭图形前调用表示开始记录形状以进行填充。pen.end_fill()在封闭图形绘制完成后调用会用fillcolor设置的颜色填充刚才记录的封闭区域。绘制基本图形pen.circle(radius, extentNone)画圆或圆弧。radius是半径extent是角度如90表示画四分之一圆。pen.dot(size, color)画一个实心圆点。理解画笔的初始状态至关重要画笔启动时位于画布中心(0,0)方向为0度即正东方向。所有的移动和转向都是相对于画笔当前的位置和方向的。这就是“相对路径”的概念。4. 分步绘制Among Us角色现在让我们把拆解好的几何部件用代码一个个组装起来。我将以一个经典的红色船员为例并假设我们从一个空白画布中心开始绘制身体。4.1 绘制身体主体椭圆turtle没有直接画椭圆的命令但我们可以用一个技巧画一个圆但在绘制过程中通过不等比的缩放来模拟椭圆。更简单实用的方法是用多边形来逼近一个椭圆。这里我采用一种常见且可控的方法通过循环画一个由许多短边组成的“圆”并在X轴或Y轴方向上进行压缩。def draw_body(pen, color, width_scale1.5): 绘制身体椭圆 Args: pen: 画笔对象 color: 身体颜色如 red width_scale: 宽度缩放因子1表示横向拉长 pen.penup() # 将画笔移动到身体椭圆的左下角起始点附近。我们计划从椭圆底部中点开始画。 pen.goto(0, -60) # 假设身体中心在(0,0)我们从底部下方一点开始画轮廓 pen.setheading(0) # 确保朝向东方 pen.pendown() pen.fillcolor(color) pen.begin_fill() pen.pencolor(color) # 方法通过画一个“压扁的圆”来模拟椭圆 # 我们画一个半径为60的圆但每前进一小段就通过计算调整轨迹来模拟横向拉伸 # 更直观的方法画一个由许多线段组成的椭圆路径 a 80 # 椭圆的半长轴横向半径 b 60 # 椭圆的半短轴纵向半径 pen.penup() pen.goto(-a, 0) # 从椭圆最左侧开始 pen.pendown() # 使用参数方程绘制椭圆x a * cos(θ), y b * sin(θ) # 我们将360度分成细小的步进来画 for i in range(361): # 0到360度 # 将角度转换为弧度 angle i * 3.14159265 / 180.0 x a * math.cos(angle) y b * math.sin(angle) pen.goto(x, y) pen.end_fill() # 画完后画笔停留在椭圆最右侧。我们把它移回一个方便的位置比如身体中心下方。 pen.penup() pen.goto(0, -60)实操心得上面的方法使用了椭圆的参数方程虽然精确但需要导入math库且代码稍复杂。对于初学者一个更简单粗暴的近似方法是用turtle画一个半径较大的圆但通过pen.setx()或pen.sety()在循环中不断微调位置来“拉扁”它。或者直接画一个非常扁长的多边形比如一个边数很多、长宽比很大的多边形。在追求效果和代码简洁性之间我通常选择“足够像”的近似法。例如你可以用pen.circle(60, steps50)画一个50边形来近似圆然后通过缩放画布来达到椭圆效果但这涉及screen的tracer和update对新手不友好。因此本项目采用上述参数方程法它一次讲清了原理。4.2 绘制背包与面罩背包是一个竖立的椭圆位于身体右侧从我们看屏幕的角度。面罩是身体上半部分的一个区域。def draw_backpack(pen, body_color): 绘制氧气背包 pen.penup() # 将画笔移动到身体右侧偏上的位置作为背包椭圆的中心 pen.goto(70, 20) pen.setheading(90) # 朝向北方开始画 pen.pendown() pen.fillcolor(body_color) # 背包通常和身体同色或略深 pen.begin_fill() pen.pencolor(body_color) # 画一个竖立的椭圆半长轴在Y轴半短轴在X轴 a_backpack 25 # Y轴半径高 b_backpack 15 # X轴半径宽 # 同样使用参数方程但交换x和y的计算并调整相位 for i in range(361): angle_rad i * 3.14159265 / 180.0 x b_backpack * math.sin(angle_rad) # 注意这里用sin让x随角度变化 y a_backpack * math.cos(angle_rad) # 用cos让y随角度变化 pen.goto(70 x, 20 y) # 以(70,20)为中心点 pen.end_fill() # 在背包上画几条装饰条纹深色矩形 stripe_color darkred pen.penup() pen.goto(55, 10) # 条纹左上角 pen.setheading(0) pen.pendown() pen.fillcolor(stripe_color) pen.begin_fill() for _ in range(2): pen.forward(30) # 条纹宽度 pen.right(90) pen.forward(5) # 条纹高度 pen.right(90) pen.end_fill() def draw_visor(pen): 绘制面罩区域一个浅色椭圆和护目镜 # 1. 绘制面罩底色一个比身体颜色浅的椭圆 pen.penup() pen.goto(0, 40) # 面罩中心大约在身体上半部分 pen.setheading(0) pen.pendown() pen.fillcolor(#E0E0E0) # 浅灰色模拟玻璃反光 pen.begin_fill() pen.pencolor(#E0E0E0) a_visor 50 b_visor 30 for i in range(361): angle_rad i * 3.14159265 / 180.0 x a_visor * math.cos(angle_rad) y b_visor * math.sin(angle_rad) pen.goto(x, 40 y) # 中心在(0,40) pen.end_fill() # 2. 绘制护目镜两个黑色的圆 pen.penup() # 左护目镜 pen.goto(-20, 50) pen.pendown() pen.fillcolor(black) pen.begin_fill() pen.circle(12) # 画一个半径为12的圆 pen.end_fill() pen.penup() # 右护目镜 pen.goto(20, 50) pen.pendown() pen.begin_fill() pen.circle(12) pen.end_fill()4.3 绘制腿部与最终整合腿部是两个简单的矩形从身体底部延伸出来。def draw_legs(pen, color): 绘制两条腿 leg_width 20 leg_height 30 # 左腿 pen.penup() pen.goto(-15, -60) # 从身体底部偏左开始 pen.setheading(270) # 朝下 pen.pendown() pen.fillcolor(color) pen.begin_fill() for _ in range(2): pen.forward(leg_height) pen.right(90) pen.forward(leg_width) pen.right(90) pen.end_fill() # 右腿 pen.penup() pen.goto(5, -60) # 从身体底部偏右开始注意与左腿有间隙 pen.setheading(270) pen.pendown() pen.begin_fill() for _ in range(2): pen.forward(leg_height) pen.right(90) pen.forward(leg_width) pen.right(90) pen.end_fill() # 主函数整合所有部件 def main(): import math # 因为draw_body等函数用到了math screen turtle.Screen() screen.bgcolor(white) artist turtle.Turtle() artist.speed(0) artist.hideturtle() # 绘制顺序身体 - 背包 - 面罩 - 腿部 # 这样可以确保某些部件如背包覆盖在身体上而腿部在身体下方视觉上 # 但实际上因为我们是从空白画布画起先画身体作为底层背景更合理。 draw_body(artist, red) draw_backpack(artist, red) draw_visor(artist) draw_legs(artist, red) # 绘制完成后保持窗口打开 turtle.done() if __name__ __main__: main()将以上所有函数代码按顺序整合到一个.py文件中运行后你就能看到一个由Python自动绘制的红色Among Us角色了。5. 代码优化与高级技巧上面的代码能够工作但它是“过程式”的所有绘图指令和坐标都硬编码在函数里。这不利于复用和修改。比如你想画一个蓝色的船员在另一个位置就需要大量修改坐标。我们来优化它。5.1 使用面向对象与参数化我们可以创建一个Crewmate类将颜色、位置、大小作为属性绘制方法作为类的方法。这样创建多个不同颜色、不同位置的船员就非常容易。import turtle import math class Crewmate: def __init__(self, colorred, x0, y0, scale1.0): 初始化船员属性 Args: color: 主体颜色 x, y: 绘制中心坐标 scale: 缩放比例1.0为原始大小 self.color color self.x x self.y y self.scale scale self.pen turtle.Turtle() self.pen.speed(0) self.pen.hideturtle() def draw_ellipse(self, a, b, center_x, center_y, color, fillTrue): 绘制一个椭圆的通用函数 self.pen.penup() # 移动到椭圆最左侧的点 self.pen.goto(center_x - a, center_y) self.pen.setheading(0) if fill: self.pen.fillcolor(color) self.pen.begin_fill() self.pen.pencolor(color) self.pen.pendown() # 应用缩放 a_scaled a * self.scale b_scaled b * self.scale center_x_scaled self.x center_x * self.scale center_y_scaled self.y center_y * self.scale for i in range(361): angle_rad i * math.pi / 180.0 dx a_scaled * math.cos(angle_rad) dy b_scaled * math.sin(angle_rad) self.pen.goto(center_x_scaled dx, center_y_scaled dy) if fill: self.pen.end_fill() self.pen.penup() def draw_rectangle(self, start_x, start_y, width, height, color): 绘制一个矩形填充 self.pen.penup() self.pen.goto(self.x start_x * self.scale, self.y start_y * self.scale) self.pen.setheading(0) self.pen.pendown() self.pen.fillcolor(color) self.pen.begin_fill() self.pen.pencolor(color) for _ in range(2): self.pen.forward(width * self.scale) self.pen.right(90) self.pen.forward(height * self.scale) self.pen.right(90) self.pen.end_fill() self.pen.penup() def draw(self): 绘制完整的船员 # 身体 self.draw_ellipse(a80, b60, center_x0, center_y0, colorself.color) # 背包 self.draw_ellipse(a15, b25, center_x70, center_y20, colorself.color) # 注意a,b交换了 # 背包条纹 self.draw_rectangle(start_x55, start_y10, width30, height5, colordarkred) # 面罩底色 self.draw_ellipse(a50, b30, center_x0, center_y40, color#E0E0E0) # 护目镜用画圆函数更简单 self.pen.penup() self.pen.goto(self.x (-20 * self.scale), self.y (50 * self.scale)) self.pen.pendown() self.pen.fillcolor(black) self.pen.begin_fill() self.pen.circle(12 * self.scale) self.pen.end_fill() self.pen.penup() self.pen.goto(self.x (20 * self.scale), self.y (50 * self.scale)) self.pen.pendown() self.pen.begin_fill() self.pen.circle(12 * self.scale) self.pen.end_fill() # 腿部 self.draw_rectangle(start_x-15, start_y-60, width20, height30, colorself.color) self.draw_rectangle(start_x5, start_y-60, width20, height30, colorself.color) # 使用类来创建多个船员 def main(): screen turtle.Screen() screen.bgcolor(white) # 创建一个红色船员在默认位置(0,0) red_crew Crewmate(colorred, x0, y0, scale1.0) red_crew.draw() # 创建一个蓝色船员在右边 blue_crew Crewmate(colorblue, x200, y0, scale0.8) blue_crew.draw() # 创建一个绿色船员在左边更大一些 green_crew Crewmate(colorgreen, x-200, y0, scale1.2) green_crew.draw() turtle.done()通过面向对象重构代码结构清晰多了。要新增一个船员只需要创建一个新的Crewmate实例并调用draw()方法。5.2 利用循环与函数减少重复代码即使在类中我们也能看到一些重复模式。例如画两个护目镜的代码几乎相同。我们可以用一个循环来处理# 优化后的护目镜绘制 goggle_positions [(-20, 50), (20, 50)] # 左右护目镜的中心偏移量 for dx, dy in goggle_positions: self.pen.penup() self.pen.goto(self.x (dx * self.scale), self.y (dy * self.scale)) self.pen.pendown() self.pen.begin_fill() self.pen.circle(12 * self.scale) self.pen.end_fill()同理腿部也可以用循环来画。这种“DRY”Don‘t Repeat Yourself原则能让代码更简洁也更容易修改比如想改变护目镜大小只需改一个地方。6. 常见问题与调试技巧实录在实际编写和运行过程中你几乎一定会遇到下面这些问题。这里是我踩过坑后总结的排查清单。6.1 图形位置错乱或偏移问题现象画出来的身体、背包、腿等部件没有组合在一起而是散落在画布各处。根本原因坐标系混乱。turtle的初始位置是(0,0)方向是0度东。每个绘图函数中的坐标如果没有考虑整体的偏移self.x, self.y和缩放self.scale或者pen.goto()的坐标计算错误就会导致错位。排查步骤打印坐标在关键的pen.goto()语句前用print(f”Moving to: {x}, {y}”)输出目标坐标检查计算逻辑是否正确。分步执行不要一次性运行全部代码。先注释掉所有部件只画身体椭圆看它是否出现在预期位置。然后取消背包的注释看背包是否相对于身体正确放置。逐步叠加能快速定位是哪个部件的坐标计算出了问题。理解相对与绝对确保你清楚每个函数内的坐标是相对于画布原点(0,0)还是相对于角色中心(self.x, self.y)。在面向对象的版本中所有坐标都应先加上self.x和self.y的偏移。6.2 填充颜色溢出或不填充问题现象图形边框颜色正确但内部没有填充色或者填充色填满了整个屏幕。根本原因begin_fill()和end_fill()没有正确配对或者绘制的路径不是封闭的。解决方案严格配对确保每个begin_fill()都有一个对应的end_fill()。最好使用上下文管理器但turtle原生不支持或仔细检查代码缩进。封闭路径用于填充的图形必须是一个封闭环。例如用goto画椭圆时要确保最后一个点精确地连接回第一个点。上面的参数方程法循环0到360度正好是闭合的。如果你用forward和left画多边形要确保最后转回起始方向并移动回起点或者直接用circle函数。抬笔干扰在begin_fill()和end_fill()之间尽量避免penup()。如果必须抬笔移动比如画两个分离的图形那么需要分别对每个封闭图形进行填充。6.3 绘图窗口一闪而过或立即关闭问题现象代码运行后图形窗口瞬间打开就关闭看不清结果。根本原因脚本执行完毕后Python进程结束其创建的所有窗口包括turtle窗口也会被关闭。标准解决方案在绘图代码的最后一定要加上turtle.done()或turtle.mainloop()。这两个函数会启动一个事件循环保持窗口打开直到用户手动关闭它。这是turtle绘图程序的“标准结尾”。在IDE中运行如果你在VSCode、PyCharm等IDE中运行有时即使加了turtle.done()子进程管理也可能导致问题。可以尝试在代码最后添加input(“Press Enter to exit…”)让程序等待一个输入从而保持窗口。6.4 绘图速度太慢问题现象画一个复杂的图形需要等待很久能看到画笔缓慢移动。优化方法设置最快速度在创建画笔后立即设置pen.speed(0)。这是最有效的一招。关闭动画使用screen.tracer(0)可以在绘制过程中完全关闭屏幕刷新等所有绘制指令执行完毕后再调用screen.update()一次性更新画面。这对绘制复杂图形或大量图形时提速明显。screen turtle.Screen() screen.tracer(0) # 关闭自动刷新 # … 所有的绘图代码 … screen.update() # 全部画完后一次性更新显示 turtle.done()减少不必要的绘制确保在移动画笔到新位置前如果不希望画线已经penup()。6.5 代码复用与维护困难问题现象想画第二个角色发现要复制粘贴大量代码并修改无数个坐标。最佳实践这就是为什么我们要在第二部分就引入面向对象编程OOP的思想。将船员抽象成一个类把颜色、位置、大小作为属性把绘制各个部件作为方法。这样创建新船员就是“实例化”一个新对象修改外观只需改动属性参数。代码的复用性和可读性会成倍提升。即使你刚开始学OOP这个项目也是一个绝佳的练习场景。当你成功画出第一个属于自己的Python版Among Us角色后可以尝试的扩展方向就太多了给他画上不同的帽子、宠物让多个角色排成队甚至尝试用turtle的onclick事件让角色可以交互……这些挑战会让你对Python和turtle库的理解更深。编程学习的乐趣就在于这种从无到有、将想法变为可视成果的创造过程。
返回列表