ساخت انیمیشن پیکاچو با پایتون Turtle

گرافیک و انیمیشن با پایتون

پروژه ۵ دقیقه مطالعه
فهرست مطالب
این مطلب از سایت tool.hamidvalad.ir گرفته شده است

معرفی

در این آموزش، نحوه ساخت انیمیشن کاراکتر پیکاچو (شخصیت محبوب پوکمون) را با استفاده از کتابخانه Turtle در پایتون یاد می‌گیرید. این پروژه شامل طراحی جزئیات کامل کاراکتر از جمله بدن، چشم‌ها، دهان، گونه‌های قرمز، گوش‌ها و دم می‌شود. کتابخانه Turtle ابزار قدرتمندی برای ایجاد گرافیک و انیمیشن‌های ساده در پایتون است.

طرح نهایی دورایمون

نصب و پیش‌نیازها

  • Python 3.8 - نسخه‌های 3.6 به بالا توصیه می‌شود
  • turtle - کتابخانه استاندارد پایتون برای گرافیک

کتابخانه Turtle برای ایجاد گرافیک برداری و انیمیشن‌های ساده طراحی شده و در این پروژه برای رسم تمام اجزای پیکاچو استفاده می‌شود.

ساختار پروژه

پروژه از یک کلاس اصلی به نام Pikachu تشکیل شده که شامل متدهای مختلف برای رسم هر بخش از کاراکتر است:

  • متد body - طراحی بدنه اصلی و پاها
  • متد leftEye و rightEye - طراحی چشم‌ها با جزئیات
  • متد mouth - طراحی دهان و زبان
  • متد leftCheek و rightCheek - طراحی گونه‌های قرمز
  • متد cap - طراحی کلاه و جزئیات اضافی
  • متد colorLeftEar و colorRightEar - طراحی گوش‌ها

کدنویسی پروژه

کلاس Pikachu با متدهای مختلف برای رسم هر بخش از کاراکتر:

Terminal
#pikachu animation using python-turtle
import turtle
def getPosition(x, y):
    turtle.setx(x)
    turtle.sety(y)
    print(x, y)


