Játékok készítése a pygame keretrendszerrel - 3. rész

Az előző két rész itt érhető el:
Játékok készítése a pygame keretrendszerrel - 2. rész
Játékok készítése a pygame keretrendszerrel - 1. rész

Ebben a részben a strandlabda programunkat fejlesztjük tovább újra. Ha valaki kipróbálta az előző rész végén olvasható javaslatomat, és betett egy újabb labdát a programba, akkor örömmel nézhette, hogy nem egy, hanem két lapda pattog az ablakban, ha netán volt annyira önálló, hogy többet is hozzáadjon, akkor több is repked ide-oda :D

Nos, szomorúan kell azonban konstatálnunk, hogy a labdák csak a falról pattannak vissza, egymásról nem :( Most ezen fogunk változtatni!

Mielőtt nekifognánk, ismerkedjünk meg az ütközésdetektálás fogalmával. Minden mozgó objektumokat tartalmazó játék esetén fontos momentum annak ellenőrzése, hogy a egy adott objektum összeütközött-e egy másikkal. Pl. a szörny elérte-e a játékost, a lövedék eltalálta-e az ellenséget, stb.
Hogyan tudjuk ezt számítógéppel elvégezni? Meglepő módon számításokkal :D
Az ehhez szükséges matematikai alapokat a koordináta-geometria és a vektoralgebra szolgáltatja, de nem kell megijedni, ebbe most nem akarok mélyebben belemenni, csak fogalmakat tisztázunk.
Az ütközések detektálása számításigényes feladat, vagyis processzorkapacitást köt le. Minél több mozgó objektum ütközését vizsgáljuk, annál többet. Mivel ez egy kényes dolog, hiszen a sok számítás a játék futási sebességére lehet hatással, a játékkészítők a gyakorlatban mindig élnek valamilyen egyszerűsítéssel ennek eldöntésére, hiszen nem biztos, hogy minden esetben pixelhelyesen kell azt eldönteni, hogy két elem fedi-e egymást.

Milyen egyszerűsítésekkel élhetünk?

Az esetek többségében elegendő két objektum jellemző adatát összehasonlítani. Pl. a játékos talpontját összehasonlítjuk egy (vagy több) padlóelem koordinátáival és szélességével, ha a padló alatta van közvetlenül, akkor rajta áll, ha nincs alatta padló, akkor leesik.

Ha a mozgás gyors, és az objektumok alakja közelít a téglalaphoz vagy körhöz, akkor elegendő az objektumok köré vagy bele írható téglalapok vagy körök (esetleg ezek méretét is csökkenthetjük, növelhetjük) koordinátáinak vizsgálatát elvégezni.

Ha ennél pontosabb vizsgálatra van szükség, vagy az objektumaink bonyolultabb alakzatok, akkor már szóbajön a pixelek szerinti összehasonlítás. Az elemek többségénél viszont, főleg ha szabálytalan alakzatok, sok az átlátszó képpont, vagyis alkothatunk az elemről egy olyan egyszínű képet, ahol csak olyan képpontok szerepelnek, ahol az elemnek van képpontja (ami nem átlátszó természetesen). Ezt a képet az elem maszkjának hívjuk. Az egyes képpontok a maszkban egy bittel ábrázolhatók, a bitenkénti összehasonlitás processzorigénye pedig jóval kisebb, mint a négy bájttal ábrázolt átlátszóságot is kezelő képpontoké.

Példa a maszkra:


Nos ennyi bevezető elég annak akit érdekel, a net tele van további anyagokkal, akit meg nem érdekel ennyire azt nem untatom tovább ezzel. Lépjünk tovább, és vizsgáljuk meg, a pygame mit tartogat nekünk az ütközések detektálásához.

A pygame.sprite modul tartalmazza ezeket a segédeszközöket:

  • pygame.sprite.spritecollide - egy sprite ütközéseit keresi egy spritegroup-ban
  • pygame.sprite.collide_rect - két sprite ütközését vizsgálja azok rect attribútumaik szerint
  • pygame.sprite.collide_rect_ratio -mint az előző, de a rect-et növeli vagy csökkenti a vizsgálat során
  • pygame.sprite.collide_circle - kör alapú ütközésvizsgálat két sprite között
  • pygame.sprite.collide_circle_ratio - mint az előző, de a megadott kört egy arányszámmal módosítja
  • pygame.sprite.collide_mask - két sprite mask alapú ütközés vizsgálata (ehhez Mask objektumokat kell a sprite-hoz készíteni)
  • pygame.sprite.groupcollide - két spritegroup minden tagjának ütközésvizsgálata
  • pygame.sprite.spritecollideany - megnézi, hogy egy sprite ütközik-e valamely másikkal egy megadott csoportban
Ezeknek mind létezik a neten leírása, akit mélyebben érdekel, nézzen utána. Mi most inkább nézzük a strandlabdáinkat!

Nos, mivel labdákról van szó, a legkézenfekvőbb megoldás, ha a pygame.sprite.collide_circle típust választjuk. Ehhez a Labda osztályunkat kibővítjük egy radius attribútummal, mert ha ez létezik, ez az ütközésvizsgáló metódus használni fogja az ütköző körök méretéhez.
Ezenkívül adunk egy collidecheck metódust a Labda osztályunkhoz, amelyet minden képkocka során meghívunk. Ez a metódus, abban az esetben, ha ütközést észlel, meghív egy másik, szintén új metódust, amely a labda aktuális sebességvektorát tükrözi a két ütközésben résztvevő labda középpontjain keresztül húzható egyenesre (ennek koordináta-geometriai hátterét most nem fejteném ki...). Ez a metódus a reflect (megjegyzés: a reflect a gyökvonáshoz igényli a math modult, ezt ne felejtsük el beimportálni).

Nézzük meg akkor a felújított Labda osztályunk két új metódusát:

    def reflect(self,p1,p2):
        p1p2_x = p2[0] - p1[0]
        p1p2_y = p2[1] - p1[1]
        p1p2_abs = math.sqrt(p1p2_x * p1p2_x + p1p2_y * p1p2_y)
        n12_x = p1p2_x / p1p2_abs
        n12_y = p1p2_y / p1p2_abs

        rx = self.vector[0] - 2 * (self.vector[0] * n12_x + self.vector[1] * n12_y) * n12_x
        ry = self.vector[1] - 2 * (self.vector[0] * n12_x + self.vector[1] * n12_y) * n12_y

        self.vector = [rx, ry]

    def checkcollision(self,masiklabda):
        if pygame.sprite.collide_circle(self,masiklabda):
            self.reflect(self.rect.center,masiklabda.rect.center)

    

A kész programunkba már három labdát tettem, és nagyobbra vettem a sebességeket, hogy gyakrabban ütközzenek. A render függvénybe raktam az ütközések ellenőrzését. Kézzel írtam be a meghívást mind a három labdára, de ha teljesen univerzálisra szeretnénk megírni, érdemes csinálni egy külön függvényt, ami a labdak csoport elemein végighaladva vizsgálja meg az ütközéseket.

A teljes programunk tehát:

#!/usr/bin/env python
# -*- coding:utf8 -*-

import sys,pygame
from pygame.locals import *
import math

# pygame inicializálása
pygame.init()

class Hatter(pygame.Surface):
    def __init__(self,size,msize,colors):
        super(Hatter,self).__init__(size)
        self.rect = self.get_rect()
        xq = size[0] / msize
        yq = size[1] / msize
        colorindex = 0
        for y in range(yq):
            firstcolor = colorindex
            for x in range(xq):
                pygame.draw.rect(self,colors[colorindex],(x*msize,y*msize,msize,msize),0)
                colorindex = 0 if colorindex == 1 else 1
            colorindex = 0 if firstcolor == 1 else 1

class Labda(pygame.sprite.Sprite):
    def __init__(self,imagelist,vector,screen_size,animspeed = 15, startpoint = [0,0],group = None):
        super(Labda,self).__init__()
        self.imagelist = imagelist
        self.image = self.imagelist[0]
        self.rect = self.image.get_rect()
        self.rect.topleft = startpoint
        self.index = 0
        self.phases = len(self.imagelist) - 1
        self.vector = vector
        width = self.rect.width
        height = self.rect.height
        self.radius = width/2
        self.xlimits = (0,screen_size[0] - width)
        self.ylimits = (0,screen_size[1] - height)
        self.speed = animspeed
        self.counter = 0
        if group != None:
            self.add((group))

    def reflect(self,p1,p2):
        p1p2_x = p2[0] - p1[0]
        p1p2_y = p2[1] - p1[1]
        p1p2_abs = math.sqrt(p1p2_x * p1p2_x + p1p2_y * p1p2_y)
        n12_x = p1p2_x / p1p2_abs
        n12_y = p1p2_y / p1p2_abs

        rx = self.vector[0] - 2 * (self.vector[0] * n12_x + self.vector[1] * n12_y) * n12_x
        ry = self.vector[1] - 2 * (self.vector[0] * n12_x + self.vector[1] * n12_y) * n12_y

        self.vector = [rx, ry]

    def checkcollision(self,masiklabda):
        if pygame.sprite.collide_circle(self,masiklabda):
            self.reflect(self.rect.center,masiklabda.rect.center)

    def update(self):
        x = self.rect.topleft[0] + self.vector[0]
        y = self.rect.topleft[1] + self.vector[1]
        if x < self.xlimits[0]:
            x = self.xlimits[0]
            self.vector[0] = -self.vector[0]
        if x > self.xlimits[1]:
            x = self.xlimits[1]
            self.vector[0] = -self.vector[0]
        if y < self.ylimits[0]:
            y = self.ylimits[0]
            self.vector[1] = -self.vector[1]
        if y > self.ylimits[1]:
            y = self.ylimits[1]
            self.vector[1] = -self.vector[1]
        self.rect.topleft = x, y
        self.counter += 1
        if self.counter > self.speed:
            self.counter = 0
            self.index += 1
            if self.index > self.phases:
                self.index = 0
            self.image = self.imagelist[self.index]

# ablak megjelenítése és a játékadatok beállítás
SCREEN_SIZE = (800,600) # ablak mérete
screen = pygame.display.set_mode(SCREEN_SIZE) # ablak "surface"

hatter = Hatter(SCREEN_SIZE,50,((192,192,192),(255,255,255)))
labdak = pygame.sprite.Group()
labdafazisok = [pygame.image.load('labda1.png').convert_alpha(),pygame.image.load('labda2.png').convert_alpha(),pygame.image.load('labda3.png').convert_alpha(),pygame.image.load('labda4.png').convert_alpha(),pygame.image.load('labda5.png').convert_alpha(),pygame.image.load('labda6.png').convert_alpha(),pygame.image.load('labda7.png').convert_alpha(),pygame.image.load('labda8.png').convert_alpha(),pygame.image.load('labda9.png').convert_alpha()]
labda = Labda(labdafazisok,[8,7],SCREEN_SIZE,4,group = labdak)
labda2 = Labda(labdafazisok,[15,9],SCREEN_SIZE,6,startpoint = [100,200], group = labdak)
labda3 = Labda(labdafazisok,[-10,-6],SCREEN_SIZE,8,startpoint = [400,400], group = labdak)
fps = 30

def kilep():
    """kilep():
    Megnézi, hogy megnyomták-e az ESC gombot
    vagy megpróbálták bezárni az ablakot"""
    for event in pygame.event.get():
        if event.type==QUIT:
            return True
        elif (event.type==KEYDOWN and event.key==K_ESCAPE):
            return True
    return False

def render():
    screen.blit(hatter,hatter.rect)
    labdak.update()
    labdak.draw(screen)
    labda.checkcollision(labda2)
    labda2.checkcollision(labda)
    labda.checkcollision(labda3)
    labda3.checkcollision(labda)
    labda3.checkcollision(labda2)

# ezzel az objektummal tudjuk szabályozni a játék sebességét
clock = pygame.time.Clock()

# fő ciklus
# ismétlődik amíg ki nem lépünk
while True:
    render() # aktuális képkocka létrehozása

    # ki akart lépni a felhasználó?
    if kilep():
        pygame.quit()
        sys.exit()

    # kirakjuk az ablak tartalmát a képernyőre
    pygame.display.flip()

    # szabályozzuk a futási sebességet
    clock.tick(fps)

Ennyit szántam erre az írásra, ha van kérdés, tegyétek fel, szívesen válaszolok (ha tudok).

Nincsenek megjegyzések:

Megjegyzés küldése