<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Blog Small Water</title>
    <link>https://smallwater.eu/blog/</link>
    <description>Aplikácie, webové nástroje a hry od malej softvérovej firmy zo Slovenska.</description>
    <language>sk</language>
    <atom:link href="https://smallwater.eu/blog/feed.xml" rel="self" type="application/rss+xml" />
    <lastBuildDate>Mon, 09 Nov 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>Ako spraviť LOD pre tisíce animovaných postáv v Unity</title>
      <link>https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/</guid>
      <pubDate>Mon, 09 Nov 2026 00:00:00 GMT</pubDate>
      <category>Tower Defense</category>
      <description>Návod na LOD pre dav v Unity s vertex animation textures: prečo rozhodujú vrcholy, nie trojuholníky, LOD ako nová sieť a výber úrovne po entitách.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/cover.webp" /><img src="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/cover.jpg" alt="Tri verzie tej istej postavy kostry s klesajúcim počtom trojuholníkov zľava doprava" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#preco-generovany-lod-pri-dave-nepomoze" data-ga="blog_toc_clicked" data-ga-heading-id="preco-generovany-lod-pri-dave-nepomoze">Prečo generovaný LOD pri dave nepomôže</a></li><li><a href="#pocitajte-vrcholy-nie-trojuholniky" data-ga="blog_toc_clicked" data-ga-heading-id="pocitajte-vrcholy-nie-trojuholniky">Počítajte vrcholy, nie trojuholníky</a></li><li><a href="#krok-1-znizte-vrcholy-v-lod-0" data-ga="blog_toc_clicked" data-ga-heading-id="krok-1-znizte-vrcholy-v-lod-0">Krok 1: Znížte vrcholy v LOD 0</a></li><li><a href="#krok-2-lod-1-zo-zjednodusenia-a-so-zdielanou-texturou" data-ga="blog_toc_clicked" data-ga-heading-id="krok-2-lod-1-zo-zjednodusenia-a-so-zdielanou-texturou">Krok 2: LOD 1 zo zjednodušenia a so zdieľanou textúrou</a></li><li><a href="#krok-3-lod-2-ako-nova-siet-pripojena-k-originalu" data-ga="blog_toc_clicked" data-ga-heading-id="krok-3-lod-2-ako-nova-siet-pripojena-k-originalu">Krok 3: LOD 2 ako nová sieť pripojená k originálu</a></li><li><a href="#krok-4-diery-kontrolujte-strojom" data-ga="blog_toc_clicked" data-ga-heading-id="krok-4-diery-kontrolujte-strojom">Krok 4: Diery kontrolujte strojom</a></li><li><a href="#krok-5-vyberajte-uroven-pre-kazdu-postavu-zvlast" data-ga="blog_toc_clicked" data-ga-heading-id="krok-5-vyberajte-uroven-pre-kazdu-postavu-zvlast">Krok 5: Vyberajte úroveň pre každú postavu zvlášť</a></li><li><a href="#vysledky-a-limity" data-ga="blog_toc_clicked" data-ga-heading-id="vysledky-a-limity">Výsledky a limity</a></li></ol></nav><p>LOD pre tisíce animovaných postáv v Unity postavte podľa počtu vrcholov, nie trojuholníkov: pri vertex animation textures sa animácia počíta na každý vrchol. Nižšie úrovne pripravte ručne, vrcholy najnižšej úrovne pripojte k povrchu originálu a úroveň vyberajte pre každú postavu zvlášť. V našej tower defense hre pre PC to pri 3 000 nepriateľoch zvýšilo FPS pri zábere do davu z 62 na 120.</p>
<p>Postup sa týka hier, kde sa na obrazovke pohybujú tisíce postáv naraz, teda tower defense, stratégií a survival hier. Naša hra je postavená v Unity s ECS a nájdete ju v <a href="https://smallwater.eu/#referencie">referenciách</a>. LOD znamená <a href="https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics)" target="_blank" rel="noopener">level of detail</a>: vzdialená postava sa kreslí z jednoduchšej siete. Obrázky sú skutočné siete nášho nepriateľa kostry, nie zábery z hry.</p>
<h2 id="preco-generovany-lod-pri-dave-nepomoze"><a class="heading-anchor" href="#preco-generovany-lod-pri-dave-nepomoze" aria-hidden="true" tabindex="-1">#</a>Prečo generovaný LOD pri dave nepomôže</h2>
<p>Unity vie úrovne LOD vygenerovať pri importe modelu (funkcia <a href="https://docs.unity3d.com/6000.2/Documentation/Manual/lod/mesh-lod-introduction.html" target="_blank" rel="noopener">Mesh LOD</a>). Pre tisíce postáv to má dva problémy.</p>
<p>Prvý je vzhľad. Automatické zjednodušenie trhalo tenké končatiny a rozbíjalo UV, teda mapovanie textúry na sieť.</p>
<p>Druhý je cena animácie. Štandardné kostrové animácie sú pre tisíce entít príliš drahé, preto sa namiesto nich používajú vertex animation textures (VAT). VAT je textúra, kde každý stĺpec patrí jednému vrcholu a každý riadok jednej snímke animácie. Shader si polohu vrcholu prečíta z textúry, takže cena animácie závisí od počtu čítaných vrcholov, a to aj pri kreslení tieňov.</p>
<p>Hornú hranicu ukazuje test s vynútenou generovanou úrovňou na všetkých postavách (Unity vygeneruje aj úrovne 3 a 4, build, 1080p, 3 000 nepriateľov, tiene zapnuté):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Vynútená úroveň</th>
<th>Trojuholníky</th>
<th>FPS</th>
</tr>
</thead>
<tbody><tr>
<td>LOD 0</td>
<td>11 miliónov</td>
<td>72</td>
</tr>
<tr>
<td>LOD 1</td>
<td>6 miliónov</td>
<td>96</td>
</tr>
<tr>
<td>LOD 2</td>
<td>3,68 milióna</td>
<td>120</td>
</tr>
<tr>
<td>LOD 4</td>
<td>1,43 milióna</td>
<td>201</td>
</tr>
</tbody></table>
</div>
<p>Priestor na zrýchlenie je veľký. Treba ho získať bez viditeľných dier a skokov medzi úrovňami.</p>
<h2 id="pocitajte-vrcholy-nie-trojuholniky"><a class="heading-anchor" href="#pocitajte-vrcholy-nie-trojuholniky" aria-hidden="true" tabindex="-1">#</a>Počítajte vrcholy, nie trojuholníky</h2>
<p>Polovičný počet trojuholníkov sám osebe nepomôže. LOD s polovicou trojuholníkov a 996 z pôvodných 1 521 vrcholov ušetril 1,9 ms na snímku, LOD s 217 vrcholmi 6,4 ms. Pri každej úrovni sledujte oba počty.</p>
<p>Vrcholov býva zbytočne veľa. Vrchol sa zdvojí na každom šve UV, lebo jedna poloha potom potrebuje dve súradnice textúry. Naše modely z AI generátora mali UV rozsekané na stovky ostrovov: kostra mala 1 521 vrcholov, ale len 608 rôznych kombinácií polohy a normály.</p>
<h2 id="krok-1-znizte-vrcholy-v-lod-0"><a class="heading-anchor" href="#krok-1-znizte-vrcholy-v-lod-0" aria-hidden="true" tabindex="-1">#</a>Krok 1: Znížte vrcholy v LOD 0</h2>
<p>Prvá úspora nestojí žiadny detail. Spojte vrcholy so zhodnou polohou a podobnou normálou (rozdiel do 25°), rozbaľte UV nanovo so švami len na ostrých hranách (nad 70°) a textúru prepočítajte z pôvodnej po trojuholníkoch. Kostra klesla z 1 521 na 1 090 vrcholov, u ostatných typov nepriateľov to bolo o 19 až 29 % menej, pri rovnakom tvare.</p>
<p>Do VAT potom zapisujte len stĺpce vrcholov, ktoré niektorá úroveň skutočne používa. Ušetrilo to 40 až 60 % pamäte animácií.</p>
<h2 id="krok-2-lod-1-zo-zjednodusenia-a-so-zdielanou-texturou"><a class="heading-anchor" href="#krok-2-lod-1-zo-zjednodusenia-a-so-zdielanou-texturou" aria-hidden="true" tabindex="-1">#</a>Krok 2: LOD 1 zo zjednodušenia a so zdieľanou textúrou</h2>
<p>LOD 1 je zjednodušený LOD 0 na približne 52 % trojuholníkov. Zjednodušujte po kúskoch (každá kombinácia kosti a súvislej časti siete) a vrcholy na hraniciach kúskov zamknite. Hlave dajte väčší rozpočet, lebo silueta hlavy je to, čo oko v dave rozozná.</p>
<p>Dôležité je ponechať textúru LOD 0. Úroveň s vlastnou textúrou alebo iným tvarom je pri prechode viditeľná aj pri podobnej siluete.</p>
<p>Pod zhruba 50 % trojuholníkov zjednodušenie z existujúcich vrcholov trhá tenké časti a samostatné kúsky. Nižšie treba ísť inak.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/levels.webp" /><img src="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/levels.png" alt="Tri verzie postavy kostry vedľa seba s klesajúcim počtom trojuholníkov" width="2400" height="1240" loading="lazy" decoding="async" /></picture><figcaption>Tá istá kostra v troch úrovniach: 873, 448 a 330 trojuholníkov, 1 090, 705 a 520 vrcholov.</figcaption></figure>
<h2 id="krok-3-lod-2-ako-nova-siet-pripojena-k-originalu"><a class="heading-anchor" href="#krok-3-lod-2-ako-nova-siet-pripojena-k-originalu" aria-hidden="true" tabindex="-1">#</a>Krok 3: LOD 2 ako nová sieť pripojená k originálu</h2>
<p>Najnižšia úroveň je nová uzavretá sieť. Originálu pridajte vrstvu hrúbky, spravte voxel remesh a zjednodušte na cieľový počet (u nás 200 až 460 trojuholníkov podľa typu nepriateľa). Výslednú sieť oblepte na povrch originálu a UV spravte automaticky. Textúru zapečte z originálu v rovnakej hustote ako LOD 0, aby sedeli aj mipmapy.</p>
<p>Nová sieť nemá váhy animácie. Každý jej vrchol preto pripojte k najbližšiemu trojuholníku originálu. Uložte si tri stĺpce toho trojuholníka a barycentrické váhy (podiely, ktoré určujú polohu bodu v trojuholníku).</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/attach.webp" /><img src="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/attach.png" alt="Schéma: červený vrchol zjednodušenej siete je prerušovanými čiarami pripojený k trom vrcholom pôvodného trojuholníka" width="1800" height="840" loading="lazy" decoding="async" /></picture><figcaption>Vrchol nižšej úrovne (červený) sa hýbe podľa troch vrcholov najbližšieho trojuholníka originálu.</figcaption></figure>
<p>Stĺpec pripojeného vrcholu vo VAT je vážený súčet polôh a normál tých troch vrcholov v každej snímke. Shader nič nerieši, pripojený vrchol je obyčajný ďalší stĺpec.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">pre každú snímku f:
    VAT[pripojený, f] = w0 * VAT[a, f] + w1 * VAT[b, f] + w2 * VAT[c, f]</code></pre>
<h2 id="krok-4-diery-kontrolujte-strojom"><a class="heading-anchor" href="#krok-4-diery-kontrolujte-strojom" aria-hidden="true" tabindex="-1">#</a>Krok 4: Diery kontrolujte strojom</h2>
<p>Obrázky z modelovacieho nástroja diery spoľahlivo neukážu. Kontrola stačí jednoduchá: vykreslite originál a kandidáta z 11 smerov v rozlíšení 120 px a porovnajte siluety.</p>
<p>Za dieru počítajte pixely originálu vo vnútri obrysu kandidáta, ktoré kandidát nepokryje, a pixely kandidáta viac ako 2 px mimo obrysu originálu. U nás je limit 1 % dier a 4 až 6 % straty. Kontrola musí rešpektovať odstraňovanie zadných stien podľa materiálu. Limity sme nastavili podľa toho, kde boli prechody v hre okom viditeľné.</p>
<h2 id="krok-5-vyberajte-uroven-pre-kazdu-postavu-zvlast"><a class="heading-anchor" href="#krok-5-vyberajte-uroven-pre-kazdu-postavu-zvlast" aria-hidden="true" tabindex="-1">#</a>Krok 5: Vyberajte úroveň pre každú postavu zvlášť</h2>
<p>Prepínanie úrovne pre celú mapu naraz je viditeľné. Keď vyberáte po entitách, prechody sa rozložia a v dave zaniknú.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Celá mapa naraz</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/select-global.webp" /><img src="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/select-global.png" alt="Dav bodiek v rovnakej farbe, všetky postavy majú naraz rovnakú úroveň" width="1120" height="840" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Po entitách</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/select-entity.webp" /><img src="https://smallwater.eu/blog/unity-lod-vertex-animation-crowd/select-entity.png" alt="Dav bodiek v pásoch podľa vzdialenosti od kamery, blízke červené, vzdialené sivé" width="1120" height="840" loading="lazy" decoding="async" /></picture></div></div><figcaption>Pohľad zhora, kamera dole. Farba je úroveň LOD: červená 0, oranžová 1, sivá 2.</figcaption></figure>
<p>Upozornenie k verzii: dokumentácia Unity 6.2 uvádza, že Entities Graphics vždy používa LOD 0. Výber po entitách podľa vzorca nižšie používame v Unity 6.6, pred nasadením si overte správanie vašej verzie:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">LOD = log2(d * C / D) * sklon + posun
C = prah * 2 * tan(fov / 2) / vyska_obrazu</code></pre>
<p>Tu je <code>d</code> vzdialenosť od kamery a <code>D</code> priemer svetových hraníc entity. Sklon a posun zvoľte tak, aby úroveň 1 začínala pri 93,3 a úroveň 2 pri 167,9 metra (kamera s fov 30° a 1440 px). Globálny prah LOD nastavte na výšku obrazu / 1440, potom vzdialenosti nezávisia od rozlíšenia.</p>
<p>Dve chyby, na ktoré si dajte pozor:</p>
<ul>
<li><strong>Mierka.</strong> Priemer <code>D</code> počítajte zo svetových hraníc, teda s mierkou prefabu. Siete pre VAT bývajú v malých jednotkách a prefab ich zväčší 2-krát až 4-krát. Bez toho vám hra celý čas kreslí LOD 0 a nič to nehlási. Správnosť overte počtom trojuholníkov v bežnom režime oproti vynútenému LOD. Pootočenie entity zväčšuje osovo zarovnané hranice, preto u nás priemer násobíme koeficientom 1,22.</li>
<li><strong>Bakovanie.</strong> Krivka výberu sa zapeká pri bakovaní SubScene a zmenu v sieti bakovanie nezachytí. Po jej úprave vynúťte nové bakovanie SubScene.</li>
</ul>
<h2 id="vysledky-a-limity"><a class="heading-anchor" href="#vysledky-a-limity" aria-hidden="true" tabindex="-1">#</a>Výsledky a limity</h2>
<p>Build pre macOS, 2560 × 1440, 3 000 nepriateľov, tiene zapnuté, hra zastavená po prehre (animácie VAT stále bežia, takže meranie platí pre grafiku):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Scéna</th>
<th>Pred</th>
<th>Po</th>
</tr>
</thead>
<tbody><tr>
<td>Celá mapa</td>
<td>90 FPS</td>
<td>130 FPS</td>
</tr>
<tr>
<td>Priblíženie do davu</td>
<td>62 FPS</td>
<td>120 FPS</td>
</tr>
</tbody></table>
</div>
<p>Na <a href="https://www.steamdeck.com/en/tech" target="_blank" rel="noopener">Steam Decku</a> (1280 × 800, 8 výpočtových jednotiek RDNA 2) je to 75 až 80 FPS na celej mape. Najbližší záber do davu má pri vysokej kvalite tieňov 33 FPS, bez tieňov 64. Tiene sú teda najväčšia zostávajúca položka: LOD znižuje aj ich cenu, ale vypnutie stále spraví veľký skok.</p>
<p>Čo LOD nerieši: kontrola dier beží v T-póze, takže natiahnutie siete v extrémnych pózach neodhalí. Jeden typ nepriateľa ešte limit dier v LOD 2 presahuje. Výpočet pohybu hordy LOD neovplyvní, ten rieši článok o <a href="https://smallwater.eu/blog/unity-dots-horde-navigation/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/unity-lod-vertex-animation-crowd/" data-ga-destination="/blog/unity-dots-horde-navigation/" data-ga-placement="inline">navigácii hordy v Unity DOTS</a> a reakcie na výbuchy <a href="https://smallwater.eu/blog/fake-ragdoll-unity-dots/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/unity-lod-vertex-animation-crowd/" data-ga-destination="/blog/fake-ragdoll-unity-dots/" data-ga-placement="inline">fake ragdoll</a>.</p>
<p>Potrebujete pomôcť s výkonom hry v Unity alebo simuláciou davu? <a href="https://smallwater.eu/#kontakt">Napíšte nám</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako pridať glow a 2D svetlá do starej 2D hry v Unity URP</title>
      <link>https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Dreamica</category>
      <description>Ako v Unity URP dať starej 2D hre selektívny glow cez Bloom a HDR, dynamické 2D svetlá a ostré UI pre dnešné displeje. Postup na príklade hry Dreamica.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/cover.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/cover.jpg" alt="Tri zábery z hry Dreamica: duch prenasleduje bublinu, veverica s delom a žiariace lasery v lese" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#preco-nestaci-stary-celoobrazovkovy-glow" data-ga="blog_toc_clicked" data-ga-heading-id="preco-nestaci-stary-celoobrazovkovy-glow">Prečo nestačí starý celoobrazovkový glow</a></li><li><a href="#krok-1-prepnite-projekt-na-urp" data-ga="blog_toc_clicked" data-ga-heading-id="krok-1-prepnite-projekt-na-urp">Krok 1: Prepnite projekt na URP</a><ol><li><a href="#siva-obrazovka-nastavte-zasobnik-kamier" data-ga="blog_toc_clicked" data-ga-heading-id="siva-obrazovka-nastavte-zasobnik-kamier">Sivá obrazovka: nastavte zásobník kamier</a></li><li><a href="#sprity-ignoruju-svetlo-prepnite-na-2d-renderer" data-ga="blog_toc_clicked" data-ga-heading-id="sprity-ignoruju-svetlo-prepnite-na-2d-renderer">Sprity ignorujú svetlo: prepnite na 2D renderer</a></li><li><a href="#castice-a-stare-pluginy" data-ga="blog_toc_clicked" data-ga-heading-id="castice-a-stare-pluginy">Častice a staré pluginy</a></li></ol></li><li><a href="#krok-2-selektivny-glow-cez-hdr-farbu-a-bloom-s-prahom-1" data-ga="blog_toc_clicked" data-ga-heading-id="krok-2-selektivny-glow-cez-hdr-farbu-a-bloom-s-prahom-1">Krok 2: Selektívny glow cez HDR farbu a Bloom s prahom 1</a><ol><li><a href="#emisna-mapa-ziari-len-cast-spritu" data-ga="blog_toc_clicked" data-ga-heading-id="emisna-mapa-ziari-len-cast-spritu">Emisná mapa: žiari len časť spritu</a></li><li><a href="#pulzovanie-v-expozicnych-stupnoch" data-ga="blog_toc_clicked" data-ga-heading-id="pulzovanie-v-expozicnych-stupnoch">Pulzovanie v expozičných stupňoch</a></li></ol></li><li><a href="#krok-3-dynamicke-2d-svetlo-okolo-hraca" data-ga="blog_toc_clicked" data-ga-heading-id="krok-3-dynamicke-2d-svetlo-okolo-hraca">Krok 3: Dynamické 2D svetlo okolo hráča</a><ol><li><a href="#rovnaky-shader-na-nadvazujucich-plochach" data-ga="blog_toc_clicked" data-ga-heading-id="rovnaky-shader-na-nadvazujucich-plochach">Rovnaký shader na nadväzujúcich plochách</a></li><li><a href="#zablesk-prechod-na-vrchol-namiesto-pripocitania" data-ga="blog_toc_clicked" data-ga-heading-id="zablesk-prechod-na-vrchol-namiesto-pripocitania">Záblesk: prechod na vrchol namiesto pripočítania</a></li></ol></li><li><a href="#krok-4-ostre-ui-pre-dnesne-displeje" data-ga="blog_toc_clicked" data-ga-heading-id="krok-4-ostre-ui-pre-dnesne-displeje">Krok 4: Ostré UI pre dnešné displeje</a></li><li><a href="#limity-nekomprimovane-textury" data-ga="blog_toc_clicked" data-ga-heading-id="limity-nekomprimovane-textury">Limity: nekomprimované textúry</a></li></ol></nav><p>Stará 2D hra v Unity na vstavanom render pipeline vyzerá na dnešnom telefóne ploško: svetlo chýba, glow buď nie je, alebo rozžiari celý obraz aj s UI, a sprity rozhrania sú rozmazané. V Unity URP to vyriešite tromi vecami: Bloomom s prahom 1 a HDR farbou len na objektoch, ktoré majú žiariť, 2D svetlami z 2D renderera a UI s väčšími spritmi, ktoré rešpektuje bezpečnú oblasť displeja.</p>
<p>Postup ukazujeme na našej hre Dreamica (<a href="https://smallwater.eu/dreamica-ios">App Store</a>, <a href="https://smallwater.eu/dreamica-android">Google Play</a>). Je to pádová adventúra, v ktorej prstom kreslíte čiary a nimi vediete bublinu so spiacim dieťaťom cez štyri svety. Pôvodne vznikla v Unity 5.3, UI bolo v plugine NGUI, glow obstarával plugin MK Glow a grafika bola pripravená pre iPhone 7 s displejom 1334 × 750 pixelov. Hrateľnosť ostala rovnaká, zmenila sa len grafika.</p>
<p>Starú hru často otvoríte kvôli obchodu, nie kvôli grafike. Google Play od augusta 2019 <a href="https://android-developers.googleblog.com/2019/01/get-your-apps-ready-for-64-bit.html" target="_blank" rel="noopener">vyžaduje 64-bitové verzie</a> a hra, ktorá má len 32-bitový build, z neho vypadne. Presne tak dopadla aj Dreamica. Keď už projekt prenášate do Unity 6, grafika je relatívne lacný bonus.</p>
<h2 id="preco-nestaci-stary-celoobrazovkovy-glow"><a class="heading-anchor" href="#preco-nestaci-stary-celoobrazovkovy-glow" aria-hidden="true" tabindex="-1">#</a>Prečo nestačí starý celoobrazovkový glow</h2>
<p>Staršie glow pluginy fungujú ako celoobrazovkový efekt. Rozžiaria všetko svetlé vrátane ikoniek tlačidiel v menu. V Dreamice ikonky pod MK Glow v Unity 6 takmer zmizli. Oficiálny balík Post Processing Stack v2 je náhrada len pre vstavaný pipeline, v URP sa už neaplikuje. Ak chcete glow, ktorý riadite po materiáloch, a k tomu dynamické 2D svetlá, prejdite na URP a použite Bloom, ktorý je v ňom zabudovaný.</p>
<h2 id="krok-1-prepnite-projekt-na-urp"><a class="heading-anchor" href="#krok-1-prepnite-projekt-na-urp" aria-hidden="true" tabindex="-1">#</a>Krok 1: Prepnite projekt na URP</h2>
<p>Po prepnutí na URP narazíte v starej 2D hre takmer isto na dve pasce. Obe sa prejavujú tak, že obraz vyzerá zle alebo presne ako predtým, hoci shadery sú v poriadku.</p>
<h3 id="siva-obrazovka-nastavte-zasobnik-kamier"><a class="heading-anchor" href="#siva-obrazovka-nastavte-zasobnik-kamier" aria-hidden="true" tabindex="-1">#</a>Sivá obrazovka: nastavte zásobník kamier</h3>
<p>Ak herné okno v Editore ukazuje len sivú plochu, ale scéna v editore aj jednorazový render kamery vyzerajú správne, chyba nie je v shaderoch, ale v skladaní kamier. Staré hry mávajú dve kamery, jednu na svet a jednu na UI, a UI kamera maže len hĺbku. To je spôsob, ako sa vrstvia kamery vo vstavanom pipeline, a URP ho nepodporuje. Potrebuje explicitný zásobník kamier (camera stack): jedna kamera je základná (Base), ostatné sú prekrytie (Overlay) zaradené do jej zásobníka. Nastavte ho v každej scéne, kde sa kamery vrstvia.</p>
<h3 id="sprity-ignoruju-svetlo-prepnite-na-2d-renderer"><a class="heading-anchor" href="#sprity-ignoruju-svetlo-prepnite-na-2d-renderer" aria-hidden="true" tabindex="-1">#</a>Sprity ignorujú svetlo: prepnite na 2D renderer</h3>
<p>Sprity s osvetleným shaderom nereagujú na žiadne svetlo, ak beží bežný URP renderer. Ten 2D svetlá vôbec nepočíta. Prepnite na 2D renderer. Aj ten však osvetlenie nekreslí, kým v scéne nie je aspoň jedno 2D svetlo. Keď niečo po zmene vyzerá presne ako predtým, najprv skontrolujte, ktorý renderer beží.</p>
<h3 id="castice-a-stare-pluginy"><a class="heading-anchor" href="#castice-a-stare-pluginy" aria-hidden="true" tabindex="-1">#</a>Častice a staré pluginy</h3>
<p>Častice na starých shaderoch sa pod 2D rendererom kreslia čierne alebo vôbec. Dajte im nový jednoduchý shader.</p>
<p>Pozor aj na Asset Store. Pri obnovení assetov vie Editor sám aplikovať „aktualizáciu“ starého pluginu, ktorá pridá metadáta a premenuje shadery. Materiály hľadajú shader podľa mena, takže sa potichu rozbijú. Po každom väčšom obnovení assetov v starom projekte si vo verziovacom systéme pozrite všetky zmenené súbory, aj tie, na ktorých práve nerobíte.</p>
<h2 id="krok-2-selektivny-glow-cez-hdr-farbu-a-bloom-s-prahom-1"><a class="heading-anchor" href="#krok-2-selektivny-glow-cez-hdr-farbu-a-bloom-s-prahom-1" aria-hidden="true" tabindex="-1">#</a>Krok 2: Selektívny glow cez HDR farbu a Bloom s prahom 1</h2>
<p>Cieľ je, aby žiarili vybrané objekty, v Dreamice nakreslená čiara, lasery, iskry, svätojánske mušky a runy, ale nie pozadie a UI. Bloom v URP rozmaže a pripočíta všetko, čo je jasnejšie ako jeho <a href="https://docs.unity3d.com/6/Documentation/Manual/urp/post-processing-bloom.html" target="_blank" rel="noopener">prah</a>. Nastavte prah na 1 a materiálom, ktoré majú žiariť, vytlačte farbu nad 1.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">shader čiary:
    farba = textúra × farba vrcholu × odtieň × intenzita    (intenzita = 6)

