ساخت بازی دایناسور گوگل کروم با پایتون

?How to Build Google Chrome Dinosaur Game with Python

12 اردیبهشت 1401
Google-Chrome-Dinosaur-Game

پیش گفتار

بازی دایناسور (همچنین به عنوان دایناسور کروم نیز شناخته می شود) یک بازی مرورگر است که توسط گوگل توسعه یافته و در مرورگر وب گوگل کروم ساخته شده است. بازیکن یک تیرکس (Trex) پیکسلی را در یک اسکرول جانبی هدایت می کند و باید بکوشد تا تیرکس به موانع برخورد نکند امتیاز بالاتر را به دست دآورد. این بازی توسط اعضای تیم Chrome UX در سال 2014 ساخته شد.

هنگامی که کاربر آفلاین است و سعی می کند یک صفحه وب در Google Chrome را باز کند، مرورگر به کاربر اطلاع می دهد که به اینترنت متصل نیست، و سپس بازی تیرکس را روی صفحه نمایش می دهد. بازی را می‌توان با فشار دادن Space راه اندازی کرد. علاوه بر این، با وارد کردن chrome://dino یا chrome://network-error/-106 در Omnibox می‌توانید به بازی دسترسی پیدا کنید.

در طول بازی، تیرکس به طور مداوم از چپ به راست در یک منظره بیابانی سیاه و سفید حرکت می کند، و بازیکن باید از روی موانع پیش رو مانند کاکتوس ها و دایناسورهای پرنده بپرد. فشار دادن space، باعث می شود دایناسور «پرش» کند، در حالی که فشار دادن کلید ↓ باعث خم شدن دایناسور می شود. با پیشرفت بازی، سرعت بازی به تدریج افزایش می‌یابد تا زمانی که کاربر به مانعی برخورد کند و باعث می‌شود بازی تمام شود.

این بازی توسط اعضای تیم Chrome UX در سال 2014 ساخته شد که متشکل از سباستین گابریل، آلن بتس و ادوارد یونگ بود. توسعه دهندگان بازی دایناسور را به این علت طراحی کردند که یک شوخی باشد و متصل نبودن اینترنت به «عصر ماقبل تاریخ» اشاره کند! این بازی در سپتامبر 2014 منتشر شد. در ابتدا، روی دستگاه‌های قدیمی‌تر کار نمی‌کرد، بنابراین کد به‌روزرسانی شد و در دسامبر همان سال دوباره منتشر شد.

در این جا قرار است که این بازی را نه بلکه برای مرورگر برای دسکتاپ خود بسازیم. این بازی با استفاده از python و pygame نوشته خواهد شد. برای ساخت این بازی این مقاله را تا آخر بخوانید.تصویر این بازی در زیر آمده است:

پیش نیازها

برای ساخت این بازی به دانستن python و pygame نیاز داریم. همچنین باید python و pygame را در سیستم خود نصب کرده باشید. به یک ویرایشگر کد هم برای نوشتن و اجرای برنامه نیاز داریم. اگر با pygame آشنایی ندارید می توانید آن را از این نشانی فرابگیرید.

در ادامه با دو فایل objects.py و main.py نیاز داریم. فایل objects.py برای ساخت اشیا برنامه به کار می رود. این بازی به صورت شی گرا نوشته می شود. فایل main.py فایل اصلی برنامه است و برای اجرا شدن برنامه این فایل را باید run یا راه اندازی کنیم.

در این بازی از تصاویر و صداهایی گوناگون استفاده می شود.این تصاویر و صداها باید در پوشه های مخصوص در کنار فایل main.py قرار گیرند، مانند تصویر زیر:

کد کامل این بازی و همچنین فایل های مورد نیاز برای ساخت این بازی در این نشانی قرار دارد.

فایل objects.py

همان طور که قبلا هم گفتم این فایل برای ساخت اشیا گوناگون برای موجودیت های مختلف در بازی به کار می رود. برای زمین کلاس Ground، برای ابر Cloud، برای دایناسور Dino، برای ستاره Star، برای دایناسور پرنده Ptera و برای کاکتوس کلاس Cactus را داریم.  کد کامل در زیر آمده است.

import pygame

SCREEN = WIDTH, HEIGHT = (1000, 500)