class Pikachu:
    def __init__(self):
        self.t = turtle.Turtle()
        t = self.t
        t.pensize(3)
        t.speed(9)
        t.ondrag(getPosition)

    def noTrace_goto(self, x, y):
        self.t.penup()
        self.t.goto(x, y)
        self.t.pendown()

    def leftEye(self, x, y):
        self.noTrace_goto(x, y)
        t = self.t
        t.seth(0)
        t.fillcolor('#333333')
        t.begin_fill()
        t.circle(22)
        t.end_fill()

        self.noTrace_goto(x, y+10)
        t.fillcolor('#000000')
        t.begin_fill()
        t.circle(10)
        t.end_fill()

        self.noTrace_goto(x+6, y + 22)
        t.fillcolor('#ffffff')
        t.begin_fill()
        t.circle(10)
        t.end_fill()

    def rightEye(self, x, y):
        self.noTrace_goto(x, y)
        t = self.t
        t.seth(0)
        t.fillcolor('#333333')
        t.begin_fill()
        t.circle(22)
        t.end_fill()

        self.noTrace_goto(x, y+10)
        t.fillcolor('#000000')
        t.begin_fill()
        t.circle(10)
        t.end_fill()

        self.noTrace_goto(x-6, y + 22)
        t.fillcolor('#ffffff')
        t.begin_fill()
        t.circle(10)
        t.end_fill()

    def mouth(self, x, y):
        self.noTrace_goto(x, y)
        t = self.t

        t.fillcolor('#88141D')
        t.begin_fill()
        # lower lip
        l1 = []
        l2 = []
        t.seth(190)
        a = 0.7
        for i in range(28):
            a += 0.1
            t.right(3)
            t.fd(a)
            l1.append(t.position())

        self.noTrace_goto(x, y)

        t.seth(10)
        a = 0.7
        for i in range(28):
            a += 0.1
            t.left(3)
            t.fd(a)
            l2.append(t.position())

        # Upper lip
        t.seth(10)
        t.circle(50, 15)
        t.left(180)
        t.circle(-50, 15)

        t.circle(-50, 40)
        t.seth(233)
        t.circle(-50, 55)
        t.left(180)
        t.circle(50, 12.1)
        t.end_fill()

        # tongue
        self.noTrace_goto(17, 54)
        t.fillcolor('#DD716F')
        t.begin_fill()
        t.seth(145)
        t.circle(40, 86)
        t.penup()
        for pos in reversed(l1[:20]):
            t.goto(pos[0], pos[1]+1.5)
        for pos in l2[:20]:
            t.goto(pos[0], pos[1]+1.5)
        t.pendown()
        t.end_fill()

        # nose
        self.noTrace_goto(-17, 94)
        t.seth(8)
        t.fd(4)
        t.back(8)

    # Red cheeks
    def leftCheek(self, x, y):
        turtle.tracer(False)
        t = self.t
        self.noTrace_goto(x, y)
        t.seth(300)
        t.fillcolor('#DD4D28')
        t.begin_fill()
        a = 2.3
        for i in range(120):
            if 0 <= i < 30 or 60 <= i < 90:
                a -= 0.05
                t.lt(3)
                t.fd(a)
            else:
                a += 0.05
                t.lt(3)
                t.fd(a)
        t.end_fill()
        turtle.tracer(True)

    def rightCheek(self, x, y):
        t = self.t
        turtle.tracer(False)
        self.noTrace_goto(x, y)
        t.seth(60)
        t.fillcolor('#DD4D28')
        t.begin_fill()
        a = 2.3
        for i in range(120):
            if 0 <= i < 30 or 60 <= i < 90:
                a -= 0.05
                t.lt(3)
                t.fd(a)
            else:
                a += 0.05
                t.lt(3)
                t.fd(a)
        t.end_fill()
        turtle.tracer(True)

    def colorLeftEar(self, x, y):
        t = self.t
        self.noTrace_goto(x, y)
        t.fillcolor('#000000')
        t.begin_fill()
        t.seth(330)
        t.circle(100, 35)
        t.seth(219)
        t.circle(-300, 19)
        t.seth(110)
        t.circle(-30, 50)
        t.circle(-300, 10)
        t.end_fill()

    def colorRightEar(self, x, y):
        t = self.t
        self.noTrace_goto(x, y)
        t.fillcolor('#000000')
        t.begin_fill()
        t.seth(300)
        t.circle(-100, 30)
        t.seth(35)
        t.circle(300, 15)
        t.circle(30, 50)
        t.seth(190)
        t.circle(300, 17)
        t.end_fill()

    def body(self):
        t = self.t

        t.fillcolor('#F6D02F')
        t.begin_fill()
        # Right face Contour
        t.penup()
        t.circle(130, 40)
        t.pendown()
        t.circle(100, 105)
        t.left(180)
        t.circle(-100, 5)

        # Right ear
        t.seth(20)
        t.circle(300, 30)
        t.circle(30, 50)
        t.seth(190)
        t.circle(300, 36)

        # upper profile
        t.seth(150)
        t.circle(150, 70)

        # left ear
        t.seth(200)
        t.circle(300, 40)
        t.circle(30, 50)
        t.seth(20)
        t.circle(300, 35)
        # print(t.pos())

        # Left face contour
        t.seth(240)
        t.circle(105, 95)
        t.left(180)
        t.circle(-105, 5)

        # left hand
        t.seth(210)
        t.circle(500, 18)
        t.seth(200)
        t.fd(10)
        t.seth(280)
        t.fd(7)
        t.seth(210)
        t.fd(10)
        t.seth(300)
        t.circle(10, 80)
        t.seth(220)
        t.fd(10)
        t.seth(300)
        t.circle(10, 80)
        t.seth(240)
        t.fd(12)
        t.seth(0)
        t.fd(13)
        t.seth(240)
        t.circle(10, 70)
        t.seth(10)
        t.circle(10, 70)
        t.seth(10)
        t.circle(300, 18)

        t.seth(75)
        t.circle(500, 8)
        t.left(180)
        t.circle(-500, 15)
        t.seth(250)
        t.circle(100, 65)

        # left foot
        t.seth(320)
        t.circle(100, 5)
        t.left(180)
        t.circle(-100, 5)
        t.seth(220)
        t.circle(200, 20)
        t.circle(20, 70)

        t.seth(60)
        t.circle(-100, 20)
        t.left(180)
        t.circle(100, 20)
        t.seth(300)
        t.circle(10, 70)

        t.seth(60)
        t.circle(-100, 20)
        t.left(180)
        t.circle(100, 20)
        t.seth(10)
        t.circle(100, 60)

    
        t.seth(180)
        t.circle(-100, 10)
        t.left(180)
        t.circle(100, 10)
        t.seth(5)
        t.circle(100, 10)
        t.circle(-100, 40)
        t.circle(100, 35)
        t.left(180)
        t.circle(-100, 10)

        # Right foot
        t.seth(290)
        t.circle(100, 55)
        t.circle(10, 50)

        t.seth(120)
        t.circle(100, 20)
        t.left(180)
        t.circle(-100, 20)

        t.seth(0)
        t.circle(10, 50)

        t.seth(110)
        t.circle(100, 20)
        t.left(180)
        t.circle(-100, 20)

        t.seth(30)
        t.circle(20, 50)

        t.seth(100)
        t.circle(100, 40)

        # Right body contour
        t.seth(200)
        t.circle(-100, 5)
        t.left(180)
        t.circle(100, 5)
        t.left(30)
        t.circle(100, 75)
        t.right(15)
        t.circle(-300, 21)
        t.left(180)
        t.circle(300, 3)

        # Right hand
        t.seth(43)
        t.circle(200, 60)

        t.right(10)
        t.fd(10)

        t.circle(5, 160)
        t.seth(90)
        t.circle(5, 160)
        t.seth(90)

        t.fd(10)
        t.seth(90)
        t.circle(5, 180)
        t.fd(10)

        t.left(180)
        t.left(20)
        t.fd(10)
        t.circle(5, 170)
        t.fd(10)
        t.seth(240)
        t.circle(50, 30)

        t.end_fill()
        self.noTrace_goto(130, 125)
        t.seth(-20)
        t.fd(5)
        t.circle(-5, 160)
        t.fd(5)

        # Fingerprint
        self.noTrace_goto(166, 130)
        t.seth(-90)
        t.fd(3)
        t.circle(-4, 180)
        t.fd(3)
        t.seth(-90)
        t.fd(3)
        t.circle(-4, 180)
        t.fd(3)

        # tail
        self.noTrace_goto(168, 134)
        t.fillcolor('#F6D02F')
        t.begin_fill()
        t.seth(40)
        t.fd(200)
        t.seth(-80)
        t.fd(150)
        t.seth(210)
        t.fd(150)
        t.left(90)
        t.fd(100)
        t.right(95)
        t.fd(100)
        t.left(110)
        t.fd(70)
        t.right(110)
        t.fd(80)
        t.left(110)
        t.fd(30)
        t.right(110)
        t.fd(32)

        t.right(106)
        t.circle(100, 25)
        t.right(15)
        t.circle(-300, 2)
        t.seth(30)
        t.fd(40)
        t.left(100)
        t.fd(70)
        t.right(100)
        t.fd(80)
        t.left(100)
        t.fd(46)
        t.seth(66)
        t.circle(200, 38)
        t.right(10)
        t.fd(10)
        t.end_fill()

        # Tail pattern
        t.fillcolor('#923E24')
        self.noTrace_goto(126.82, -156.84)
        t.begin_fill()

        t.seth(30)
        t.fd(40)
        t.left(100)
        t.fd(40)
        t.pencolor('#923e24')
        t.seth(-30)
        t.fd(30)
        t.left(140)
        t.fd(20)
        t.right(150)
        t.fd(20)
        t.left(150)
        t.fd(20)
        t.right(150)
        t.fd(20)
        t.left(130)
        t.fd(18)
        t.pencolor('#000000')
        t.seth(-45)
        t.fd(67)
        t.right(110)
        t.fd(80)
        t.left(110)
        t.fd(30)
        t.right(110)
        t.fd(32)
        t.right(106)
        t.circle(100, 25)
        t.right(15)
        t.circle(-300, 2)
        t.end_fill()

        # Hat, eyes, mouth, cheeks
        self.cap(-134.07, 147.81)
        self.mouth(-5, 25)
        self.leftCheek(-126, 32)
        self.rightCheek(107, 63)
        self.colorLeftEar(-250, 100)
        self.colorRightEar(140, 270)
        self.leftEye(-85, 90)
        self.rightEye(50, 110)
        t.hideturtle()

    def cap(self, x, y):
        self.noTrace_goto(x, y)
        t = self.t
        t.fillcolor('#CD0000')
        t.begin_fill()
        t.seth(200)
        t.circle(400, 7)
        t.left(180)
        t.circle(-400, 30)
        t.circle(30, 60)
        t.fd(50)
        t.circle(30, 45)
        t.fd(60)
        t.left(5)
        t.circle(30, 70)
        t.right(20)
        t.circle(200, 70)
        t.circle(30, 60)
        t.fd(70)
        t.right(35)
        t.fd(50)
        t.circle(8, 100)
        t.end_fill()
        self.noTrace_goto(-168.47, 185.52)
        t.seth(36)
        t.circle(-270, 54)
        t.left(180)
        t.circle(270, 27)
        t.circle(-80, 98)

        t.fillcolor('#444444')
        t.begin_fill()
        t.left(180)
        t.circle(80, 197)
        t.left(58)
        t.circle(200, 45)
        t.end_fill()

        self.noTrace_goto(-58, 270)
        t.pencolor('#228B22')
        t.dot(35)

        self.noTrace_goto(-30, 280)
        t.fillcolor('#228B22')
        t.begin_fill()
        t.seth(100)
        t.circle(30, 180)
        t.seth(190)
        t.fd(15)
        t.seth(100)
        t.circle(-45, 180)
        t.right(90)
        t.fd(15)
        t.end_fill()
        t.pencolor('#000000')

    def start(self):
        self.body()


