Hra typu Asteroids

Dnes to všechno – třídy, grafiku, seznamy, a tak dále – spojíme dohromady do závěrečného projektu. Doufám, že se ti bude líbit!

Zkusíme udělat klon hry Asteroids, která poprvé vyšla na konci sedmdesátých let. V našem podání bude hra nakonec vypadat nějak takhle:

Screenshot hry typu Asteroids

Projekt je to docela složitý a – jako většina praktických projektů – využívá i některých věcí, které ještě na kurzu nebyly. Věřím, že ale přesto zvládneš všechno pochopit nebo dohledat!

A ještě jedna věc: protože začátečnický kurz končí, začneme kód psát v angličtině, aby se pak dal sdílet s celým světem.

Procházíš-li si projekt doma, je možné, že narazíš na něco s čím si nebudeš vědět rady. Kdyby se to stalo, prosím, ozvi se nám! Rádi ti s projektem pomůžeme.

Vesmírná loď

První krok bude naprogramovat vesmírnou loď, která půjde ovládat klávesnicí.

  • Vesmírnou loď bude reprezentovat objekt třídy Spaceship.
  • Každá loď má vlastní atributy x a y (pozice), x_speed a y_speed (rychlost), rotation (úhel natočení) a sprite (2D objekt v Pygletu s polohou, rotací a obrázkem).
  • Loď má metodu tick, která obstarává mechaniku týkající se lodi – posouvání, natáčení a ovládání.
  • Všechny objekty ve hře si budeme dávat do globálního seznamu objects. Zatím tam bude jenom vesmírná loď.
  • Co se ovládání týče, stisknuté klávesy si uchovávej v množině (angl. set), což je datový typ podobný seznamu, jen nemá dané pořadí prvků a každý prvek v ní může být pouze jednou. (Na množinu se dá dívat i jako na slovník bez hodnot.) Je k dispozici tahák na množiny a pythonní dokumentace obsahuje k množinám tutoriál i podrobný popis. Vesmírná loď se pak do množiny „podívá” v rámci své metody tick.
  • Můžeš použít sadu obrázků, které nakreslil Kenney Vleugels a zpřístupnil je zadarmo komukoli. Nebo si nakresli/stáhni vlastní!
  • Ve hře později použijeme velké množství Sprite-ů a vykreslovat je jeden po druhém by trvalo docela dlouho. Všechny Sprite-y proto přidej do kolekce pyglet.graphics.Batch, kterou pak Pyglet umí efektivně vykreslit najednou. Do „batche” jde přidávat pomocí argumentu při vytváření Sprite() a odebírat pomocí sprite.delete(). Například:

    batch = pyglet.graphics.Batch()
    sprite1 = pyglet.sprite.Sprite(obrazek, batch=batch)
    sprite2 = pyglet.sprite.Sprite(obrazek, batch=batch)
    
    # a potom můžeš vykreslit všechny najednou:
    batch.draw()
    

    Kolekci batch si stejně jako objects uchovávej globálně.

  • Aby se objekty hýbaly a otáčely podle svých středů, je dobré nastavit „kotvu“ obrázku na jeho střed (jinak je kotva v levém dolním rohu):

    image = pyglet.image.load(...)
    image.anchor_x = image.width // 2
    image.anchor_y = image.height // 2
    self.sprite = pyglet.sprite.Sprite(image, batch=batch)
    
  • Pro pohyb raketky půjde použít klávesy s šipkami doleva, doprava a rovně. Šipky do stran raketu točí, šipka dopředu zrychluje pohyb tím směrem, kam je raketka otočená.

    • Základní pohyb raketky je jednoduchý: k x-ové souřadnici se přičte x-ová rychlost krát uplynulý čas a to samé v y-ové souřadnici i pro úhel otočení:

        self.x = self.x + dt * self.x_speed
        self.y = self.y + dt * self.y_speed
        self.rotation = self.rotation + dt * rotation_speed
      

      Rychlost otáčení závisí na stisknutých šipkách (doleva nebo doprava). V jednom případě je záporná, v druhém kladná. Vhodnou hodnotu zvol experimentováním – začni třeba u 4 radiánů za sekundu. Všechny podobné „magické hodnoty“ je vhodné definovat jako konstanty – tedy proměnné, které na začátku nastavíš a nikdy je neměníš. Bývá zvykem je označovat velkými písmeny a dávat je na začátek souboru, hned za importy:

        ROTATION_SPEED = 4  # radians per second
      
    • Zrychlení je trochu složitější: k x-ové rychlosti se přičte kosinus úhlu otočení krát uplynulý čas. U y-ové osy se použije sinus.

        self.x_speed += dt * ACCELERATION * math.cos(self.rotation)
        self.y_speed += dt * ACCELERATION * math.sin(self.rotation)
      

      Všimni si v příkladu konstanty ACCELERATION. Tu opět zvol podle uvážení.

    • Když máš hodnoty self.x, self.y a self.rotation spočítané, nezapomeň je promítnout do self.sprite, jinak se nic zajímavého nestane.

      Pozor na to, že funkce math.sin a math.cos používají radiány, kdežto pyglet používá pro Sprite.rotation stupně. (A k tomu je navíc 0° jinde, a otáčí se na opačnou stranu.) Pro sprite je tedy potřeba úhel převést:

        self.sprite.rotation = 90 - math.degrees(self.rotation)
        self.sprite.x = self.x
        self.sprite.y = self.y
      
    • Když raketka vyletí z okýnka ven, vrať ji zpátky do hry na druhé straně okýnka. (Zkontroluj si, že to funguje na všech čtyřech stranách.)
  • Bonus 1: Zkus si přidat několik raketek, každou trochu jinak natočenou.

    Každý jednotlivý objekt třídy Spaceship si udržuje vlastní stav, takže by nemělo být složité jich vytvořit víc (a všechny ovládat najednou).

  • Bonus 2: Možná sis všimla „skoku”, když raketa vyletí z okýnka a vrátí se na druhé straně. Tomu se dá zabránit tak, že vlevo, vpravo, nahoře i dole vedle naší „scény” vykreslíš celou scénu ještě jednou.

    Pyglet na to má speciální nízkoúrovňové funkce, kterými můžeš říct „tady kresli všechno posunuté o X pixelů vlevo”. Úplné vysvětlení by bylo na dlouho, takže si zatím jen zkopíruj kód:

    from pyglet import gl
    
    def draw():
        window.clear()
    
        for x_offset in (-window.width, 0, window.width):
            for y_offset in (-window.height, 0, window.height):
                # Remember the current state
                gl.glPushMatrix()
                # Move everything drawn from now on by (x_offset, y_offset, 0)
                gl.glTranslatef(x_offset, y_offset, 0)
    
                # Draw
                batch.draw()
    
                # Restore remembered state (this cancels the glTranslatef)
                gl.glPopMatrix()
    

    Pro přehled, dokumentace k použitým funkcím je tady: glPushMatrix, glPopMatrix, glTranslatef.

Povedlo se? Můžeš létat vesmírem? Čas to všechno dát do Gitu!

Projdi si předchozí body, jestli máš opravdu všechno, a můžeš pokračovat dál!

Asteroidy