class Ground():
    def __init__(self):
        self.image = pygame.image.load('Assets/ground.png')
        self.rect = self.image.get_rect()

        self.width = self.image.get_width()
        self.x1 = 0
        self.x2 = self.width
        self.y = 250

    def update(self, speed):
        self.x1 -= speed
        self.x2 -= speed

        if self.x1 <= -self.width:
            self.x1 = self.width

        if self.x2 <= -self.width:
            self.x2 = self.width

    def draw(self, win):
        win.blit(self.image, (self.x1, self.y))
        win.blit(self.image, (self.x2, self.y))


class Dino():
    def __init__(self, x, y):
        self.x, self.base = x, y

        self.run_list = []
        self.duck_list = []

        for i in range(1, 4):
            img = pygame.image.load(f'Assets/Dino/{i}.png')
            img = pygame.transform.scale(img, (52, 58))
            self.run_list.append(img)

        for i in range(4, 6):
            img = pygame.image.load(f'Assets/Dino/{i}.png')
            img = pygame.transform.scale(img, (70, 38))
            self.duck_list.append(img)

        self.dead_image = pygame.image.load(f'Assets/Dino/8.png')
        self.dead_image = pygame.transform.scale(self.dead_image, (52,58))

        self.reset()

        self.vel = 0
        self.gravity = 1
        self.jumpHeight = 15
        self.isJumping = False

    def reset(self):
        self.index = 0
        self.image = self.run_list[self.index]
        self.rect = self.image.get_rect()
        self.rect.x = self.x
        self.rect.bottom = self.base

        self.alive = True
        self.counter = 0

    def update(self, jump, duck):
        if self.alive:
            if not self.isJumping and jump:
                self.vel = -self.jumpHeight
                self.isJumping = True

            self.vel += self.gravity
            if self.vel >= self.jumpHeight:
                self.vel = self.jumpHeight

            self.rect.y += self.vel
            if self.rect.bottom > self.base:
                self.rect.bottom = self.base
                self.isJumping = False

            if duck:
                self.counter += 1
                if self.counter >= 6:
                    self.index = (self.index + 1) % len(self.duck_list)
                    self.image = self.duck_list[self.index]
                    self.rect = self.image.get_rect()
                    self.rect.x = self.x
                    self.rect.bottom = self.base
                    self.counter = 0

            elif self.isJumping:
                self.index = 0
                self.counter = 0
                self.image = self.run_list[self.index]
            else:
                self.counter += 1
                if self.counter >= 4:
                    self.index = (self.index + 1) % len(self.run_list)
                    self.image = self.run_list[self.index]
                    self.rect = self.image.get_rect()
                    self.rect.x = self.x
                    self.rect.bottom = self.base
                    self.counter = 0

            self.mask = pygame.mask.from_surface(self.image)

        else:
            self.image = self.dead_image

    def draw(self, win):
        win.blit(self.image, self.rect)

class Cactus(pygame.sprite.Sprite):
    def __init__(self, type):
        super(Cactus, self).__init__()

        self.image_list = []
        for i in range(5):
            scale = 0.65
            img = pygame.image.load(f'Assets/Cactus/{i+1}.png')
            w, h = img.get_size()
            img = pygame.transform.scale(img, (int(w*scale), int(h*scale)))
            self.image_list.append(img)

        self.image = self.image_list[type-1]
        self.rect = self.image.get_rect()
        self.rect.x = WIDTH + 10
        self.rect.bottom = 260

    def update(self, speed, dino):
        if dino.alive:
            self.rect.x -= speed
            if self.rect.right <= 0:
                self.kill()

            self.mask = pygame.mask.from_surface(self.image)

    def draw(self, win):
        win.blit(self.image, self.rect)