Bloom:
    prah = 1, sila = 0,3
    žiari len to, čo má po výpočte jas nad 1</code></pre>
<p>Pozadie a tlačidlá majú farby od 0 do 1, cez prah neprejdú a ostanú ostré. Tým zmizne aj problém s vybielenými ikonkami. Podmienkou je kamera s HDR. Bez neho sa farba oreže na 1 a nežiari nič. 2D renderer prepínač HDR v inšpektore kamery neukazuje, preto ho zapnite skriptom.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Nízky prah, celý obraz</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/glow-fullscreen.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/glow-fullscreen.jpg" alt="Celoobrazovkový glow: žiari čiara aj okraj tlačidla a svetlé časti pozadia" width="1440" height="840" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Prah 1, HDR materiál</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/glow-selective.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/glow-selective.jpg" alt="Selektívny glow: žiari len nakreslená čiara, tlačidlo a pozadie sú ostré" width="1440" height="840" loading="lazy" decoding="async" /></picture></div></div><figcaption>Ilustrácia princípu. Vľavo celoobrazovkový glow s nízkym prahom, vpravo žiari len materiál s intenzitou nad 1.</figcaption></figure>
<p>Keď je glow takto pod kontrolou, nemusí byť voliteľný. V Dreamice sme prepínač glow z nastavení zrušili, glow je súčasť vzhľadu.</p>
<h3 id="emisna-mapa-ziari-len-cast-spritu"><a class="heading-anchor" href="#emisna-mapa-ziari-len-cast-spritu" aria-hidden="true" tabindex="-1">#</a>Emisná mapa: žiari len časť spritu</h3>
<p>Ak má žiariť len časť spritu, pridajte mu emisnú mapu (emission map): druhú, čiernobielu textúru, ktorá hovorí, ktorá časť spritu žiari. Len tá časť dostane farbu nad 1, takže pri rune svieti jadro, nie celý obrys.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/rune-emission.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/rune-emission.png" alt="Runa, jej čiernobiela emisná mapa a výsledok so žiariacim jadrom" width="1800" height="600" loading="lazy" decoding="async" /></picture><figcaption>Sprite runy, emisná mapa (biela žiari) a výsledok po Bloome.</figcaption></figure>
<h3 id="pulzovanie-v-expozicnych-stupnoch"><a class="heading-anchor" href="#pulzovanie-v-expozicnych-stupnoch" aria-hidden="true" tabindex="-1">#</a>Pulzovanie v expozičných stupňoch</h3>
<p>Ak má žiarenie pulzovať, nenásobte farbu lineárne. HDR materiál môže mať jas okolo 2 na ôsmu a lineárne násobenie od 0 do 10 na ňom nie je vidieť vôbec. Pulz počítajte v expozičných stupňoch (EV), rovnako ako posuvník intenzity vo výbere HDR farby v Unity:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">farba = odtieň × 2 ^ ev,   ev kmitá medzi minimom a maximom</code></pre>
<h2 id="krok-3-dynamicke-2d-svetlo-okolo-hraca"><a class="heading-anchor" href="#krok-3-dynamicke-2d-svetlo-okolo-hraca" aria-hidden="true" tabindex="-1">#</a>Krok 3: Dynamické 2D svetlo okolo hráča</h2>
<p>K hráčovi dajte bodové 2D svetlo a okolo neho mäkkú žiaru. V Dreamice jedna sínusovka riadi naraz jas žiary, polomer svetla aj jeho intenzitu, takže pulzujú spolu. Farbu svetla môže určovať prostredie: v lese je biela, v močiari oranžová, v zime fialová, v pekle žltá. Pozadia a prekážky prepnite na osvetlené shadery, inak na svetlo nereagujú.</p>
<p>Krátke svetlá oživia udalosti. Keď vystrelí veverica s delom, rozsvieti sa jantárové svetlo s intenzitou 3, ktoré za 0,3 sekundy zhasne, a iskry z hlavne žiaria. Žiariť nemusí všetko: prach pri zmiznutí žaluďa je zámerne hnedý a nežiari.</p>
<h3 id="rovnaky-shader-na-nadvazujucich-plochach"><a class="heading-anchor" href="#rovnaky-shader-na-nadvazujucich-plochach" aria-hidden="true" tabindex="-1">#</a>Rovnaký shader na nadväzujúcich plochách</h3>
<p>Ak na seba nadväzujú dve pozadia, zdrojové obrázky majú rovnaký jas, a predsa je medzi nimi viditeľný šev, skontrolujte shadery. V Dreamice malo úvodné pozadie a nekonečné pozadie lesa každé iný shader. Jeden odtieň materiálu 0,733 ignoroval, druhý ho použil a stmavil les na 73 %. Riešenie je rovnaký shader na oboch plochách.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Dva shadery</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/seam-before.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/seam-before.jpg" alt="Hranica medzi dvoma pozadiami lesa, spodná časť je výrazne tmavšia" width="1024" height="1120" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Jeden shader</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/seam-after.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/seam-after.jpg" alt="Rovnaká hranica, obe pozadia majú rovnaký jas" width="1024" height="1120" loading="lazy" decoding="async" /></picture></div></div><figcaption>Hranica medzi úvodným pozadím a lesom. Vľavo spodná časť stmavená odtieňom 0,733, ktorý horný shader ignoruje.</figcaption></figure>
<h3 id="zablesk-prechod-na-vrchol-namiesto-pripocitania"><a class="heading-anchor" href="#zablesk-prechod-na-vrchol-namiesto-pripocitania" aria-hidden="true" tabindex="-1">#</a>Záblesk: prechod na vrchol namiesto pripočítania</h3>
<p>Pri prechode do ďalšieho sveta svetlo a žiara vzplanú: nábeh 0,6 sekundy, krátke podržanie, doznenie 1,4 sekundy a žiara sa pritom raz otočí. Ak záblesk len pripočítate k pulzu, vrchol závisí od toho, v akej fáze je sínusovka práve vtedy, a každý záblesk je inak silný. Plynulo prejdite z pulzu na pevný vrchol:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">váha = krivka záblesku (0 až 1)
hodnota = pulz + (vrchol - pulz) × váha</code></pre>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Pripočítanie</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/surge-additive.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/surge-additive.png" alt="Graf intenzity: pulz a dva záblesky s rôzne vysokým vrcholom" width="1440" height="720" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Prechod na vrchol</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/surge-lerp.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/surge-lerp.png" alt="Graf intenzity: pulz a dva záblesky s rovnakým vrcholom na bodkovanej čiare" width="1440" height="720" loading="lazy" decoding="async" /></picture></div></div><figcaption>Dva záblesky v rôznej fáze pulzu (oranžový a modrý). Vľavo sa vrcholy líšia, vpravo oba dosiahnu rovnakú hodnotu.</figcaption></figure>
<aside class="app-box app-box-callout"><img class="app-box-img" src="https://smallwater.eu/images/dreamica/1-chase.jpg" alt="" width="700" height="1515" loading="lazy" decoding="async" /><div class="app-box-body"><p class="app-box-eyebrow">Vyskúšajte</p><h2 class="app-box-title">Dreamica</h2><p>Ručne kreslená pádová adventúra, v ktorej kreslíš fyzikálne čiary a vedieš magickú bublinu. Poznámky z vývoja a novinky.</p><div class="app-box-links"><a class="btn btn-primary" href="https://smallwater.eu/dreamica-ios" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="dreamica" data-ga-destination="/dreamica-ios" data-ga-app="dreamica" data-ga-store="app_store">App Store</a><a class="btn btn-ghost" href="https://smallwater.eu/dreamica-android" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="dreamica" data-ga-destination="/dreamica-android" data-ga-app="dreamica" data-ga-store="google_play">Google Play</a></div></div></aside>
<h2 id="krok-4-ostre-ui-pre-dnesne-displeje"><a class="heading-anchor" href="#krok-4-ostre-ui-pre-dnesne-displeje" aria-hidden="true" tabindex="-1">#</a>Krok 4: Ostré UI pre dnešné displeje</h2>
<p>UI kreslené pre iPhone 7 má malé sprity. Tlačidlo v menu Dreamice malo sprite 128 × 64 pixelov a na telefóne s trojnásobnou hustotou bodov sa ťahalo na niekoľkonásobok. Prekreslite sprity vo väčšom rozlíšení: nové tlačidlo má 800 × 434, ukazovatele pokroku narástli z 200 × 26 na 800 × 104.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">128 × 64</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/button-old.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/button-old.jpg" alt="Detail rozmazaného okraja tlačidla" width="1120" height="608" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">800 × 434</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/button-new.webp" /><img src="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/button-new.jpg" alt="Detail ostrého okraja tlačidla s viditeľnou textúrou" width="1120" height="608" loading="lazy" decoding="async" /></picture></div></div><figcaption>Rovnaké tlačidlo pri rovnakej veľkosti na displeji. Vľavo pôvodný sprite, vpravo nový.</figcaption></figure>
<p>Ak UI stojí na NGUI, zvážte prepis. Kotvy v NGUI sa dopočítavajú po častiach počas viacerých snímok, dlhé reťaze kotiev sa občas ustália zle a mierka sa líši medzi Editorom, telefónom a iPadom. V Dreamice to opravy spoľahlivo nevyriešili, preto sme UI prepísali na uGUI a TextMeshPro. Postupujte obrazovku po obrazovke a starú verziu majte zapnutú vedľa ako predlohu. UI škálujte podľa výšky voči referenčnému rozlíšeniu (u nás 434 × 941 bodov) a rešpektujte bezpečnú oblasť displeja, teda výrez a zaoblené rohy. Po prepise NGUI z projektu úplne odstráňte, nech v ňom neostanú dva systémy UI.</p>
<h2 id="limity-nekomprimovane-textury"><a class="heading-anchor" href="#limity-nekomprimovane-textury" aria-hidden="true" tabindex="-1">#</a>Limity: nekomprimované textúry</h2>
<p>Tento postup mení len grafiku. Textúry sme v Dreamice vedome nekomprimovali, lebo sme chceli plnú ostrosť. Cena je veľkosť: už pôvodná hra mala v Editore 313 MB textúr v pamäti a v App Store má 299 MB. Kompresia by podľa nášho odhadu zmenšila textúry približne na 40 MB. Ak vám na veľkosti hry záleží, je to prvé miesto, kde ušetriť.</p>
<p>Iný príklad Unity hry od prvého riadku kódu po vydanie nájdete v článku o <a href="https://smallwater.eu/blog/shmup-apple-watch-devlog/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/dreamica-remaster-unity-urp-glow/" data-ga-destination="/blog/shmup-apple-watch-devlog/" data-ga-placement="inline">hre Space Impact Watch</a>, výkonu v Unity sa venuje článok o <a href="https://smallwater.eu/blog/unity-dots-horde-navigation/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/dreamica-remaster-unity-urp-glow/" data-ga-destination="/blog/unity-dots-horde-navigation/" data-ga-placement="inline">navigácii hordy v Unity DOTS</a>. Ak je hra ešte v Unity 4, neotvárajte ju priamo v Unity 6; bezpečný postup opisuje návod <a href="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/dreamica-remaster-unity-urp-glow/" data-ga-destination="/blog/unity-4-to-unity-6-upgrade/" data-ga-placement="inline">ako previesť projekt z Unity 4 do Unity 6</a>. Ak máte starú Unity hru, ktorá vypadla z obchodov, a chcete ju vrátiť späť, <a href="https://smallwater.eu/#kontakt">ozvite sa nám</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako spraviť fake ragdoll pre tisíce postáv v Unity DOTS</title>
      <link>https://smallwater.eu/blog/fake-ragdoll-unity-dots/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/fake-ragdoll-unity-dots/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Tower Defense</category>
      <description>Ako odhodiť tisíce nepriateľov výbuchom v Unity DOTS bez fyziky kostí: telo ako jeden bod, deterministický odpal, dopad na priechodnú bunku a rotácia.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/cover.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/cover.jpg" alt="Postavy vymrštené výbuchom letia po oblúkoch a otáčajú sa vo vzduchu" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#preco-nie-skutocny-ragdoll" data-ga="blog_toc_clicked" data-ga-heading-id="preco-nie-skutocny-ragdoll">Prečo nie skutočný ragdoll</a></li><li><a href="#telo-ako-jeden-bod" data-ga="blog_toc_clicked" data-ga-heading-id="telo-ako-jeden-bod">Telo ako jeden bod</a></li><li><a href="#fazy-zrazeneho-nepriatela" data-ga="blog_toc_clicked" data-ga-heading-id="fazy-zrazeneho-nepriatela">Fázy zrazeného nepriateľa</a></li><li><a href="#odpal-vyssie-nie-dalej" data-ga="blog_toc_clicked" data-ga-heading-id="odpal-vyssie-nie-dalej">Odpal: vyššie, nie ďalej</a></li><li><a href="#dopad-na-priechodnu-bunku" data-ga="blog_toc_clicked" data-ga-heading-id="dopad-na-priechodnu-bunku">Dopad na priechodnú bunku</a><ol><li><a href="#odraz-od-stien-vo-vzduchu-nestaci" data-ga="blog_toc_clicked" data-ga-heading-id="odraz-od-stien-vo-vzduchu-nestaci">Odraz od stien vo vzduchu nestačí</a></li><li><a href="#predikcia-drahy-pri-odpale" data-ga="blog_toc_clicked" data-ga-heading-id="predikcia-drahy-pri-odpale">Predikcia dráhy pri odpale</a></li></ol></li><li><a href="#rotacia-naplanovana-podla-vysky-letu" data-ga="blog_toc_clicked" data-ga-heading-id="rotacia-naplanovana-podla-vysky-letu">Rotácia naplánovaná podľa výšky letu</a></li><li><a href="#model-polozeny-na-zem" data-ga="blog_toc_clicked" data-ga-heading-id="model-polozeny-na-zem">Model položený na zem</a></li><li><a href="#mrtvi-nepriatelia-telo-decal-a-prach" data-ga="blog_toc_clicked" data-ga-heading-id="mrtvi-nepriatelia-telo-decal-a-prach">Mŕtvi nepriatelia: telo, decal a prach</a></li><li><a href="#pasce-v-unity-entities" data-ga="blog_toc_clicked" data-ga-heading-id="pasce-v-unity-entities">Pasce v Unity Entities</a></li><li><a href="#vykon" data-ga="blog_toc_clicked" data-ga-heading-id="vykon">Výkon</a></li><li><a href="#limity-riesenia" data-ga="blog_toc_clicked" data-ga-heading-id="limity-riesenia">Limity riešenia</a></li></ol></nav><p>Keď výbuch v hre odhodí stovky nepriateľov naraz, skutočný ragdoll pre každého z nich je príliš drahý. Fake ragdoll v Unity DOTS spravíte tak, že celé telo nepriateľa simulujete ako jeden bod s vlastnou jednoduchou fyzikou a model iba otáčate okolo stredu tela. Pád a vstávanie sú obyčajné animačné klipy, kosti sa nesimulujú vôbec.</p>
<p>Postup ukazujeme na tower defense hre pre PC, ktorú robíme v Unity s ECS (<a href="https://smallwater.eu/#referencie">viac v referenciách</a>). Bomba alebo fireball tam vymršťuje desiatky až stovky nepriateľov naraz. Pri 2 000 telách vo vzduchu stojí simulácia 0,53 až 0,59 ms na tik (tik je jeden krok hernej simulácie s pevnou dĺžkou).</p>
<h2 id="preco-nie-skutocny-ragdoll"><a class="heading-anchor" href="#preco-nie-skutocny-ragdoll" aria-hidden="true" tabindex="-1">#</a>Prečo nie skutočný ragdoll</h2>
<p>Skutočný ragdoll je desať a viac fyzikálnych telies spojených kĺbmi na každú postavu. Pri tisíckach postáv na obrazovke to nie je reálne.</p>
<p>Druhý dôvod je determinizmus: z rovnakého stavu musí vyjsť rovnaký výsledok. V našej hre na tom stojí opakovanie prehratej vlny z kontrolného bodu a kontrolujeme to hashom stavu simulácie po 1 800 tikoch.</p>
<h2 id="telo-ako-jeden-bod"><a class="heading-anchor" href="#telo-ako-jeden-bod" aria-hidden="true" tabindex="-1">#</a>Telo ako jeden bod</h2>
<p>Namiesto fyzikálneho enginu použite vlastnú jednoduchú 2,5D fyziku: poloha na mriežke, výška nad zemou, balistický let, odraz od stien a trenie. V našej hre sa takto pohybuje aj bomba a nepriateľ zasiahnutý výbuchom sa na chvíľu stane takým istým telesom. Inšpiráciou bolo, ako odletujú vojaci v hre <a href="https://store.steampowered.com/app/1272320/Diplomacy_is_Not_an_Option/" target="_blank" rel="noopener">Diplomacy is Not an Option</a>.</p>
<p>Simulácia pozná len polohu, výšku a uhol natočenia. Otáčanie modelu do nej nezapisujte. Patrí do dodatočnej transformačnej matice entity, ktorú balík Entities (ECS v Unity) podporuje pre nerovnomernú mierku (<a href="https://docs.unity3d.com/Packages/com.unity.entities@1.3/api/Unity.Transforms.PostTransformMatrix.html" target="_blank" rel="noopener">dokumentácia</a>).</p>
<h2 id="fazy-zrazeneho-nepriatela"><a class="heading-anchor" href="#fazy-zrazeneho-nepriatela" aria-hidden="true" tabindex="-1">#</a>Fázy zrazeného nepriateľa</h2>
<p>Zrazený nepriateľ prejde tromi fázami a potom sa vráti do davu:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">pri zásahu:
    vypočítaj rýchlosť odpalu (sila, vzdialenosť od výbuchu, náhoda zo seedu)
    nájdi dopad na priechodnú bunku (pozri nižšie)
    zapni stav &quot;zrazený&quot;, uvoľni miesto, ktoré mal obsadené pri cieli útoku
let:       teleso letí po parabole, model sa otáča okolo stredu tela
dopad:     poloha podľa natočenia (chrbát alebo brucho), krátke doklopenie
ľah:       čaká pevný počet tikov
vstávanie: klip vstávania z chrbta alebo z brucha, potom späť do davu</code></pre>
<p>Stav „zrazený“ spravte ako komponent, ktorý sa dá zapnúť a vypnúť bez presúvania entity medzi chunkmi (pamäťovými blokmi, v ktorých ECS drží entity s rovnakou sadou komponentov). Navigácia, odtláčanie v dave aj rozhodovanie o útoku zrazených jednoducho preskočia. Ako funguje navigácia a odtláčanie veľkého davu, popisuje článok o <a href="https://smallwater.eu/blog/unity-dots-horde-navigation/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/fake-ragdoll-unity-dots/" data-ga-destination="/blog/unity-dots-horde-navigation/" data-ga-placement="inline">navigácii hordy v Unity DOTS</a>.</p>
<h2 id="odpal-vyssie-nie-dalej"><a class="heading-anchor" href="#odpal-vyssie-nie-dalej" aria-hidden="true" tabindex="-1">#</a>Odpal: vyššie, nie ďalej</h2>
<p>Najjednoduchší odpal je pod uhlom 45° ± 10°. Pri sile bomby 18 m/s a gravitácii 20 m/s² vychádza vrchol letu okolo 4 m. Telo preletí nízko nad davom a efekt sa v ňom stratí.</p>
<p>Vyššia sila nepomôže, lebo s ňou rastie aj dolet. Rozdeľte rýchlosť na dve zložky a každú násobte zvlášť: uhol 60° ± 5°, vodorovná zložka × 0,85, zvislá × 1,3. Vrchol je potom okolo 10 m, dolet ostane okolo 15,5 m a let trvá približne 2 sekundy.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/launch-arcs.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/launch-arcs.png" alt="Bočný pohľad: nízky prerušovaný oblúk a vysoký oblúk ponad dom so siluetami postavy, ktorá sa počas letu otáča" width="2200" height="1520" loading="lazy" decoding="async" /></picture><figcaption>Prerušovane odpal pod 45°, plnou čiarou odpal pod 60° s oddelenými násobkami. Dom má 5 m.</figcaption></figure>
<p>Smer sa od výbuchu odchyľuje o ± 15°, sila o ± 20 % a na okraji výbuchu klesá na polovicu. Náhodu neberte z globálneho generátora, ale z hashu seedu levelu, identifikátora nepriateľa a poradového čísla výbuchu. Požiadavky na zrazenie spracúvajte zoradené, lebo joby bežiace na viacerých vláknach ich vytvoria v ľubovoľnom poradí. Dva behy tej istej vlny tak dajú tie isté oblúky.</p>
<p>Odhodenie je áno alebo nie. Každý zraziteľný nepriateľ pri zásahu aspoň spadne (rýchlosť je zdola obmedzená na 2 m/s), odolnosť mení len dolet. Imunitu dajte samostatným príznakom, v našej hre ho má najväčší typ nepriateľa. Keby imunita vyplývala z odolnosti (pri 0,95 vychádza rýchlosť pod 2 m/s) a slabé odhodenie by sa rušilo, nepriateľ by bol imúnny len náhodou.</p>
<h2 id="dopad-na-priechodnu-bunku"><a class="heading-anchor" href="#dopad-na-priechodnu-bunku" aria-hidden="true" tabindex="-1">#</a>Dopad na priechodnú bunku</h2>
<p>Nepriateľ nesmie dopadnúť do rokliny, na útes ani do pôdorysu veže. Priechodnosť sa určuje po bunkách mriežky a pre každú veľkostnú triedu nepriateľa zvlášť.</p>
<h3 id="odraz-od-stien-vo-vzduchu-nestaci"><a class="heading-anchor" href="#odraz-od-stien-vo-vzduchu-nestaci" aria-hidden="true" tabindex="-1">#</a>Odraz od stien vo vzduchu nestačí</h3>
<p>Prvé, čo napadne, je brať nepriechodné bunky ako steny, od ktorých sa letiace telo odrazí. Pri útese to vyzerá prirodzene, pri rokline nie: nad roklinou je vzduch, ale bunka je nepriechodná, takže sa telo odrazí od neviditeľného stĺpa nad jej okrajom. Navyše nič nezaručuje, že po odraze skončí na dobrom mieste.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/dead-end-bounce.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/dead-end-bounce.png" alt="Bočný pohľad: telo letí cez roklinu, v strede letu sa odrazí od neviditeľného stĺpa nad jej okrajom a padne späť" width="2200" height="1640" loading="lazy" decoding="async" /></picture><figcaption>Odraz od nepriechodnej bunky. Telo sa odrazí od vzduchu nad okrajom rokliny.</figcaption></figure>
<h3 id="predikcia-drahy-pri-odpale"><a class="heading-anchor" href="#predikcia-drahy-pri-odpale" aria-hidden="true" tabindex="-1">#</a>Predikcia dráhy pri odpale</h3>
<p>Spoľahlivejšie je zistiť dopad hneď pri odpale. Let nasimulujte dopredu tým istým krokom ako skutočnú fyziku a skontrolujte bunku dopadu pre veľkostnú triedu nepriateľa. Ak nie je priechodná, skúšajte ďalšie varianty v pevnom poradí:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">pre uhol v [0, +25, -25, +50, -50, ... , +175, -175]:
    pre násobok vodorovnej rýchlosti v [1, 0.7, 0.4, 0.1]:
        nasimuluj let
        ak je bunka dopadu priechodná pre triedu nepriateľa: použi tento smer a koniec
