Гузариш ба мундариҷа
Мавзӯи 6 аз 6

Спрайтҳо ва «Мор»

Ин барои чӣ лозим аст

То даме ки дар бозӣ як қаҳрамон ва ду девор ҳаст, тағйирёбандаҳои алоҳида ва Rect кифоя мекунанд. Вале вақте ки объектҳо бисёранд — ситораҳо, душманон, тирҳо, тангаҳо, — коди аз тағйирёбандаҳои алоҳида зуд ба шӯла табдил меёбад. Ҳар навъи объектҳоро ҳамчун спрайт (синф бо тасвир, мавқеъ ва рафтори худ) тасвир кардан ва объектҳои монандро ба гурӯҳҳо ҷамъ кардан қулайтар аст. Гурӯҳ худаш барои ҳамаи спрайтҳояш нав мекунад, мекашад ва бархӯрдҳоро месанҷад.

Дар қисми дуюми мавзӯъ усули асосии бозии хуб сохташударо мебинем: қоидаҳо алоҳида аз расмкашӣ. Мантиқи «Мор» дар масъалаи охирин бар ҳамин сохта шудааст.

Қадами 1. Синфи спрайт

Спрайт — объекти бозӣ: он тасвири image (чӣ гуна менамояд) ва росткунҷаи rect (дар куҷо ҷойгир аст) дорад. Дар pygame спрайт — синфи ворисии pygame.sprite.Sprite аст.

import pygame