class Ptera(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super(Ptera, self).__init__()

        self.image_list = []
        for i in range(2):
            scale = 0.65
            img = pygame.image.load(f'Assets/Ptera/{i+1}.png')
            w, h = img.get_size()
            img = pygame.transform.scale(img, (int(w*scale), int(h*scale)))
            self.image_list.append(img)

        self.index = 0
        self.image = self.image_list[self.index]
        self.rect = self.image.get_rect(center=(x, y))

        self.counter = 0

    def update(self, speed, dino):
        if dino.alive:
            self.rect.x -= speed
            if self.rect.right <= 0:
                self.kill()

            self.counter += 1
            if self.counter >= 6:
                self.index = (self.index + 1) % len(self.image_list)
                self.image = self.image_list[self.index]
                self.counter = 0

            self.mask = pygame.mask.from_surface(self.image)

    def draw(self, win):
        win.blit(self.image, self.rect)


class Cloud(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super(Cloud, self).__init__()
        self.image = pygame.image.load(f'Assets/cloud.png')
        self.image = pygame.transform.scale(self.image, (60, 18))
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def update(self, speed, dino):
        if dino.alive:
            self.rect.x -= speed
            if self.rect.right <= 0:
                self.kill()

    def draw(self, win):
        win.blit(self.image, self.rect)

class Star(pygame.sprite.Sprite):
    def __init__(self, x, y, type):
        super(Star, self).__init__()
        image = pygame.image.load(f'Assets/stars.png')
        self.image_list = []
        for i in range(3):
            img = image.subsurface((0, 20*(i), 18, 18))
            self.image_list.append(img)
        self.image = self.image_list[type-1]
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def update(self, speed, dino):
        if dino.alive:
            self.rect.x -= speed
            if self.rect.right <= 0:
                self.kill()

    def draw(self, win):
        win.blit(self.image, self.rect)

کلاس Ground

class Ground():
    def __init__(self):
        self.image = pygame.image.load('Assets/ground.png')
        self.rect = self.image.get_rect()

        self.width = self.image.get_width()
        self.x1 = 0
        self.x2 = self.width
        self.y = 250

    def update(self, speed):
        self.x1 -= speed
        self.x2 -= speed

        if self.x1 <= -self.width:
            self.x1 = self.width

        if self.x2 <= -self.width:
            self.x2 = self.width

    def draw(self, win):
        win.blit(self.image, (self.x1, self.y))
        win.blit(self.image, (self.x2, self.y))

کلاس Ground برای ایجاد زمین به کار می رود. در واقع این کلاس تصویر زمین را وارد بازی می کند و آن را در جای مناسب قرار می د هد.این کلاس مشخص می کند که تصویر زمین یعنی ground.png در چه مختصاتی قرار بگیرد.

این تصویر متحرک است و تمام آن در صفحه قرار نمی گیرد. یعنی طول تصویر از طول صفحه نمایش بیش تر است و تنها بخشی از آن دیده می شود. با حرکت دایناسور تصویر زمین هم تغییر می کند و به عبارت دیگر به روز آوری می شود و این توهم را ایجاد می کند که تصویر حرکت می کند. این کار در متد update در این کلاس اتفاق می افتد. پارامتر این متد سرعت یا speed است. برای رسم تصویر زمین از متد draw استفاده می کنیم.

کلاس Dino

class Dino():
    def __init__(self, x, y):
        self.x, self.base = x, y

        self.run_list = []
        self.duck_list = []

        for i in range(1, 4):
            img = pygame.image.load(f'Assets/Dino/{i}.png')
            img = pygame.transform.scale(img, (52, 58))
            self.run_list.append(img)

        for i in range(4, 6):
            img = pygame.image.load(f'Assets/Dino/{i}.png')
            img = pygame.transform.scale(img, (70, 38))
            self.duck_list.append(img)

        self.dead_image = pygame.image.load(f'Assets/Dino/8.png')
        self.dead_image = pygame.transform.scale(self.dead_image, (52,58))

        self.reset()

        self.vel = 0
        self.gravity = 1
        self.jumpHeight = 15
        self.isJumping = False

    def reset(self):
        self.index = 0
        self.image = self.run_list[self.index]
        self.rect = self.image.get_rect()
        self.rect.x = self.x
        self.rect.bottom = self.base

        self.alive = True
        self.counter = 0

    def update(self, jump, duck):
        if self.alive:
            if not self.isJumping and jump:
                self.vel = -self.jumpHeight
                self.isJumping = True

            self.vel += self.gravity
            if self.vel >= self.jumpHeight:
                self.vel = self.jumpHeight

            self.rect.y += self.vel
            if self.rect.bottom > self.base:
                self.rect.bottom = self.base
                self.isJumping = False

            if duck:
                self.counter += 1
                if self.counter >= 6:
                    self.index = (self.index + 1) % len(self.duck_list)
                    self.image = self.duck_list[self.index]
                    self.rect = self.image.get_rect()
                    self.rect.x = self.x
                    self.rect.bottom = self.base
                    self.counter = 0

            elif self.isJumping:
                self.index = 0
                self.counter = 0
                self.image = self.run_list[self.index]
            else:
                self.counter += 1
                if self.counter >= 4:
                    self.index = (self.index + 1) % len(self.run_list)
                    self.image = self.run_list[self.index]
                    self.rect = self.image.get_rect()
                    self.rect.x = self.x
                    self.rect.bottom = self.base
                    self.counter = 0

            self.mask = pygame.mask.from_surface(self.image)

        else:
            self.image = self.dead_image

    def draw(self, win):
        win.blit(self.image, self.rect)

این کلاس برای نمایش دایناسور به کار می رود. این دایناسور متحرک است و از تصاویر مختلفی برای نمایش آن استفاده می شود. وظیفه این کلاس بارگذاری این عکس ها در برنامه و تعیین موقعیت دایناسور است.

عکس ها از شماره یک تا سه دایناسور در حالت ایستاده و عکس ها با شماره 4 و 5 دایناسور را حالت خمیده نشان می دهند. سرعت، ارتفاع پرش، گرانش زمین و وضعیت پرش دایناسور در این کلاس مشخص می شود.

همچنین اگر دایناسور به مانعی برخورد کند این کلاس تصویر دایناسور مرده که تصویر 8.png است را نمایش می دهد. اگر دایناسور به مانعی برخورد کند بازی باید دوباره به حالت اول برگردد و همه چیز از اول آغاز شود. این کار با متد reset در کلاس Dino انجام می شود.

متد بعدی در این کلاس update است.این متد ابتدا بررسی می کند که دایناسور زنده است یا نه. اگر دایناسور زنده بود حرکت آن چه در حالت ایستاده و چه در حالت خمیده مدیریت می شود. همچنین پرش آن نیز در این جا مدیریت می شود. رسم دایناسور در متد draw انجام می شود.

کلاس Cactus

class Cactus(pygame.sprite.Sprite):
    def __init__(self, type):
        super(Cactus, self).__init__()

        self.image_list = []
        for i in range(5):
            scale = 0.65
            img = pygame.image.load(f'Assets/Cactus/{i+1}.png')
            w, h = img.get_size()
            img = pygame.transform.scale(img, (int(w*scale), int(h*scale)))
            self.image_list.append(img)

        self.image = self.image_list[type-1]
        self.rect = self.image.get_rect()
        self.rect.x = WIDTH + 10
        self.rect.bottom = 260

    def update(self, speed, dino):
        if dino.alive:
            self.rect.x -= speed
            if self.rect.right <= 0:
                self.kill()

            self.mask = pygame.mask.from_surface(self.image)

    def draw(self, win):
        win.blit(self.image, self.rect)

از این کلاس نیز برای رسم کاکتوس های گوناگون استفاده می شود. حرکت کاکتوس و برخورد آن با دایناسور و نیز رسم آن در این جا صورت می گیرد. اگر کاکتوس از سمت چپ صفحه خارج شود شی مربوط به آن حذف خواهد شد.

کلاس Ptera

class Ptera(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super(Ptera, self).__init__()

        self.image_list = []
        for i in range(2):
            scale = 0.65
            img = pygame.image.load(f'Assets/Ptera/{i+1}.png')
            w, h = img.get_size()
            img = pygame.transform.scale(img, (int(w*scale), int(h*scale)))
            self.image_list.append(img)

        self.index = 0
        self.image = self.image_list[self.index]
        self.rect = self.image.get_rect(center=(x, y))

        self.counter = 0

    def update(self, speed, dino):
        if dino.alive:
            self.rect.x -= speed
            if self.rect.right <= 0:
                self.kill()

            self.counter += 1
            if self.counter >= 6:
                self.index = (self.index + 1) % len(self.image_list)
                self.image = self.image_list[self.index]
                self.counter = 0

            self.mask = pygame.mask.from_surface(self.image)

    def draw(self, win):
        win.blit(self.image, self.rect)

دایناسورهای پرنده در ارتفاع های گوناگون نمایش داده خواهند شد و کاری که دایناسور باید بکند این است که به آن برخورد نکند. این کلاس نیز مانند کلاس های پیشین عکس ها را بارگذاری کرده و در برنامه از آن ها استفاده خواهد کرد. اگر دایناسور پرنده از سمت چپ صفحه خارج شود شی مربوط به آن حذف خواهد شد. با متد draw این دایناسورهای پرنده در صفحه رسم خواهند شد.

کلاس Cloud

class Cloud(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super(Cloud, self).__init__()
        self.image = pygame.image.load(f'Assets/cloud.png')
        self.image = pygame.transform.scale(self.image, (60, 18))
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def update(self, speed, dino):
        if dino.alive:
            self.rect.x -= speed
            if self.rect.right <= 0:
                self.kill()

    def draw(self, win):
        win.blit(self.image, self.rect)

برای رسم ابرها در آسمان به کار می رود و دخالتی در روند بازی ندارد و فقط برای زیبا سازی به کار می رود. اگر ابرها از سمت چپ صفحه خارج شوند شی مربوط به آن ها حذف خواهد شد. با متد draw ابرها در آسمان رسم خواهند شد.

کلاس Star

class Star(pygame.sprite.Sprite):
    def __init__(self, x, y, type):
        super(Star, self).__init__()
        image = pygame.image.load(f'Assets/stars.png')
        self.image_list = []
        for i in range(3):
            img = image.subsurface((0, 20*(i), 18, 18))
            self.image_list.append(img)
        self.image = self.image_list[type-1]
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def update(self, speed, dino):
        if dino.alive:
            self.rect.x -= speed
            if self.rect.right <= 0:
                self.kill()

    def draw(self, win):
        win.blit(self.image, self.rect)

برای رسم ستاره ها در آسمان به کار می رود و مانند ابرها دخالتی در روند بازی ندارد و فقط برای زیبا سازی به کار می رود. اگر ستاره ها از سمت چپ صفحه خارج شوند شی مربوط به آن ها حذف خواهد شد. با متد draw ابرها در آسمان رسم خواهند شد.

فایل main.py

وارد کردن کتابخانه ها و فایل ها

برای ساخت این بازی از pygame استفاده می کنیم. پس باید آن را وارد برنامه کنیم. همچنین  از random برای تولید اعداد تصادفی برای اشیای مختلف استفاده می کنیم. این بازی به صورت شی گرا نوشته می شود و برای هر یک از موجودیت های بازی یک کلاس در نظر می گیریم. این کلاس ها را در فایل objects.py ایجاد می کنیم و سپس آن را وارد فایل اصلی خود یعنی main.py می کنیم.

import random
import pygame

from objects import Ground, Dino, Cactus, Cloud, Ptera, Star

تعریف متغیرها و ثابت ها

همه متغیرها و ثابت هایی که در طول بازی به آن ها نیاز داریم را تعریف می کنیم.

pygame.init()
SCREEN = WIDTH, HEIGHT = (1000, 500)
win = pygame.display.set_mode(SCREEN)

ICON=pygame.image.load('Assets/start_img.png')
pygame.display.set_caption("Dino")
pygame.display.set_icon(ICON)

clock = pygame.time.Clock()
FPS = 60

WHITE = (225,225,225)
BLACK = (0, 0, 0)
GRAY = (32, 33, 36)

start_img = pygame.image.load('Assets/start_img.png')
start_img = pygame.transform.scale(start_img, (60, 64))

game_over_img = pygame.image.load('Assets/game_over.png')
game_over_img = pygame.transform.scale(game_over_img, (200, 36))

replay_img = pygame.image.load('Assets/replay.png')
replay_img = pygame.transform.scale(replay_img, (40, 36))
replay_rect = replay_img.get_rect()
replay_rect.x = WIDTH // 2 - 20
replay_rect.y = 150

numbers_img = pygame.image.load('Assets/numbers.png')
numbers_img = pygame.transform.scale(numbers_img, (120, 12))

jump_fx = pygame.mixer.Sound('Sounds/jump.wav')
die_fx = pygame.mixer.Sound('Sounds/die.wav')
checkpoint_fx = pygame.mixer.Sound('Sounds/checkPoint.wav')

ground = Ground()
dino = Dino(50, 260)

cactus_group = pygame.sprite.Group()
ptera_group = pygame.sprite.Group()
cloud_group = pygame.sprite.Group()
stars_group = pygame.sprite.Group()

keys = []
GODMODE = False
DAYMODE = False
LYAGAMI = False

counter = 0
enemy_time = 100
cloud_time = 500
stars_time = 175

SPEED = 5
jump = False
duck = False

score = 0
high_score = 0

start_page = True
mouse_pos = (-1, -1)

running = True

این متغیرها و ثابت ها شامل رنگ ها، اشیا، بارگذاری تصاویر و صدا و غیره خواهد بود.

راه اندازی دوباره بازی

def reset():
    global counter, SPEED, score, high_score

    if score and score >= high_score:
        high_score = score

    counter = 0
    SPEED = 5
    score = 0

    cactus_group.empty()
    ptera_group.empty()
    cloud_group.empty()
    stars_group.empty()

    dino.reset()

اگر بازیکن به مانعی برخورد کند بازی باید از اول شروع شود، یعنی یک بازی کاملا جدید داشته باشیم. این کار را با متد reset انجام می دهیم. در این متد امتیازی که به دست آوردیم، اگر از امتیاز قبلی که داشتیم بیش تر بود، امتیاز جدید جایگزین امتیاز قبلی می شود. آرایه ی مربوط به اشیا بازی خالی می شوند و شی dino نیز reset می شود تا یک بازی کاملا جدید داشته باشیم.

حلقه بازی

while running:
    jump = False
    if DAYMODE:
        win.fill(WHITE)
    else:
        win.fill(GRAY)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
                running = False

            if event.key == pygame.K_SPACE:
                if start_page:
                    start_page = False
                elif dino.alive:
                    jump = True
                    jump_fx.play()
                else:
                    reset()

            if event.key == pygame.K_UP:
                jump = True
                jump_fx.play()

            if event.key == pygame.K_DOWN:
                duck = True

            key = pygame.key.name(event.key)
            keys.append(key)
            keys = keys[-7:]
            if ''.join(keys).upper() == 'GODMODE':
                GODMODE = not GODMODE

            if ''.join(keys).upper() == 'DAYMODE':
                DAYMODE = not DAYMODE

            if ''.join(keys).upper() == 'LYAGAMI':
                LYAGAMI = not LYAGAMI

            if ''.join(keys).upper() == 'SPEEDUP':
                SPEED += 2

            if ''.join(keys).upper() == 'IAMRICH':
                score += 10000

            if ''.join(keys).upper() == 'HISCORE':
                high_score = 99999

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_SPACE or event.key == pygame.K_UP:
                jump = False

            if event.key == pygame.K_DOWN:
                duck = False

        if event.type == pygame.MOUSEBUTTONDOWN:
            mouse_pos = event.pos

        if event.type == pygame.MOUSEBUTTONUP:
            mouse_pos = (-1, -1)

    if start_page:
        win.blit(start_img, (50, 200))
    else:
        if dino.alive:
            counter += 1
            if counter % int(enemy_time) == 0:
                if random.randint(1, 10) == 5:
                    y = random.choice([85, 130])
                    ptera = Ptera(WIDTH, y)
                    ptera_group.add(ptera)
                else:
                    type = random.randint(1, 4)
                    cactus = Cactus(type)
                    cactus_group.add(cactus)

            if counter % cloud_time == 0:
                y = random.randint(40, 100)
                cloud = Cloud(WIDTH, y)
                cloud_group.add(cloud)

            if counter % stars_time == 0:
                type = random.randint(1, 3)
                y = random.randint(40, 100)
                star = Star(WIDTH, y, type)
                stars_group.add(star)

            if counter % 100 == 0:
                SPEED += 0.1
                enemy_time -= 0.5

            if counter % 5 == 0:
                score += 1

            if score and score % 100 == 0:
                checkpoint_fx.play()

            if not GODMODE:
                for cactus in cactus_group:
                    if LYAGAMI:
                        dx = cactus.rect.x - dino.rect.x
                        if 0 <= dx <= (70 + (score//100)):
                            jump = True

                    if pygame.sprite.collide_mask(dino, cactus):
                        SPEED = 0
                        dino.alive = False
                        die_fx.play()

                for cactus in ptera_group:
                    if LYAGAMI:
                        dx = ptera.rect.x - dino.rect.x
                        if 0 <= dx <= 70:
                            if dino.rect.top <= ptera.rect.top:
                                jump = True
                            else:
                                duck = True
                        else:
                            duck = False

                    if pygame.sprite.collide_mask(dino, ptera):
                        SPEED = 0
                        dino.alive = False
                        die_fx.play()

        ground.update(SPEED)
        ground.draw(win)
        cloud_group.update(SPEED-3, dino)
        cloud_group.draw(win)
        stars_group.update(SPEED-3, dino)
        stars_group.draw(win)
        cactus_group.update(SPEED, dino)
        cactus_group.draw(win)
        ptera_group.update(SPEED-1, dino)
        ptera_group.draw(win)
        dino.update(jump, duck)
        dino.draw(win)

        string_score = str(score).zfill(5)
        for i, num in enumerate(string_score):
            win.blit(numbers_img, (520+11*i, 10), (10*int(num), 0, 10, 12))

        if high_score:
            win.blit(numbers_img, (425, 10), (100, 0, 20, 12))
            string_score = f'{high_score}'.zfill(5)
            for i, num in enumerate(string_score):
                win.blit(numbers_img, (455+11*i, 10), (10*int(num), 0, 10, 12))

        if not dino.alive:
            win.blit(game_over_img, (WIDTH//2-100, 80))
            win.blit(replay_img, replay_rect)

            if replay_rect.collidepoint(mouse_pos):
                reset()

    pygame.draw.rect(win, WHITE, (0, 0, WIDTH, HEIGHT), 4)
    clock.tick(FPS)
    pygame.display.update()

pygame.quit()

حلقه بازی (game loop) که حلقه اصلی (main loop) نیز نامیده می شود سه کار را انجام می دهد:

  1. رویداد ها را کنترل می کند مانند کلیک کردن موس یا فشار دادن دکمه ای روی صفحه کلیک و غیره.
  2. وضعیت بازی را به روز یا ویرایش می کند.
  3. وضعیت بازی را روی صفحه نشان می دهد.

 وضعیت بازی راهی ساده برای مراجعه به همه متغیرهای یک بازی است. در بسیاری از بازی ها، وضعیت بازی مقادیر متغیرها را دارد مانند تعداد جان ها، موقعیت بازیکنان و موقعیت دشمنان که در بالا یا پایین صفحه با شکلی ویژه نشان داده می شود. هر گاه رویدادی مانند آسیب دیدن بازیکن (که یکی از جان های او را کم می کند)، حرکت کردن دشمن یا غیره در دنیای بازی اتفاق بیفتد می گوییم که وضعیت بازی تغییر کرده است.

اگر تا به حال بازی ای انجام داده اید که به شما امکان ذخیره کردن وضعیت بازی را می دهد، وضعیت بازی در نقطه ای می ماند که شما آن را ذخیره کرده اید. در بیش تر بازی ها، درنگ در بازی از تغییر وضعیت بازی جلوگیری می کند. از آن جایی که معمولا وضعیت بازی در پاسخ به رویدادها (مانند کلیک موس یا فشار دادن صفحه کلید) یا گذشت زمان به روز می شود، حلقه بازی به طور مداوم در هر ثانیه برای هر رویداد جدیدی که اتفاق افتاده است، بررسی هایی را انجام می دهد. در داخل حلقه اصلی کدی وجود دارد که بررسی می کند کدام رویدادها ایجاد شده اند (با Pygame، این بررسی با فراخوانی تابع pygame.event.get() انجام می شود). حلقه اصلی همچنین کدی دارد که بر اساس رویداد ایجاد شده ، وضعیت بازی را به روز می کند. به این کار معمولا رسیدگی به رویدادها یا مدیریت رویدادها گفته می شود.

 اولین کاری که در این حلقه انجام می شود تعیین روز یا شب بودن بازی است. برای بازی در روز DAYMODE باید True باشد در غیر این صورت بازی در شب انجام می شود.

jump = False
if DAYMODE:
    win.fill(WHITE)
else:
    win.fill(GRAY)

در این جا باید رویدادها مدیریت شوند مانند خروج از بازی و فشرده شدن کلیدهای صفحه کلید. برای خروج از بازی رویداد QUIT باید بررسی شود مانند کد زیر:

for event in pygame.event.get():

if event.type == pygame.QUIT:

running = False

اگر کلید Escape یا q فشرده شود باید از بازی خارج شویم:

if event.type == pygame.KEYDOWN:

if event.key == pygame.K_ESCAPE or event.key == pygame.K_q:

running = False

دایناسور با فشردن کلید space یا کلید پیکانی بالا پرش می کند. پس رویداد فشرده شدن کلید هم باید مدیریت شود.

if event.key == pygame.K_SPACE:

if start_page:

start_page = False

elif dino.alive:

jump = True

jump_fx.play()

else:

reset()

اگر کلید space یا کلید پیکانی بالا رها شود دایناسور باید پایین بیاید:

if event.key == pygame.K_UP:

jump = True

jump_fx.play()

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

if event.key == pygame.K_DOWN:

duck = True

در ادامه باید اشیا ایجاد شده از کلاس هایی که در فایل objects.py تعریف کرده ایم را مقداردهی کنیم. جای کاکتوس ها، دایناسورهای پرنده، دایناسور و غیره را این اشیا و مقداری که به آن ها می دهیم مشخص می کند. ابتدا باید بررسی شود دایناسور زنده است یا نه. اگر زنده بود جای تک تک اشیا را تعیین خواهیم کرد.

else:
        if dino.alive:
            counter += 1
            if counter % int(enemy_time) == 0:
                if random.randint(1, 10) == 5:
                    y = random.choice([85, 180])
                    ptera = Ptera(WIDTH, y)
                    ptera_group.add(ptera)
                else:
                    type = random.randint(1, 4)
                    cactus = Cactus(type)
                    cactus_group.add(cactus)

            if counter % cloud_time == 0:
                y = random.randint(40, 100)
                cloud = Cloud(WIDTH, y)
                cloud_group.add(cloud)

            if counter % stars_time == 0:
                type = random.randint(1, 3)
                y = random.randint(40, 100)
                star = Star(WIDTH, y, type)
                stars_group.add(star)

            if counter % 100 == 0:
                SPEED += 0.1
                enemy_time -= 0.5

            if counter % 5 == 0:
                score += 1

            if score and score % 100 == 0:
                checkpoint_fx.play()

            if not GODMODE:
                for cactus in cactus_group:
                    if LYAGAMI:
                        dx = cactus.rect.x - dino.rect.x
                        if 0 <= dx <= (70 + (score//100)):
                            jump = True

                    if pygame.sprite.collide_mask(dino, cactus):
                        SPEED = 0
                        dino.alive = False
                        die_fx.play()

                for cactus in ptera_group:
                    if LYAGAMI:
                        dx = ptera.rect.x - dino.rect.x
                        if 0 <= dx <= 70:
                            if dino.rect.top <= ptera.rect.top:
                                jump = True
                            else:
                                duck = True
                        else:
                            duck = False

                    if pygame.sprite.collide_mask(dino, ptera):
                        SPEED = 0
                        dino.alive = False
                        die_fx.play()

با استفاده از اشیا ایجاد شده از کلاس هایی که از پیش تعریف کردیم تصاویر را در صفحه نمایش رسم کرده و نشان می دهیم.

ground.update(SPEED)

ground.draw(win)

cloud_group.update(SPEED-3, dino)

cloud_group.draw(win)

stars_group.update(SPEED-3, dino)

stars_group.draw(win)

cactus_group.update(SPEED, dino)

cactus_group.draw(win)

ptera_group.update(SPEED-1, dino)

ptera_group.draw(win)

dino.update(jump, duck)

dino.draw(win)

با حرکت دایناسور و رد کردن موانع امتیاز باید محاسبه و افزایش یابد. امتیاز باید امتیازهای قبلی که به دست آوردیم مقایسه شود و بزرگترین آن ها در بالای بازی نمایش داده شود.

string_score = str(score).zfill(5)
for i, num in enumerate(string_score):
    win.blit(numbers_img, (520+11*i, 10), (10*int(num), 0, 10, 12))

if high_score:
    win.blit(numbers_img, (425, 10), (100, 0, 20, 12))
    string_score = f'{high_score}'.zfill(5)
    for i, num in enumerate(string_score):
        win.blit(numbers_img, (455+11*i, 10), (10*int(num), 0, 10, 12))

اگر دایناسور زنده نبود یعنی با یکی از موانع برخورد کرده بود باید صفحه Game Over نمایش داده شود. این صفحه شامل دکمه reset یا بازنشانی برای راه اندازی دوباره بازی است. اگر این دکمه فشار داده شود بازی باید از اول شروع شود. این دکمه باید با موس کلیک شود. پس رویداد مربوط به کلیک شدن موس را در زیر خواهیم داشت:

if not dino.alive:

    win.blit(game_over_img, (WIDTH//2-100, 80))

    win.blit(replay_img, replay_rect)




if replay_rect.collidepoint(mouse_pos):

    reset()

در آخر نیز باید پنجره بازی را نمایش دهیم و بازی را برای هر گونه تغییر در بازی بررسی و به روز رسانی کنیم.

pygame.draw.rect(win, WHITE, (0, 0, WIDTH, HEIGHT), 4)

clock.tick(FPS)

pygame.display.update()




pygame.quit()

منبع: وب سایت github

نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.