def main():
    print('Started Painting the Pikachu... ')
    turtle.screensize(800, 600)
    turtle.title('Pikachu')
    pikachu = Pikachu()
    pikachu.start()
    turtle.mainloop()


if __name__ == '__main__':
    main()

مقداردهی اولیه

در متد __init__، شیء Turtle تنظیم می‌شود:

def __init__(self):
    self.t = turtle.Turtle()
    t = self.t
    t.pensize(3)
    t.speed(9)
    t.ondrag(getPosition)

متد noTrace_goto

برای حرکت بدون رسم خط استفاده می‌شود:

def noTrace_goto(self, x, y):
    self.t.penup()
    self.t.goto(x, y)
    self.t.pendown()

طراحی چشم‌ها

هر چشم شامل سه لایه: پایه خاکستری، مردمک سیاه و برق سفید:

def leftEye(self, x, y):
    self.noTrace_goto(x, y)
    t = self.t
    t.seth(0)
    t.fillcolor('#333333')
    t.begin_fill()
    t.circle(22)
    t.end_fill()

جزئیات فنی پروژه

ساختار کلی کلاس Pikachu

کلاس Pikachu شامل متدهای متعددی است که هر کدام مسئول رسم بخش خاصی از کاراکتر هستند. این رویکرد ماژولار امکان مدیریت و تغییر آسان هر بخش را فراهم می‌کند.