Přidej druhý typ vesmírného objektu: Asteroid.

  • Asteroidy a vesmírné lodě mají mnoho společného: každý takový vesmírný objekt bude mít polohu, rychlost, natočení a pravidla, jak se pohybuje. Vytvoř proto třídu SpaceObject, ve které bude všechno to společné, a z ní poděď třídu Spaceship, ve které zůstane kód specifický pro vesmírnou loď (t.j. ovládání klávesnicí, obrázek lodě, začátek v prostředku obrazovky).
  • Část kódu pro pohyb bude společná pro všechny vesmírné objekty (např. věci kolem zrychlení); část bude specifická jen pro raketku (ovládání pomocí klávesnice). Využij funkci super() z lekce o dědičnosti.
  • Napiš ještě třídu Asteroid, která taky dědí ze SpaceObject, ale má svoje vlastní chování: začíná buď na levé nebo spodní straně obrazovky s náhodnou rychlostí a ke každému asteroidu se přiřadí náhodně vybraný obrázek. (V Asteroidech je levý a pravý okraj v podstatě to samé; a stejně tak horní a spodní.)
  • A pak pár asterojdíků různých velikostí přidej na začátku do hry.

Povedlo se? Máš dva typy objektů? Čas to všechno dát do Gitu!

Zase si projdi, jestli máš všechno hotové, a jdeme na další část!

Kolize

Naše asteroidy jsou zatím docela neškodné. Pojďme to změnit.

  • V této sekci bude tvým úkolem zjistit, kdy loď narazila do asteroidu. Pro zjednodušení si každý objekt nahradíme kolečkem a budeme počítat, kdy se srazí kolečka. Každý objekt bude potřebovat mít poloměr – atribut radius.
  • Aby bylo vidět co si hra o objektech „myslí”, nakresli si nad každým objektem příslušné kolečko. Nejlepší je to udělat pomocí pyglet.gl a trochy matematiky; pro teď si jen opiš funkci draw_circle a pro každý objekt ji zavolej. Až to bude všechno fungovat, můžeš funkci dát pryč.

    def draw_circle(x, y, radius):
        iterations = 20
        s = math.sin(2*math.pi / iterations)
        c = math.cos(2*math.pi / iterations)
    
        dx, dy = radius, 0
    
        gl.glBegin(gl.GL_LINE_STRIP)
        for i in range(iterations+1):
            gl.glVertex2f(x+dx, y+dy)
            dx, dy = (dx*c - dy*s), (dy*c + dx*s)
        gl.glEnd()
    
  • Když asteroid narazí do lodi, loď exploduje a zmizí. Explozi necháme na později, teď je důležité odebrání objektu ze hry. Dej ho do metody SpaceObject.delete, protože vyndávat ze hry se dá jakýkoli objekt. V této metodě musíš objekt jednak odstranit ze seznamu objects a pak zrušit jeho Sprite, aby se už v rámci batch nevykresloval.
  • A jak udělat ono narážení? V rámci Spaceship.tick projdi každý objekt, zjisti jestli vzdálenost mezi lodí a objektem je menší než součet poloměrů (t.j. narazily do sebe) a pokud ano, zavolej na objektu metodu hit_by_spaceship.

    Zjišťování vzdálenosti ve hře, kde se objekty které vyletí ven vrací na druhé straně, není úplně přímočaré, takže si příslušný kód pro teď jen zkopíruj:

    def distance(a, b, wrap_size):
        """Distance in one direction (x or y)"""
        result = abs(a - b)
        if result > wrap_size / 2:
            result = wrap_size - result
        return result
    
    def overlaps(a, b):
        """Returns true iff two space objects overlap"""
        distance_squared = (distance(a.x, b.x, window.width) ** 2 +
                            distance(a.y, b.y, window.height) ** 2)
        max_distance_squared = (a.radius + b.radius) ** 2
        return distance_squared < max_distance_squared
    

    Většina objektů v dokončené hře (např. oheň z rakety, střela) nebude při kolizi s lodí dělat nic, takže metoda SpaceObject.hit_by_spaceship by neměla dělat nic (musí jen existovat). Jen asteroid loď rozbije, takže předefinuj Asteroid.hit_by_spaceship, aby zavolala delete lodi.

    Protože lodí může být v naší hře obecně více, musí asteroid vědět, se kterou lodí se srazil, aby ji mohl rozbít. Metoda hit_by_spaceship by tedy na to měla mít argument.

Povedlo se? Konečně se dá prohrát? Čas to všechno zkontrolovat, dát do Gitu a můžeme pokračovat!

Útok

Teď zkusíme asteroidy rozbíjet.

  • Raketka umí jednou za 0,3 s vystřelit laser. Ulož si pro každou raketku (jako atribut) číslo, které po každém výstřelu nastav na 0,3 a pak ho v metodě tick nech klesat o 1 za vteřinu. Když bude záporné, může hráč vystřelit znovu.
  • Když hráč drží mezerník a může vystřelit, vystřelí. Ve hře se to projeví tak, že se přidá objekt nové třídy Laser. Začne na souřadnicích raketky, s natočením raketky a s rychlostí raketky plus něco navíc ve směru natočení.
  • Každý objekt třídy Laser si „pamatuje“, jak dlouho ještě bude ve hře. Na začátku se tohle číslo nastaví tak, aby přeletěl zhruba něco víc než jednu obrazovku. Když dojde čas, Laser zmizí.
  • Ve své metodě tick laser projde všechny objekty, a pokud se s některým překrývá, tak na něm zavolá metodu hit_by_laser. U většiny objektů tahle metoda nedělá nic, jen asteroidy bude rozbíjet.
  • Když se laser dotkne asteroidu, asteroid se rozdělí na dva menší (nebo, je-li už příliš malý, zmizí úplně).

    Rychlosti nových asteroidů si můžeš nastavit podle sebe – důležité je jen, aby každý menší asteroid letěl jinam. Většinou bývají nové asteroidy rychlejší než ten původní.

  • A to je vše! Máš funkční hru!

Povedlo se? Dá se i vyhrát? Čas to všechno dát do Gitu!

Dokončení a rozšíření

Chceš-li ve hře pokračovat, tady jsou další nápady. Můžeš je dělat v jakémkoli pořadí – nebo si vymysli vlastní rozšíření!

  • Je hra příliš těžká?

    Můžeš přidat životy: na začátku jsou tři, a dokud nějaký zbývá, raketka se po zásahu asteroidem objeví znovu uprostřed, s nulovou rychlostí. Hra by taky při tomto „restartu” měla ignorovat držené klávesy, dokud je hráč znovu nezmáčkne (nejlépe pomocí pressed_keys.clear()).

    Počet náhradních lodí můžeš ukázat ikonkami na spodku obrazovky.

    Bonus: Několik vteřin po „restartu” může být raketka nezničitelná, aby měla čas odletět, když je zrovna uprostřed okýnka asteroid.

  • Je hra příliš lehká?

    Přidej úrovně: až hráč vystřílí všechny asteroidy, postoupí na další úroveň, kde je asteroidů víc než v té předchozí.

    Číslo úrovně můžeš ukázat pomocí pyglet.text.Label.

  • Je pozadí příliš černé?

    V sadě obrázků v adresáři Backgrounds si vyber pozadí, a vytapetuj s ním celý vesmír.

  • Je hra moc strohá?

    Přidej oheň a exploze! Chovají se podobně jako Laser, jen nic neničí a můžou třeba měnit barvu podle toho, jak dlouho už jsou ve hře.

    Na efekty můžeš použít obrázky „Smoke particle assets”, které nakreslil opět Kenney Vleugels. Doporučuji „White Puff”, které můžeš zmenšit (např. sprite.scale = 1/10), přibarvit (např. sprite.color = 255, 100, 0) nebo částečně zprůhlednit (např. sprite.opacity = 100).

    Doporučuji si na efekty udělat nový Batch a vykreslit ho před tím hlavním, aby efekty nepřekrývaly herní objekty.

  • Nepoznáš, kdy jsi prohrála nebo vyhrála?

    Na konci můžeš ukázat veliký nápis GAME OVER nebo WINNER.

  • Nudíš se?

    V původní hře se občas objeví UFO, které občas vystřelí na místo, kde je právě hráčova raketka, takže pokud hráč stojí pořád na jednom místě a jenom se točí dokola, UFO ho sestřelí. Můžeš zkusit dodělat třídu Ufo a z Laser podědit ShipLaser a UfoLaser.