ak nič nevyšlo: vodorovná rýchlosť = 0 (výskok na mieste)</code></pre>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/fit-search.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/fit-search.png" alt="Pohľad zhora na mriežku s roklinou a vyvýšeným útesom, preškrtnuté zamietnuté dopady a zvýraznený prvý priechodný" width="2200" height="1400" loading="lazy" decoding="async" /></picture><figcaption>Výbuch, nepriateľ, zamietnuté dopady a vybraný. Sivé body sú všetky kandidátne dopady.</figcaption></figure>
<p>Najhorší prípad je 60 simulácií letu na jedného nepriateľa (15 smerov × 4 násobky). Zvislá rýchlosť sa nemení, takže výška letu ostáva rovnaká a skracuje sa len dolet. Ak nepriateľa počas letu zasiahne ďalší výbuch, predikciu spustite znova.</p>
<figure class="post-figure post-figure-video"><video class="post-video" muted loop playsinline controls preload="none" poster="https://smallwater.eu/blog/fake-ragdoll-unity-dots/ragdoll-loop.jpg" width="1280" height="720" data-lazy-video aria-label="Ilustrácia v 2D: dvaja nepriatelia by dopadli do rokliny (krížiky), predikcia im skráti let."><source src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/ragdoll-loop.mp4" type="video/mp4" /></video><figcaption>Ilustrácia v 2D: dvaja nepriatelia by dopadli do rokliny (krížiky), predikcia im skráti let.</figcaption></figure>
<p>Aby telo po dopade neskĺzlo ani neodskočilo, nastavte mu koeficient odrazu od zeme 0 a veľké trenie. Ostane tam, kde predikcia povedala.</p>
<h2 id="rotacia-naplanovana-podla-vysky-letu"><a class="heading-anchor" href="#rotacia-naplanovana-podla-vysky-letu" aria-hidden="true" tabindex="-1">#</a>Rotácia naplánovaná podľa výšky letu</h2>
<p>Najjednoduchšie je točiť model náhodnou rýchlosťou, napríklad 180 až 540 °/s, a po dopade ho za 0,2 s doklopiť na najbližší ľah. Doklopenie je však vidieť, keď telo dopadne takmer vzpriamene. Dorovnávať rýchlosť otáčania v každom tiku tak, aby uhol pri dopade vyšiel presne vodorovne, pomôže len čiastočne: aj nízky odhod sa točí ako salto a otáčanie nesúvisí s tým, ako vysoko telo letí.</p>
<p>Lepšie je naplánovať rotáciu pri odpale z výšky letu:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">výška = vz² / (2 g), čas letu z vz a výšky
cieľ = najbližšia vodorovná poloha v náhodnom smere (štvrť otočky zo stoja)
cieľ += polotočka za každých 2,5 m výšky, najviac 3
rýchlosť otáčania = (cieľ - aktuálny uhol) / čas letu</code></pre>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/tumble-plan.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/tumble-plan.png" alt="Dva oblúky: nízky odhod, pri ktorom sa postava len prevalí, a vysoký, pri ktorom urobí tri a štvrť otočky" width="2200" height="1200" loading="lazy" decoding="async" /></picture><figcaption>Nízky odhod (vrchol okolo 2 m) urobí 90°, bomba (okolo 10 m) 90° a tri polotočky.</figcaption></figure>
<p>Strana dopadu vyplýva z uhla: tvárou k zemi znamená brucho, inak chrbát. Podľa nej sa vyberie klip vstávania. Ležiaci alebo vstávajúci nepriateľ si pri ďalšom zásahu ponechá aktuálny uhol a neskočí späť do vzpriamenej polohy.</p>
<h2 id="model-polozeny-na-zem"><a class="heading-anchor" href="#model-polozeny-na-zem" aria-hidden="true" tabindex="-1">#</a>Model položený na zem</h2>
<p>Model sa otáča okolo stredu tela, ale zem je pod jeho najnižším bodom a ten sa pri otáčaní mení. Odhad posunu z dvoch konštánt a sínusu uhla nesedí pre modely s rozhodenými rukami v klipe pádu: v našej hre sa model kostry (bežný nepriateľ) zanáral až 1,15 m pod zem a model nekromanta až 1,73 m.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Odhad</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/ground-before.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/ground-before.png" alt="Šesť natočení postavy, časti pod zemou sú červené" width="2200" height="840" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Zmerané</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/ground-after.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/ground-after.png" alt="Šesť natočení postavy, každé sa dotýka zeme najnižším bodom" width="2200" height="840" loading="lazy" decoding="async" /></picture></div></div><figcaption>Rovnaké natočenia s posunom z konštánt a zo zmeranej tabuľky.</figcaption></figure>
<p>Spoľahlivejšie je najnižší bod zmerať. Pri zapekaní (bake) animácií spočítajte pre klip pádu najnižší bod tela pri 72 natočeniach po 5° a za behu z tabuľky interpolujte. Najhlbšie zanorenie tak klesne na 4 mm. Jedno minimum cez všetky snímky klipu však nestačí: telo by po dopade viselo 0,15 až 0,28 m nad zemou, kým ho klip vstávania nestiahne dole. Tabuľka preto musí mať rozmer snímky × natočenia.</p>
<p>Pri pomalej naplánovanej rotácii sa ukáže ešte jeden efekt. Ak model kladiete najnižším bodom na výšku letu, stred tela pri prechode zo stoja do vodorovnej polohy klesne o rozdiel polovice výšky a polovice hrúbky postavy. Telo potom na začiatku letu stúpa len pomaly a až potom zrýchli. Model preto zdvihnite o rozdiel polovičného rozmeru v stoji a pri aktuálnom uhle, najviac o výšku letu. Stred tela potom ide po parabole.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/flight-lift.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/flight-lift.png" alt="Graf výšky stredu tela v čase: prerušovaná ideálna parabola, červená krivka s poklesom a modrá, ktorá parabolu sleduje" width="2200" height="1040" loading="lazy" decoding="async" /></picture><figcaption>Výška stredu tela počas letu: bez zdvihu (červená) a so zdvihom (modrá).</figcaption></figure>
<h2 id="mrtvi-nepriatelia-telo-decal-a-prach"><a class="heading-anchor" href="#mrtvi-nepriatelia-telo-decal-a-prach" aria-hidden="true" tabindex="-1">#</a>Mŕtvi nepriatelia: telo, decal a prach</h2>
<p>Nepriateľ zabitý výbuchom má odletieť tiež. Vytvorte preň letiace telo: kópiu entity bez herných komponentov (životy, odmena, cieľ). Telo preletí, dopadne, chvíľu leží a zmizne. Na jeho mieste ostane decal mŕtvoly (textúra premietnutá na zem) ako vo Warcrafte 3 a obláčik prachu. V našej hre je strop 1 000 letiacich tiel a 1 500 decalov.</p>
<p>Decal je pohľad zhora na snímku z klipu pádu, natočený rovnako ako ležiace telo, takže výmena tela za decal nie je vidieť. Prvá snímka klipu vstávania sa nehodí, lebo postavu z chrbta už posadí. Obláčiky prachu zlučujte v bunke 1,5 m. Bomba do davu tak vytvorí pár väčších oblakov namiesto stoviek malých.</p>
<h2 id="pasce-v-unity-entities"><a class="heading-anchor" href="#pasce-v-unity-entities" aria-hidden="true" tabindex="-1">#</a>Pasce v Unity Entities</h2>
<ul>
<li><strong>Matica z bakera zmizne.</strong> Ak dodatočnú maticu pridáte pri bake, transformačný systém ju pri rovnomernej mierke odstráni a zrazení sa nehýbu. Pri bake si preto vyžiadajte nerovnomernú mierku. Vtedy sa však do tej istej matice presunie celá mierka prefabu, takže ju neprepisujte samotnou rotáciou. Inak má zrazený nepriateľ so zväčšením 2× polovičnú veľkosť, aj keď vstane. Mierku si uložte zvlášť a maticu skladajte z mierky a rotácie.</li>
<li><strong>Výška koreňa pri importe klipov.</strong> Pri humanoid klipoch sa zvislý pohyb koreňa (root) zahodí a postavy pri vstávaní prepadnú pod podlahu. Výšku koreňa zapečte do pózy. Voľbu ukotviť pózu na chodidlá nepoužívajte, posunie celé telo o meter dole.</li>
<li><strong>Matica v uzavretom tvare.</strong> Posun, rotácia a spätný posun ako tri násobenia matíc sú v jobe merateľne pomalšie než ručne napísaný vzorec pre výslednú maticu.</li>
<li><strong>Burst so starou verziou.</strong> Po zmene polí komponentu, ktorý číta Burst job, môže editor padať alebo hlásiť nezmysly. Pomôže reštart editora.</li>
</ul>
<h2 id="vykon"><a class="heading-anchor" href="#vykon" aria-hidden="true" tabindex="-1">#</a>Výkon</h2>
<p>Merané v Unity Editore na M3 Pro, 10 000 kostier v benchmark scéne, sila odpalu 18. Časy sú súčet CPU času Burst jobov cez všetky vlákna, nie čas snímky. Graf a tabuľka sú bez predikcie dráhy a tabuliek dopadu.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/fake-ragdoll-unity-dots/bench.webp" /><img src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/bench.png" alt="Graf času na tik podľa počtu zrazených nepriateľov pre simuláciu a prípravu vizuálu, s cieľovou čiarou 0,5 ms" width="2200" height="1120" loading="lazy" decoding="async" /></picture><figcaption>Simulácia a príprava vizuálu bez predikcie dráhy. Cieľ 0,5 ms pri 2 000 zrazených.</figcaption></figure>
<div class="table-wrap"><table>
<thead>
<tr>
<th>zrazených</th>
<th>simulácia (ms/tik)</th>
<th>príprava vizuálu (ms)</th>
</tr>
</thead>
<tbody><tr>
<td>500</td>
<td>0,28 až 0,30</td>
<td>0,15</td>
</tr>
<tr>
<td>2 000</td>
<td>0,53</td>
<td>0,30 až 0,55</td>
</tr>
<tr>
<td>5 000</td>
<td>0,74 až 0,75</td>
<td>0,49 až 0,91</td>
</tr>
<tr>
<td>10 000</td>
<td>0,96 až 1,01</td>
<td>0,68 až 1,24</td>
</tr>
</tbody></table>
</div>
<ul>
<li>Simulácia rastie lineárne, zhruba o 0,25 ms na tisíc zrazených. S predikciou dráhy a tabuľkami dopadu je to pri 2 000 zrazených 0,58 až 0,59 ms, teda mierne nad cieľom 0,5 ms. Rozumný strop je preto 3 000 súčasne zrazených, čo je okolo 0,8 ms.</li>
<li>Výbuch do skupiny 359 nepriateľov pri stene stojí v systéme požiadaviek na zrazenie 1,3 až 1,9 ms v jednej snímke. Väčšinu tvorí predikcia dráhy.</li>
<li>FPS z editora sa porovnať nedá, medzi dvoma behmi tej istej konfigurácie bol rozdiel až 80 FPS.</li>
</ul>
<h2 id="limity-riesenia"><a class="heading-anchor" href="#limity-riesenia" aria-hidden="true" tabindex="-1">#</a>Limity riešenia</h2>
<p>Príprava vizuálu stúpla v inej testovacej scéne až na 1,3 ms, pravdepodobne pre čítanie tabuľky dopadu pre každé telo. Je to prvý kandidát na optimalizáciu. FPS v builde a záťaž pri hromadnom zabití 1 000 nepriateľov naraz (cieľ do 1,5 ms navyše na snímku) zatiaľ nemáme zmerané.</p>
<p>Ak riešite podobnú simuláciu davu alebo efektov v Unity a potrebujete pomoc, <a href="https://smallwater.eu/#kontakt">napíšte nám</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako naprogramovať shmup pre Apple Watch</title>
      <link>https://smallwater.eu/blog/shmup-apple-watch-devlog/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/shmup-apple-watch-devlog/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Space Impact Watch</category>
      <description>Návod na shmup pre Apple Watch: virtuálne rozlíšenie, ovládanie korunkou, kolízie obdĺžnikmi, pooling, levely ako tabuľka časov a generátor vĺn.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/cover.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/cover.jpg" alt="Pixelová scéna na zelenom displeji: loď vľavo strieľa na kométy, rakety a bossa vpravo" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#pevne-virtualne-rozlisenie-84-48" data-ga="blog_toc_clicked" data-ga-heading-id="pevne-virtualne-rozlisenie-84-48">Pevné virtuálne rozlíšenie 84 × 48</a></li><li><a href="#ovladanie-korunkou-a-automaticka-strelba" data-ga="blog_toc_clicked" data-ga-heading-id="ovladanie-korunkou-a-automaticka-strelba">Ovládanie korunkou a automatická streľba</a></li><li><a href="#herna-slucka-v-jednej-scene" data-ga="blog_toc_clicked" data-ga-heading-id="herna-slucka-v-jednej-scene">Herná slučka v jednej scéne</a></li><li><a href="#kolizie-obdlznikmi-namiesto-fyzikalneho-enginu" data-ga="blog_toc_clicked" data-ga-heading-id="kolizie-obdlznikmi-namiesto-fyzikalneho-enginu">Kolízie obdĺžnikmi namiesto fyzikálneho enginu</a></li><li><a href="#pooling-len-tam-kde-sa-oplati" data-ga="blog_toc_clicked" data-ga-heading-id="pooling-len-tam-kde-sa-oplati">Pooling len tam, kde sa oplatí</a></li><li><a href="#levely-ako-tabulka-casov" data-ga="blog_toc_clicked" data-ga-heading-id="levely-ako-tabulka-casov">Levely ako tabuľka časov</a></li><li><a href="#nekonecny-rezim-z-jedneho-pseudonahodneho-cisla" data-ga="blog_toc_clicked" data-ga-heading-id="nekonecny-rezim-z-jedneho-pseudonahodneho-cisla">Nekonečný režim z jedného pseudonáhodného čísla</a></li><li><a href="#limity-tohto-pristupu" data-ga="blog_toc_clicked" data-ga-heading-id="limity-tohto-pristupu">Limity tohto prístupu</a></li></ol></nav><p>Shmup (shoot &#39;em up, strieľačka, v ktorej loď letí proti vlnám nepriateľov) na Apple Watch naráža na tri veci: malý displej, prst, ktorý zakryje polovicu hry, a rôzne veľkosti hodiniek. Riešia sa tromi rozhodnutiami: celá hra beží v pevnej mriežke 84 × 48 bodov, kolízie sa počítajú ručne obdĺžnikmi a levely sú tabuľky s časmi, nie kód. Loď sa ovláda korunkou Digital Crown a strieľa sama.</p>
<p>Postup ukazujeme na hre <a href="https://smallwater.eu/space-impact-watch">Space Impact Watch</a>, remaku strieľačky Space Impact z telefónu Nokia 3310 z roku 2000, ktorá beží na hodinkách aj na iPhone a iPade. Príklady sú zo SpriteKitu, 2D herného frameworku od Apple, ale princípy platia pre každý shmup na malú obrazovku.</p>
<h2 id="pevne-virtualne-rozlisenie-84-48"><a class="heading-anchor" href="#pevne-virtualne-rozlisenie-84-48" aria-hidden="true" tabindex="-1">#</a>Pevné virtuálne rozlíšenie 84 × 48</h2>
<p>Najprv sa rozhodnite, v akých jednotkách bude hra počítať. Ak použijete body obrazovky, svet bude na každej veľkosti hodiniek inak veľký. Lepšie je zvoliť malé virtuálne rozlíšenie a všetko počítať v ňom. V našej hre je to 84 × 48, presne ako mal <a href="https://www.adafruit.com/products/338" target="_blank" rel="noopener">displej Nokie 3310</a>. Pozície, rýchlosti, veľkosti spritov aj dráhy nepriateľov sú v týchto jednotkách. Pri štarte scény sa raz vypočíta, koľko bodov obrazovky má jeden virtuálny pixel (šírka scény deleno 84), a všetko sa ním len vynásobí.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/grid.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/grid.png" alt="Herná scéna s viditeľnou mriežkou 84 × 48 virtuálnych pixelov" width="1008" height="576" loading="lazy" decoding="async" /></picture><figcaption>Level na mriežke 84 × 48. Každý štvorček je jeden virtuálny pixel, hodinky ho vykreslia ako blok niekoľkých skutočných bodov.</figcaption></figure>
<p>Čo tým získate:</p>
<ul>
<li><strong>Rovnaký vzhľad na každom zariadení.</strong> Hra vyzerá rovnako na každej veľkosti hodiniek aj na iPade, mení sa len veľkosť bloku.</li>
<li><strong>Level dizajn v malých celých číslach.</strong> Nepriateľ letí vo výške 13, raketa zatáča v bode x = 20.</li>
<li><strong>Jednoduchú grafiku.</strong> Sprity sú jednofarebné a kreslené v desaťnásobnej veľkosti, jeden virtuálny pixel je v zdrojovom obrázku blok 10 × 10. Farbu dostanú až v hre. Na svetlých leveloch sú čierne na zelenom pozadí, na tmavých sa to otočí.</li>
</ul>
<h2 id="ovladanie-korunkou-a-automaticka-strelba"><a class="heading-anchor" href="#ovladanie-korunkou-a-automaticka-strelba" aria-hidden="true" tabindex="-1">#</a>Ovládanie korunkou a automatická streľba</h2>
<p>Na hodinkách prst zakryje polovicu hry, preto sa loď neovláda dotykom. Pohyb rieši Digital Crown: otočenie korunky posunie loď hore alebo dole. Pridajte nastavenie citlivosti a možnosť otočiť smer.</p>
<p>Streľba je automatická, v našej hre každých 0,4 sekundy, od štvrtého levelu každých 0,3. Displej slúži len na jedno gesto: potiahnutím doprava hráč použije jednu z troch špeciálnych zbraní.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/weapons.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/weapons.png" alt="Tri špeciálne zbrane: zvislá čiara, navádzaná raketa a laser zasahujúci bossa" width="1104" height="240" loading="lazy" decoding="async" /></picture><figcaption>Tri špeciálne zbrane: čiara, ktorá prejde celou výškou plochy, navádzaná raketa a laser.</figcaption></figure>
<p>Na hodinkách je vibrácia pri zásahu rovnako dôležitá ako animácia, malý výbuch na displeji si hráč ľahko nevšimne. Haptiku volajte cez jedno rozhranie (zásah, smrť nepriateľa, porazený boss), ktoré každá platforma vykoná po svojom: hodinky vibráciou, iPhone vlastným spôsobom. Verzia pre telefón nemá korunku, namiesto nej má tlačidlá na displeji, ktoré používajú rovnaké nastavenie citlivosti aj smeru.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/watch-2021.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/watch-2021.png" alt="Screenshot hry v simulátore Apple Watch, loď proti bossovi" width="368" height="448" loading="lazy" decoding="async" /></picture><figcaption>Hra v simulátore Apple Watch: loď proti bossovi.</figcaption></figure>
<h2 id="herna-slucka-v-jednej-scene"><a class="heading-anchor" href="#herna-slucka-v-jednej-scene" aria-hidden="true" tabindex="-1">#</a>Herná slučka v jednej scéne</h2>
<p>Celá hra môže byť jedna scéna s jednou slučkou:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">každý snímok:
    posuň všetky objekty
    skontroluj kolízie
    podľa času v leveli pridaj nových nepriateľov</code></pre>
<p>Veci pridávajte v poradí, v ktorom sa dajú čo najskôr testovať:</p>
<ul>
<li><strong>Streľba a pohyb hráča.</strong> Bez nich sa nedá nič otestovať.</li>
<li><strong>Nepriatelia a kolízie.</strong> Prvému nepriateľovi stačí letieť rovno doľava a zomrieť po jednom zásahu.</li>
<li><strong>Vibrácie a animácie zásahu.</strong> Na malom displeji bez nich hráč nevie, že zasiahol.</li>
<li><strong>Pozadie a HUD.</strong> Životy, skóre, počet špeciálnych zbraní. Ak HUD na niektorom leveli prekáža, presuňte ho dole.</li>
</ul>
<p>Na konci máte hrateľnú slučku so životmi, skóre, výbuchmi, pozadím a respawnom hráča. Ak hernú logiku držíte oddelene od vykresľovania a vstupu, môžu ju zdieľať verzie pre hodinky aj telefón.</p>
<h2 id="kolizie-obdlznikmi-namiesto-fyzikalneho-enginu"><a class="heading-anchor" href="#kolizie-obdlznikmi-namiesto-fyzikalneho-enginu" aria-hidden="true" tabindex="-1">#</a>Kolízie obdĺžnikmi namiesto fyzikálneho enginu</h2>
<p>SpriteKit má fyzikálny engine a ponúka sa použiť ho aj na kolízie. Každý objekt dostane fyzikálne telo vytvorené z obrysu spritu, presnú detekciu kolízií a kategóriu (hráč, nepriateľ, strela, špeciálna zbraň) a engine sám hlási dvojice, ktoré sa dotkli. Apple v <a href="https://developer.apple.com/documentation/spritekit/getting-started-with-physics-bodies" target="_blank" rel="noopener">dokumentácii k fyzikálnym telám</a> uvádza, že telá z alfa kanála textúry sú najpresnejšie a zároveň výpočtovo najdrahšie. V našej hre sme to skúsili a vrátili sme sa k ručnej kontrole.</p>
<p>Ručná kontrola je jedna funkcia, ktorá po sebe prejde dvojice skupín a porovná obdĺžniky:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">pre každú dvojicu skupín:
    (hráč a nepriatelia, hráč a strely, strely a nepriatelia, strely a strely)
    pre každý objekt A z prvej skupiny a objekt B z druhej:
        ak sa obdĺžnik A prekrýva s obdĺžnikom B:
            vyrieš zásah</code></pre>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/hitbox.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/hitbox.png" alt="Dve dvojice spritov s vyznačenými obdĺžnikmi kolízie, vľavo sa prekrývajú len rohy obdĺžnikov" width="960" height="352" loading="lazy" decoding="async" /></picture><figcaption>Vľavo: obdĺžniky sa prekrývajú, pixely nie, hra to počíta ako zásah. Vpravo: skutočný zásah.</figcaption></figure>
<p>Výhody: poradie kontrol máte pod kontrolou a celé pravidlo „čo sa stane pri zásahu“ je na jednom mieste. Nepresnosť je malá, lebo na mriežke 84 × 48 sú sprity také drobné, že obdĺžnik a skutočný obrys sa líšia len o pár virtuálnych pixelov.</p>
<h2 id="pooling-len-tam-kde-sa-oplati"><a class="heading-anchor" href="#pooling-len-tam-kde-sa-oplati" aria-hidden="true" tabindex="-1">#</a>Pooling len tam, kde sa oplatí</h2>
<p>Pooling znamená, že objekt po zániku nezmažete, len ho skryjete a nabudúce použijete znova. Je lákavé poolovať všetko vrátane výbuchov a power-upov. Má to však cenu: každá kolízia a každý update musí najprv overiť, či je objekt vôbec živý. Stačí jedna zabudnutá kontrola a skrytý nepriateľ zabije hráča alebo skrytá strela zostrelí raketu. Keď sme v našej hre pooling zo všetkého odstraňovali, mazali sme hlavne tieto kontroly.</p>
<p>Jednoduché pravidlo: poolujte to, čoho je veľa a vzniká to stále rovnako. Hráč si pri štarte vytvorí 21 striel a potom ich len recykluje. Nepriatelia a ich strely sa tiež znovu používajú, výbuchy a power-upy vznikajú a zanikajú normálne.</p>
<h2 id="levely-ako-tabulka-casov"><a class="heading-anchor" href="#levely-ako-tabulka-casov" aria-hidden="true" tabindex="-1">#</a>Levely ako tabuľka časov</h2>
<p>Level nemusí byť kód, stačí zoznam záznamov. Každý záznam hovorí, čo sa objaví, v ktorej sekunde, v akej výške a ako sa pohybuje. Pohyb je zoznam bodov, cez ktoré nepriateľ letí rovnakou rýchlosťou. Voliteľne pribudnú časy výstrelov, výpad bossa alebo generátor menších nepriateľov.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">každý snímok:
    čas = teraz - začiatok levelu - čas v pauze
    pre každý záznam v tabuľke levelu:
        ak záznam ešte nebol použitý a čas &gt; záznam.čas:
            vytvor objekt podľa záznamu
            označ záznam ako použitý</code></pre>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/level1.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/level1.png" alt="Časová os prvého levelu: zľava doprava sekundy, výška ikon zodpovedá výške, v ktorej sa nepriateľ objaví" width="1004" height="120" loading="lazy" decoding="async" /></picture><figcaption>Prvý level: 39 záznamov za 67 sekúnd. Kométy, potom vlny rakiet, power-upy (červené) a na konci boss.</figcaption></figure>
<p>Takto zapísaný level sa ľahko ladí: posunúť vlnu o pol sekundy znamená zmeniť jedno číslo. Na časovej osi je vidno aj rytmus, o ktorý ide: krátka vlna, pauza, iný typ nepriateľa, power-up pred ťažším úsekom.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/path.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/path.png" alt="Raketa letiaca cik-cak cez hernú plochu, body zlomu dráhy sú vyznačené" width="1008" height="576" loading="lazy" decoding="async" /></picture><figcaption>Dráha rakety z prvého levelu: šesť bodov, cez ktoré letí rovnakou rýchlosťou.</figcaption></figure>
<p>Nevýhodu cítiť pri väčších zmenách. Najhustejší level našej hry má 71 riadkov a taká tabuľka sa bez editora číta zle. Výhodnejšie je zapisovať levely ako vlny (skupina nepriateľov s jedným vzorom pohybu), nie ako jednotlivé lode.</p>
<h2 id="nekonecny-rezim-z-jedneho-pseudonahodneho-cisla"><a class="heading-anchor" href="#nekonecny-rezim-z-jedneho-pseudonahodneho-cisla" aria-hidden="true" tabindex="-1">#</a>Nekonečný režim z jedného pseudonáhodného čísla</h2>
<p>Pre rebríček (v našej hre Game Center) potrebujete režim, ktorý nekončí. Ručne písanú tabuľku v ňom nahradí generátor, ktorý ju vyrába za behu. Vyrobí 20-ciferné pseudonáhodné číslo a jednotlivé dvojice číslic rozhodnú o jednej vlne:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">číslo = ďalšie pseudonáhodné 20-ciferné číslo
typ = power-up, ak číslice 4 až 5 &gt; 90, inak nepriateľ
sprite = číslice 1 až 2 modulo počet druhov nepriateľov
pauza pred vlnou = číslica 3 / 2 sekundy (aspoň 1)
výška = číslica 7, prepočítaná na výšku plochy
vzor pohybu = číslice 10 až 11 modulo 10
počet lodí vo vlne = číslica 17 / 2 + 1
rýchlosť = číslica 13 × (1 + poradie vlny / 10), v rozsahu 20 až 50
každá tridsiata vlna = boss, generovanie stojí, kým boss žije</code></pre>
<p>Seed generátora (počiatočná hodnota) nechajte v hre pevný. Každý hráč potom dostane rovnaké poradie vĺn a skóre v rebríčku sa dá porovnávať.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/patterns.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/patterns.png" alt="Desať malých hracích plôch, na každej jeden vzor pohybu nepriateľa vyznačený prerušovanou čiarou" width="912" height="228" loading="lazy" decoding="async" /></picture><figcaption>Desať vzorov pohybu z generátora. Konkrétny tvar závisí od výšky, v ktorej sa vlna objaví.</figcaption></figure>
<p>Ako sa takýto režim vyvíja, ukazuje simulácia generátora mimo hry (300 vĺn, rovnaký seed ako v hre). Prvý boss príde po 29 vlnách, teda po 74 nepriateľoch a 3 power-upoch, zhruba po 130 sekundách. Prvých 30 vĺn letí minimálnou rýchlosťou 20, lebo jedna číslica je ešte menšia ako dolná hranica. Priemerná rýchlosť potom rastie na 28,5, 37,5 a 41, od 120. vlny je väčšina nepriateľov na maxime. Power-up je približne každá jedenásta vlna.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/shmup-apple-watch-devlog/ranked.webp" /><img src="https://smallwater.eu/blog/shmup-apple-watch-devlog/ranked.png" alt="Časová os prvých 135 sekúnd nekonečného režimu s ikonami nepriateľov a bossom na konci" width="1104" height="120" loading="lazy" decoding="async" /></picture><figcaption>Prvých 135 sekúnd nekonečného režimu podľa simulácie generátora. Vlny sú dlhšie a nepravidelnejšie než v ručne písanom leveli.</figcaption></figure>
<p>Slabina je, že generátor nepozná rytmus. Ručný level strieda tlak a oddych, generátor nie. Čiastočne to rieši pauza počas bossa a rast rýchlosti, ale ručne písané levely sa hrajú lepšie.</p>
<h2 id="limity-tohto-pristupu"><a class="heading-anchor" href="#limity-tohto-pristupu" aria-hidden="true" tabindex="-1">#</a>Limity tohto prístupu</h2>
<ul>
<li><strong>Žiadne automatické testy.</strong> Všetko sa overuje ručne v simulátore a na hodinkách.</li>
<li><strong>Globálny stav.</strong> Väčšina stavu hry sú globálne premenné jednej scény. Pre hru len na hodinky je to pohodlné, pri pridaní verzie pre iPhone už nie.</li>
<li><strong>Levely bez editora.</strong> Tabuľka časov sa dobre ladí po malých krokoch, pri väčších zmenách by si zaslúžila editor.</li>
</ul>
<p>Ak robíte hru na hodinky alebo inú malú obrazovku a chcete s niečím pomôcť, <a href="https://smallwater.eu/#kontakt">napíšte nám</a>. Ako generujeme hlavolamy v ďalšej našej aplikácii, sme opísali v článku o <a href="https://smallwater.eu/blog/sudoku-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/shmup-apple-watch-devlog/" data-ga-destination="/blog/sudoku-generator/" data-ga-placement="inline">generátore sudoku</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako vygenerovať sudoku s jediným riešením</title>
      <link>https://smallwater.eu/blog/sudoku-generator/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/sudoku-generator/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Krížovky</category>
      <description>Návod na generátor sudoku: rýchly solver s bitovými maskami, náhodná plná mriežka, mazanie s kontrolou jediného riešenia a nastavenie obťažnosti.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/sudoku-generator/cover.webp" /><img src="https://smallwater.eu/blog/sudoku-generator/cover.jpg" alt="Rozpracované sudoku s malými číslicami kandidátov a vyznačeným políčkom s jediným kandidátom" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#preco-prave-jedno-riesenie" data-ga="blog_toc_clicked" data-ga-heading-id="preco-prave-jedno-riesenie">Prečo práve jedno riešenie</a></li><li><a href="#krok-1-napiste-rychly-solver" data-ga="blog_toc_clicked" data-ga-heading-id="krok-1-napiste-rychly-solver">Krok 1: Napíšte rýchly solver</a></li><li><a href="#krok-2-vyplnte-celu-mriezku" data-ga="blog_toc_clicked" data-ga-heading-id="krok-2-vyplnte-celu-mriezku">Krok 2: Vyplňte celú mriežku</a></li><li><a href="#preco-nestaci-vymazat-nahodne-policka" data-ga="blog_toc_clicked" data-ga-heading-id="preco-nestaci-vymazat-nahodne-policka">Prečo nestačí vymazať náhodné políčka</a></li><li><a href="#krok-3-mazte-s-kontrolou-jednoznacnosti" data-ga="blog_toc_clicked" data-ga-heading-id="krok-3-mazte-s-kontrolou-jednoznacnosti">Krok 3: Mažte s kontrolou jednoznačnosti</a></li><li><a href="#krok-4-nastavte-obtaznost-poctom-prazdnych-policok" data-ga="blog_toc_clicked" data-ga-heading-id="krok-4-nastavte-obtaznost-poctom-prazdnych-policok">Krok 4: Nastavte obťažnosť počtom prázdnych políčok</a></li><li><a href="#rychlost-a-spolahlivost-generatora" data-ga="blog_toc_clicked" data-ga-heading-id="rychlost-a-spolahlivost-generatora">Rýchlosť a spoľahlivosť generátora</a></li><li><a href="#kde-pocet-prazdnych-policok-nestaci" data-ga="blog_toc_clicked" data-ga-heading-id="kde-pocet-prazdnych-policok-nestaci">Kde počet prázdnych políčok nestačí</a></li></ol></nav><p>Sudoku s jediným riešením vygenerujete v dvoch krokoch. Najprv vyplňte prázdnu mriežku 9×9 celú, backtrackingom s náhodným poradím číslic. Potom z nej po jednom a v náhodnom poradí mažte políčka a po každom vymazaní solverom skontrolujte, či má zadanie stále práve jedno riešenie. Ak nie, číslicu vráťte späť. Na bežnom procesore to trvá v mediáne menej ako milisekundu.</p>
<p>Postup je vhodný pre každú hru alebo aplikáciu, ktorá potrebuje neobmedzený počet nových zadaní bez ručnej prípravy. Takto vznikajú sudoku napríklad v našej aplikácii s krížovkami a hlavolamami (<a href="https://smallwater.eu/crosswords-ios">App Store</a>, <a href="https://smallwater.eu/crosswords-android">Google Play</a>).</p>
<h2 id="preco-prave-jedno-riesenie"><a class="heading-anchor" href="#preco-prave-jedno-riesenie" aria-hidden="true" tabindex="-1">#</a>Prečo práve jedno riešenie</h2>
<p>Pravidlá sudoku pozná každý: v každom riadku, stĺpci a štvorci 3×3 je každá číslica 1 až 9 práve raz. Menej sa hovorí o tom, že dobré zadanie má <strong>jediné</strong> riešenie. Až vtedy sa dá vyriešiť čistou logikou, bez hádania.</p>
<p>Má to aj praktický dôsledok. Hra si zvyčajne pamätá jedno riešenie a podľa neho kontroluje výsledok a dáva nápovedy. Ak má zadanie riešenia dve, hráč môže dôjsť k tomu druhému, ktoré je rovnako správne, a hra mu ho neuzná.</p>
<h2 id="krok-1-napiste-rychly-solver"><a class="heading-anchor" href="#krok-1-napiste-rychly-solver" aria-hidden="true" tabindex="-1">#</a>Krok 1: Napíšte rýchly solver</h2>
<p>Solver je program, ktorý sudoku vyrieši. Pracuje backtrackingom: vyplní políčko jednou z možných číslic, pokračuje ďalším políčkom, a keď narazí na spor, vráti sa a skúsi inú číslicu. Budete ho potrebovať dvakrát: na vyplnenie prázdnej mriežky a na kontrolu jednoznačnosti. Kontrola beží po každom vymazanom políčku, teda 40 až 70-krát na jedno zadanie, preto musí byť rýchla. Dve veci rozhodujú:</p>
<ul>
<li><strong>Bitové masky.</strong> Pre každý riadok, stĺpec a štvorec si držte 9-bitové číslo s už použitými číslicami. Kandidáti políčka, teda číslice, ktoré doň ešte môžu prísť, sú potom jedna operácia: všetky číslice okrem tých, ktoré sú v jeho riadku, stĺpci alebo štvorci. Počet kandidátov je počet jednotkových bitov a dá sa vopred vypočítať do tabuľky s 512 hodnotami.</li>
<li><strong>Najprv políčko s najmenším počtom kandidátov.</strong> Nevyberajte prvé prázdne políčko zľava, ale to, ktoré má najmenej možností (v literatúre heuristika MRV, minimum remaining values). Políčko s jediným kandidátom vyplňte hneď, políčko bez kandidáta znamená slepú vetvu a vraciate sa.</li>
</ul>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/sudoku-generator/candidates.webp" /><img src="https://smallwater.eu/blog/sudoku-generator/candidates.png" alt="Rozpracovaná mriežka s malými číslicami kandidátov v prázdnych políčkach, jedno políčko s jediným kandidátom je zvýraznené" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Kandidáti v prázdnych políčkach. Zvýraznené políčko má jediného kandidáta, solver ho vyplní ako prvé.</figcaption></figure>
<p>Na ťažkých zadaniach potrebuje kontrola jednoznačnosti s týmto pravidlom v mediáne 311 krokov. Ak solver berie políčka postupne zľava doprava, v mediáne 28 579 krokov a v najhorších 5 % prípadov vyše 300 000. Rozdiel je zhruba 90-násobný.</p>
<h2 id="krok-2-vyplnte-celu-mriezku"><a class="heading-anchor" href="#krok-2-vyplnte-celu-mriezku" aria-hidden="true" tabindex="-1">#</a>Krok 2: Vyplňte celú mriežku</h2>
<p>Plnú mriežku dostanete tým istým solverom spusteným na prázdnej mriežke. Jediný rozdiel je, že kandidátov políčka skúšate v náhodnom poradí. Prvé nájdené riešenie je náhodná, platná, úplne vyplnená mriežka.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/sudoku-generator/solved.webp" /><img src="https://smallwater.eu/blog/sudoku-generator/solved.png" alt="Úplne vyplnená mriežka sudoku 9×9" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Náhodná plná mriežka. Z nej vznikne zadanie.</figcaption></figure>
<h2 id="preco-nestaci-vymazat-nahodne-policka"><a class="heading-anchor" href="#preco-nestaci-vymazat-nahodne-policka" aria-hidden="true" tabindex="-1">#</a>Prečo nestačí vymazať náhodné políčka</h2>
<p>Najjednoduchší nápad je vymazať z plnej mriežky N náhodných políčok, napríklad 40, 50 alebo 55 podľa obťažnosti. Zadanie vyzerá v poriadku, len často nemá jediné riešenie.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/sudoku-generator/ambiguous.webp" /><img src="https://smallwater.eu/blog/sudoku-generator/ambiguous.png" alt="Zadanie sudoku, v ktorom štyri políčka v rohoch obdĺžnika majú dve možné dvojice číslic" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Zadanie z náhodného vymazávania. Vo vyznačených políčkach sedia obe dvojice číslic, zadanie má dve riešenia.</figcaption></figure>
<p>Typický prípad je na obrázku. Štyri políčka v rohoch obdĺžnika, ktorý zasahuje do dvoch štvorcov, sa dajú vyplniť dvoma spôsobmi a zvyšok mriežky to nijako nerozhodne. Riešitelia sudoku tento vzor poznajú ako deadly pattern. Na 1 000 zadaniach pre každú obťažnosť vyzerá výsledok takto:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Obťažnosť</th>
<th align="right">Vymazaných políčok</th>
<th align="right">Zadaní s viac ako jedným riešením</th>
</tr>
</thead>
<tbody><tr>
<td>Ľahká</td>
<td align="right">40</td>
<td align="right">59 %</td>
</tr>
<tr>
<td>Stredná</td>
<td align="right">50</td>
<td align="right">96 %</td>
</tr>
<tr>
<td>Ťažká</td>
<td align="right">55</td>
<td align="right">100 %</td>
</tr>
</tbody></table>
</div>
<p>Ťažké zadanie z náhodného vymazávania má v priemere stovky riešení. Kontrola jednoznačnosti po každom vymazaní je preto nutná.</p>
<h2 id="krok-3-mazte-s-kontrolou-jednoznacnosti"><a class="heading-anchor" href="#krok-3-mazte-s-kontrolou-jednoznacnosti" aria-hidden="true" tabindex="-1">#</a>Krok 3: Mažte s kontrolou jednoznačnosti</h2>
<p>Prejdite všetkých 81 políčok v náhodnom poradí. Každé skúste vymazať a solverom spočítajte riešenia, ale počítanie zastavte pri dvoch. Viac netreba vedieť: jedno riešenie znamená, že políčko môže ostať prázdne, dve znamenajú, že ho treba vrátiť.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">zadanie = kópia plnej mriežky
prázdnych = 0
pre každé políčko v náhodnom poradí:
    ak prázdnych == cieľ: koniec
    číslica = zadanie[políčko]
    zadanie[políčko] = prázdne
    ak počet_riešení(zadanie, najviac 2) == 1:
        prázdnych = prázdnych + 1
    inak:
        zadanie[políčko] = číslica</code></pre>
<p>Políčko, ktoré raz vrátite, už neskúšajte znova. Ďalšie mazanie zadanie len uvoľňuje, takže ak políčko nešlo vymazať teraz, nepôjde ani neskôr.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/sudoku-generator/dig.webp" /><img src="https://smallwater.eu/blog/sudoku-generator/dig.png" alt="Rozpracované ťažké zadanie s 37 prázdnymi políčkami a jednou zvýraznenou číslicou, ktorá sa musela vrátiť" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Po 37 vymazaných políčkach. Zvýraznenú číslicu sa vymazať nepodarilo, zadanie by malo dve riešenia, preto sa vrátila.</figcaption></figure>
<h2 id="krok-4-nastavte-obtaznost-poctom-prazdnych-policok"><a class="heading-anchor" href="#krok-4-nastavte-obtaznost-poctom-prazdnych-policok" aria-hidden="true" tabindex="-1">#</a>Krok 4: Nastavte obťažnosť počtom prázdnych políčok</h2>
<p>Najjednoduchšia miera obťažnosti je počet vymazaných políčok. Tri úrovne, ktoré používame v našej hre:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Obťažnosť</th>
<th align="right">Prázdnych políčok</th>
<th align="right">Zostáva číslic</th>
<th align="right">Priemerne vrátených políčok</th>
</tr>
</thead>
<tbody><tr>
<td>Ľahká</td>
<td align="right">40</td>
<td align="right">41</td>
<td align="right">0,9</td>
</tr>
<tr>
<td>Stredná</td>
<td align="right">50</td>
<td align="right">31</td>
<td align="right">4,5</td>
</tr>
<tr>
<td>Ťažká</td>
<td align="right">55</td>
<td align="right">26</td>
<td align="right">12,7</td>
</tr>
</tbody></table>
</div>
<p>Čím menej číslic ostáva, tým častejšie by ďalšie vymazanie pokazilo jednoznačnosť a políčko sa musí vrátiť. Pri ťažkej úrovni sa jeden prechod občas zastaví skôr, než dosiahne 55 prázdnych políčok. Vtedy vygenerujte novú plnú mriežku a skúste to znova. Pri meraní na 1 000 ťažkých zadaniach stačil prvý prechod v 97,6 % prípadov a nikdy nebolo treba viac ako 3 pokusy.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/sudoku-generator/final.webp" /><img src="https://smallwater.eu/blog/sudoku-generator/final.png" alt="Hotové ťažké zadanie sudoku s 26 číslicami" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Hotové ťažké zadanie: 26 číslic a jediné riešenie.</figcaption></figure>
<h2 id="rychlost-a-spolahlivost-generatora"><a class="heading-anchor" href="#rychlost-a-spolahlivost-generatora" aria-hidden="true" tabindex="-1">#</a>Rýchlosť a spoľahlivosť generátora</h2>
<p>Generátor sme spustili na 1 000 zadaniach pre každú obťažnosť (Node.js 22 na serverovom procesore, telefón bude pomalší):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Obťažnosť</th>
<th align="right">Medián času</th>
<th align="right">95 % zadaní do</th>
<th align="right">Najdlhšie</th>
<th align="right">Zadaní s viac riešeniami</th>
</tr>
</thead>
<tbody><tr>
<td>Ľahká</td>
<td align="right">0,13 ms</td>
<td align="right">0,29 ms</td>
<td align="right">3,5 ms</td>
<td align="right">0</td>
</tr>
<tr>
<td>Stredná</td>
<td align="right">0,21 ms</td>
<td align="right">0,38 ms</td>
<td align="right">0,8 ms</td>
<td align="right">0</td>
</tr>
<tr>
<td>Ťažká</td>
<td align="right">0,48 ms</td>
<td align="right">1,39 ms</td>
<td align="right">5,5 ms</td>
<td align="right">0</td>
</tr>
</tbody></table>
</div>
<p>Každé vygenerované zadanie sme navyše nezávisle skontrolovali solverom. Všetkých 3 000 malo jediné riešenie.</p>
<h2 id="kde-pocet-prazdnych-policok-nestaci"><a class="heading-anchor" href="#kde-pocet-prazdnych-policok-nestaci" aria-hidden="true" tabindex="-1">#</a>Kde počet prázdnych políčok nestačí</h2>
<p>Počet vodítok je hrubá miera obťažnosti. Dve zadania s 26 číslicami môžu byť rôzne ťažké podľa toho, aké logické techniky vyžadujú. Presnejšia obťažnosť by zadanie vyriešila ako človek a zaznamenala najťažšiu techniku, ktorú musela použiť. Na mobilnú hru, kde si hráč vyberá z troch úrovní, počet vodítok stačí.</p>
<aside class="app-box app-box-callout"><img class="app-box-img" src="https://smallwater.eu/images/crosswords/1-crosswords.jpg" alt="" width="698" height="1516" loading="lazy" decoding="async" /><div class="app-box-body"><p class="app-box-eyebrow">Vyskúšajte</p><h2 class="app-box-title">Krížovky</h2><p>Krížovky, osemsmerovky, sudoku a maľované krížovky v jednej offline aplikácii. Ako vznikajú hádanky a čo je nové.</p><div class="app-box-links"><a class="btn btn-primary" href="https://smallwater.eu/crosswords-ios" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="crosswords" data-ga-destination="/crosswords-ios" data-ga-app="crosswords" data-ga-store="app_store">App Store</a><a class="btn btn-ghost" href="https://smallwater.eu/crosswords-android" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="crosswords" data-ga-destination="/crosswords-android" data-ga-app="crosswords" data-ga-store="google_play">Google Play</a></div></div></aside>
<p>Ak riešite podobný problém, generujete hlavolamy alebo potrebujete mobilnú aplikáciu, <a href="https://smallwater.eu/#kontakt">napíšte nám</a>. Ako vygenerovať osemsmerovku s tajničkou, opisuje <a href="https://smallwater.eu/blog/word-search-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/sudoku-generator/" data-ga-destination="/blog/word-search-generator/" data-ga-placement="inline">samostatný návod</a>. Švédskej krížovke s tajničkou sa venuje <a href="https://smallwater.eu/blog/swedish-crossword-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/sudoku-generator/" data-ga-destination="/blog/swedish-crossword-generator/" data-ga-placement="inline">ďalší návod</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako vygenerovať švédsku krížovku s tajničkou</title>
      <link>https://smallwater.eu/blog/swedish-crossword-generator/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/swedish-crossword-generator/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Krížovky</category>
      <description>Návod na generátor švédskej krížovky: maska cez hill climbing, plnenie slovami ako CSP s backtrackingom a tajnička hľadaná až v hotovej mriežke.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/swedish-crossword-generator/cover.webp" /><img src="https://smallwater.eu/blog/swedish-crossword-generator/cover.jpg" alt="Dve švédske krížovky 10×10: vľavo náhodná maska s penalizovanými políčkami, vpravo vyplnená mriežka s koralovými písmenami tajničky" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#co-musi-svedska-krizovka-splnat" data-ga="blog_toc_clicked" data-ga-heading-id="co-musi-svedska-krizovka-splnat">Čo musí švédska krížovka spĺňať</a></li><li><a href="#preco-nestaci-plnit-mriezku-slovo-po-slove" data-ga="blog_toc_clicked" data-ga-heading-id="preco-nestaci-plnit-mriezku-slovo-po-slove">Prečo nestačí plniť mriežku slovo po slove</a></li><li><a href="#krok-1-maska-cez-hill-climbing" data-ga="blog_toc_clicked" data-ga-heading-id="krok-1-maska-cez-hill-climbing">Krok 1: maska cez hill climbing</a></li><li><a href="#krok-2-plnenie-slovami-ako-csp" data-ga="blog_toc_clicked" data-ga-heading-id="krok-2-plnenie-slovami-ako-csp">Krok 2: plnenie slovami ako CSP</a></li><li><a href="#krok-3-tajnicka-az-v-hotovej-mriezke" data-ga="blog_toc_clicked" data-ga-heading-id="krok-3-tajnicka-az-v-hotovej-mriezke">Krok 3: tajnička až v hotovej mriežke</a></li><li><a href="#preco-nevkladat-tajnicku-vopred-ako-v-novinach" data-ga="blog_toc_clicked" data-ga-heading-id="preco-nevkladat-tajnicku-vopred-ako-v-novinach">Prečo nevkladať tajničku vopred ako v novinách</a></li><li><a href="#vykon-generatora-a-preco-nebezi-v-telefone" data-ga="blog_toc_clicked" data-ga-heading-id="vykon-generatora-a-preco-nebezi-v-telefone">Výkon generátora a prečo nebeží v telefóne</a></li><li><a href="#generovanie-vo-velkom" data-ga="blog_toc_clicked" data-ga-heading-id="generovanie-vo-velkom">Generovanie vo veľkom</a></li><li><a href="#co-riesenie-nepokryva" data-ga="blog_toc_clicked" data-ga-heading-id="co-riesenie-nepokryva">Čo riešenie nepokrýva</a></li></ol></nav><p>Švédsku krížovku s tajničkou vygenerujete v dvoch fázach: najprv lokálnym prehľadávaním nájdite masku, teda rozmiestnenie políčok s legendami a políčok na písmená, a potom ju vyplňte slovami zo slovníka ako úlohu s obmedzeniami (CSP) s backtrackingom. Tajničku do mriežky nevkladajte vopred. Až keď je krížovka hotová, hľadajte vtip, ktorého pointa sa v nej dá prečítať zľava doprava a zhora nadol.</p>
<p>Takto vznikajú švédske krížovky v našej aplikácii s krížovkami a osemsmerovkami (<a href="https://smallwater.eu/crosswords-ios">App Store</a>, <a href="https://smallwater.eu/crosswords-android">Google Play</a>). Jedna mriežka stojí niekoľko až desiatky sekúnd procesorového času, preto ich generujeme vopred na výkonnejšom počítači a do aplikácie ich pribalíme. Osemsmerovka sa generuje úplne inak a jednoduchšie, postup je v článku <a href="https://smallwater.eu/blog/word-search-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/swedish-crossword-generator/" data-ga-destination="/blog/word-search-generator/" data-ga-placement="inline">Ako vygenerovať osemsmerovku s tajničkou</a>.</p>
<h2 id="co-musi-svedska-krizovka-splnat"><a class="heading-anchor" href="#co-musi-svedska-krizovka-splnat" aria-hidden="true" tabindex="-1">#</a>Čo musí švédska krížovka spĺňať</h2>
<p>Švédska krížovka nemá čierne políčka. Každé políčko je písmeno alebo legenda so šípkou doprava či nadol. Z toho vyplývajú tvrdé podmienky:</p>
<ul>
<li>Každé písmeno patrí vodorovnému aj zvislému slovu. Písmeno, ktoré kríži len jedno slovo, sa nedá skontrolovať a v krížovke pôsobí ako chyba.</li>
<li>Každé slovo má aspoň dve písmená a pred ním je legenda. Preto je prvý riadok a prvý stĺpec vždy celý z legiend.</li>
<li>Písmená tvoria jeden súvislý celok, žiadne odtrhnuté ostrovy.</li>
<li>Každé slovo musí byť v slovníku a mať aspoň jednu legendu. Žiadne slovo sa v mriežke neopakuje.</li>
</ul>
<p>K tomu mäkké kritériá kvality: málo dvojpísmenových slov, žiadne legendy, ktoré neukazujú na nič, a čo najmenej veľmi dlhých slov, ktoré sa ťažko plnia. Problém je výpočtovo ťažký. <a href="https://link.springer.com/chapter/10.1007/978-3-642-30347-0_15" target="_blank" rel="noopener">Engel a kol. (2012)</a> ukázali, že už samotné plnenie masky slovami je NP-úplné.</p>
<h2 id="preco-nestaci-plnit-mriezku-slovo-po-slove"><a class="heading-anchor" href="#preco-nestaci-plnit-mriezku-slovo-po-slove" aria-hidden="true" tabindex="-1">#</a>Prečo nestačí plniť mriežku slovo po slove</h2>
<p>Prvý nápad je postupovať ako človek s ceruzkou: rozdeliť tajničku na slová, položiť ich do riadku ako v novinách, cez každé jej písmeno preložiť krížiace slovo a zvyšok mriežky dopĺňať greedy algoritmom. Greedy algorithm v každom kroku vyberie možnosť, ktorá vyzerá najlepšie práve teraz, a rozhodnutie už nikdy neprehodnotí. Kde nič nepasuje, ostane diera. A keďže sa greedy plnenie nevie vrátiť, zlých prvých desať slov zvyšok mriežky už nezachráni.</p>
<p>Lepšia heuristika výberu slov pomôže len trochu. Môžete skúšať dĺžky krížiacich slov od stredných, okolo piatich písmen, ku krajným. Alebo použiť evolučný prístup: populácia rozpracovaných krížoviek, z každej päť potomkov s jedným slovom navyše, ohodnotenie podľa počtu zaplnených políčok, počtu samohlások a rovnomernosti písmen a do ďalšej generácie postúpia len najlepšie. Výsledky sú o niečo hustejšie, ale diery ostanú, lebo heuristika vidí len najbližší krok.</p>
<p>Riešenie prináša bakalárska práca Jakoba Engela z TU Mníchov (2009), ktorý je aj prvým autorom spomínaného článku. Úlohu rozdeľte na dve nezávislé fázy: najprv nájdite dobrú masku bez ohľadu na slová, potom ju plňte. Engel na masku použil evolučný algoritmus. My používame jednoduchšie lokálne prehľadávanie s náhodnými otrasmi, opísané v nasledujúcom kroku.</p>
<h2 id="krok-1-maska-cez-hill-climbing"><a class="heading-anchor" href="#krok-1-maska-cez-hill-climbing" aria-hidden="true" tabindex="-1">#</a>Krok 1: maska cez hill climbing</h2>
<p>Maska je mriežka, v ktorej je každé políčko buď legenda, alebo písmeno. Na jej hľadanie stačí hill climbing: lokálne prehľadávanie, ktoré robí malé zmeny a ponechá tie, ktoré riešenie nezhoršia.</p>
<p>Začnite náhodne, s legendami asi na 22 % vnútorných políčok. Každú masku ohodnoťte pokutou. Tvrdé porušenia (príliš krátke slovo, písmeno bez krížiaceho slova, odtrhnutý ostrov) nech stoja zväčša 1 000 bodov, mäkké nedostatky (dvojpísmenové slová nad rozpočet, legenda, ktorá neukazuje na žiadne slovo, zhluky legiend) 50 až 500.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/swedish-crossword-generator/mask-random.webp" /><img src="https://smallwater.eu/blog/swedish-crossword-generator/mask-random.png" alt="Náhodná maska 10×10 s množstvom červeno a oranžovo zvýraznených problémových políčok" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>Náhodná maska. Červené políčka sú písmená s porušením, oranžové legendy, ktoré nikam neukazujú. Pokuta 35 500.</figcaption></figure>
<p>Potom v cykle zmeňte jedno políčko z legendy na písmeno alebo naopak, prípadne posuňte legendu o políčko šikmo. Zmenu ponechajte, ak sa pokuta nezvýšila, inak ju vráťte. Polovicu zmien namierte na políčka s najvyššou pokutou, potom hrubé chyby zmiznú rýchlo:</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/swedish-crossword-generator/mask-mid.webp" /><img src="https://smallwater.eu/blog/swedish-crossword-generator/mask-mid.png" alt="Tá istá maska po 150 krokoch, problémových políčok ostalo len niekoľko" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>Po 150 krokoch. Pokuta 1 770.</figcaption></figure>
<p>Hill climbing ľahko uviazne v lokálnom minime. Keď sa 2 000 krokov nič nezlepší, mriežku „zatraste“: v najlepšej doteraz nájdenej maske náhodne prehoďte päť políčok a hľadajte ďalej. Na grafe sú to špičky. Ak ani po troch zatraseniach nie je lepšia maska a nemá tvrdé porušenia, skončite.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/swedish-crossword-generator/penalty.webp" /><img src="https://smallwater.eu/blog/swedish-crossword-generator/penalty.png" alt="Graf pokuty v logaritmickej mierke počas 8 300 krokov: rýchly pád z 35 500 na 200 a tri krátke špičky po zatrasení" width="1800" height="850" loading="lazy" decoding="async" /></picture><figcaption>Pokuta počas generovania masky 10×10. Body označujú dve predchádzajúce snímky.</figcaption></figure>
<p>Výsledná maska má zvyškovú pokutu 200 za mäkké nedostatky, ale žiadne tvrdé porušenie:</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/swedish-crossword-generator/mask-final.webp" /><img src="https://smallwater.eu/blog/swedish-crossword-generator/mask-final.png" alt="Hotová maska 10×10 so šípkami v políčkach legiend a prázdnymi políčkami na písmená" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>Hotová maska. Šípky ukazujú smer slova.</figcaption></figure>
<p>Pokuty za legendu bez slova a za odtrhnutý ostrov sa oplatí mať v maske od začiatku. Na veľkom súbore hotových krížoviek bez nich malo 13,2 % legiend neukazujúcich na žiadne slovo a 23,5 % mriežok odtrhnutý ostrov písmen. S nimi je to 5,0 % a 0 %.</p>
<h2 id="krok-2-plnenie-slovami-ako-csp"><a class="heading-anchor" href="#krok-2-plnenie-slovami-ako-csp" aria-hidden="true" tabindex="-1">#</a>Krok 2: plnenie slovami ako CSP</h2>
<p>Z masky vznikne zoznam slotov, teda miest na slová so známou dĺžkou a kríženiami. Plnenie je klasická úloha s obmedzeniami (constraint satisfaction problem, CSP): každý slot je premenná, jeho doména sú slová, ktoré sa doň zmestia, a obmedzenia sú zhodné písmená v kríženiach. Rieši sa backtrackingom, teda prehľadávaním, ktoré sa pri slepej vetve vráti o krok späť a skúsi inú možnosť:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">pre každý slot: doména = všetky slová danej dĺžky
kým nie sú všetky sloty vyplnené:
    vyber slot s najmenšou doménou (pri zhode ten s najviac kríženiami)
    pre každé slovo z domény, od najlepšieho:
        polož slovo
        zúž domény krížiacich slotov na slová so zhodným písmenom
        ak niektorá doména ostala prázdna: vráť zmenu, skús ďalšie slovo
        inak pokračuj ďalším slotom
    ak žiadne slovo nevyhovuje: vráť sa o slot späť
po 50 000 krokoch to vzdaj</code></pre>
<p>V literatúre sa tieto heuristiky volajú MRV (minimum remaining values, najprv slot s najmenej možnosťami) a forward checking (po každom položení zúžiť domény susedov, aby sa slepá vetva odhalila hneď, nie o desať slov neskôr). Slovník si predpočítajte podľa dĺžky, pozície a písmena. Slová so „Š na treťom mieste“ sú potom jeden prienik množín, nie prechod celým slovníkom.</p>
<p>Dôležité je aj poradie slov. Prednosť dajte slovám, ktoré majú v slovníku viac rôznych legiend. Je to jednoduchý ukazovateľ bežného krížovkárskeho slova. Bez neho sa mriežka zaplní neznámymi skratkami.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/swedish-crossword-generator/filled.webp" /><img src="https://smallwater.eu/blog/swedish-crossword-generator/filled.png" alt="Maska 10×10 vyplnená anglickými slovami" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>Vyplnená mriežka. Obrázky v článku používajú anglický slovník, čísla v tabuľkách sú zo slovenského.</figcaption></figure>
<h2 id="krok-3-tajnicka-az-v-hotovej-mriezke"><a class="heading-anchor" href="#krok-3-tajnicka-az-v-hotovej-mriezke" aria-hidden="true" tabindex="-1">#</a>Krok 3: tajnička až v hotovej mriežke</h2>
<p>Pri plnení ešte neviete, aká bude tajnička. Prečítajte písmená hotovej mriežky zľava doprava a zhora nadol a hľadajte vtip, ktorého pointa je v tomto reťazci podpostupnosťou. Písmená teda idú v správnom poradí, ale nemusia byť vedľa seba. Ak sa nájde, rozmiestnite písmená tajničky čo najrovnomernejšie po mriežke, aby sa pointa nedala uhádnuť z jedného slova.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/swedish-crossword-generator/secret-order.webp" /><img src="https://smallwater.eu/blog/swedish-crossword-generator/secret-order.png" alt="Vyplnená mriežka s desiatimi koralovými písmenami S, P, A, C, E, S, U, I, T, S očíslovanými v poradí čítania" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>Tajnička SPACESUITS v poradí čítania. Do tejto mriežky by sa zmestilo 111 rôznych pointí z nášho anglického zoznamu vtipov.</figcaption></figure>
<p>Toto poradie (najprv mriežka, potom vtip) zásadne zvyšuje počet hotových krížoviek. Jedna mriežka 12×12 v našom meraní pasuje v priemere na 86 vtipov zo slovenského zoznamu (5 960 vtipov). Vždy sa teda nájde taký, ktorý ešte v danej veľkosti nebol použitý.</p>
<h2 id="preco-nevkladat-tajnicku-vopred-ako-v-novinach"><a class="heading-anchor" href="#preco-nevkladat-tajnicku-vopred-ako-v-novinach" aria-hidden="true" tabindex="-1">#</a>Prečo nevkladať tajničku vopred ako v novinách</h2>
<p>V novinách je tajnička zvyčajne pokope, v jednom riadku a rozdelená na slová. Dá sa to dosiahnuť dvoma spôsobmi a oba dávajú veľmi málo výsledkov.</p>
<p>Prvý spôsob je vložiť tajničku do mriežky vopred. Vtedy sa do nej musí zmestiť. Pointa má u nás v priemere 10,3 písmena a 2 slová. Takto vyzerá podiel vtipov zo slovenského zoznamu, ktoré sa do danej veľkosti vôbec dajú položiť:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Mriežka</th>
<th align="right">Celá pointa v jednom riadku</th>
<th align="right">Pointa rozdelená na slová (najviac 3)</th>
</tr>
</thead>
<tbody><tr>
<td>8×8</td>
<td align="right">0 %</td>
<td align="right">22 %</td>
</tr>
<tr>
<td>10×10</td>
<td align="right">0,1 %</td>
<td align="right">64 %</td>
</tr>
<tr>
<td>12×12</td>
<td align="right">45 %</td>
<td align="right">89 %</td>
</tr>
<tr>
<td>15×15</td>
<td align="right">84 %</td>
<td align="right">98 %</td>
</tr>
</tbody></table>
</div>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/swedish-crossword-generator/newspaper.webp" /><img src="https://smallwater.eu/blog/swedish-crossword-generator/newspaper.png" alt="Mriežka 10×10, v ktorej sú slová tajničky KNOW a RULES položené vopred ako celé vodorovné slová v koralovej farbe" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>Tajnička vložená vopred ako celé slová v riadkoch, tak ako v novinách. Vygenerované tým istým generátorom.</figcaption></figure>
<p>S tajničkou vloženou vopred uspeje plnenie pri malých mriežkach približne rovnako často ako bez nej. Pri 15×15 to bolo 5 zo 60 pokusov oproti 8 bez tajničky, pri 18×18 ani jeden zo 60. Každá taká mriežka navyše slúži jedinému vtipu a pri malých veľkostiach sa väčšina vtipov nedá ani položiť.</p>
<p>Druhý spôsob je nechať mriežku vzniknúť voľne a pointu v nej hľadať ako súvislý úsek. Na vzorke 2 200 hotových slovenských krížoviek (200 z každej veľkosti od 8×8 po 18×18) sa celá pointa ako súvislý úsek v jednom riadku alebo stĺpci našla len v 81 mriežkach (3,7 %). Keď môže každé slovo pointy ležať inde, je to 443 mriežok (20 %), a to ešte bez podmienky, že slová budú pri sebe. Pri čítaní v poradí sa vtip našiel v každej z nich. Za cenu tajničky roztrúsenej po mriežke dostanete rádovo viac krížoviek.</p>
<aside class="app-box app-box-callout"><img class="app-box-img" src="https://smallwater.eu/images/crosswords/1-crosswords.jpg" alt="" width="698" height="1516" loading="lazy" decoding="async" /><div class="app-box-body"><p class="app-box-eyebrow">Vyskúšajte</p><h2 class="app-box-title">Krížovky</h2><p>Krížovky, osemsmerovky, sudoku a maľované krížovky v jednej offline aplikácii. Ako vznikajú hádanky a čo je nové.</p><div class="app-box-links"><a class="btn btn-primary" href="https://smallwater.eu/crosswords-ios" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="crosswords" data-ga-destination="/crosswords-ios" data-ga-app="crosswords" data-ga-store="app_store">App Store</a><a class="btn btn-ghost" href="https://smallwater.eu/crosswords-android" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="crosswords" data-ga-destination="/crosswords-android" data-ga-app="crosswords" data-ga-store="google_play">Google Play</a></div></div></aside>
<h2 id="vykon-generatora-a-preco-nebezi-v-telefone"><a class="heading-anchor" href="#vykon-generatora-a-preco-nebezi-v-telefone" aria-hidden="true" tabindex="-1">#</a>Výkon generátora a prečo nebeží v telefóne</h2>
<p>Generátor bežal 60 pokusov pre každú veľkosť so slovenským slovníkom, v jednom vlákne (JavaScript v Node 22, serverový procesor Intel Xeon 2,8 GHz v cloude). Pokus je jedna maska a jedno plnenie. „Úspešné plnenie“ znamená, že CSP našiel riešenie do 50 000 krokov.</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Mriežka</th>
<th align="right">Maska</th>
<th align="right">Plnenie</th>
<th align="right">Úspešné plnenie</th>
<th align="right">Slov v mriežke</th>
<th align="right">Vtipov na mriežku</th>
</tr>
</thead>
<tbody><tr>
<td>8×8</td>
<td align="right">6 ms</td>
<td align="right">0,50 s</td>
<td align="right">5 %</td>
<td align="right">21</td>
<td align="right">0 (z 3 mriežok)</td>
</tr>
<tr>
<td>10×10</td>
<td align="right">0,16 s</td>
<td align="right">0,86 s</td>
<td align="right">20 %</td>
<td align="right">33</td>
<td align="right">26</td>
</tr>
<tr>
<td>12×12</td>
<td align="right">0,25 s</td>
<td align="right">1,45 s</td>
<td align="right">43 %</td>
<td align="right">52</td>
<td align="right">86</td>
</tr>
<tr>
<td>15×15</td>
<td align="right">0,32 s</td>
<td align="right">1,82 s</td>
<td align="right">13 %</td>
<td align="right">80</td>
<td align="right">424</td>
</tr>
<tr>
<td>18×18</td>
<td align="right">0,52 s</td>
<td align="right">1,82 s</td>
<td align="right">13 %</td>
<td align="right">118</td>
<td align="right">725</td>
</tr>
</tbody></table>
</div>
<p>Čo z meraní vyplýva:</p>
<ul>
<li><strong>Malá mriežka nie je ľahká.</strong> V 8×8 je veľa krátkych slotov s veľkým počtom krížení a málo voľnosti, plnenie uspeje len v 5 % pokusov. Jedna vyplnená mriežka stojí v priemere asi 10 sekúnd procesora pri 8×8, 4 sekundy pri 12×12 a 18 sekúnd pri 18×18.</li>
<li><strong>Väčšina pokusov skončí neúspechom.</strong> Pokusy preto spúšťajte paralelne na všetkých jadrách a berte prvý dosť dobrý výsledok.</li>
<li><strong>V telefóne to nemá zmysel.</strong> Hráč nechce pri otvorení obrazovky čakať desiatky sekúnd a aplikácia by musela niesť celý slovník s legendami. Generátor v aplikácii navyše musí často púšťať UI k slovu, inak aplikácia zamŕza.</li>
</ul>
<h2 id="generovanie-vo-velkom"><a class="heading-anchor" href="#generovanie-vo-velkom" aria-hidden="true" tabindex="-1">#</a>Generovanie vo veľkom</h2>
<p>Generátor preto patrí na počítač s viacjadrovým procesorom. U nás má každé jadro vlastné vlákno so slovníkom načítaným raz pri štarte. Mriežka je hotová, keď niektoré vlákno vráti výsledok s dostatočne malým počtom nevyužitých políčok. Nepretržitý režim generuje dookola jednu krížovku z každej veľkosti v každom jazyku. Takto vzniklo vyše 14 000 krížoviek v štyroch jazykoch (slovenčina, čeština, angličtina, nemčina) a jedenástich veľkostiach od 8×8 po 18×18. Každý vtip je v danej veľkosti a jazyku najviac raz.</p>
<p>Pri takom objeme rozhodujú dve veci:</p>
<ul>
<li><strong>Formát.</strong> Úplný zápis má okolo 28 kB na krížovku a celý korpus 390 MB. Väčšina sa dá dopočítať (súradnice, typy políčok, čísla legiend). Kompaktný zápis s mriežkou ako jedným reťazcom má okolo 2 kB, celý korpus 15 MB a v inštalačnom balíku aplikácie asi 7 MB.</li>
<li><strong>Meranie kvality.</strong> Podiel legiend bez slova a mriežok s ostrovmi merajte po každom generovaní a nechajte kontrolu zlyhať, ak sa čísla zhoršia.</li>
</ul>
<h2 id="co-riesenie-nepokryva"><a class="heading-anchor" href="#co-riesenie-nepokryva" aria-hidden="true" tabindex="-1">#</a>Čo riešenie nepokrýva</h2>
<ul>
<li><strong>Legendy bez slov.</strong> Priemerná krížovka má stále 2,7 legendy, ktoré neukazujú na nič. Ľavý horný roh je taký vždy.</li>
<li><strong>Slovník bez frekvencií.</strong> Nemecký slovník z Wiktionary má na väčšinu slov jedinú legendu, takže poradie podľa počtu legiend skoro nerozlišuje a do mriežky sa dostávajú zriedkavé slová. Pomohol by frekvenčný slovník, zatiaľ ho nemáme.</li>
<li><strong>Tajnička nie je pokope.</strong> Kto je zvyknutý z novín, môže ju hľadať v riadku. Je to kompromis, nie ideál.</li>
</ul>
<p>Ak riešite podobný problém s generovaním obsahu alebo potrebujete mobilnú aplikáciu, <a href="https://smallwater.eu/#kontakt">napíšte nám</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako vygenerovať 3D model telefónu v prehliadači</title>
      <link>https://smallwater.eu/blog/threejs-csg-3d-phone-model/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/threejs-csg-3d-phone-model/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Ecranify</category>
      <description>Návod na 3D model telefónu v prehliadači cez Three.js a CSG: telo s bevel hranou, výrezy tlačidiel a reproduktorov, Web Worker a rýchle odčítanie otvorov.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/cover.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/cover.jpg" alt="Dva vygenerované 3D modely telefónu, jeden spredu s tmavým displejom a druhý zozadu" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#co-budete-potrebovat" data-ga="blog_toc_clicked" data-ga-heading-id="co-budete-potrebovat">Čo budete potrebovať</a></li><li><a href="#krok-1-vytiahnite-obdlznik-do-3d" data-ga="blog_toc_clicked" data-ga-heading-id="krok-1-vytiahnite-obdlznik-do-3d">Krok 1: Vytiahnite obdĺžnik do 3D</a></li><li><a href="#krok-2-vyrezte-vanu-pre-displej" data-ga="blog_toc_clicked" data-ga-heading-id="krok-2-vyrezte-vanu-pre-displej">Krok 2: Vyrežte vaňu pre displej</a></li><li><a href="#krok-3-bocne-prvky-pripravte-ako-vrtaky" data-ga="blog_toc_clicked" data-ga-heading-id="krok-3-bocne-prvky-pripravte-ako-vrtaky">Krok 3: Bočné prvky pripravte ako vrtáky</a></li><li><a href="#krok-4-predne-detaily-nevrtajte" data-ga="blog_toc_clicked" data-ga-heading-id="krok-4-predne-detaily-nevrtajte">Krok 4: Predné detaily nevŕtajte</a></li><li><a href="#krok-5-presunte-vypocet-do-web-workera" data-ga="blog_toc_clicked" data-ga-heading-id="krok-5-presunte-vypocet-do-web-workera">Krok 5: Presuňte výpočet do Web Workera</a></li><li><a href="#krok-6-odcitajte-vsetky-otvory-naraz" data-ga="blog_toc_clicked" data-ga-heading-id="krok-6-odcitajte-vsetky-otvory-naraz">Krok 6: Odčítajte všetky otvory naraz</a></li><li><a href="#krok-7-pocet-segmentov-majte-ako-parameter" data-ga="blog_toc_clicked" data-ga-heading-id="krok-7-pocet-segmentov-majte-ako-parameter">Krok 7: Počet segmentov majte ako parameter</a></li><li><a href="#render-na-serveri-bez-grafickej-karty" data-ga="blog_toc_clicked" data-ga-heading-id="render-na-serveri-bez-grafickej-karty">Render na serveri bez grafickej karty</a></li></ol></nav><p>3D model telefónu vygenerujete v prehliadači tak, že obdĺžnik so zaoblenými rohmi vytiahnete do hĺbky a potom z neho booleovskými operáciami (CSG) vyrežete prednú stranu, tlačidlá, reproduktory a konektor. Celé zariadenie potom popisuje pár čísel v milimetroch a nový model je zmena týchto čísel, nie modelovanie v 3D programe.</p>
<p>Hodí sa to všade, kde potrebujete veľa podobných zariadení alebo ich má upravovať používateľ: konfigurátory, mockupy, náhľady produktov. Ako príklad z praxe slúži <a href="https://ecranify.com" target="_blank" rel="noopener">Ecranify</a>, nástroj na 3D mockupy pre screenshoty do App Store a Google Play, v ktorom nie je ani jeden hotový 3D model. Nižšie je postup krok za krokom aj s meraním, ktoré ukazuje, kde sa dá na výpočte ušetriť najviac.</p>
<h2 id="co-budete-potrebovat"><a class="heading-anchor" href="#co-budete-potrebovat" aria-hidden="true" tabindex="-1">#</a>Čo budete potrebovať</h2>
<ul>
<li><a href="https://threejs.org" target="_blank" rel="noopener">Three.js</a> na scénu, kamery, svetlá a hlavne geometriu. Geometria je v Three.js mesh, teda sieť trojuholníkov.</li>
<li>Knižnicu <a href="https://github.com/gkjohnson/three-bvh-csg" target="_blank" rel="noopener">three-bvh-csg</a> na booleovské operácie nad meshmi. Zrýchľuje ich priestorovým indexom (BVH, bounding volume hierarchy), vďaka ktorému netestuje každý trojuholník proti každému.</li>
<li>Popis zariadenia ako dáta, napríklad JSON s rozmermi tela, polomerom rohov, veľkosťou bevelu a zoznamom prvkov na bokoch. Všetky polohy zadávajte v milimetroch od stredu zariadenia.</li>
</ul>
<h2 id="krok-1-vytiahnite-obdlznik-do-3d"><a class="heading-anchor" href="#krok-1-vytiahnite-obdlznik-do-3d" aria-hidden="true" tabindex="-1">#</a>Krok 1: Vytiahnite obdĺžnik do 3D</h2>
<p>Nakreslite 2D obdĺžnik so zaoblenými rohmi v skutočných rozmeroch, napríklad 163,4 × 78 mm, a vytiahnite ho (extrúzia) do hĺbky 8,75 mm. Dostanete dosku s ostrými hranami. Žiadny telefón také nemá, preto pri vyťahovaní zapnite aj bevel, teda zaoblenie hrany. Three.js robí oboje v jednom kroku.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Len extrúzia</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/slab-sharp.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/slab-sharp.jpg" alt="Hranatá doska bez zaoblených rohov a hrán" width="1200" height="800" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">+ rohy a bevel</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/slab-bevel.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/slab-bevel.jpg" alt="Rovnaká doska so zaoblenými rohmi aj hranami" width="1200" height="800" loading="lazy" decoding="async" /></picture></div></div><figcaption>Rovnaký tvar, iné dve čísla: polomer rohu a veľkosť bevelu.</figcaption></figure>
<p>Pozor na jednu vec: bevel sa pridáva navonok. Ak chcete presný vonkajší rozmer, zmenšite 2D tvar aj hĺbku o veľkosť bevelu.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">b = min(bevel, hĺbka / 2)
tvar = zaoblený obdĺžnik(šírka - 2b, výška - 2b, polomer rohu - b)
telo = vytiahni(tvar, hĺbka - 2b, bevel b)</code></pre>
<p>Hladkosť oblúkov (teseláciu) určuje počet segmentov, na ktoré sa oblúk rozdelí. Viac segmentov znamená hladší tvar a viac trojuholníkov. Toto číslo neskôr rozhodne o rýchlosti celého výpočtu, preto sa k nemu vrátime v kroku 7.</p>
<h2 id="krok-2-vyrezte-vanu-pre-displej"><a class="heading-anchor" href="#krok-2-vyrezte-vanu-pre-displej" aria-hidden="true" tabindex="-1">#</a>Krok 2: Vyrežte vaňu pre displej</h2>
<p>CSG (constructive solid geometry) skladá telesá z jednoduchších telies pomocou troch operácií: zjednotenie, rozdiel a prienik. Rozdiel funguje ako vrták: jedno teleso odoberie materiál z druhého.</p>
<p>Displej telefónu nie je zarovno s rámom, sedí v plytkej vani. Tú vyrobíte tak, že spravíte druhú, o 1,4 mm menšiu kópiu tela, necháte z nej len prednú časť hrubú 0,7 mm (prienik s veľkým kvádrom) a túto časť odčítate od tela. Do vzniknutej vane potom vložíte tmavú plochu displeja.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Rezné teleso</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/pocket-cutter.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/pocket-cutter.jpg" alt="Telo telefónu s modrým priesvitným rezným telesom na prednej strane" width="1200" height="800" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Po odčítaní</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/pocket-carved.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/pocket-carved.jpg" alt="Telo s vyrezanou vaňou a tmavým displejom vo vnútri" width="1200" height="800" loading="lazy" decoding="async" /></picture></div></div><figcaption>Modré teleso je to, čo sa odčíta. Vpravo už je vo vani displej.</figcaption></figure>
<p>Dva praktické detaily:</p>
<ul>
<li><strong>Kváder na prienik musí byť naozaj veľký.</strong> Stačí mu orezávať hĺbku, ale v šírke a výške musí presahovať celé zariadenie. Neodvodzujte ho od hĺbky: napríklad 20-násobok hĺbky je pri tenkom vysokom telefóne menej ako jeho výška a hore a dole ostane neorezaný rám.</li>
<li><strong>Zväčšite rezné teleso o pár promile.</strong> Keď majú dve telesá presne rovnakú plochu, CSG si nevie rady, ktorá strana je vnútri. Mierka 1,002 tomu predíde a okom ju nevidno.</li>
</ul>
<h2 id="krok-3-bocne-prvky-pripravte-ako-vrtaky"><a class="heading-anchor" href="#krok-3-bocne-prvky-pripravte-ako-vrtaky" aria-hidden="true" tabindex="-1">#</a>Krok 3: Bočné prvky pripravte ako vrtáky</h2>
<p>Každý prvok na boku je rovnaký recept s iným vrtákom. Tlačidlo napríklad vznikne z troch telies: zaoblený obdĺžnik o kúsok hlbší ako tlačidlo sa odčíta od rámu, okraj otvoru dostane malé skosenie a do otvoru sa vloží o niečo menšie teleso, ktoré z rámu trochu vytŕča.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Vrtáky</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/button-cutters.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/button-cutters.jpg" alt="Bok telefónu s dvoma modrými reznými telesami tlačidiel" width="1200" height="800" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Výrez a tlačidlo</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/button-cut.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/button-cut.jpg" alt="Bok telefónu s vyrezanými otvormi a vloženými tlačidlami" width="1200" height="800" loading="lazy" decoding="async" /></picture></div></div><figcaption>Dve tlačidlá na pravom boku pred odčítaním a po ňom.</figcaption></figure>
<p>Reproduktory sú rad malých valcov, konektor je zaoblený obdĺžnik. Do každého otvoru vložte niečo tmavé: tenký disk do mriežky, platničku do konektora. Prázdna diera v tenkej škrupine pôsobí ako chyba, diera s tmavou výplňou pôsobí ako hardvér.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/bottom-edge.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/bottom-edge.jpg" alt="Spodná hrana telefónu s dvoma radmi otvorov reproduktora a konektorom uprostred" width="1200" height="800" loading="lazy" decoding="async" /></picture><figcaption>Otvory reproduktora a konektor. Všetko sú odčítané valce a zaoblené obdĺžniky.</figcaption></figure>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">pre každý prvok v zozname prvkov boku:
    vrták = tvar prvku (valec, zaoblený obdĺžnik, ...)
    umiestni vrták na hranu podľa polohy v mm
    pridaj vrták do zoznamu rezov
    ak má prvok výplň: pridaj výplň do scény</code></pre>
<p>Pridať tlačidlo je potom zápis do dát. Prvkov rýchlo pribúda: iPad Pro 13″ má 58 otvorov reproduktora, 4 tlačidlá, 2 antény a konektor, teda vyše 60 odčítaní od jedného tela.</p>
<h2 id="krok-4-predne-detaily-nevrtajte"><a class="heading-anchor" href="#krok-4-predne-detaily-nevrtajte" aria-hidden="true" tabindex="-1">#</a>Krok 4: Predné detaily nevŕtajte</h2>
<p>Výrez pre kameru, ostrovček v hornej časti displeja alebo indikátor domovskej obrazovky sú ploché. Rezať pre ne geometriu je zbytočná práca. Nalepte ich ako decal, teda obrázok premietnutý na povrch. Premietajte ho na skrytú rovinu cez celú prednú stranu a polohu zadávajte v rovnakých milimetroch ako všetko ostatné. Výnimkou je okrúhle domovské tlačidlo starších iPhonov. To má skutočnú hĺbku, ktorú vidno zboku, a preto ide cez CSG.</p>
<aside class="app-box app-box-callout"><img class="app-box-img" src="https://smallwater.eu/images/ecranify/home.png" alt="" width="1400" height="1219" loading="lazy" decoding="async" /><div class="app-box-body"><p class="app-box-eyebrow">Vyskúšajte</p><h2 class="app-box-title">Ecranify</h2><p>Nástroj v prehliadači na fotorealistické 3D mockupy zariadení pre screenshoty do App Store a Google Play. Poznámky k WebGL a novinky.</p><div class="app-box-links"><a class="btn btn-primary" href="https://ecranify.com" target="_blank" rel="noopener" data-ga="blog_app_link_clicked" data-ga-category="ecranify" data-ga-destination="https://ecranify.com" data-ga-app="ecranify" data-ga-store="web">ecranify.com</a></div></div></aside>
<h2 id="krok-5-presunte-vypocet-do-web-workera"><a class="heading-anchor" href="#krok-5-presunte-vypocet-do-web-workera" aria-hidden="true" tabindex="-1">#</a>Krok 5: Presuňte výpočet do Web Workera</h2>
<p>JavaScript v prehliadači beží v jednom vlákne. To isté vlákno počíta, kreslí stránku a reaguje na kliky. Kým beží CSG, stránka stojí a nepomôže ani spinner, lebo aj ten potrebuje to isté vlákno.</p>
<p>Celé zostavenie zariadenia preto spustite vo <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API" target="_blank" rel="noopener">Web Workeri</a>, teda v samostatnom vlákne na pozadí. Worker dostane popis zariadenia, vráti hotové polia vrcholov a stránka ostane plynulá. Pri zmene parametra starý worker ukončite a spustite nový, nech nedopočítava zastaraný stav.</p>
<p>Výsledok z workera chvíľu trvá, preto pridajte okamžitý náhľad. Upravovaný prvok ukážte ako modrý priesvitný „duch“, jednoduchý kváder alebo valec s pár desiatkami trojuholníkov, ktorý sa dá postaviť v hlavnom vlákne hneď. Keď worker skončí, duch zmizne.</p>
<h2 id="krok-6-odcitajte-vsetky-otvory-naraz"><a class="heading-anchor" href="#krok-6-odcitajte-vsetky-otvory-naraz" aria-hidden="true" tabindex="-1">#</a>Krok 6: Odčítajte všetky otvory naraz</h2>
<p>Najpriamočiarejší spôsob je odčítať prvky postupne: otvor 1 od tela, potom otvor 2 od výsledku a tak ďalej. Každé odčítanie však znova postaví priestorový index celého tela, čo je najdrahšia časť výpočtu. Pri desiatkach prvkov čas rastie veľmi rýchlo.</p>
<p>Lepšie je najprv zozbierať všetky vrtáky pre dané teleso, spojiť ich do jedného meshu a odčítať raz.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">rezy = []
pre každý prvok: pridaj jeho vrták do rezy
spojený = spoj všetky rezy do jedného meshu
telo = telo mínus spojený
ak spojenie zlyhá: odčítaj rezy po jednom</code></pre>
<p>Meranie na zjednodušenej kópii postupu (Three.js 0.170, three-bvh-csg 0.0.17, Node 22, cloudový server s 2 jadrami Intel Xeon 2,1 GHz, medián z 5 behov):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Teleso</th>
<th>Otvory</th>
<th>Po jednom</th>
<th>Naraz</th>
</tr>
</thead>
<tbody><tr>
<td>telefón, 17 676 trojuholníkov</td>
<td>25</td>
<td>2,47 s</td>
<td>0,26 s</td>
</tr>
<tr>
<td>tablet, 17 676 trojuholníkov</td>
<td>58</td>
<td>5,55 s</td>
<td>0,89 s</td>
</tr>
<tr>
<td>tablet, 68 108 trojuholníkov</td>
<td>58</td>
<td>22,8 s</td>
<td>3,1 s</td>
</tr>
</tbody></table>
</div>
<p>Jedna podmienka: spojenie a jedno odčítanie dá rovnaký výsledok ako postupné odčítanie, len ak sa vrtáky neprekrývajú. Prvky sú na rôznych miestach, takže to bežne platí. Ak ale používateľ posunie dva prvky na seba, v mieste prekrytia môže vzniknúť drobná chyba.</p>
<h2 id="krok-7-pocet-segmentov-majte-ako-parameter"><a class="heading-anchor" href="#krok-7-pocet-segmentov-majte-ako-parameter" aria-hidden="true" tabindex="-1">#</a>Krok 7: Počet segmentov majte ako parameter</h2>
<p>Teselácia, ktorá stačí na zobrazenie, je pre CSG často zbytočne hustá. Pri 64 segmentoch na roh a 32 na bevel má telo 68 108 trojuholníkov. Polovicu (32 a 16) na 2 mm hrane nevidno, ale telo má len 17 676 trojuholníkov a tretí riadok tabuľky ukazuje, čo to robí s časom.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">64 a 32 segmentov</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/tessellation-64.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/tessellation-64.png" alt="Drôtený model rohu telefónu s veľmi hustou sieťou trojuholníkov" width="1200" height="800" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">32 a 16 segmentov</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/threejs-csg-3d-phone-model/tessellation-32.webp" /><img src="https://smallwater.eu/blog/threejs-csg-3d-phone-model/tessellation-32.png" alt="Drôtený model rovnakého rohu s polovičným počtom segmentov" width="1200" height="800" loading="lazy" decoding="async" /></picture></div></div><figcaption>Roh tela zhora. Vpravo je štvrtina trojuholníkov.</figcaption></figure>
<p>Malým prvkom s polomerom do 2 mm (otvory reproduktora, skrutky) dajte polovicu segmentov, náhľadom v galérii ešte menej. Kvalitu preto nedávajte ako konštantu do kódu, ale ako parameter zostavenia.</p>
<h2 id="render-na-serveri-bez-grafickej-karty"><a class="heading-anchor" href="#render-na-serveri-bez-grafickej-karty" aria-hidden="true" tabindex="-1">#</a>Render na serveri bez grafickej karty</h2>
<p>Ak potrebujete rovnakú scénu vykresliť aj na serveri, napríklad ako zálohu exportu, beží v Chrome bez obrazovky (headless), ktorý bez grafickej karty kreslí softvérovo. Tam sa každá neefektivita znásobí. V Ecranify najťažší tablet bez spomenutých úprav narazil na limit 15 minút. So spojenými rezmi, menšími textúrami, nižšou teseláciou a cache hotovej geometrie podľa hashu popisu zariadenia trvá render okolo pol minúty a opakovaný okolo 5 sekúnd. Podrobnosti aj s interaktívnymi ukážkami sú v <a href="https://ecranify.com/guides/how-we-build-3d-device-mockups" target="_blank" rel="noopener">anglickom článku na ecranify.com</a>.</p>
<p>Ak riešite podobný problém s 3D v prehliadači alebo potrebujete appku na zákazku, <a href="https://smallwater.eu/#kontakt">napíšte nám</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako previesť projekt z Unity 4 do Unity 6</title>
      <link>https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Onager</category>
      <description>Ako dostať projekt z Unity 4 do Unity 6 bez straty scén: prevod na text cez Unity 2019.4, port UnityScriptu do C# so zachovaním GUID a obnova starého HUD.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/cover.webp" /><img src="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/cover.jpg" alt="Stavba z blokov v leveli Onagera na podlahe z mliečneho skla" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#co-sa-stane-ked-projekt-otvorite-v-unity-6" data-ga="blog_toc_clicked" data-ga-heading-id="co-sa-stane-ked-projekt-otvorite-v-unity-6">Čo sa stane, keď projekt otvoríte v Unity 6</a></li><li><a href="#krok-1-zaznamenajte-povodny-stav" data-ga="blog_toc_clicked" data-ga-heading-id="krok-1-zaznamenajte-povodny-stav">Krok 1: Zaznamenajte pôvodný stav</a></li><li><a href="#krok-2-prevod-na-text-cez-unity-2019-4" data-ga="blog_toc_clicked" data-ga-heading-id="krok-2-prevod-na-text-cez-unity-2019-4">Krok 2: Prevod na text cez Unity 2019.4</a></li><li><a href="#krok-3-vysledok-overte-v-editore-nie-citanim-yaml" data-ga="blog_toc_clicked" data-ga-heading-id="krok-3-vysledok-overte-v-editore-nie-citanim-yaml">Krok 3: Výsledok overte v editore, nie čítaním YAML</a></li><li><a href="#krok-4-unityscript-do-c-s-rovnakym-guid" data-ga="blog_toc_clicked" data-ga-heading-id="krok-4-unityscript-do-c-s-rovnakym-guid">Krok 4: UnityScript do C# s rovnakým GUID</a></li><li><a href="#krok-5-obnovte-hud-z-povodnych-binarnych-scen" data-ga="blog_toc_clicked" data-ga-heading-id="krok-5-obnovte-hud-z-povodnych-binarnych-scen">Krok 5: Obnovte HUD z pôvodných binárnych scén</a></li><li><a href="#po-prevode-stare-triky-nahradte" data-ga="blog_toc_clicked" data-ga-heading-id="po-prevode-stare-triky-nahradte">Po prevode: staré triky nahraďte</a></li><li><a href="#co-postup-nevyriesi" data-ga="blog_toc_clicked" data-ga-heading-id="co-postup-nevyriesi">Čo postup nevyrieši</a></li></ol></nav><p>Projekt z Unity 4 neotvárajte priamo v Unity 6 a hlavne v ňom nič neukladajte. Unity 6 staré binárne scény načíta len čiastočne a uložením stratu potvrdí; bezpečná cesta vedie cez Unity 2019.4, ktoré starý formát vie prečítať aj zapísať ako text, a až potom do Unity 6.</p>
<p>Týka sa to projektov, ktoré majú scény a prefaby uložené v binárnom formáte a skripty v UnityScripte, jazyku podobnom JavaScriptu, ktorý Unity kedysi podporovalo popri C#. Postup ukážeme na hre Onager, fyzikálnej hre s katapultom napísanej v Unity 4.1.2 a v UnityScripte. Projekt mal 126 skriptov, 75 scén a 78 prefabov, po prevode beží na Unity 6000.5 s URP (Universal Render Pipeline) a je zadarmo na <a href="https://smallwater.eu/onager-ios">App Store</a> aj <a href="https://smallwater.eu/onager-android">Google Play</a>.</p>
<h2 id="co-sa-stane-ked-projekt-otvorite-v-unity-6"><a class="heading-anchor" href="#co-sa-stane-ked-projekt-otvorite-v-unity-6" aria-hidden="true" tabindex="-1">#</a>Čo sa stane, keď projekt otvoríte v Unity 6</h2>
<p>Najjednoduchší prístup je otvoriť projekt v Unity 6 a prepnúť serializáciu na Force Text, teda na ukladanie assetov ako čitateľný YAML. To nie je preklad súborov. Unity každý asset načíta cez svoj importér a zapíše späť ako YAML, takže všetko prejde loaderom Unity 6. Ten pri každej scéne hlási, že našiel Transform bez GameObjectu a „visiaci“ komponent zmaže. Uloženie to spraví natrvalo.</p>
<p>Rozsah straty sa dá zmerať nástrojom binary2text, ktorý je priložený k editoru a číta serializovaný súbor bez importéra. V Onagerovi sme jeho výpis porovnali s výsledným YAML a počítali len komponenty, ktoré zmizli z GameObjectov, ktoré prežili:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Komponent</th>
<th align="right">Stratené</th>
</tr>
</thead>
<tbody><tr>
<td>Camera</td>
<td align="right">130</td>
</tr>
<tr>
<td>ParticleSystem</td>
<td align="right">132</td>
</tr>
<tr>
<td>Transform</td>
<td align="right">167</td>
</tr>
<tr>
<td>BoxCollider</td>
<td align="right">104</td>
</tr>
<tr>
<td>Rigidbody</td>
<td align="right">100</td>
</tr>
<tr>
<td>GUITexture / GUIText</td>
<td align="right">303 / 179</td>
</tr>
</tbody></table>
</div>
<p>Bez straty nebola ani jedna zo 75 scén. Zmizli celé kamery pre HUD a hlavná kamera dostala novú, predvolenú. Dôležité je, že škoda vzniká už pri načítaní, nie pri uložení. Kým projekt nie je prevedený, nesmie ho Unity 6 uložiť, ani jednu scénu, ani jeden prefab.</p>
<h2 id="krok-1-zaznamenajte-povodny-stav"><a class="heading-anchor" href="#krok-1-zaznamenajte-povodny-stav" aria-hidden="true" tabindex="-1">#</a>Krok 1: Zaznamenajte pôvodný stav</h2>
<p>Skôr než na niečo siahnete, odfoťte si každú scénu a pre každú scénu si nástrojom binary2text uložte výpis pôvodného súboru. Podľa výpisu budete v kroku 3 kontrolovať kamery a v kroku 5 obnovovať HUD. Pôvodné binárne súbory nechajte v histórii gitu, nemažte ich.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/before-level-1-1.webp" /><img src="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/before-level-1-1.jpg" alt="Prvý level v Unity 6 pred remasterom" width="1280" height="720" loading="lazy" decoding="async" /></picture><figcaption>Prvý level Onagera v Unity 6, ešte so starým renderom a pôvodnými materiálmi. Takýto záznam si spravte z každej scény skôr, než na niečo siahnete.</figcaption></figure>
<h2 id="krok-2-prevod-na-text-cez-unity-2019-4"><a class="heading-anchor" href="#krok-2-prevod-na-text-cez-unity-2019-4" aria-hidden="true" tabindex="-1">#</a>Krok 2: Prevod na text cez Unity 2019.4</h2>
<p>Unity 2019.4 je posledný editor, ktorý formát z Unity 4.1 vie prečítať aj zapísať. Prevod robte so zatvoreným editorom v samostatnej kópii repozitára:</p>
<ul>
<li><strong>Verzia projektu.</strong> V súbore s verziou projektu nastavte pôvodnú verziu (v Onagerovi 4.1.2), aby editor zvolil cestu pre starý projekt. Unity ju pri otvorení prepíše, stačí, že platí pri prvom importe.</li>
<li><strong>Prázdne balíky.</strong> Vyprázdnite zoznam závislostí balíkov. Jedna chyba kompilácie znamená, že nebude fungovať žiadna položka menu z editorových skriptov, a to vyzerá, ako keby sa menu vôbec neobjavilo.</li>
<li><strong>Import od nuly.</strong> Zmažte priečinok Library a nechajte projekt naimportovať.</li>
<li><strong>Force Text a vynútené prepísanie.</strong> Unity 2019.4 po zmene na Force Text existujúce súbory samo neprepíše. Treba to vyvolať funkciou na reserializáciu assetov, a to len assetov, nie aj ich meta súborov. Prepísanie všetkých meta súborov spôsobí, že Unity 6 potom importuje stále dokola.</li>
</ul>
<p>Jedna pasca je v gite. Ak v nastavení atribútov gitu označíte scény ako text s koncom riadku LF, git z binárnych scén pri ukladaní do repozitára vyhodí bajty, ktoré vyzerajú ako koniec riadku. V Onagerovi to poškodilo osem scén. Na assety Unity atribút pre konce riadkov nedávajte, Unity aj tak zapisuje LF.</p>
<h2 id="krok-3-vysledok-overte-v-editore-nie-citanim-yaml"><a class="heading-anchor" href="#krok-3-vysledok-overte-v-editore-nie-citanim-yaml" aria-hidden="true" tabindex="-1">#</a>Krok 3: Výsledok overte v editore, nie čítaním YAML</h2>
<p>Čítanie prevedených súborov vedie k sebavedomým a nesprávnym záverom. Skrátené záznamy inštancií prefabov vyzerajú ako strata, rovnako ako kamera, ktorá sa len presunula do prefabu. Rozhoduje to, čo Unity postaví v pamäti. Každú prevedenú scénu otvorte a jej kamery porovnajte s výpisom originálu z kroku 1.</p>
<p>V Onagerovi mali po prevode všetkých 75 scén rovnaké kamery aj ich poradie. Stratilo sa len to, čo žiadny dnešný editor nepozná: 569 GUITexture, 179 GUIText a staré časticové systémy, ktoré Unity odstránilo v roku 2018.</p>
<h2 id="krok-4-unityscript-do-c-s-rovnakym-guid"><a class="heading-anchor" href="#krok-4-unityscript-do-c-s-rovnakym-guid" aria-hidden="true" tabindex="-1">#</a>Krok 4: UnityScript do C# s rovnakým GUID</h2>
<p>Unity <a href="https://blog.unity.com/community/unityscripts-long-ride-off-into-the-sunset" target="_blank" rel="noopener">ukončilo UnityScript postupne od verzie 2017.2</a>, takže ručný port do C# je jediná cesta. Scény odkazujú na skripty cez GUID, jedinečný identifikátor, ktorý Unity ukladá do meta súboru každého assetu, a hodnoty polí ukladajú podľa mena poľa. Ak to pri porte dodržíte, všetky odkazy sa pripoja samy:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">pre každý skript X v UnityScripte:
  vytvor C# skript s triedou presne menom X
  skopíruj GUID z meta súboru starého skriptu do meta súboru nového
  verejné polia: rovnaké meno, rovnaký typ, aj s preklepmi
  nič nepremenúvaj, kým projekt nebeží</code></pre>
<p>V Onagerovi sa takto po porte všetkých 126 skriptov pripojilo 12 935 komponentov v 73 scénach a v Inspectore zostali všetky nastavené hodnoty.</p>
<p>Textovým hľadaním v projekte sa nedá zistiť, ktoré skripty sa používajú. Binárny prefab má GUID uložený ako bajty, nie ako text. V Onagerovi tak vyzeralo nepoužívaných dvadsaťjeden skriptov a všetky boli živé. Dajte si pozor aj na pozostatky starších pokusov o port: jeden prefab drevenej kocky ukazoval na dva skripty z takého pokusu a len kvôli nemu chýbalo 1 140 komponentov. Ak treba v binárnom súbore zmeniť GUID, vymeňte ho priamo v bajtoch, šestnásť za šestnásť, aby sa súbor neposunul.</p>
<h2 id="krok-5-obnovte-hud-z-povodnych-binarnych-scen"><a class="heading-anchor" href="#krok-5-obnovte-hud-z-povodnych-binarnych-scen" aria-hidden="true" tabindex="-1">#</a>Krok 5: Obnovte HUD z pôvodných binárnych scén</h2>
<p>GUITexture a GUIText v Unity 6 nie sú a s nimi zmizne celý HUD, ktorý na nich stál. Kresliť ho nanovo netreba. Pôvodné binárne scény z histórie gitu binary2text prečíta bez ohľadu na to, či triedy ešte existujú. Vytiahnite z nich každý obrázok a text HUD s textúrou, farbou, pozíciou a zarovnaním a skriptom ich vráťte na rovnaké objekty v scénach. V Onagerovi to bolo 577 obrázkov a 180 textov.</p>
<p>Takto sa zachránia aj texty, ktoré nikde inde nie sú. V Onagerovi to bolo 111 textov: všetky titulky tutoriálov a celá tabuľa s kreditmi. Pri písaní HUD od nuly by ste si ich museli vymyslieť. Pri prenose farby počítajte s jednou vecou, ktorú dokumentácia nehovorí: staré GUITexture farbu pri vykreslení zdvojnásobovalo. Všetkých 577 obrázkov malo farbu presne 0,5, čiže šedá znamenala bielu.</p>
<h2 id="po-prevode-stare-triky-nahradte"><a class="heading-anchor" href="#po-prevode-stare-triky-nahradte" aria-hidden="true" tabindex="-1">#</a>Po prevode: staré triky nahraďte</h2>
<p>Staré projekty často obchádzali obmedzenia vtedajšieho Unity a tieto obchádzky sa po prevode oplatí prerobiť:</p>
<ul>
<li><strong>Odraz v podlahe.</strong> Onager mal lesklú podlahu bez skutočného odrazu. Pod podlahou stál druhý, zrkadlovo otočený svet: kópia každého objektu, ktorej pozíciu skript každý tretí snímok skopíroval z originálu, v jednom leveli 50 až 70 klonov. Nahradiť sa dá jednou kamerou navyše, ktorá odraz kreslí v polovičnom rozlíšení. Odrážajú sa potom aj častice a trosky, čo klonovaný svet nevedel.</li>
<li><strong>Mierenie.</strong> Ak náhľad dráhy predpovedá, kam strelu pošle sila pridaná v prvom fyzikálnom kroku, strela dopadne inde než značka. Spoľahlivejšie je vypočítať rýchlosť raz, náhľad nakresliť presne z nej a strele ju priradiť priamo.</li>
<li><strong>Výkon.</strong> Merajte na slabom telefóne. Na Motorola G52 klesol Onager z 20 na 6 fps len kvôli shaderu stien, viac než rozlíšenie, tiene a odraz dokopy. Na nízkych a stredných detailoch preto majú steny jednoduchší materiál.</li>
</ul>
<figure class="post-figure post-figure-video"><video class="post-video" muted loop playsinline controls preload="none" poster="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/gravity-shot.jpg" width="720" height="720" data-lazy-video aria-label="Gravitačná strela v prevedenom Onagerovi: guľa zdvihne bloky, okolo nej krúži vír a modré svetlo."><source src="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/gravity-shot.mp4" type="video/mp4" /></video><figcaption>Gravitačná strela v prevedenom Onagerovi: guľa zdvihne bloky, okolo nej krúži vír a modré svetlo.</figcaption></figure>
<aside class="app-box app-box-callout"><img class="app-box-img" src="https://smallwater.eu/images/onager/1-aim.jpg" alt="" width="1600" height="736" loading="lazy" decoding="async" /><div class="app-box-body"><p class="app-box-eyebrow">Vyskúšajte</p><h2 class="app-box-title">Onager</h2><p>3D fyzikálny hlavolam s katapultom: 60 levelov búrania. Fyzika, level dizajn a novinky z vydaní.</p><div class="app-box-links"><a class="btn btn-primary" href="https://smallwater.eu/onager-ios" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="onager" data-ga-destination="/onager-ios" data-ga-app="onager" data-ga-store="app_store">App Store</a><a class="btn btn-ghost" href="https://smallwater.eu/onager-android" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="onager" data-ga-destination="/onager-android" data-ga-app="onager" data-ga-store="google_play">Google Play</a></div></div></aside>
<figure class="post-figure post-figure-video"><video class="post-video" muted loop playsinline controls preload="none" poster="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/rainbow-shot.jpg" width="720" height="720" data-lazy-video aria-label="Dúhová strela: biele body vychádzajú priamo z povrchu zasiahnutého bloku."><source src="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/rainbow-shot.mp4" type="video/mp4" /></video><figcaption>Dúhová strela: biele body vychádzajú priamo z povrchu zasiahnutého bloku.</figcaption></figure>
<p>Ak riešite fyziku tisícov objektov v súčasnom Unity, pozrite si aj <a href="https://smallwater.eu/blog/fake-ragdoll-unity-dots/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/unity-4-to-unity-6-upgrade/" data-ga-destination="/blog/fake-ragdoll-unity-dots/" data-ga-placement="inline">falošný ragdoll pre tisíce nepriateľov v Unity DOTS</a>. Ako po prevode pridať starej 2D hre glow a 2D svetlá v URP, opisuje <a href="https://smallwater.eu/blog/dreamica-remaster-unity-urp-glow/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/unity-4-to-unity-6-upgrade/" data-ga-destination="/blog/dreamica-remaster-unity-urp-glow/" data-ga-placement="inline">samostatný návod</a>.</p>
<h2 id="co-postup-nevyriesi"><a class="heading-anchor" href="#co-postup-nevyriesi" aria-hidden="true" tabindex="-1">#</a>Čo postup nevyrieši</h2>
<p>Komponenty, ktoré dnešné Unity nepozná, sa nepripoja. GUITexture a GUIText treba obnoviť ako v kroku 5, staré časticové systémy sa stratia. Mená tried a polí ostanú presne ako v pôvodnom projekte, aj s preklepmi a jazykom, v ktorom ich niekto kedysi napísal. Scény sa na ne viažu menom, takže premenovanie má zmysel robiť len cez editor, ktorý odkazy prepíše sám.</p>
<p>Ak máte starý projekt v Unity a potrebujete ho dostať do súčasnej verzie, <a href="https://smallwater.eu/#kontakt">napíšte nám</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako navigovať hordu tisícov nepriateľov v Unity DOTS</title>
      <link>https://smallwater.eu/blog/unity-dots-horde-navigation/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/unity-dots-horde-navigation/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Tower Defense</category>
      <description>Návod na pohyb tisícov nepriateľov v Unity DOTS: flow field, odtláčanie od susedov a stien, cena podľa hustoty davu, zápchy v úžinách a výkon.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/cover.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/cover.jpg" alt="Horda nepriateľov ako svetlé bodky obteká prekážky k cieľu, pod ňou šípky flow fieldu a červená mapa hustoty davu" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#krok-1-flow-field-namiesto-cesty-pre-kazdeho" data-ga="blog_toc_clicked" data-ga-heading-id="krok-1-flow-field-namiesto-cesty-pre-kazdeho">Krok 1: Flow field namiesto cesty pre každého</a></li><li><a href="#krok-2-samostatne-pole-pre-kazdu-velkost-nepriatela" data-ga="blog_toc_clicked" data-ga-heading-id="krok-2-samostatne-pole-pre-kazdu-velkost-nepriatela">Krok 2: Samostatné pole pre každú veľkosť nepriateľa</a></li><li><a href="#krok-3-odtlacanie-od-susedov" data-ga="blog_toc_clicked" data-ga-heading-id="krok-3-odtlacanie-od-susedov">Krok 3: Odtláčanie od susedov</a></li><li><a href="#krok-4-odtlacanie-od-stien" data-ga="blog_toc_clicked" data-ga-heading-id="krok-4-odtlacanie-od-stien">Krok 4: Odtláčanie od stien</a></li><li><a href="#krok-5-cena-podla-hustoty-davu-a-spomalenie" data-ga="blog_toc_clicked" data-ga-heading-id="krok-5-cena-podla-hustoty-davu-a-spomalenie">Krok 5: Cena podľa hustoty davu a spomalenie</a></li><li><a href="#krok-6-zapcha-v-uzine-cakat-nie-tlacit" data-ga="blog_toc_clicked" data-ga-heading-id="krok-6-zapcha-v-uzine-cakat-nie-tlacit">Krok 6: Zápcha v úžine: čakať, nie tlačiť</a></li><li><a href="#krok-7-predbiehanie-velkych-nepriatelov" data-ga="blog_toc_clicked" data-ga-heading-id="krok-7-predbiehanie-velkych-nepriatelov">Krok 7: Predbiehanie veľkých nepriateľov</a></li><li><a href="#krok-8-sloty-pri-cieli-utoku" data-ga="blog_toc_clicked" data-ga-heading-id="krok-8-sloty-pri-cieli-utoku">Krok 8: Sloty pri cieli útoku</a></li><li><a href="#vykon-simulacie-a-vykreslovania" data-ga="blog_toc_clicked" data-ga-heading-id="vykon-simulacie-a-vykreslovania">Výkon simulácie a vykresľovania</a></li><li><a href="#limity-riesenia" data-ga="blog_toc_clicked" data-ga-heading-id="limity-riesenia">Limity riešenia</a></li></ol></nav><p>Hordu tisícov nepriateľov v Unity DOTS navigujte jedným flow fieldom pre celú mapu a niekoľkými lacnými lokálnymi pravidlami: odtláčanie od susedov a od stien, cena cesty podľa hustoty davu a stav „čakám“ v zápche. V Unity Editore na MacBooku s M3 Pro to drží okolo 9 600 nepriateľov naraz pri 90 FPS.</p>
<p>Problém vzniká v tower defense, strategických a survival hrách, kde k cieľu ide naraz tisíce jednotiek. Klasické hľadanie cesty pre každú zvlášť je príliš drahé a bez lokálnych pravidiel sa dav prekrýva, lepí na steny a zasekáva v úžinách. Unity DOTS (Data-Oriented Technology Stack, teda Entities, ECS a Job System) dáva výkon na tisíce entít, samotnú navigáciu však nerieši. Postup nižšie používame v našej tower defense hre pre PC (je v <a href="https://smallwater.eu/#referencie">referenciách</a>). Obrázky sú zo zjednodušenej 2D simulácie, ktorá beží podľa rovnakých pravidiel, nie zábery z hry.</p>
<h2 id="krok-1-flow-field-namiesto-cesty-pre-kazdeho"><a class="heading-anchor" href="#krok-1-flow-field-namiesto-cesty-pre-kazdeho" aria-hidden="true" tabindex="-1">#</a>Krok 1: Flow field namiesto cesty pre každého</h2>
<p>Flow field je mriežka cez celú mapu, v ktorej každá bunka vie, ktorým smerom sa z nej ide k cieľu. Nepriateľ nehľadá cestu, len si prečíta smer z miesta, kde stojí. Cena je rovnaká pre 10 aj 10 000 nepriateľov.</p>
<p>Mapu rozdeľte na bunky po 1 m (náš testovací level má 128 × 128) a pre každý cieľ raz spočítajte vzdialenosť z každej bunky k cieľu. Statické pole stačí spočítať v editore pri príprave levelu a uložiť do assetu.</p>
<p>Najjednoduchšia verzia je Dijkstra cez 8 susedov a v každej bunke jeden z 8 smerov. Funguje, ale nepriatelia chodia po uhlopriečkach a v rade sa lepia na steny.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">8 smerov</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/flow-8dir.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/flow-8dir.png" alt="Flow field s ôsmimi smermi, dráhy nepriateľov idú po uhlopriečkach a lepia sa na hranu prekážky" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Plynulé pole</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/flow-smooth.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/flow-smooth.png" alt="Plynulý flow field, dráhy sa oblúkom zbiehajú okolo rohu prekážky s odstupom od steny" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div></div><figcaption>Rovnaká mapa, dráhy osamotených nepriateľov. Vľavo 8 smerov, vpravo plynulé pole s prirážkou pri stenách.</figcaption></figure>
<p>Lepší výsledok dáva plynulé pole. Vzdialenosť spočítajte riešením <a href="https://en.wikipedia.org/wiki/Eikonal_equation" target="_blank" rel="noopener">Eikonalovej rovnice</a> (rovnica šírenia vlny, ktorej riešením je najkratšia vzdialenosť v spojitom priestore, nie len po hranách mriežky) metódou fast sweeping, ktorá mriežku opakovane prechádza zo štyroch rohov. Smer potom nie je smer bunky, ale gradient vzdialenosti v presnom mieste nepriateľa. K cene bunky pripočítajte prirážku za blízkosť steny, takže pole samo drží dav kúsok od prekážok.</p>
<h2 id="krok-2-samostatne-pole-pre-kazdu-velkost-nepriatela"><a class="heading-anchor" href="#krok-2-samostatne-pole-pre-kazdu-velkost-nepriatela" aria-hidden="true" tabindex="-1">#</a>Krok 2: Samostatné pole pre každú veľkosť nepriateľa</h2>
<p>Veľký nepriateľ sa nezmestí všade tam, kam malý. Zaveďte veľkostné triedy: každá má vlastnú priechodnosť (bunka musí byť dosť ďaleko od steny) a vlastné pole pre každý cieľ. Triedy držte ako dáta levelu, nie v kóde. U nás sú tri.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/size-classes.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/size-classes.png" alt="Mapa s úzkou chodbou: malí nepriatelia idú chodbou, väčší oranžoví ju obchádzajú spodom" width="1792" height="1008" loading="lazy" decoding="async" /></picture><figcaption>Malí nepriatelia idú úzkou chodbou, veľkí (oranžoví) sa do nej nezmestia a obchádzajú ju.</figcaption></figure>
<h2 id="krok-3-odtlacanie-od-susedov"><a class="heading-anchor" href="#krok-3-odtlacanie-od-susedov" aria-hidden="true" tabindex="-1">#</a>Krok 3: Odtláčanie od susedov</h2>
<p>Flow field povie smer, ale o susedoch nevie nič. Základom je zjednodušený model boids (klasické pravidlá kŕdľa): každý nepriateľ sa odpudzuje od susedov v okruhu 0,9 m. Susedov hľadajte v spatial hashi, teda v priestorovej mriežke, kde každá bunka drží zoznam jednotiek v nej. Hash postavte každý tik nanovo a zoraďte, aby bolo poradie deterministické.</p>
<p>Samotné odpudzovanie nestačí. Pri 3 000 nepriateľoch malo 83,7 % z nich najbližšieho suseda bližšie ako 0,8 m a najmenšia vzdialenosť bola 4 cm, teda dvaja nepriatelia skoro v sebe.</p>
<p>Vyššia váha odpudzovania problém nerieši. Pri váhe 6 namiesto 1,5 klesol podiel tesných na 54,6 %, pri 12 na 44,3 %, pri 24 už nič (48,2 %). Dav navyše začne poskakovať, lebo sa sčítajú neobmedzené tlaky od mnohých susedov naraz.</p>
<p>Pomôže pozičná korekcia. Po pohybe rozsuňte každú prekrývajúcu sa dvojicu, každého o polovicu prieniku, a posun za tik obmedzte na 0,15 m. Bez stropu korekcia skáče rovnako ako vysoká váha. So stropom zostane tesných 60,8 %, ale najmenšia vzdialenosť stúpne na 28 cm a dav neposkakuje.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Len odpudzovanie</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/overlap-before.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/overlap-before.png" alt="Dav pri úžine, veľká časť nepriateľov je červená, prekrývajú sa" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Korekcia prekrytia</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/overlap-after.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/overlap-after.png" alt="Rovnaký dav s korekciou prekrytia, červení ostali len tesne pred úžinou" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div></div><figcaption>Červení sa so susedom prekrývajú o viac ako 20 cm. Na voľnej ploche korekcia pomáha, pred úžinou nie.</figcaption></figure>
<h2 id="krok-4-odtlacanie-od-stien"><a class="heading-anchor" href="#krok-4-odtlacanie-od-stien" aria-hidden="true" tabindex="-1">#</a>Krok 4: Odtláčanie od stien</h2>
<p>Pri stene korekcia medzi nepriateľmi zlyháva. Pohyb do steny je zakázaný (posun sa rozloží po osiach a zložka do steny sa zahodí), takže pritlačený nepriateľ nemá kam uhnúť a korekcia od suseda sa stratí. Z 938 prekrývajúcich sa nepriateľov malo nenulový tlak od steny len 35.</p>
<p>Pridajte preto odtláčanie od najbližších neprechodných buniek v 4 smeroch aj diagonálne. Dve chyby, ktorým sa dá vyhnúť:</p>
<ul>
<li><strong>Vnútorný roh.</strong> Tlak od oboch hrán a od rohu sa sčíta a nepriateľa to odhodí. Tlak od rohu pridávajte len pri vonkajšom rohu.</li>
<li><strong>Skutočné steny.</strong> Odtláčajte od skutočných stien, nie od priechodnosti veľkostnej triedy. Pre veľkého nepriateľa je „stena“ už meter pred skutočnou. V ústí chodby tak tlak od rohu vyrovnal pohyb veľkého nepriateľa dopredu a celá skupina za ním zostala stáť.</li>
</ul>
<h2 id="krok-5-cena-podla-hustoty-davu-a-spomalenie"><a class="heading-anchor" href="#krok-5-cena-podla-hustoty-davu-a-spomalenie" aria-hidden="true" tabindex="-1">#</a>Krok 5: Cena podľa hustoty davu a spomalenie</h2>
<p>Aj s plynulým poľom ide celý dav po najkratšej ceste tesne okolo rohu, bez ohľadu na to, koľko nepriateľov tam už je. Riešenie ponúka <a href="https://grail.cs.washington.edu/projects/continuum-crowds" target="_blank" rel="noopener">Continuum Crowds</a> (Treuille a kol., 2006): cena bunky rastie s hustotou davu. Raz za 10 tikov spočítajte nepriateľov v bunkách, vyhlaďte to a prepočítajte pole jedného cieľa. Pri preplnenom rohu potom pole pošle nepriateľa okolo, cez voľnejšie bunky.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Bez hustoty</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/density-off.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/density-off.png" alt="Dav obchádza prekážku tesne okolo rohu v úzkom prúde, červená mapa hustoty je sústredená" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Cena podľa hustoty</span><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/density-on.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/density-on.png" alt="Dav obchádza prekážku širokým oblúkom, hustota je rozložená" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div></div><figcaption>Rovnaký dav okolo rovnakého rohu. Červená je vyhladená hustota davu.</figcaption></figure>
<p>Druhá polovica je spomalenie. Nepriateľ v hustej bunke ide pomalšie, najmenej na 15 % rýchlosti. Na 2 250 nepriateľoch išli v riedkych bunkách priemerne na 94 % rýchlosti, v stredne hustých na 71 % a v zápche na 20 %. Nech spomalenie začína skôr: s prahom voľnej hustoty 0,6 nepriateľa na bunku namiesto 1 klesol podiel tesných v úžine na 36,7 %.</p>
<p>Praktická rada: polia, ktoré vznikajú až za behu (napríklad mapa hustoty), alokujte skôr, ako ich prvýkrát použije job. Job s ešte nevytvoreným poľom zhodí Unity Editor natívne, bez výnimky.</p>
<h2 id="krok-6-zapcha-v-uzine-cakat-nie-tlacit"><a class="heading-anchor" href="#krok-6-zapcha-v-uzine-cakat-nie-tlacit" aria-hidden="true" tabindex="-1">#</a>Krok 6: Zápcha v úžine: čakať, nie tlačiť</h2>
<p>Keď sa niet kam rozšíriť, ostáva úžina. Najjednoduchší nápad je zahodiť zložku rýchlosti smerom k susedovi, ktorého sa nepriateľ dotýka. Zadní potom stoja namiesto toho, aby uhli, a zápchu udržiavajú.</p>
<p>Uhnúť nabok, napríklad o 35° na stranu s menej susedmi, je lepšie, ale dav sa začne mihotať. Vyhladenie natáčania a nižšia rýchlosť otáčania (180 namiesto 360 °/s) príznak len tlmia. Príčina je inde: pri vyrovnanej hustote si nepriateľ volí stranu každý tik inak. Pomôže hysteréza, teda zvýhodnenie predchádzajúceho rozhodnutia: strana z minulého tiku dostane pri porovnaní zľavu 3 susedov.</p>
<p>Výsledné riešenie spája kontaktné obmedzenie a tri stavy: ide, obchádza, čaká. Zložka rýchlosti do dotýkajúceho sa suseda sa zahodí, bočná ostane. Kto nemá voľnú stranu ani miesto pred sebou, čaká a netlačí.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">pre každého nepriateľa, každý tik:
    rýchlosť = smer z flow fieldu × rýchlosť × spomalenie podľa hustoty
    rýchlosť += odpudzovanie od susedov
    ak sa vpredu dotýka suseda:
        ak je voľná strana: obchádzaj (strana s hysterézou)
        inak: čakaj
    zahoď zložku rýchlosti do dotýkajúcich sa susedov
    posuň sa, zložku do steny zahoď
potom pre všetkých:
    rozsuň prekrývajúce sa dvojice (polovica prieniku, najviac 0,15 m)
    odtlač od stien</code></pre>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/crowd-states.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/crowd-states.png" alt="Dav pred úžinou, biele bodky idú, červené čakajú, za úžinou ide rad s odstupmi" width="1792" height="1008" loading="lazy" decoding="async" /></picture><figcaption>Pred úžinou: bieli idú, červení čakajú. Za úžinou ide rad s odstupmi.</figcaption></figure>
<p>Na tri veci si dajte pozor:</p>
<ul>
<li><strong>Trpezlivosť.</strong> Pri opačne idúcej dvojici nech čaká ten s vyšším ID. Reťaz susedov ho však môže blokovať tisíce tikov, preto po limite čakania skúsi núdzovo uhnúť.</li>
<li><strong>Nulová rýchlosť.</strong> Nepriateľ s nulovou želanou rýchlosťou sa zo stavu „čaká“ nikdy nevráti a po vlne ostane stáť na voľnej ploche. Kto sa nechce hýbať, nech je vždy v stave „ide“.</li>
<li><strong>Úzky kužeľ.</strong> Suseda vpredu hľadajte v úzkom kuželi s krátkou sondou. Pri 45° posielalo nastavenie do čakania alebo obchádzania 55 až 60 % davu aj na voľnej ploche, pri 30° a kratšej sonde 41 %.</li>
</ul>
<h2 id="krok-7-predbiehanie-velkych-nepriatelov"><a class="heading-anchor" href="#krok-7-predbiehanie-velkych-nepriatelov" aria-hidden="true" tabindex="-1">#</a>Krok 7: Predbiehanie veľkých nepriateľov</h2>
<p>Rôzne veľké a rôzne rýchle typy nepriateľov prinášajú ďalšie prípady. Náš veľký pomalý nepriateľ má polomer v dave 1,25 m a ide 1,1 m/s. Rýchly nepriateľ za ním ho nevedel predbehnúť: bočné sondy šikmo vpred boli stále bližšie k veľkému ako súčet polomerov, obe strany vyšli obsadené a rýchly čakal. Oprava: suseda, ktorého nepriateľ obchádza, berie sonda do úvahy len na jeho strane. Sused presne vpredu neblokuje žiadnu.</p>
<p>Veľký nepriateľ naopak odhadzuje malých z cesty do strán, vo výseku ±45° pred sebou. Neodhadzujte tých, čo idú jeho smerom rýchlejšie, ako chce ísť on, inak odhodí aj tých, čo ho práve predbiehajú.</p>
<h2 id="krok-8-sloty-pri-cieli-utoku"><a class="heading-anchor" href="#krok-8-sloty-pri-cieli-utoku" aria-hidden="true" tabindex="-1">#</a>Krok 8: Sloty pri cieli útoku</h2>
<p>Ak nepriatelia útočia zblízka, potrebujú miesto okolo cieľa. Naša veža má 24 slotov po obvode pôdorysu 5 × 5 m, každý nepriateľ zaberá jeden a ostatní čakajú v rade.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/unity-dots-horde-navigation/tower-slots.webp" /><img src="https://smallwater.eu/blog/unity-dots-horde-navigation/tower-slots.png" alt="Veža s 24 slotmi po obvode, väčšina obsadená, žltá dráha obchádza vežu oblúkom k voľnému slotu" width="1408" height="960" loading="lazy" decoding="async" /></picture><figcaption>Obsadené sloty útočia, voľné sú prerušované. Nový nepriateľ obchádza vežu po oblúku k voľnému slotu, za plnou stranou čaká rad.</figcaption></figure>
<ul>
<li><strong>Obchádzanie po kruhu.</strong> Ak ponúkate slot len vtedy, keď k nemu vedie priama čiara, zadné sloty ostanú prázdne, lebo spredu čiara k nim pretína vežu. Nepriateľ nech k voľnému slotu obchádza vežu po oblúku.</li>
<li><strong>Bez vytláčania.</strong> Nevytláčajte nepriateľa idúceho k slotu v prospech iného žiadateľa, spúšťa to reťaz vytláčaní.</li>
<li><strong>Bez odtláčania pri slote.</strong> Nepriateľa prichádzajúceho vedľa obsadeného slotu odtláča sused, takže skončí 13 cm od cieľa alebo úplne mimo. Nepriatelia v slote a do 0,5 m od neho sa navzájom neodtláčajú.</li>
</ul>
<h2 id="vykon-simulacie-a-vykreslovania"><a class="heading-anchor" href="#vykon-simulacie-a-vykreslovania" aria-hidden="true" tabindex="-1">#</a>Výkon simulácie a vykresľovania</h2>
<p>Benchmark v Unity Editore (M3 Pro, okolo 9 600 jednoduchých nepriateľov, dve úžiny) dal 90 FPS pri 1440p a 100 pri 1080p. Rozlíšenie FPS skoro nemení, zrýchlenie času áno: pri 10× padlo na 20 FPS, lebo na jednu snímku pripadá desať krokov simulácie. Pri normálnej rýchlosti má simulácia rezervu.</p>
<p>S detailným modelom nepriateľa namiesto kapsuly sa úzke hrdlo presunie do vykresľovania:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Variant</th>
<th align="right">Naraz</th>
<th align="right">Trojuholníkov na model</th>
<th align="right">Medián FPS</th>
</tr>
</thead>
<tbody><tr>
<td>Kapsula</td>
<td align="right">2 612</td>
<td align="right">844</td>
<td align="right">197</td>
</tr>
<tr>
<td>Detailný model, s tieňmi</td>
<td align="right">2 976</td>
<td align="right">3 578</td>
<td align="right">48</td>
</tr>
<tr>
<td>Detailný model, bez tieňov</td>
<td align="right">2 976</td>
<td align="right">3 578</td>
<td align="right">85</td>
</tr>
<tr>
<td>Zjednodušený model, s tieňmi</td>
<td align="right">2 945</td>
<td align="right">878</td>
<td align="right">335</td>
</tr>
</tbody></table>
</div>
<p>V simulácii sa oplatia tri veci:</p>
<ul>
<li><strong>Dva prechody susedov.</strong> Lacný (dotyky, blokovanie) beží vždy, drahý (bočné sondy, hustota strán) len pre zablokovaných, čo je vo voľnom prúde menšina. Vzdialenosti porovnávajte v druhej mocnine, bez odmocniny.</li>
<li><strong>Jednoduché triedenie.</strong> Synchrónne zoradenie spatial hashu trvalo pri 3 000, 10 000, 20 000 a 50 000 záznamoch 0,37, 1,49, 3,16 a 8,46 ms, verzia v jobe 0,18, 0,93, 3,12 a 15,53 ms. Job nie je konzistentne rýchlejší, jednoduché triedenie stačí.</li>
<li><strong>Hash stavu.</strong> Pri ticku 600 a 1800 spočítajte hash pozícií a životov. Refaktor musí dať rovnaký hash, zmena správania ho zmení a viete ju vysvetliť. Funguje to vďaka deterministickému poradiu v spatial hashi.</li>
</ul>
<h2 id="limity-riesenia"><a class="heading-anchor" href="#limity-riesenia" aria-hidden="true" tabindex="-1">#</a>Limity riešenia</h2>
<ul>
<li>Dva plné protismerné davy v jednej úžine sa zaseknú natrvalo. Je to limit modelu bez fyziky.</li>
<li>Čísla sú z Unity Editora na M3 Pro. Meranie na Steam Decku a v skutočnom builde zatiaľ nemáme, len odhad z verejných benchmarkov procesorov.</li>
</ul>
<p>Ak chcete skúsiť vlastný dav, <a href="https://docs.unity3d.com/Packages/com.unity.entities@6.5/manual/" target="_blank" rel="noopener">dokumentácia Unity Entities</a> je dobrý začiatok. Podobný postup od najjednoduchšieho riešenia k funkčnému nájdete aj v článku o <a href="https://smallwater.eu/blog/sudoku-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/unity-dots-horde-navigation/" data-ga-destination="/blog/sudoku-generator/" data-ga-placement="inline">generátore sudoku</a>. Ako nepriateľov v takom dave odhodiť výbuchom bez drahého ragdollu pre každého, opisuje návod <a href="https://smallwater.eu/blog/fake-ragdoll-unity-dots/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/unity-dots-horde-navigation/" data-ga-destination="/blog/fake-ragdoll-unity-dots/" data-ga-placement="inline">ako spraviť fake ragdoll v Unity DOTS</a>. Ak riešite podobnú simuláciu v Unity a chcete pomoc, <a href="https://smallwater.eu/#kontakt">napíšte nám</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ako vygenerovať osemsmerovku s tajničkou</title>
      <link>https://smallwater.eu/blog/word-search-generator/</link>
      <guid isPermaLink="true">https://smallwater.eu/blog/word-search-generator/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Krížovky</category>
      <description>Návod na generátor osemsmerovky s tajničkou: príprava slovníka, losovanie dĺžky slov, plnenie mriežky bez backtrackingu a úspešnosť podľa veľkosti mriežky.</description>
      <content:encoded><![CDATA[<picture><source type="image/webp" srcset="https://smallwater.eu/blog/word-search-generator/cover.webp" /><img src="https://smallwater.eu/blog/word-search-generator/cover.jpg" alt="Osemsmerovka 10×10 s farebne vyznačenými slovami a tajničkou v koralových políčkach" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Obsah"><p class="post-toc-title">Obsah</p><ol><li><a href="#pravidla-mriezky" data-ga="blog_toc_clicked" data-ga-heading-id="pravidla-mriezky">Pravidlá mriežky</a></li><li><a href="#krok-1-pripravte-slovnik" data-ga="blog_toc_clicked" data-ga-heading-id="krok-1-pripravte-slovnik">Krok 1: Pripravte slovník</a></li><li><a href="#krok-2-vyberte-tajnicku" data-ga="blog_toc_clicked" data-ga-heading-id="krok-2-vyberte-tajnicku">Krok 2: Vyberte tajničku</a></li><li><a href="#preco-nezacinat-tajnickou" data-ga="blog_toc_clicked" data-ga-heading-id="preco-nezacinat-tajnickou">Prečo nezačínať tajničkou</a></li><li><a href="#krok-3-plnte-mriezku-kym-neostane-presne-n-dier" data-ga="blog_toc_clicked" data-ga-heading-id="krok-3-plnte-mriezku-kym-neostane-presne-n-dier">Krok 3: Plňte mriežku, kým neostane presne N dier</a></li><li><a href="#krok-4-losujte-dlzku-slova-s-rozumom" data-ga="blog_toc_clicked" data-ga-heading-id="krok-4-losujte-dlzku-slova-s-rozumom">Krok 4: Losujte dĺžku slova s rozumom</a></li><li><a href="#krok-5-vlejte-tajnicku-do-dier" data-ga="blog_toc_clicked" data-ga-heading-id="krok-5-vlejte-tajnicku-do-dier">Krok 5: Vlejte tajničku do dier</a></li><li><a href="#ked-sa-mriezka-nepodari" data-ga="blog_toc_clicked" data-ga-heading-id="ked-sa-mriezka-nepodari">Keď sa mriežka nepodarí</a></li><li><a href="#uspesnost-a-rychlost-podla-velkosti-mriezky" data-ga="blog_toc_clicked" data-ga-heading-id="uspesnost-a-rychlost-podla-velkosti-mriezky">Úspešnosť a rýchlosť podľa veľkosti mriežky</a></li><li><a href="#tip-rovnaka-mriezka-pri-kazdom-spusteni" data-ga="blog_toc_clicked" data-ga-heading-id="tip-rovnaka-mriezka-pri-kazdom-spusteni">Tip: rovnaká mriežka pri každom spustení</a></li></ol></nav><p>Osemsmerovku s tajničkou vygenerujete tak, že do prázdnej mriežky ukladáte náhodné slová zo slovníka, kým v nej neostane voľných presne toľko políčok, koľko písmen má tajnička. Tie potom vyplníte tajničkou zľava doprava a zhora nadol. Celé to v telefóne trvá pár milisekúnd a nepotrebuje backtracking, teda vracanie sa k skorším rozhodnutiam, keď sa generátor zasekne.</p>
<p>Osemsmerovka je mriežka písmen, v ktorej hráč hľadá a škrtá slová zo zoznamu. Tajnička je text, ktorý sa prečíta zo zvyšných, neprečiarknutých písmen. Generátor preto musí poskladať mriežku, ktorá je úplne plná a v ktorej po vyškrtaní slov ostane presne tajnička. Postup nižšie je ten, ktorým generujeme osemsmerovky v našej aplikácii s krížovkami a osemsmerovkami (<a href="https://smallwater.eu/crosswords-ios">App Store</a>, <a href="https://smallwater.eu/crosswords-android">Google Play</a>).</p>
<h2 id="pravidla-mriezky"><a class="heading-anchor" href="#pravidla-mriezky" aria-hidden="true" tabindex="-1">#</a>Pravidlá mriežky</h2>
<p>Skôr než začnete, ujasnite si, čo má hotová mriežka spĺňať. V našej aplikácii platí:</p>
<ul>
<li>Slová idú v ôsmich smeroch: vodorovne, zvisle a po oboch uhlopriečkach, každým smerom aj pospiatky.</li>
<li>Dve slová sa môžu prekrývať najviac v jednom písmene.</li>
<li>Žiadne slovo nesmie prechádzať políčkom tajničky.</li>
<li>Mriežka musí byť plná. Prázdne políčko je chyba.</li>
<li>Tajnička sa číta v poradí zľava doprava a zhora nadol zo všetkých neprečiarknutých písmen.</li>
</ul>
<h2 id="krok-1-pripravte-slovnik"><a class="heading-anchor" href="#krok-1-pripravte-slovnik" aria-hidden="true" tabindex="-1">#</a>Krok 1: Pripravte slovník</h2>
<p>Slovník rozdeľte raz na začiatku do skupín podľa dĺžky slova. Výber náhodného slova danej dĺžky je potom jeden náhodný index. Ak by ste pri každom slove filtrovali celý slovník, generovanie sa citeľne spomalí, lebo slov sa do jednej mriežky skúša stovky.</p>
<p>Náš slovenský slovník má 16 713 slov s dĺžkou od 3 do 21 písmen, najviac je sedem- a osempísmenových. Pár praktických rád:</p>
<ul>
<li><strong>Nechajte v slovníku aj vlastné mená a skratky.</strong> V mriežke sa potom objaví napríklad HOUSTON alebo MAINE. Bez krátkych a „divných“ slov sa mriežka nedá dostatočne zahustiť.</li>
<li><strong>Vulgarizmy filtrujte samostatným zoznamom</strong>, ktorý sa dá vypnúť, ak hráč chce obsah pre dospelých.</li>
<li><strong>Pri viacerých jazykoch načítajte len ten, v ktorom sa hrá.</strong> Anglický slovník má vyše 414 000 slov. Načítanie všetkých štyroch jazykov naraz pri štarte aplikácie zamrazí slabší telefón aj na 11 sekúnd.</li>
</ul>
<h2 id="krok-2-vyberte-tajnicku"><a class="heading-anchor" href="#krok-2-vyberte-tajnicku" aria-hidden="true" tabindex="-1">#</a>Krok 2: Vyberte tajničku</h2>
<p>Tajnička by mala mať aspoň toľko písmen, koľko je strana mriežky, a najviac o štyri viac. Kratšia v mriežke zanikne, dlhšia vyžaduje príliš veľa dier.</p>
<p>V našej aplikácii je tajnička pointa vtipu. Hráč škrtá slová a zo zvyšných písmen si prečíta, ako vtip končí. Tajničku skladáme z konca vtipu: odzadu pridávame celé slová, kým nespadne do uvedeného rozsahu.</p>
<p>Obrázky nižšie používajú anglický vtip „Two snakes walk into a bar. Turns out they were lizards.“ (Dva hady vojdú do baru. Ukáže sa, že to boli jaštery.) a mriežku 10×10. Tajnička je WERELIZARDS, teda 11 písmen. Slová v obrázkoch sú z anglického slovníka zúženého na bežné slová, aby sa dali čítať.</p>
<h2 id="preco-nezacinat-tajnickou"><a class="heading-anchor" href="#preco-nezacinat-tajnickou" aria-hidden="true" tabindex="-1">#</a>Prečo nezačínať tajničkou</h2>
<p>Prvý nápad je robiť to, čo by človek robil ceruzkou: najprv rozhodiť písmená tajničky na náhodné políčka v poradí čítania a potom okolo nich skladať slová.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/word-search-generator/v1-seed.webp" /><img src="https://smallwater.eu/blog/word-search-generator/v1-seed.png" alt="Prázdna mriežka 10×10 s jedenástimi náhodne rozhodenými písmenami tajničky" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Písmená tajničky rozhodené do mriežky ešte pred slovami.</figcaption></figure>
<p>Vyzerá to rozumne, kým nepríde koniec. Generátor si vyberie prázdne políčko, skúsi cezeň dostať slovo vo všetkých smeroch a posunoch, a keď mriežka zhustne, ostanú izolované diery. Jedna diera medzi dvoma písmenami tajničky sa nedá zaplniť ničím, lebo najkratšie slovo má tri písmená a cez tajničku slovo ísť nesmie.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/word-search-generator/v1-stuck.webp" /><img src="https://smallwater.eu/blog/word-search-generator/v1-stuck.png" alt="Takmer plná mriežka s troma prázdnymi políčkami, ktoré sa už nedajú zaplniť" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Po 10 000 pokusoch ostali tri diery, ktoré nevyplní žiadne slovo.</figcaption></figure>
<p>Na vzorke 200 mriežok 10×10 tento prístup skončil s plnou mriežkou len v 28 % prípadov, priemerne ostali 1 až 2 diery. Opravovať ich dodatočne je zložité. Tajničku preto do mriežky nedávajte, kým nie sú hotové slová.</p>
<h2 id="krok-3-plnte-mriezku-kym-neostane-presne-n-dier"><a class="heading-anchor" href="#krok-3-plnte-mriezku-kym-neostane-presne-n-dier" aria-hidden="true" tabindex="-1">#</a>Krok 3: Plňte mriežku, kým neostane presne N dier</h2>
<p>Plnenie je jednoduchý greedy algorithm: každé slovo sa položí hneď, ako sa zmestí, a už sa neodoberá. Generátor si pamätá iba jedno číslo: koľko políčok je ešte prázdnych. Slovo položí len vtedy, ak by po ňom neostalo menej prázdnych políčok, než má tajnička písmen (N).</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">prázdne = riadky × stĺpce
kým prázdne &gt; N a pokusov &lt; 2000:
    slovo = náhodné slovo zvolenej dĺžky
    opakuj najviac 50-krát:
        začiatok = náhodné políčko, smer = náhodný z 8
        ak sa slovo zmestí podľa pravidiel mriežky:
            nové = počet prázdnych políčok, ktoré slovo zaplní
            ak prázdne - nové &gt;= N:
                polož slovo
                prázdne = prázdne - nové
                pokračuj ďalším slovom
ak prázdne != N: mriežku zahoď a začni odznova</code></pre>
<p>Prvé slová padnú do prázdnej mriežky voľne:</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/word-search-generator/step-1.webp" /><img src="https://smallwater.eu/blog/word-search-generator/step-1.png" alt="Mriežka s prvými tromi slovami: jedno vodorovne a dve zvisle" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Po troch slovách. Každé slovo má vlastnú farbu.</figcaption></figure>
<p>Postupne slová začínajú zdieľať písmená. To je vítané, lebo každé zdieľané písmeno je jedno políčko, ktoré netreba zapĺňať:</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/word-search-generator/step-2.webp" /><img src="https://smallwater.eu/blog/word-search-generator/step-2.png" alt="Mriežka s trinástimi slovami, viac ako tretina políčok je prázdna" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Trinásť slov, 40 prázdnych políčok.</figcaption></figure>
<p>Keď počet prázdnych políčok klesne na N, plnenie končí. Kde tie diery sú, je jedno. Môžu byť roztrúsené kdekoľvek, lebo tajnička sa nimi nemusí dať nijako prejsť:</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/word-search-generator/step-3.webp" /><img src="https://smallwater.eu/blog/word-search-generator/step-3.png" alt="Mriežka s dvadsiatimi dvoma slovami a jedenástimi prázdnymi políčkami s prerušovaným okrajom" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Ostalo presne 11 prázdnych políčok, rovnako ako má WERELIZARDS písmen.</figcaption></figure>
<h2 id="krok-4-losujte-dlzku-slova-s-rozumom"><a class="heading-anchor" href="#krok-4-losujte-dlzku-slova-s-rozumom" aria-hidden="true" tabindex="-1">#</a>Krok 4: Losujte dĺžku slova s rozumom</h2>
<p>Dlhé slová robia osemsmerovku zaujímavou, krátke ju dokážu dokončiť. Dĺžku preto losujte z dvoch normálnych rozdelení (normálne rozdelené náhodné čísla dostanete napríklad <a href="https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform" target="_blank" rel="noopener">Box-Mullerovou transformáciou</a>):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Pravdepodobnosť</th>
<th>Dĺžka slova</th>
</tr>
</thead>
<tbody><tr>
<td>50 %</td>
<td>okolo 6 písmen, smerodajná odchýlka 2</td>
</tr>
<tr>
<td>30 %</td>
<td>okolo 5 písmen, smerodajná odchýlka 1</td>
</tr>
<tr>
<td>20 %</td>
<td>3 písmená</td>
</tr>
</tbody></table>
</div>
<p>Dĺžka je vždy aspoň 3 a nanajvýš strana mriežky. K tomu dve pravidlá, bez ktorých sa generátor na konci zasekáva:</p>
<ul>
<li><strong>Na konci vynúťte presnú dĺžku.</strong> Keď do cieľa chýba 5 a menej políčok, hľadajte slovo presne takej dĺžky, aká chýba (najmenej 3). Inak sa bude generátor donekonečna pokúšať o šesťpísmenové slová, ktoré sa už nezmestia.</li>
<li><strong>Trojpísmenové slová dávajte na prídel.</strong> Prvé tri pokusy o mriežku nesmú obsahovať ani jedno, pokusy 4 až 6 jedno, ďalšie tri dve a tak ďalej. Trojpísmenové slová sú v slovníku často skratky typu DNA alebo CPU a v osemsmerovke pôsobia lacno. Povoľte ich, až keď to inak nejde.</li>
</ul>
<h2 id="krok-5-vlejte-tajnicku-do-dier"><a class="heading-anchor" href="#krok-5-vlejte-tajnicku-do-dier" aria-hidden="true" tabindex="-1">#</a>Krok 5: Vlejte tajničku do dier</h2>
<p>Prejdite mriežku zľava doprava a zhora nadol a do každého prázdneho políčka zapíšte ďalšie písmeno tajničky. Keďže dier je presne N, tajnička sa zmestí bezo zvyšku.</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/word-search-generator/step-4.webp" /><img src="https://smallwater.eu/blog/word-search-generator/step-4.png" alt="Tá istá mriežka, prázdne políčka vyplnené písmenami tajničky s poradovými číslami 1 až 11" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Písmená tajničky v poradí čítania. Hráč ich nájde ako posledné neprečiarknuté.</figcaption></figure>
<p>Hráč vidí len toto:</p>
<figure class="post-figure"><picture><source type="image/webp" srcset="https://smallwater.eu/blog/word-search-generator/final.webp" /><img src="https://smallwater.eu/blog/word-search-generator/final.png" alt="Hotová osemsmerovka 10×10 bez vyznačenia slov" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Hotová mriežka tak, ako ju vidí hráč.</figcaption></figure>
<aside class="app-box app-box-callout"><img class="app-box-img" src="https://smallwater.eu/images/crosswords/1-crosswords.jpg" alt="" width="698" height="1516" loading="lazy" decoding="async" /><div class="app-box-body"><p class="app-box-eyebrow">Vyskúšajte</p><h2 class="app-box-title">Krížovky</h2><p>Krížovky, osemsmerovky, sudoku a maľované krížovky v jednej offline aplikácii. Ako vznikajú hádanky a čo je nové.</p><div class="app-box-links"><a class="btn btn-primary" href="https://smallwater.eu/crosswords-ios" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="crosswords" data-ga-destination="/crosswords-ios" data-ga-app="crosswords" data-ga-store="app_store">App Store</a><a class="btn btn-ghost" href="https://smallwater.eu/crosswords-android" data-ga="blog_app_link_clicked,outbound_store_click" data-ga-category="crosswords" data-ga-destination="/crosswords-android" data-ga-app="crosswords" data-ga-store="google_play">Google Play</a></div></div></aside>
<h2 id="ked-sa-mriezka-nepodari"><a class="heading-anchor" href="#ked-sa-mriezka-nepodari" aria-hidden="true" tabindex="-1">#</a>Keď sa mriežka nepodarí</h2>
<p>Ak generátor ani po 2 000 pokusoch nedosiahne presne N prázdnych políčok, celú mriežku zahoďte a začnite znova, najviac 100-krát. Žiadne opravovanie dier. Je to hrubé, ale pri mriežkach do 12×12 to stačí s rezervou. Väčšie mriežky potrebujú viac pokusov, ako ukazuje tabuľka nižšie.</p>
<h2 id="uspesnost-a-rychlost-podla-velkosti-mriezky"><a class="heading-anchor" href="#uspesnost-a-rychlost-podla-velkosti-mriezky" aria-hidden="true" tabindex="-1">#</a>Úspešnosť a rýchlosť podľa veľkosti mriežky</h2>
<p>Generátor bežal na 300 mriežkach pre každú veľkosť, so skutočnými vtipmi a slovenským slovníkom (Node.js 22 na serverovom procesore, telefón bude pomalší; obrázky v článku sú z anglického slovníka, čísla zo slovenského, lebo je menší a teda prísnejší test). „Na prvý pokus“ znamená, že prvé kolo plnenia skončilo plnou mriežkou.</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Mriežka</th>
<th align="right">Na prvý pokus</th>
<th align="right">Priemer pokusov</th>
<th align="right">Medián času</th>
<th align="right">Slov v mriežke</th>
<th align="right">Podiel uhlopriečok</th>
</tr>
</thead>
<tbody><tr>
<td>8×8</td>
<td align="right">78 %</td>
<td align="right">1,30</td>
<td align="right">1,2 ms</td>
<td align="right">12,7</td>
<td align="right">24 %</td>
</tr>
<tr>
<td>10×10</td>
<td align="right">70 %</td>
<td align="right">1,42</td>
<td align="right">2,2 ms</td>
<td align="right">20,4</td>
<td align="right">27 %</td>
</tr>
<tr>
<td>12×12</td>
<td align="right">57 %</td>
<td align="right">1,73</td>
<td align="right">4,7 ms</td>
<td align="right">30,1</td>
<td align="right">29 %</td>
</tr>
<tr>
<td>15×15</td>
<td align="right">21 %</td>
<td align="right">5,46</td>
<td align="right">20 ms</td>
<td align="right">48,7</td>
<td align="right">33 %</td>
</tr>
</tbody></table>
</div>
<p>Čo z toho vyplýva pre vlastný generátor:</p>
<ul>
<li><strong>Priemerné slovo má 4,7 písmena</strong>, hoci cieľ je okolo 6. Dlhé slová sa do zahustenej mriežky jednoducho častejšie nezmestia.</li>
<li><strong>Asi 9 až 10 % políčok zdieľajú dve slová.</strong> Netreba to nastavovať, vyplýva to z pravidla „najviac jedno spoločné písmeno“.</li>
<li><strong>Uhlopriečok bude menej, než čakáte.</strong> Smer sa losuje rovnomerne z ôsmich, takže by mali tvoriť polovicu slov. V malej mriežke však uhlopriečne slovo oveľa častejšie vypadne za okraj. Na 8×8 tvoria uhlopriečky 24 % slov, na 15×15 už 33 %. Ak chcete viac uhlopriečok, dajte im pri losovaní smeru vyššiu váhu.</li>
</ul>
<h2 id="tip-rovnaka-mriezka-pri-kazdom-spusteni"><a class="heading-anchor" href="#tip-rovnaka-mriezka-pri-kazdom-spusteni" aria-hidden="true" tabindex="-1">#</a>Tip: rovnaká mriežka pri každom spustení</h2>
<p>Na screenshoty do obchodov potrebujete, aby sa vygenerovala vždy tá istá mriežka. Namiesto bežného generátora náhodných čísel použite v takom režime jednoduchý lineárny kongruenčný generátor s pevným seedom (napríklad odvodeným z jazyka). Ak držíte všetku aritmetiku v rozsahu, ktorý desatinné čísla v JavaScripte reprezentujú presne, výsledok je rovnaký na každom zariadení. Rovnakým trikom sú vygenerované aj obrázky v tomto článku.</p>
<p>Ak riešite podobný problém, generujete hlavolamy alebo potrebujete mobilnú aplikáciu, <a href="https://smallwater.eu/#kontakt">napíšte nám</a>. Krátke ukážky z vývoja dávame aj na <a href="https://www.youtube.com/@smallwaterinc" target="_blank" rel="noopener">YouTube</a>. Podobne krok za krokom je opísané aj to, <a href="https://smallwater.eu/blog/sudoku-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/word-search-generator/" data-ga-destination="/blog/sudoku-generator/" data-ga-placement="inline">ako vygenerovať sudoku s jediným riešením</a>. Ak potrebujete krížovku namiesto osemsmerovky, pozrite si, <a href="https://smallwater.eu/blog/swedish-crossword-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/blog/word-search-generator/" data-ga-destination="/blog/swedish-crossword-generator/" data-ga-placement="inline">ako vygenerovať švédsku krížovku s tajničkou</a>.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