الگوریتم‌های استفاده شده:

  • الگوریتم تغییر اندازه پویا: در رسم گونه‌ها برای ایجاد انحناهای طبیعی
  • ترسیم با کمان‌های متوالی: برای ایجاد خطوط منحنی نرم
  • ذخیره و بازیابی موقعیت‌ها: برای رسم دقیق‌تر بخش‌های متصل
  • بهینه‌سازی با turtle.tracer: برای افزایش سرعت رسم بخش‌های پیچیده

رنگ‌های استفاده شده:

  • بدن: #F6D02F (زرد اصلی)
  • چشم‌ها: #333333 (خاکستری)
  • گونه‌ها: #DD4D28 (نارنجی-قرمز)
  • دهان: #88141D (قرمز تیره)

نکات بهینه‌سازی

  • استفاده از turtle.tracer(False) در بخش‌های پیچیده برای افزایش سرعت
  • ذخیره موقعیت‌ها در لیست برای استفاده مجدد
  • استفاده از متد noTrace_goto برای حرکت‌های بدون رسم
  • تنظیم سرعت مناسب با speed(9) برای تعادل بین سرعت و مشاهده

تکنیک‌های پیشرفته طراحی

مدیریت مختصات و موقعیت‌یابی

در این پروژه از سیستم مختصات دکارتی استفاده شده است. نقطه (0,0) مرکز صفحه است:

  • محور X: مثبت به سمت راست، منفی به سمت چپ
  • محور Y: مثبت به سمت بالا، منفی به سمت پایین
  • زاویه‌ها: بر حسب درجه، 0 درجه به سمت راست