Povedlo se? Vypadá to a chová se to profesionálně? Čas to všechno dát do Gitu!

{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2019/pyladies-ostrava-podzim:asteroids:0",
      "title": "Asteroids",
      "html": "\n          \n    \n\n    <h1>Hra typu Asteroids</h1>\n<p>Dnes to v&#x161;echno &#x2013; t&#x159;&#xED;dy, grafiku, seznamy, a tak d&#xE1;le &#x2013;\nspoj&#xED;me dohromady do z&#xE1;v&#x11B;re&#x10D;n&#xE9;ho projektu.\nDouf&#xE1;m, &#x17E;e se ti bude l&#xED;bit!</p>\n<p>Zkus&#xED;me ud&#x11B;lat klon hry <a href=\"https://en.wikipedia.org/wiki/Asteroids_%28video_game%29\">Asteroids</a>,\nkter&#xE1; poprv&#xE9; vy&#x161;la na konci sedmdes&#xE1;t&#xFD;ch let.\nV na&#x161;em pod&#xE1;n&#xED; bude hra nakonec vypadat n&#x11B;jak takhle:</p>\n<p><span class=\"figure\"><a href=\"/2019/pyladies-ostrava-podzim/projects/asteroids/static/screenshot.png\"><img src=\"/2019/pyladies-ostrava-podzim/projects/asteroids/static/screenshot.png\" alt=\"Screenshot hry typu Asteroids\"></a></span></p>\n<p>Projekt je to docela slo&#x17E;it&#xFD; a &#x2013; jako v&#x11B;t&#x161;ina\npraktick&#xFD;ch projekt&#x16F; &#x2013; vyu&#x17E;&#xED;v&#xE1; i n&#x11B;kter&#xFD;ch v&#x11B;c&#xED;, kter&#xE9; je&#x161;t&#x11B; na kurzu nebyly.\nV&#x11B;&#x159;&#xED;m, &#x17E;e ale p&#x159;esto zvl&#xE1;dne&#x161; v&#x161;echno pochopit nebo dohledat!</p>\n<p>A je&#x161;t&#x11B; jedna v&#x11B;c: proto&#x17E;e za&#x10D;&#xE1;te&#x10D;nick&#xFD; kurz kon&#x10D;&#xED;,\nza&#x10D;neme k&#xF3;d ps&#xE1;t v angli&#x10D;tin&#x11B;, aby se pak dal sd&#xED;let s&#xA0;cel&#xFD;m sv&#x11B;tem.</p>\n<div class=\"admonition note\"><p>Proch&#xE1;z&#xED;&#x161;-li si projekt doma, je mo&#x17E;n&#xE9;, &#x17E;e naraz&#xED;&#x161; na\nn&#x11B;co s &#x10D;&#xED;m si nebude&#x161; v&#x11B;d&#x11B;t rady.\nKdyby se to stalo, pros&#xED;m, ozvi se n&#xE1;m!\nR&#xE1;di ti s projektem pom&#x16F;&#x17E;eme.</p>\n</div><h2>Vesm&#xED;rn&#xE1; lo&#x10F;</h2>\n<p>Prvn&#xED; krok bude naprogramovat vesm&#xED;rnou lo&#x10F;, kter&#xE1; p&#x16F;jde ovl&#xE1;dat kl&#xE1;vesnic&#xED;.</p>\n<ul>\n<li>Vesm&#xED;rnou lo&#x10F; bude reprezentovat objekt t&#x159;&#xED;dy <code>Spaceship</code>.</li>\n<li>Ka&#x17E;d&#xE1; lo&#x10F; m&#xE1; vlastn&#xED; atributy <code>x</code> a <code>y</code> (pozice),\n<code>x_speed</code> a <code>y_speed</code> (rychlost), <code>rotation</code> (&#xFA;hel nato&#x10D;en&#xED;) a\n<code>sprite</code> (2D objekt v Pygletu s polohou, rotac&#xED; a obr&#xE1;zkem).</li>\n<li>Lo&#x10F; m&#xE1; metodu <code>tick</code>, kter&#xE1; obstar&#xE1;v&#xE1;\nmechaniku t&#xFD;kaj&#xED;c&#xED; se lodi &#x2013; posouv&#xE1;n&#xED;, nat&#xE1;&#x10D;en&#xED; a ovl&#xE1;d&#xE1;n&#xED;.</li>\n<li>V&#x161;echny objekty ve h&#x159;e si budeme d&#xE1;vat do glob&#xE1;ln&#xED;ho seznamu <code>objects</code>.\nZat&#xED;m tam bude jenom vesm&#xED;rn&#xE1; lo&#x10F;.</li>\n<li>Co se ovl&#xE1;d&#xE1;n&#xED; t&#xFD;&#x10D;e, stisknut&#xE9; kl&#xE1;vesy si uchov&#xE1;vej v <em>mno&#x17E;in&#x11B;</em> (angl. <code>set</code>),\nco&#x17E; je datov&#xFD; typ podobn&#xFD; seznamu, jen nem&#xE1; dan&#xE9; po&#x159;ad&#xED;\nprvk&#x16F; a ka&#x17E;d&#xFD; prvek v n&#xED; m&#x16F;&#x17E;e b&#xFD;t pouze jednou.\n(Na mno&#x17E;inu se d&#xE1; d&#xED;vat i jako na slovn&#xED;k bez hodnot.)\nJe k dispozici <a href=\"https://github.com/pyvec/cheatsheets/blob/master/sets/sets-cs.pdf\">tah&#xE1;k na mno&#x17E;iny</a>\na pythonn&#xED; dokumentace obsahuje k mno&#x17E;in&#xE1;m\n<a href=\"https://docs.python.org/3/tutorial/datastructures.html#sets\">tutori&#xE1;l</a>\ni <a href=\"https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset\">podrobn&#xFD; popis</a>.\nVesm&#xED;rn&#xE1; lo&#x10F; se pak do mno&#x17E;iny &#x201E;pod&#xED;v&#xE1;&#x201D; v r&#xE1;mci\nsv&#xE9; metody <code>tick</code>.</li>\n<li>M&#x16F;&#x17E;e&#x161; pou&#x17E;&#xED;t <a href=\"http://opengameart.org/content/space-shooter-redux\">sadu obr&#xE1;zk&#x16F;</a>,\nkter&#xE9; nakreslil <a href=\"http://kenney.nl\">Kenney Vleugels</a>\na zp&#x159;&#xED;stupnil je zadarmo komukoli. Nebo si nakresli/st&#xE1;hni vlastn&#xED;!</li>\n<li><p>Ve h&#x159;e pozd&#x11B;ji pou&#x17E;ijeme velk&#xE9; mno&#x17E;stv&#xED;\n<code>Sprite</code>-&#x16F; a vykreslovat je jeden po druh&#xE9;m by trvalo docela dlouho.\nV&#x161;echny <code>Sprite</code>-y proto p&#x159;idej do kolekce\n<a href=\"https://pythonhosted.org/pyglet/api/pyglet.graphics.Batch-class.html\">pyglet.graphics.Batch</a>,\nkterou pak Pyglet um&#xED; efektivn&#x11B; vykreslit najednou.\nDo &#x201E;batche&#x201D; jde p&#x159;id&#xE1;vat pomoc&#xED; argumentu p&#x159;i vytv&#xE1;&#x159;en&#xED; <code>Sprite()</code>\na odeb&#xED;rat pomoc&#xED; <code>sprite.delete()</code>. Nap&#x159;&#xED;klad:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">batch</span> <span class=\"o\">=</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">graphics</span><span class=\"o\">.</span><span class=\"n\">Batch</span><span class=\"p\">()</span>\n<span class=\"n\">sprite1</span> <span class=\"o\">=</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">sprite</span><span class=\"o\">.</span><span class=\"n\">Sprite</span><span class=\"p\">(</span><span class=\"n\">obrazek</span><span class=\"p\">,</span> <span class=\"n\">batch</span><span class=\"o\">=</span><span class=\"n\">batch</span><span class=\"p\">)</span>\n<span class=\"n\">sprite2</span> <span class=\"o\">=</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">sprite</span><span class=\"o\">.</span><span class=\"n\">Sprite</span><span class=\"p\">(</span><span class=\"n\">obrazek</span><span class=\"p\">,</span> <span class=\"n\">batch</span><span class=\"o\">=</span><span class=\"n\">batch</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># a potom m&#x16F;&#x17E;e&#x161; vykreslit v&#x161;echny najednou:</span>\n<span class=\"n\">batch</span><span class=\"o\">.</span><span class=\"n\">draw</span><span class=\"p\">()</span>\n</pre></div><p>Kolekci <code>batch</code> si stejn&#x11B; jako <code>objects</code> uchov&#xE1;vej glob&#xE1;ln&#x11B;.</p>\n</li>\n<li><p>Aby se objekty h&#xFD;baly a ot&#xE1;&#x10D;ely podle sv&#xFD;ch st&#x159;ed&#x16F;, je dobr&#xE9; nastavit &#x201E;kotvu&#x201C;\nobr&#xE1;zku na jeho st&#x159;ed (jinak je kotva v lev&#xE9;m doln&#xED;m rohu):</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">image</span> <span class=\"o\">=</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">image</span><span class=\"o\">.</span><span class=\"n\">load</span><span class=\"p\">(</span><span class=\"o\">...</span><span class=\"p\">)</span>\n<span class=\"n\">image</span><span class=\"o\">.</span><span class=\"n\">anchor_x</span> <span class=\"o\">=</span> <span class=\"n\">image</span><span class=\"o\">.</span><span class=\"n\">width</span> <span class=\"o\">//</span> <span class=\"mi\">2</span>\n<span class=\"n\">image</span><span class=\"o\">.</span><span class=\"n\">anchor_y</span> <span class=\"o\">=</span> <span class=\"n\">image</span><span class=\"o\">.</span><span class=\"n\">height</span> <span class=\"o\">//</span> <span class=\"mi\">2</span>\n<span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">sprite</span> <span class=\"o\">=</span> <span class=\"n\">pyglet</span><span class=\"o\">.</span><span class=\"n\">sprite</span><span class=\"o\">.</span><span class=\"n\">Sprite</span><span class=\"p\">(</span><span class=\"n\">image</span><span class=\"p\">,</span> <span class=\"n\">batch</span><span class=\"o\">=</span><span class=\"n\">batch</span><span class=\"p\">)</span>\n</pre></div></li>\n<li><p>Pro pohyb raketky p&#x16F;jde pou&#x17E;&#xED;t kl&#xE1;vesy s &#x161;ipkami doleva, doprava a rovn&#x11B;.\n&#x160;ipky do stran raketu to&#x10D;&#xED;, &#x161;ipka dop&#x159;edu zrychluje pohyb t&#xED;m sm&#x11B;rem, kam je\nraketka oto&#x10D;en&#xE1;.</p>\n<ul>\n<li><p>Z&#xE1;kladn&#xED; pohyb raketky je jednoduch&#xFD;: k <var>x</var>-ov&#xE9;\nsou&#x159;adnici se p&#x159;i&#x10D;te <var>x</var>-ov&#xE1; rychlost kr&#xE1;t uplynul&#xFD; &#x10D;as\na to sam&#xE9; v <var>y</var>-ov&#xE9; sou&#x159;adnici i pro &#xFA;hel oto&#x10D;en&#xED;:</p>\n<div class=\"highlight\"><pre><span></span>  <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">x</span> <span class=\"o\">+</span> <span class=\"n\">dt</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">x_speed</span>\n  <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">y</span> <span class=\"o\">+</span> <span class=\"n\">dt</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">y_speed</span>\n  <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">rotation</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">rotation</span> <span class=\"o\">+</span> <span class=\"n\">dt</span> <span class=\"o\">*</span> <span class=\"n\">rotation_speed</span>\n</pre></div><p>Rychlost ot&#xE1;&#x10D;en&#xED; z&#xE1;vis&#xED; na stisknut&#xFD;ch &#x161;ipk&#xE1;ch (doleva nebo doprava).\n  V jednom p&#x159;&#xED;pad&#x11B; je z&#xE1;porn&#xE1;, v druh&#xE9;m kladn&#xE1;. Vhodnou hodnotu zvol\n  experimentov&#xE1;n&#xED;m &#x2013; za&#x10D;ni t&#x159;eba u 4 radi&#xE1;n&#x16F; za sekundu.\n  V&#x161;echny podobn&#xE9; &#x201E;magick&#xE9; hodnoty&#x201C; je vhodn&#xE9; definovat\n  jako konstanty &#x2013; tedy prom&#x11B;nn&#xE9;, kter&#xE9; na za&#x10D;&#xE1;tku nastav&#xED;&#x161; a nikdy\n  je nem&#x11B;n&#xED;&#x161;. B&#xFD;v&#xE1; zvykem je ozna&#x10D;ovat velk&#xFD;mi p&#xED;smeny a d&#xE1;vat je na\n  za&#x10D;&#xE1;tek souboru, hned za importy:</p>\n<div class=\"highlight\"><pre><span></span>  <span class=\"n\">ROTATION_SPEED</span> <span class=\"o\">=</span> <span class=\"mi\">4</span>  <span class=\"c1\"># radians per second</span>\n</pre></div></li>\n<li><p>Zrychlen&#xED; je trochu slo&#x17E;it&#x11B;j&#x161;&#xED;: k  <var>x</var>-ov&#xE9; rychlosti\nse p&#x159;i&#x10D;te kosinus &#xFA;hlu oto&#x10D;en&#xED; kr&#xE1;t uplynul&#xFD; &#x10D;as.\nU <var>y</var>-ov&#xE9; osy se pou&#x17E;ije sinus.</p>\n<div class=\"highlight\"><pre><span></span>  <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">x_speed</span> <span class=\"o\">+=</span> <span class=\"n\">dt</span> <span class=\"o\">*</span> <span class=\"n\">ACCELERATION</span> <span class=\"o\">*</span> <span class=\"n\">math</span><span class=\"o\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">rotation</span><span class=\"p\">)</span>\n  <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">y_speed</span> <span class=\"o\">+=</span> <span class=\"n\">dt</span> <span class=\"o\">*</span> <span class=\"n\">ACCELERATION</span> <span class=\"o\">*</span> <span class=\"n\">math</span><span class=\"o\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">rotation</span><span class=\"p\">)</span>\n</pre></div><p>V&#x161;imni si v p&#x159;&#xED;kladu konstanty <code>ACCELERATION</code>. Tu op&#x11B;t zvol podle uv&#xE1;&#x17E;en&#xED;.</p>\n</li>\n<li><p>Kdy&#x17E; m&#xE1;&#x161; hodnoty <code>self.x</code>, <code>self.y</code> a <code>self.rotation</code> spo&#x10D;&#xED;tan&#xE9;, nezapome&#x148;\nje prom&#xED;tnout do <code>self.sprite</code>, jinak se nic zaj&#xED;mav&#xE9;ho nestane.</p>\n<p>Pozor na to, &#x17E;e funkce <code>math.sin</code> a <code>math.cos</code> pou&#x17E;&#xED;vaj&#xED; radi&#xE1;ny,\nkde&#x17E;to <code>pyglet</code> pou&#x17E;&#xED;v&#xE1; pro <code>Sprite.rotation</code> stupn&#x11B;.\n(A k tomu je nav&#xED;c 0&#xB0; jinde, a ot&#xE1;&#x10D;&#xED; se na opa&#x10D;nou stranu.)\nPro sprite je tedy pot&#x159;eba &#xFA;hel p&#x159;ev&#xE9;st:</p>\n<div class=\"highlight\"><pre><span></span>  <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">sprite</span><span class=\"o\">.</span><span class=\"n\">rotation</span> <span class=\"o\">=</span> <span class=\"mi\">90</span> <span class=\"o\">-</span> <span class=\"n\">math</span><span class=\"o\">.</span><span class=\"n\">degrees</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">rotation</span><span class=\"p\">)</span>\n  <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">sprite</span><span class=\"o\">.</span><span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">x</span>\n  <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">sprite</span><span class=\"o\">.</span><span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">y</span>\n</pre></div></li>\n<li>Kdy&#x17E; raketka vylet&#xED; z ok&#xFD;nka ven, vra&#x165;\nji zp&#xE1;tky do hry na druh&#xE9; stran&#x11B; ok&#xFD;nka.\n(Zkontroluj si, &#x17E;e to funguje na v&#x161;ech &#x10D;ty&#x159;ech stran&#xE1;ch.)</li>\n</ul>\n</li>\n</ul>\n<ul>\n<li><p><strong>Bonus 1</strong>: Zkus si p&#x159;idat n&#x11B;kolik raketek,\nka&#x17E;dou trochu jinak nato&#x10D;enou.</p>\n<p>Ka&#x17E;d&#xFD; jednotliv&#xFD; objekt t&#x159;&#xED;dy <code>Spaceship</code>\nsi udr&#x17E;uje vlastn&#xED; stav, tak&#x17E;e by nem&#x11B;lo b&#xFD;t slo&#x17E;it&#xE9;\njich vytvo&#x159;it v&#xED;c (a v&#x161;echny ovl&#xE1;dat najednou).</p>\n</li>\n<li><p><strong>Bonus 2</strong>:\nMo&#x17E;n&#xE1; sis v&#x161;imla &#x201E;skoku&#x201D;, kdy&#x17E;\nraketa vylet&#xED; z ok&#xFD;nka a vr&#xE1;t&#xED; se na druh&#xE9; stran&#x11B;.\nTomu se d&#xE1; zabr&#xE1;nit tak, &#x17E;e\nvlevo, vpravo, naho&#x159;e i dole vedle na&#x161;&#xED; &#x201E;sc&#xE9;ny&#x201D;\nvykresl&#xED;&#x161; celou sc&#xE9;nu je&#x161;t&#x11B; jednou.</p>\n<p>Pyglet na to m&#xE1; speci&#xE1;ln&#xED; n&#xED;zko&#xFA;rov&#x148;ov&#xE9; funkce,\nkter&#xFD;mi m&#x16F;&#x17E;e&#x161; &#x159;&#xED;ct &#x201E;tady kresli v&#x161;echno posunut&#xE9; o\nX pixel&#x16F; vlevo&#x201D;. &#xDA;pln&#xE9; vysv&#x11B;tlen&#xED; by bylo na dlouho,\ntak&#x17E;e si zat&#xED;m jen zkop&#xED;ruj k&#xF3;d:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">pyglet</span> <span class=\"kn\">import</span> <span class=\"n\">gl</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">draw</span><span class=\"p\">():</span>\n    <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">clear</span><span class=\"p\">()</span>\n\n    <span class=\"k\">for</span> <span class=\"n\">x_offset</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"o\">-</span><span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">width</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">width</span><span class=\"p\">):</span>\n        <span class=\"k\">for</span> <span class=\"n\">y_offset</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"o\">-</span><span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">height</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">height</span><span class=\"p\">):</span>\n            <span class=\"c1\"># Remember the current state</span>\n            <span class=\"n\">gl</span><span class=\"o\">.</span><span class=\"n\">glPushMatrix</span><span class=\"p\">()</span>\n            <span class=\"c1\"># Move everything drawn from now on by (x_offset, y_offset, 0)</span>\n            <span class=\"n\">gl</span><span class=\"o\">.</span><span class=\"n\">glTranslatef</span><span class=\"p\">(</span><span class=\"n\">x_offset</span><span class=\"p\">,</span> <span class=\"n\">y_offset</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># Draw</span>\n            <span class=\"n\">batch</span><span class=\"o\">.</span><span class=\"n\">draw</span><span class=\"p\">()</span>\n\n            <span class=\"c1\"># Restore remembered state (this cancels the glTranslatef)</span>\n            <span class=\"n\">gl</span><span class=\"o\">.</span><span class=\"n\">glPopMatrix</span><span class=\"p\">()</span>\n</pre></div><p>Pro p&#x159;ehled, dokumentace k pou&#x17E;it&#xFD;m funkc&#xED;m je tady:\n<a href=\"https://www.opengl.org/sdk/docs/man2/xhtml/glPushMatrix.xml\">glPushMatrix, glPopMatrix</a>,\n<a href=\"https://www.opengl.org/sdk/docs/man2/xhtml/glTranslate.xml\">glTranslatef</a>.</p>\n</li>\n</ul>\n<p>Povedlo se? M&#x16F;&#x17E;e&#x161; l&#xE9;tat vesm&#xED;rem?\n&#x10C;as to v&#x161;echno d&#xE1;t do Gitu!</p>\n<p>Projdi si p&#x159;edchoz&#xED; body, jestli m&#xE1;&#x161; opravdu v&#x161;echno, a m&#x16F;&#x17E;e&#x161; pokra&#x10D;ovat d&#xE1;l!</p>\n<h2>Asteroidy</h2>\n<p>P&#x159;idej druh&#xFD; typ vesm&#xED;rn&#xE9;ho objektu: <code>Asteroid</code>.</p>\n<ul>\n<li>Asteroidy a vesm&#xED;rn&#xE9; lod&#x11B; maj&#xED; mnoho spole&#x10D;n&#xE9;ho:\nka&#x17E;d&#xFD; takov&#xFD; vesm&#xED;rn&#xFD; objekt bude m&#xED;t polohu,\nrychlost, nato&#x10D;en&#xED; a pravidla, jak se pohybuje.\nVytvo&#x159; proto t&#x159;&#xED;du <code>SpaceObject</code>,\nve kter&#xE9; bude v&#x161;echno to spole&#x10D;n&#xE9;, a z n&#xED; pod&#x11B;&#x10F;\nt&#x159;&#xED;du <code>Spaceship</code>, ve kter&#xE9; z&#x16F;stane\nk&#xF3;d specifick&#xFD; pro vesm&#xED;rnou lo&#x10F; (t.j. ovl&#xE1;d&#xE1;n&#xED;\nkl&#xE1;vesnic&#xED;, obr&#xE1;zek lod&#x11B;, za&#x10D;&#xE1;tek v prost&#x159;edku\nobrazovky).</li>\n<li>&#x10C;&#xE1;st k&#xF3;du pro pohyb bude spole&#x10D;n&#xE1; pro v&#x161;echny\nvesm&#xED;rn&#xE9; objekty (nap&#x159;. v&#x11B;ci kolem zrychlen&#xED;);\n&#x10D;&#xE1;st bude specifick&#xE1; jen pro raketku (ovl&#xE1;d&#xE1;n&#xED;\npomoc&#xED; kl&#xE1;vesnice).\nVyu&#x17E;ij funkci <code>super()</code> z <a href=\"/2019/pyladies-ostrava-podzim/beginners/inheritance/\">lekce o d&#x11B;di&#x10D;nosti</a>.</li>\n<li>Napi&#x161; je&#x161;t&#x11B; t&#x159;&#xED;du <code>Asteroid</code>,\nkter&#xE1; taky d&#x11B;d&#xED; ze <code>SpaceObject</code>,\nale m&#xE1; svoje vlastn&#xED; chov&#xE1;n&#xED;:\nza&#x10D;&#xED;n&#xE1; bu&#x10F; na lev&#xE9; nebo spodn&#xED; stran&#x11B; obrazovky\ns&#xA0;n&#xE1;hodnou rychlost&#xED;\na ke ka&#x17E;d&#xE9;mu asteroidu se p&#x159;i&#x159;ad&#xED;\nn&#xE1;hodn&#x11B; vybran&#xFD; obr&#xE1;zek.\n(V Asteroidech je lev&#xFD; a prav&#xFD; okraj v podstat&#x11B;\n  to sam&#xE9;; a stejn&#x11B; tak horn&#xED; a spodn&#xED;.)</li>\n<li>A pak p&#xE1;r asterojd&#xED;k&#x16F; r&#x16F;zn&#xFD;ch velikost&#xED; p&#x159;idej\nna za&#x10D;&#xE1;tku do hry.</li>\n</ul>\n<p>Povedlo se? M&#xE1;&#x161; dva typy objekt&#x16F;?\n&#x10C;as to v&#x161;echno d&#xE1;t do Gitu!</p>\n<p>Zase si projdi, jestli m&#xE1;&#x161; v&#x161;echno hotov&#xE9;,\na jdeme na dal&#x161;&#xED; &#x10D;&#xE1;st!</p>\n<h2>Kolize</h2>\n<p>Na&#x161;e asteroidy jsou zat&#xED;m docela ne&#x161;kodn&#xE9;. Poj&#x10F;me to zm&#x11B;nit.</p>\n<ul>\n<li>V t&#xE9;to sekci bude tv&#xFD;m &#xFA;kolem zjistit, kdy\nlo&#x10F; narazila do asteroidu.\nPro zjednodu&#x161;en&#xED; si ka&#x17E;d&#xFD; objekt nahrad&#xED;me\nkole&#x10D;kem a budeme po&#x10D;&#xED;tat, kdy se sraz&#xED; kole&#x10D;ka.\nKa&#x17E;d&#xFD; objekt bude pot&#x159;ebovat m&#xED;t polom&#x11B;r &#x2013; atribut <code>radius</code>.</li>\n<li><p>Aby bylo vid&#x11B;t co si hra o objektech &#x201E;mysl&#xED;&#x201D;,\nnakresli si nad ka&#x17E;d&#xFD;m objektem p&#x159;&#xED;slu&#x161;n&#xE9; kole&#x10D;ko.\nNejlep&#x161;&#xED; je to ud&#x11B;lat pomoc&#xED;\n<a href=\"http://pyglet.readthedocs.org/en/latest/programming_guide/gl.html\">pyglet.gl</a>\na trochy matematiky; pro te&#x10F; si jen opi&#x161; funkci\n<code>draw_circle</code> a pro ka&#x17E;d&#xFD; objekt ji zavolej.\nA&#x17E; to bude v&#x161;echno fungovat, m&#x16F;&#x17E;e&#x161; funkci d&#xE1;t pry&#x10D;.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">draw_circle</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">radius</span><span class=\"p\">):</span>\n    <span class=\"n\">iterations</span> <span class=\"o\">=</span> <span class=\"mi\">20</span>\n    <span class=\"n\">s</span> <span class=\"o\">=</span> <span class=\"n\">math</span><span class=\"o\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"o\">*</span><span class=\"n\">math</span><span class=\"o\">.</span><span class=\"n\">pi</span> <span class=\"o\">/</span> <span class=\"n\">iterations</span><span class=\"p\">)</span>\n    <span class=\"n\">c</span> <span class=\"o\">=</span> <span class=\"n\">math</span><span class=\"o\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"o\">*</span><span class=\"n\">math</span><span class=\"o\">.</span><span class=\"n\">pi</span> <span class=\"o\">/</span> <span class=\"n\">iterations</span><span class=\"p\">)</span>\n\n    <span class=\"n\">dx</span><span class=\"p\">,</span> <span class=\"n\">dy</span> <span class=\"o\">=</span> <span class=\"n\">radius</span><span class=\"p\">,</span> <span class=\"mi\">0</span>\n\n    <span class=\"n\">gl</span><span class=\"o\">.</span><span class=\"n\">glBegin</span><span class=\"p\">(</span><span class=\"n\">gl</span><span class=\"o\">.</span><span class=\"n\">GL_LINE_STRIP</span><span class=\"p\">)</span>\n    <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">iterations</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span>\n        <span class=\"n\">gl</span><span class=\"o\">.</span><span class=\"n\">glVertex2f</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"o\">+</span><span class=\"n\">dx</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"o\">+</span><span class=\"n\">dy</span><span class=\"p\">)</span>\n        <span class=\"n\">dx</span><span class=\"p\">,</span> <span class=\"n\">dy</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">dx</span><span class=\"o\">*</span><span class=\"n\">c</span> <span class=\"o\">-</span> <span class=\"n\">dy</span><span class=\"o\">*</span><span class=\"n\">s</span><span class=\"p\">),</span> <span class=\"p\">(</span><span class=\"n\">dy</span><span class=\"o\">*</span><span class=\"n\">c</span> <span class=\"o\">+</span> <span class=\"n\">dx</span><span class=\"o\">*</span><span class=\"n\">s</span><span class=\"p\">)</span>\n    <span class=\"n\">gl</span><span class=\"o\">.</span><span class=\"n\">glEnd</span><span class=\"p\">()</span>\n</pre></div></li>\n<li>Kdy&#x17E; asteroid naraz&#xED; do lodi, lo&#x10F; exploduje a zmiz&#xED;.\nExplozi nech&#xE1;me na pozd&#x11B;ji, te&#x10F; je d&#x16F;le&#x17E;it&#xE9; odebr&#xE1;n&#xED; objektu ze hry.\nDej ho do metody <code>SpaceObject.delete</code>,\nproto&#x17E;e vynd&#xE1;vat ze hry se d&#xE1; jak&#xFD;koli objekt.\nV t&#xE9;to metod&#x11B; mus&#xED;&#x161; objekt jednak odstranit\nze seznamu <code>objects</code> a pak zru&#x161;it jeho <code>Sprite</code>, aby se u&#x17E; v r&#xE1;mci\n<code>batch</code> nevykresloval.</li>\n<li><p>A jak ud&#x11B;lat ono nar&#xE1;&#x17E;en&#xED;?\nV r&#xE1;mci <code>Spaceship.tick</code> projdi\nka&#x17E;d&#xFD; objekt, zjisti jestli vzd&#xE1;lenost mezi lod&#xED;\na objektem je men&#x161;&#xED; ne&#x17E; sou&#x10D;et polom&#x11B;r&#x16F;\n(t.j. narazily do sebe) a pokud ano,\nzavolej na objektu metodu <code>hit_by_spaceship</code>.</p>\n<p>Zji&#x161;&#x165;ov&#xE1;n&#xED; vzd&#xE1;lenosti ve h&#x159;e, kde se\n<a href=\"https://en.wikipedia.org/wiki/Wraparound_%28video_games%29\">objekty kter&#xE9; vylet&#xED; ven vrac&#xED; na druh&#xE9; stran&#x11B;</a>,\nnen&#xED; &#xFA;pln&#x11B; p&#x159;&#xED;mo&#x10D;ar&#xE9;, tak&#x17E;e si p&#x159;&#xED;slu&#x161;n&#xFD; k&#xF3;d pro te&#x10F; jen zkop&#xED;ruj:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">distance</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">b</span><span class=\"p\">,</span> <span class=\"n\">wrap_size</span><span class=\"p\">):</span>\n    <span class=\"sd\">&quot;&quot;&quot;Distance in one direction (x or y)&quot;&quot;&quot;</span>\n    <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">a</span> <span class=\"o\">-</span> <span class=\"n\">b</span><span class=\"p\">)</span>\n    <span class=\"k\">if</span> <span class=\"n\">result</span> <span class=\"o\">&gt;</span> <span class=\"n\">wrap_size</span> <span class=\"o\">/</span> <span class=\"mi\">2</span><span class=\"p\">:</span>\n        <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">wrap_size</span> <span class=\"o\">-</span> <span class=\"n\">result</span>\n    <span class=\"k\">return</span> <span class=\"n\">result</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">overlaps</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"sd\">&quot;&quot;&quot;Returns true iff two space objects overlap&quot;&quot;&quot;</span>\n    <span class=\"n\">distance_squared</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">distance</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">b</span><span class=\"o\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">width</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"mi\">2</span> <span class=\"o\">+</span>\n                        <span class=\"n\">distance</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">b</span><span class=\"o\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">height</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"mi\">2</span><span class=\"p\">)</span>\n    <span class=\"n\">max_distance_squared</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">.</span><span class=\"n\">radius</span> <span class=\"o\">+</span> <span class=\"n\">b</span><span class=\"o\">.</span><span class=\"n\">radius</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"mi\">2</span>\n    <span class=\"k\">return</span> <span class=\"n\">distance_squared</span> <span class=\"o\">&lt;</span> <span class=\"n\">max_distance_squared</span>\n</pre></div><p>V&#x11B;t&#x161;ina objekt&#x16F; v dokon&#x10D;en&#xE9; h&#x159;e (nap&#x159;. ohe&#x148; z\nrakety, st&#x159;ela) nebude p&#x159;i kolizi s lod&#xED; d&#x11B;lat nic,\ntak&#x17E;e metoda <code>SpaceObject.hit_by_spaceship</code>\nby nem&#x11B;la d&#x11B;lat nic (mus&#xED; jen existovat).\nJen asteroid lo&#x10F; rozbije, tak&#x17E;e p&#x159;edefinuj\n<code>Asteroid.hit_by_spaceship</code>, aby\nzavolala <code>delete</code> lodi.</p>\n<p>Proto&#x17E;e lod&#xED; m&#x16F;&#x17E;e b&#xFD;t v na&#x161;&#xED; h&#x159;e obecn&#x11B; v&#xED;ce, mus&#xED; asteroid\nv&#x11B;d&#x11B;t, se kterou lod&#xED; se srazil, aby ji mohl rozb&#xED;t.\nMetoda <code>hit_by_spaceship</code> by tedy na to m&#x11B;la m&#xED;t argument.</p>\n</li>\n</ul>\n<p>Povedlo se? Kone&#x10D;n&#x11B; se d&#xE1; prohr&#xE1;t?\n&#x10C;as to v&#x161;echno zkontrolovat, d&#xE1;t do Gitu a m&#x16F;&#x17E;eme pokra&#x10D;ovat!</p>\n<h2>&#xDA;tok</h2>\n<p>Te&#x10F; zkus&#xED;me asteroidy rozb&#xED;jet.</p>\n<ul>\n<li>Raketka um&#xED; jednou za 0,3 s vyst&#x159;elit laser.\nUlo&#x17E; si pro ka&#x17E;dou raketku (jako atribut) &#x10D;&#xED;slo,\nkter&#xE9; po ka&#x17E;d&#xE9;m v&#xFD;st&#x159;elu nastav na 0,3\na pak ho v metod&#x11B; <code>tick</code> nech klesat o 1 za vte&#x159;inu.\nKdy&#x17E; bude z&#xE1;porn&#xE9;, m&#x16F;&#x17E;e hr&#xE1;&#x10D; vyst&#x159;elit znovu.</li>\n<li>Kdy&#x17E; hr&#xE1;&#x10D; dr&#x17E;&#xED; mezern&#xED;k a m&#x16F;&#x17E;e vyst&#x159;elit, vyst&#x159;el&#xED;.\nVe h&#x159;e se to projev&#xED; tak, &#x17E;e se p&#x159;id&#xE1; objekt nov&#xE9; t&#x159;&#xED;dy <code>Laser</code>.\nZa&#x10D;ne na sou&#x159;adnic&#xED;ch raketky, s nato&#x10D;en&#xED;m raketky\na s rychlost&#xED; raketky plus n&#x11B;co nav&#xED;c ve sm&#x11B;ru nato&#x10D;en&#xED;.</li>\n<li>Ka&#x17E;d&#xFD; objekt t&#x159;&#xED;dy <code>Laser</code> si &#x201E;pamatuje&#x201C;,\njak dlouho je&#x161;t&#x11B; bude ve h&#x159;e.\nNa za&#x10D;&#xE1;tku se tohle &#x10D;&#xED;slo nastav&#xED; tak, aby p&#x159;elet&#x11B;l\nzhruba n&#x11B;co v&#xED;c ne&#x17E; jednu obrazovku.\nKdy&#x17E; dojde &#x10D;as, <code>Laser</code> zmiz&#xED;.</li>\n<li>Ve sv&#xE9; metod&#x11B; <code>tick</code> laser projde\nv&#x161;echny objekty, a pokud se s n&#x11B;kter&#xFD;m p&#x159;ekr&#xFD;v&#xE1;,\ntak na n&#x11B;m zavol&#xE1; metodu <code>hit_by_laser</code>.\nU v&#x11B;t&#x161;iny objekt&#x16F; tahle metoda ned&#x11B;l&#xE1; nic,\njen asteroidy bude rozb&#xED;jet.</li>\n<li><p>Kdy&#x17E; se laser dotkne asteroidu, asteroid se\nrozd&#x11B;l&#xED; na dva men&#x161;&#xED; (nebo, je-li u&#x17E; p&#x159;&#xED;li&#x161; mal&#xFD;, zmiz&#xED; &#xFA;pln&#x11B;).</p>\n<p>Rychlosti nov&#xFD;ch asteroid&#x16F; si m&#x16F;&#x17E;e&#x161; nastavit\npodle sebe &#x2013; d&#x16F;le&#x17E;it&#xE9; je jen, aby ka&#x17E;d&#xFD; men&#x161;&#xED;\nasteroid let&#x11B;l jinam.\nV&#x11B;t&#x161;inou b&#xFD;vaj&#xED; nov&#xE9; asteroidy rychlej&#x161;&#xED; ne&#x17E; ten p&#x16F;vodn&#xED;.</p>\n</li>\n<li>A to je v&#x161;e! M&#xE1;&#x161; funk&#x10D;n&#xED; hru!</li>\n</ul>\n<p>Povedlo se? D&#xE1; se i vyhr&#xE1;t? &#x10C;as to v&#x161;echno d&#xE1;t do Gitu!</p>\n<h2>Dokon&#x10D;en&#xED; a roz&#x161;&#xED;&#x159;en&#xED;</h2>\n<p>Chce&#x161;-li ve h&#x159;e pokra&#x10D;ovat, tady jsou dal&#x161;&#xED; n&#xE1;pady.\nM&#x16F;&#x17E;e&#x161; je d&#x11B;lat v jak&#xE9;mkoli po&#x159;ad&#xED; &#x2013; nebo si vymysli\nvlastn&#xED; roz&#x161;&#xED;&#x159;en&#xED;!</p>\n<ul>\n<li><p>Je hra p&#x159;&#xED;li&#x161; t&#x11B;&#x17E;k&#xE1;?</p>\n<p>M&#x16F;&#x17E;e&#x161; p&#x159;idat &#x17E;ivoty: na za&#x10D;&#xE1;tku jsou t&#x159;i,\na dokud n&#x11B;jak&#xFD; zb&#xFD;v&#xE1;, raketka se po z&#xE1;sahu\nasteroidem objev&#xED; znovu uprost&#x159;ed,\ns nulovou rychlost&#xED;.\nHra by taky p&#x159;i tomto &#x201E;restartu&#x201D; m&#x11B;la ignorovat\ndr&#x17E;en&#xE9; kl&#xE1;vesy, dokud je hr&#xE1;&#x10D; znovu nezm&#xE1;&#x10D;kne\n(nejl&#xE9;pe pomoc&#xED; <code>pressed_keys.clear()</code>).</p>\n<p>Po&#x10D;et n&#xE1;hradn&#xED;ch lod&#xED; m&#x16F;&#x17E;e&#x161; uk&#xE1;zat ikonkami\nna spodku obrazovky.</p>\n<p><strong>Bonus:</strong> N&#x11B;kolik vte&#x159;in po\n&#x201E;restartu&#x201D; m&#x16F;&#x17E;e b&#xFD;t raketka nezni&#x10D;iteln&#xE1;,\naby m&#x11B;la &#x10D;as odlet&#x11B;t, kdy&#x17E; je zrovna uprost&#x159;ed\nok&#xFD;nka asteroid.</p>\n</li>\n<li><p>Je hra p&#x159;&#xED;li&#x161; lehk&#xE1;?</p>\n<p>P&#x159;idej &#xFA;rovn&#x11B;: a&#x17E; hr&#xE1;&#x10D; vyst&#x159;&#xED;l&#xED; v&#x161;echny asteroidy,\npostoup&#xED; na dal&#x161;&#xED; &#xFA;rove&#x148;, kde je asteroid&#x16F; v&#xED;c ne&#x17E; v t&#xE9; p&#x159;edchoz&#xED;.</p>\n<p>&#x10C;&#xED;slo &#xFA;rovn&#x11B; m&#x16F;&#x17E;e&#x161; uk&#xE1;zat pomoc&#xED;\n<a href=\"http://pyglet.readthedocs.org/en/latest/programming_guide/text.html\">pyglet.text.Label</a>.</p>\n</li>\n<li><p>Je pozad&#xED; p&#x159;&#xED;li&#x161; &#x10D;ern&#xE9;?</p>\n<p>V sad&#x11B; obr&#xE1;zk&#x16F; v adres&#xE1;&#x159;i <code>Backgrounds</code>\nsi vyber pozad&#xED;, a vytapetuj s n&#xED;m cel&#xFD; vesm&#xED;r.</p>\n</li>\n<li><p>Je hra moc stroh&#xE1;?</p>\n<p>P&#x159;idej ohe&#x148; a exploze!\nChovaj&#xED; se podobn&#x11B; jako <code>Laser</code>,\njen nic neni&#x10D;&#xED; a m&#x16F;&#x17E;ou t&#x159;eba m&#x11B;nit barvu podle\ntoho, jak dlouho u&#x17E; jsou ve h&#x159;e.</p>\n<p>Na efekty m&#x16F;&#x17E;e&#x161; pou&#x17E;&#xED;t obr&#xE1;zky\n<a href=\"http://opengameart.org/content/smoke-particle-assets\">&#x201E;Smoke particle assets&#x201D;</a>,\nkter&#xE9; nakreslil op&#x11B;t <a href=\"http://kenney.nl\">Kenney Vleugels</a>.\nDoporu&#x10D;uji &#x201E;White Puff&#x201D;, kter&#xE9; m&#x16F;&#x17E;e&#x161; zmen&#x161;it\n(nap&#x159;. <code>sprite.scale = 1/10</code>),\np&#x159;ibarvit\n(nap&#x159;. <code>sprite.color = 255, 100, 0</code>)\nnebo &#x10D;&#xE1;ste&#x10D;n&#x11B; zpr&#x16F;hlednit\n(nap&#x159;. <code>sprite.opacity = 100</code>).</p>\n<p>Doporu&#x10D;uji si na efekty ud&#x11B;lat nov&#xFD; <code>Batch</code>\na vykreslit ho p&#x159;ed t&#xED;m hlavn&#xED;m, aby efekty\nnep&#x159;ekr&#xFD;valy hern&#xED; objekty.</p>\n</li>\n<li><p>Nepozn&#xE1;&#x161;, kdy jsi prohr&#xE1;la nebo vyhr&#xE1;la?</p>\n<p>Na konci m&#x16F;&#x17E;e&#x161; uk&#xE1;zat velik&#xFD; n&#xE1;pis GAME OVER nebo WINNER.</p>\n</li>\n<li><p>Nud&#xED;&#x161; se?</p>\n<p>V p&#x16F;vodn&#xED; h&#x159;e se ob&#x10D;as objev&#xED; UFO, kter&#xE9; ob&#x10D;as\nvyst&#x159;el&#xED; na m&#xED;sto, kde je pr&#xE1;v&#x11B; hr&#xE1;&#x10D;ova raketka,\ntak&#x17E;e pokud hr&#xE1;&#x10D; stoj&#xED; po&#x159;&#xE1;d na jednom m&#xED;st&#x11B; a\njenom se to&#x10D;&#xED; dokola, UFO ho sest&#x159;el&#xED;.\nM&#x16F;&#x17E;e&#x161; zkusit dod&#x11B;lat t&#x159;&#xED;du <code>Ufo</code>\na z <code>Laser</code> pod&#x11B;dit <code>ShipLaser</code> a <code>UfoLaser</code>.</p>\n</li>\n</ul>\n<p>Povedlo se? Vypad&#xE1; to a chov&#xE1; se to profesion&#xE1;ln&#x11B;?\n&#x10C;as to v&#x161;echno d&#xE1;t do Gitu!</p>\n\n\n        "
    }
  }
}