class Enemy(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()                       # ҳатмӣ: спрайтро омода кардан
        self.image = pygame.Surface((30, 30))    # тасвири 30 × 30
        self.image.fill((220, 60, 60))
        self.rect = self.image.get_rect(topleft=(x, y))

    def update(self):                            # рафтор дар як кадр
        self.rect.x += 2

e = Enemy(10, 20)
print(e.rect)
e.update()
e.update()
print(e.rect.x)

Натиҷа:

<rect(10, 20, 30, 30)>
14
  • super().__init__() ҳатмист: бе он гурӯҳҳо бо спрайт кор карда наметавонанд.
  • image.get_rect(topleft=(x, y)) росткунҷаро ба андозаи тасвир месозад ва фавран онро ба нуқтаи лозимӣ мегузорад. «Даста»-и дигарро ҳам нишон додан мумкин аст: center=…, midbottom=….
  • Дар update менависанд, ки спрайт дар як кадр чӣ мекунад: ҳаракат мекунад, рангро иваз мекунад, месанҷад, ки вақти нопадид шудан расидааст ё не.

Қадами 2. Гурӯҳ

Гурӯҳ (pygame.sprite.Group) — маҷмӯи спрайтҳо, ки як корро бо ҳамаашон якбора карда метавонад.

import pygame

class Dot(pygame.sprite.Sprite):
    def __init__(self, x):
        super().__init__()
        self.image = pygame.Surface((4, 4))
        self.rect = self.image.get_rect(topleft=(x, 0))

    def update(self):
        self.rect.y += 10

dots = pygame.sprite.Group(Dot(0), Dot(20))
dots.add(Dot(40))
dots.update()                         # update()-и ҳар спрайт
print(len(dots), [d.rect.y for d in dots])

Натиҷа:

3 [10, 10, 10]
Метод Чӣ мекунад
group.add(s1, s2…) илова кардани спрайтҳо
group.update() даъвати update()-и ҳар як
group.draw(screen) кашидани image-и ҳар спрайт дар rect-и он
len(group) дар гурӯҳ чанд спрайт ҳаст
for s in group: аз назар гузаронидани спрайтҳо
group.empty() хориҷ кардани ҳамаи спрайтҳо

Аргументҳое, ки ба group.update(...) дода мешаванд, ба методи update-и ҳар спрайт мерасанд — ҳамин тавр, масалан, вақти кадрро додан мумкин аст.

Қадами 3. kill — хориҷ кардани спрайт

sprite.kill() спрайтро аз ҳамаи гурӯҳҳое, ки дар онҳо аст, хориҷ мекунад. Баъд аз ин гурӯҳ онро дигар нав намекунад ва намекашад. Ҳамин тавр объектҳои аз экран рафта, душманони задашуда, тангаҳои ҷамъшударо хориҷ мекунанд.

import pygame

class Drop(pygame.sprite.Sprite):
    def __init__(self, y):
        super().__init__()
        self.rect = pygame.Rect(0, y, 5, 5)

    def update(self):
        self.rect.y += 30
        if self.rect.y > 100:      # дур ба поён парид — хориҷ мекунем
            self.kill()

rain = pygame.sprite.Group(Drop(0), Drop(50))
for frame in range(4):
    rain.update()
    print(len(rain))

Натиҷа:

2
1
1
0

Спрайт метавонад худаш дар update-и худ self.kill()-ро даъват кунад — ин усули оддист. Агар хориҷ кардани объектҳои нолозимро фаромӯш кунед, гурӯҳ бепоён калон мешавад: ҳар кадр ҳазорҳо ситораи ноаён нав ва кашида мешаванд ва бозӣ оҳиста-оҳиста суст мешавад.

Маҳз кай объекти аз канор гузаштаро хориҷ кардан — бозӣ ҳал мекунад: объекте, ки танҳо ба канор расид, ҳанӯз нисфан намоён аст ва нопадид шудани он ба чашм намоён мешавад.

Қадами 4. Бархӯрди спрайтҳо

Барои гурӯҳҳо функсияҳои бархӯрди худ ҳаст — онҳо rect-и спрайтҳоро месанҷанд:

Функсия Чӣ бармегардонад
pygame.sprite.spritecollide(sprite, group, dokill) рӯйхати спрайтҳои гурӯҳ, ки sprite ба онҳо мерасад
pygame.sprite.spritecollideany(sprite, group) ягон як спрайти расида ё None
pygame.sprite.groupcollide(g1, g2, kill1, kill2) луғати «спрайт аз g1 → рӯйхати расидаҳо аз g2»

Аргументи dokill ҳал мекунад, ки спрайтҳои расидаро аз гурӯҳ хориҷ кардан лозим аст ё не. Бубинем, ки агар хориҷ накунем, чӣ мешавад:

import pygame

class Box(pygame.sprite.Sprite):
    def __init__(self, x):
        super().__init__()
        self.rect = pygame.Rect(x, 0, 10, 10)

gems = pygame.sprite.Group(Box(0), Box(100))
hero = Box(5)
total = 0
for frame in range(3):
    total += len(pygame.sprite.spritecollide(hero, gems, False))
print(total, len(gems))

Натиҷа:

3 2

Қаҳрамон се кадр болои як гавҳар меистад ва гавҳар се маротиба ҳисоб шуд, дар гурӯҳ бошад ҳанӯз ду адад ҳаст. Бо dokill=True спрайти расида фавран нопадид мешавад ва бори дуюм онро ёфтан мумкин нест.

spritecollideany вақте қулай аст, ки худи далел муҳим бошад («оё бозингар ба душман расид?»), вале он танҳо як спрайтро бармегардонад — агар якбора якчанд расида бошанд, боқимонда коркард нашуда мемонанд.

groupcollide ҳар ҷуфтро аз ду гурӯҳ месанҷад — масалан, ҳамаи тирҳоро бар зидди ҳамаи душманон:

import pygame

class Box(pygame.sprite.Sprite):
    def __init__(self, x):
        super().__init__()
        self.rect = pygame.Rect(x, 0, 10, 10)

bullets = pygame.sprite.Group(Box(0), Box(200))
enemies = pygame.sprite.Group(Box(5), Box(300))
hits = pygame.sprite.groupcollide(bullets, enemies, True, True)
print(len(hits), len(bullets), len(enemies))

Натиҷа:

1 1 1

Як тир ба як душман расид ва ҳар ду нопадид шуданд; боқимонда монданд.

Қадами 5. Қоидаҳо алоҳида аз расмкашӣ

Арзишмандтарин чиз дар бозӣ — қоидаҳо: дар як қадам чӣ рух медиҳад. Онҳоро бо функсияи алоҳида навиштан қулай аст, ки ҳолатро мегирад ва ҳолати навро бармегардонад, ҳеҷ чиз намекашад. Ин усул се бартарӣ дорад:

  • функсияро бе равзана санҷидан осон аст — санҷишҳо дар ин курс ҳамин тавр кор мекунанд;
  • даври бозӣ кӯтоҳ мешавад: ҳодисаҳо → қадами қоидаҳо → расмкашӣ;
  • хаторо дар қоидаҳо ёфтан осонтар аст, вақте ки он бо расмкашӣ омехта нест.

Дар «Мор» мор — рӯйхати катакҳои (x, y) аст, сар аввал. Як қадам — сари нав ба аввали рӯйхат ва шояд аз даст додани сегменти охирин:

snake = [(3, 1), (2, 1), (1, 1)]
head = (snake[0][0] + 1, snake[0][1])     # қадам ба рост
print([head] + snake[:-1])                # дум рафт — дарозӣ ҳамон
print([head] + snake)                     # хӯрок хӯрд — дум монд

Натиҷа:

[(4, 1), (3, 1), (2, 1)]
[(4, 1), (3, 1), (2, 1), (1, 1)]

Буриши snake[:-1] — ҳамаи катакҳо, ғайр аз охирин. Санҷиши баромадан аз майдон — нобаробарии дучанд: катак дар дохили майдони cols × rows аст, агар 0 ≤ x &lt; cols ва 0 ≤ y &lt; rows бошад.

cols, rows = 5, 4
for cell in [(0, 0), (4, 3), (5, 0), (2, -1)]:
    print(cell, 0 <= cell[0] < cols and 0 <= cell[1] < rows)

Натиҷа:

(0, 0) True
(4, 3) True
(5, 0) False
(2, -1) False

Дар дохил чӣ тавр кор мекунад

Сар ва думи мор якбора ҳаракат мекунанд. Қатори одамонеро тасаввур кунед, ки давра мезананд: аввалин ба ҷое қадам мегузорад, ки охирин ҳозир истода буд, — ва ҳеҷ кас бо касе бархӯрд намекунад, зеро охирин дар ҳамин қадам аз он ҷо рафт. Бинобар ин муҳим аст, ки сари навро бо кадом бадан муқоиса кунем: бо бадани кӯҳна (ки дум ҳанӯз дар ҷояш аст) ё бо баданае, ки баъд аз қадам мешавад. Агар мор хӯрок хӯрда бошад, дум дар ҷояш мемонад — ва он гоҳ катаки дум банд аст.

Гурӯҳи спрайтҳо дар дохил — танҳо маҷмӯи объектҳост. kill() спрайтро аз ҳар гурӯҳе, ки дар он навишта шудааст, хориҷ мекунад, худи спрайт бошад дар хотир дорад, ки дар кадом гурӯҳҳост. Агар ба спрайт дигар ягон истинод набошад, Python хотираи онро озод мекунад.

Хатоҳои маъмул

1. super().__init__() фаромӯш шуд. Гурӯҳ ҳангоми илова кардани спрайт хато медиҳад. Дар сатри аввали __init__ super().__init__()-ро даъват кунед.

2. Спрайтҳои нолозим хориҷ карда намешаванд. Ситораҳое, ки аз экран рафтанд, дар гурӯҳ мемонанд — бозӣ суст мешавад. kill()-ро даъват кунед.

3. dokill=False барои бонусҳо. Ашё то даме ки бозингар болои он меистад, ҳар кадр ҳисоб мешавад. Барои ашёи ҷамъшаванда — True.

4. spritecollideany ба ҷои spritecollide. Танҳо як спрайтро бармегардонад — ҳангоми тӯдаи ашё қисме коркард намешавад.

5. Санҷиши марг аз рӯи бадани кӯҳна. Мор ҳангоми танҳо аз паси думаш хазидан «мемирад». Фикр кунед, ки бадан баъд аз қадам дар куҷо мешавад.

Варақаи ёддошт

Чӣ Код
синфи спрайт class Enemy(pygame.sprite.Sprite): + super().__init__()
тасвир ва ҷой self.image = pygame.Surface((w, h)), self.rect = self.image.get_rect(topleft=(x, y))
рафтор дар кадр def update(self): ...
гурӯҳ group = pygame.sprite.Group(s1, s2), group.add(s)
ҳамаро нав кардан ва кашидан group.update(), group.draw(screen)
чандто len(group)
хориҷ кардани спрайт sprite.kill()
спрайтҳои расида pygame.sprite.spritecollide(sprite, group, dokill)
ягон як pygame.sprite.spritecollideany(sprite, group)
ду гурӯҳ pygame.sprite.groupcollide(g1, g2, kill1, kill2)
мор бе дум snake[:-1]
катак дар майдон 0 <= x < cols and 0 <= y < rows