کنترل جهت با متد seth (setheading)

متد seth جهت لاک‌پشت را بر اساس درجه تنظیم می‌کند:

t.seth(0)    # شرق (راست)
t.seth(90)   # شمال (بالا)
t.seth(180)  # غرب (چپ)
t.seth(270)  # جنوب (پایین)
t.seth(45)   # شمال شرقی

ترسیم منحنی‌های پیچیده با circle

متد circle برای رسم دایره و کمان استفاده می‌شود:

t.circle(100)        # دایره کامل با شعاع 100
t.circle(100, 90)   # کمان 90 درجه با شعاع 100
t.circle(100, 180)  # کمان 180 درجه (نیم دایره)
t.circle(-100, 90)  # کمان در جهت عقربه‌های ساعت

مدیریت پر کردن اشکال

برای پر کردن اشکال با رنگ از سه متد استفاده می‌شود:

t.fillcolor('#F6D02F')  # تنظیم رنگ پر کردن
t.begin_fill()         # شروع ناحیه پر شدن
# رسم شکل مورد نظر
t.end_fill()           # پایان ناحیه و پر کردن

بهینه‌سازی عملکرد با turtle.tracer

برای افزایش سرعت در بخش‌های پیچیده:

turtle.tracer(False)  # غیرفعال کردن نمایش لحظه‌ای
# رسم بخش‌های پیچیده
turtle.tracer(True)   # فعال کردن نمایش و به روزرسانی

امکانات توسعه و گسترش

ایده‌های برای توسعه پروژه:

  • افزایش انیمیشن: اضافه کردن پلک زدن، حرکت دهان و تغییر حالت چهره
  • تعامل با کاربر: واکنش به کلیک ماوس یا فشار کلید
  • صداگذاری: اضافه کردن افکت‌های صوتی با کتابخانه pygame
  • چندکاراکتری: رسم سایر پوکمون‌ها با ساختار مشابه
  • ذخیره سازی: قابلیت ذخیره تصویر نهایی در فرمت‌های مختلف
  • کنترل حرکتی: اضافه کردن کنترل‌کننده برای حرکت کاراکتر

مهارت‌های آموزشی این پروژه:

  • درک عمیق از برنامه‌نویسی شی‌گرا (OOP)
  • کار با سیستم مختصات و هندسه محاسباتی
  • مدیریت رنگ و طراحی گرافیکی
  • بهینه‌سازی کد برای عملکرد بهتر
  • مدیریت پروژه‌های گرافیکی پیچیده
  • دیباگ و عیب‌یابی در برنامه‌نویسی گرافیکی

سیستم رنگ‌ها

در این پروژه از کدهای HEX برای رنگ‌ها استفاده شده است:

  • #F6D02F - زرد برای بدن پیکاچو
  • #333333 - خاکستری تیره برای پایه چشم‌ها
  • #000000 - سیاه برای مردمک چشم
  • #88141D - قرمز برای دهان
  • #DD4D28 - نارنجی برای گونه‌ها
  • #DD716F - صورتی برای زبان
  • #CD0000 - قرمز برای کلاه
  • #228B22 - سبز برای دکمه کلاه

نحوه اجرا

برای اجرای پروژه مراحل زیر را دنبال کنید:

  1. کد را در فایلی با نام pikachu.py ذخیره کنید
  2. ترمینال را در مسیر فایل باز کنید
  3. دستور زیر را اجرا کنید: python pikachu.py
  4. پنجره Turtle باز شده و طراحی پیکاچو شروع می‌شود
  5. برای بستن پنجره، روی ضرب‌در (X) کلیک کنید

پروسه طراحی ممکن است چند ثانیه طول بکشد چون هر بخش با دقت رسم می‌شود.

توسعه و بهبود

برای توسعه این پروژه می‌توانید:

  • انیمیشن حرکتی به کاراکتر اضافه کنید
  • صداهای پیکاچو را به پروژه اضافه کنید
  • کاراکترهای دیگر پوکمون را طراحی کنید
  • کنترل‌های تعاملی برای حرکت کاراکتر اضافه کنید
  • پس‌زمینه‌های مختلف ایجاد کنید

منابع و مستندات

  • Python Documentation
  • Tutle Documentation
  • رنگ‌های HEX: https://htmlcolorcodes.com/