<?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>Small Water blog</title>
    <link>https://smallwater.eu/hu/blog/</link>
    <description>Alkalmazások, webes eszközök és játékok egy kis szlovákiai szoftvercégtől.</description>
    <language>hu</language>
    <atom:link href="https://smallwater.eu/hu/blog/feed.xml" rel="self" type="application/rss+xml" />
    <lastBuildDate>Mon, 09 Nov 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>LOD készítése több ezer animált karakterhez Unityben</title>
      <link>https://smallwater.eu/hu/blog/unity-lod-vertex-animation-crowd/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/unity-lod-vertex-animation-crowd/</guid>
      <pubDate>Mon, 09 Nov 2026 00:00:00 GMT</pubDate>
      <category>Tower Defense</category>
      <description>Útmutató tömeg LOD-hoz Unityben vertex animation textures használatával: miért a csúcsok számítanak, LOD új hálóként és entitásonkénti kiválasztás.</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="Ugyanannak a csontváz karakternek három változata balról jobbra csökkenő háromszögszámmal" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#miert-nem-segit-a-generalt-lod-egy-tomegnel" data-ga="blog_toc_clicked" data-ga-heading-id="miert-nem-segit-a-generalt-lod-egy-tomegnel">Miért nem segít a generált LOD egy tömegnél</a></li><li><a href="#a-csucsokat-szamolja-ne-a-haromszogeket" data-ga="blog_toc_clicked" data-ga-heading-id="a-csucsokat-szamolja-ne-a-haromszogeket">A csúcsokat számolja, ne a háromszögeket</a></li><li><a href="#1-lepes-csokkentse-a-csucsokat-a-lod-0-ban" data-ga="blog_toc_clicked" data-ga-heading-id="1-lepes-csokkentse-a-csucsokat-a-lod-0-ban">1. lépés: Csökkentse a csúcsokat a LOD 0-ban</a></li><li><a href="#2-lepes-lod-1-egyszerusitessel-kozos-texturaval" data-ga="blog_toc_clicked" data-ga-heading-id="2-lepes-lod-1-egyszerusitessel-kozos-texturaval">2. lépés: LOD 1 egyszerűsítéssel, közös textúrával</a></li><li><a href="#3-lepes-lod-2-az-eredetihez-kotott-uj-halokent" data-ga="blog_toc_clicked" data-ga-heading-id="3-lepes-lod-2-az-eredetihez-kotott-uj-halokent">3. lépés: LOD 2 az eredetihez kötött új hálóként</a></li><li><a href="#4-lepes-geppel-ellenorizze-a-lyukakat" data-ga="blog_toc_clicked" data-ga-heading-id="4-lepes-geppel-ellenorizze-a-lyukakat">4. lépés: Géppel ellenőrizze a lyukakat</a></li><li><a href="#5-lepes-karakterenkent-valasszon-szintet" data-ga="blog_toc_clicked" data-ga-heading-id="5-lepes-karakterenkent-valasszon-szintet">5. lépés: Karakterenként válasszon szintet</a></li><li><a href="#eredmenyek-es-korlatok" data-ga="blog_toc_clicked" data-ga-heading-id="eredmenyek-es-korlatok">Eredmények és korlátok</a></li></ol></nav><p>A több ezer animált karakter LOD-ját Unityben a csúcsok száma alapján építse fel, ne a háromszögek szerint: vertex animation textures esetén az animációt csúcsonként kell kiszámítani. Az alsóbb szinteket készítse el kézzel, a legalsó szint csúcsait kösse az eredeti felületéhez, a szintet pedig karakterenként válassza ki. PC-s tower defense játékunkban ez 3 000 ellenfélnél a tömegbe közelítve 62-ről 120-ra emelte az FPS-t.</p>
<p>Az eljárás azokra a játékokra vonatkozik, ahol egyszerre több ezer karakter mozog a képernyőn: tower defense, stratégiai és survival játékokra. A miénk Unityben, ECS-sel készül, és a <a href="https://smallwater.eu/hu/#referencie">referenciák</a> között található. A LOD a <a href="https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics)" target="_blank" rel="noopener">level of detail</a> rövidítése: a távoli karaktert egyszerűbb hálóból rajzoljuk. A képek csontváz ellenfelünk valódi hálóit mutatják, nem játékbeli felvételek.</p>
<h2 id="miert-nem-segit-a-generalt-lod-egy-tomegnel"><a class="heading-anchor" href="#miert-nem-segit-a-generalt-lod-egy-tomegnel" aria-hidden="true" tabindex="-1">#</a>Miért nem segít a generált LOD egy tömegnél</h2>
<p>A Unity a modell importálásakor képes LOD szinteket generálni (a <a href="https://docs.unity3d.com/6000.2/Documentation/Manual/lod/mesh-lod-introduction.html" target="_blank" rel="noopener">Mesh LOD</a> funkció). Több ezer karakternél ennek két gondja van.</p>
<p>Az első a kinézet. Az automatikus egyszerűsítés felszakította a vékony végtagokat és elrontotta az UV-t, vagyis a textúra leképezését a hálóra.</p>
<p>A második az animáció ára. A hagyományos csontvázas animáció több ezer entitásnál túl drága, ezért vertex animation textures (VAT) kerül a helyére. A VAT olyan textúra, amelyben minden oszlop egy csúcshoz, minden sor egy animációs képkockához tartozik. A shader a textúrából olvassa ki a csúcs helyzetét, így az animáció ára a kiolvasott csúcsok számától függ, az árnyékok rajzolásakor is.</p>
<p>A plafont egy teszt mutatja, ahol minden karakterre egy generált szintet kényszerítettünk (a Unity a 3. és 4. szintet is előállítja, build, 1080p, 3 000 ellenfél, bekapcsolt árnyékok):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Kényszerített szint</th>
<th>Háromszögek</th>
<th>FPS</th>
</tr>
</thead>
<tbody><tr>
<td>LOD 0</td>
<td>11 millió</td>
<td>72</td>
</tr>
<tr>
<td>LOD 1</td>
<td>6 millió</td>
<td>96</td>
</tr>
<tr>
<td>LOD 2</td>
<td>3,68 millió</td>
<td>120</td>
</tr>
<tr>
<td>LOD 4</td>
<td>1,43 millió</td>
<td>201</td>
</tr>
</tbody></table>
</div>
<p>A gyorsításra bőven van tér. Ezt látható lyukak és a szintek közti ugrások nélkül kell kihasználni.</p>
<h2 id="a-csucsokat-szamolja-ne-a-haromszogeket"><a class="heading-anchor" href="#a-csucsokat-szamolja-ne-a-haromszogeket" aria-hidden="true" tabindex="-1">#</a>A csúcsokat számolja, ne a háromszögeket</h2>
<p>A fele annyi háromszög önmagában nem segít. Egy LOD fele annyi háromszöggel és az eredeti 1 521 csúcsból 996-tal 1,9 ms-ot takarított meg képkockánként, egy 217 csúcsos LOD 6,4 ms-ot. Minden szintnél kövesse mindkét számot.</p>
<p>Gyakran fölöslegesen sok a csúcs. Minden UV-varratnál megkettőződik a csúcs, mert egy pozícióhoz ilyenkor két textúrakoordináta tartozik. Az AI-generátorból származó modelljeink UV-ja több száz szigetre volt vágva: a csontváznak 1 521 csúcsa volt, de csak 608 különböző pozíció-normál párosa.</p>
<h2 id="1-lepes-csokkentse-a-csucsokat-a-lod-0-ban"><a class="heading-anchor" href="#1-lepes-csokkentse-a-csucsokat-a-lod-0-ban" aria-hidden="true" tabindex="-1">#</a>1. lépés: Csökkentse a csúcsokat a LOD 0-ban</h2>
<p>Az első megtakarítás nem jár részletvesztéssel. Egyesítse az azonos pozíciójú és hasonló normálú csúcsokat (25° eltérésig), bontsa ki újra az UV-t csak az éles éleken (70° felett) varrattal, és a textúrát számolja át a régiből háromszögenként. A csontváz 1 521-ről 1 090 csúcsra csökkent, a többi ellenféltípus 19-29 %-kal, változatlan alakkal.</p>
<p>Ezután csak azoknak a csúcsoknak az oszlopait írja a VAT-ba, amelyeket valamelyik szint ténylegesen használ. Ez az animációs memória 40-60 %-át spórolta meg.</p>
<h2 id="2-lepes-lod-1-egyszerusitessel-kozos-texturaval"><a class="heading-anchor" href="#2-lepes-lod-1-egyszerusitessel-kozos-texturaval" aria-hidden="true" tabindex="-1">#</a>2. lépés: LOD 1 egyszerűsítéssel, közös textúrával</h2>
<p>A LOD 1 a háromszögek körülbelül 52 %-ára egyszerűsített LOD 0. Darabonként egyszerűsítsen (a csont és a háló összefüggő részének minden párosítása), és rögzítse a darabok határán lévő csúcsokat. A fejnek adjon nagyobb keretet, mert a fej sziluettjét ismeri fel a szem a tömegben.</p>
<p>Fontos megtartani a LOD 0 textúráját. Az önálló textúrájú vagy más alakú szint átmenetkor hasonló sziluett mellett is látszik.</p>
<p>A háromszögek nagyjából 50 %-a alatt a meglévő csúcsokból való egyszerűsítés felszakítja a vékony részeket és a különálló darabokat. Lejjebb másképp kell haladni.</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="A csontváz karakter három változata egymás mellett, csökkenő háromszögszámmal" width="2400" height="1240" loading="lazy" decoding="async" /></picture><figcaption>Ugyanaz a csontváz három szinten: 873, 448 és 330 háromszög, 1 090, 705 és 520 csúcs.</figcaption></figure>
<h2 id="3-lepes-lod-2-az-eredetihez-kotott-uj-halokent"><a class="heading-anchor" href="#3-lepes-lod-2-az-eredetihez-kotott-uj-halokent" aria-hidden="true" tabindex="-1">#</a>3. lépés: LOD 2 az eredetihez kötött új hálóként</h2>
<p>A legalsó szint új, zárt háló. Vastagítsa meg az eredetit egy réteggel, futtasson voxel remesht, és egyszerűsítsen a célszámra (nálunk ellenféltípustól függően 200-460 háromszög). Az eredményt húzza rá az eredeti felületére, az UV-t pedig készítse automatikusan. A textúrát az eredetiből süsse a LOD 0-val azonos sűrűségben, hogy a mipmapek is egyezzenek.</p>
<p>Az új hálónak nincsenek animációs súlyai. Ezért minden csúcsát kösse az eredeti legközelebbi háromszögéhez. Mentse el a háromszög három oszlopát és a baricentrikus súlyokat (az arányokat, amelyek meghatározzák a pont helyét a háromszögön belül).</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="Vázlat: az egyszerűsített háló piros csúcsa szaggatott vonalakkal kapcsolódik az eredeti háromszög három csúcsához" width="1800" height="840" loading="lazy" decoding="async" /></picture><figcaption>Az alsóbb szint csúcsa (piros) a legközelebbi eredeti háromszög három csúcsával együtt mozog.</figcaption></figure>
<p>A kötött csúcs VAT-oszlopa minden képkockában a három csúcs pozíciójának és normáljának súlyozott összege. A shadernek semmi különöset nem kell tennie, a kötött csúcs csak egy újabb oszlop.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">minden f képkockára:
    VAT[kötött, f] = w0 * VAT[a, f] + w1 * VAT[b, f] + w2 * VAT[c, f]</code></pre>
<h2 id="4-lepes-geppel-ellenorizze-a-lyukakat"><a class="heading-anchor" href="#4-lepes-geppel-ellenorizze-a-lyukakat" aria-hidden="true" tabindex="-1">#</a>4. lépés: Géppel ellenőrizze a lyukakat</h2>
<p>A modellező eszköz képei nem mutatják megbízhatóan a lyukakat. Elég egy egyszerű ellenőrzés: renderelje az eredetit és a jelöltet 11 irányból 120 px-es felbontásban, és hasonlítsa össze a sziluetteket.</p>
<p>Lyuknak számítsanak az eredeti pixelei a jelölt körvonalán belül, amelyeket a jelölt nem fed le, valamint a jelölt pixelei az eredeti körvonalán több mint 2 px-lel kívül. Nálunk a határ 1 % lyuk és 4-6 % veszteség. Az ellenőrzésnek figyelembe kell vennie az anyagtól függő hátlap-eltávolítást. A határokat az alapján állítottuk be, hogy hol látszottak az átmenetek a játékban.</p>
<h2 id="5-lepes-karakterenkent-valasszon-szintet"><a class="heading-anchor" href="#5-lepes-karakterenkent-valasszon-szintet" aria-hidden="true" tabindex="-1">#</a>5. lépés: Karakterenként válasszon szintet</h2>
<p>Ha az egész térképen egyszerre vált a szint, az látszik. Entitásonkénti kiválasztásnál az átmenetek eloszlanak és eltűnnek a tömegben.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Egész térkép egyszerre</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="Egyszínű pontok tömege, minden karakter ugyanazon a szinten" width="1120" height="840" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Entitásonként</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="Pontok tömege a kamerától mért távolság szerinti sávokban, közel piros, távol szürke" width="1120" height="840" loading="lazy" decoding="async" /></picture></div></div><figcaption>Felülnézet, a kamera alul. A szín a LOD szint: piros 0, narancs 1, szürke 2.</figcaption></figure>
<p>Verziófigyelmeztetés: a Unity 6.2 dokumentációja szerint az Entities Graphics mindig LOD 0-t használ. Az alábbi képlet szerinti entitásonkénti kiválasztást Unity 6.6-ban használjuk, ezért előbb ellenőrizze a saját verziója viselkedését:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">LOD = log2(d * C / D) * meredekseg + eltolas
C = kuszob * 2 * tan(fov / 2) / kepmagassag</code></pre>
<p>Itt <code>d</code> a kamerától mért távolság, <code>D</code> pedig az entitás világbeli határainak átmérője. A meredekséget és az eltolást úgy válassza meg, hogy az 1. szint 93,3, a 2. szint 167,9 méternél kezdődjön (30°-os fov és 1440 px-es kamera). A globális LOD-küszöböt állítsa képmagasság / 1440 értékre, így a távolságok nem függenek a felbontástól.</p>
<p>Két hiba, amelyre figyeljen:</p>
<ul>
<li><strong>Méretarány.</strong> A <code>D</code> átmérőt a világbeli határokból számolja, tehát a prefab méretarányával együtt. A VAT-hálók gyakran kis egységekben vannak, a prefab 2-4-szeresére nagyítja őket. Enélkül a játék végig LOD 0-t rajzol, és nem jelez semmit. Ellenőrizze a háromszögek számával normál módban egy kényszerített LOD-hoz képest. Az entitás elforgatása megnöveli a tengelyhez igazított határokat, ezért nálunk az átmérőt 1,22-es szorzóval számoljuk.</li>
<li><strong>Sütés.</strong> A kiválasztási görbe a SubScene sütésekor kerül rögzítésre, és a sütés nem veszi észre a háló változását. Módosítás után kényszerítse ki a SubScene újrasütését.</li>
</ul>
<h2 id="eredmenyek-es-korlatok"><a class="heading-anchor" href="#eredmenyek-es-korlatok" aria-hidden="true" tabindex="-1">#</a>Eredmények és korlátok</h2>
<p>macOS build, 2560 × 1440, 3 000 ellenfél, bekapcsolt árnyékok, a játék game over után megállítva (a VAT-animációk továbbra is futnak, így a mérés a grafikára érvényes):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Jelenet</th>
<th>Előtte</th>
<th>Utána</th>
</tr>
</thead>
<tbody><tr>
<td>Teljes térkép</td>
<td>90 FPS</td>
<td>130 FPS</td>
</tr>
<tr>
<td>Közelítés a tömegbe</td>
<td>62 FPS</td>
<td>120 FPS</td>
</tr>
</tbody></table>
</div>
<p>A <a href="https://www.steamdeck.com/en/tech" target="_blank" rel="noopener">Steam Decken</a> (1280 × 800, 8 RDNA 2 számítási egység) ez 75-80 FPS a teljes térképen. A tömeghez legközelebbi nézet magas árnyékminőséggel 33 FPS, árnyékok nélkül 64. Az árnyékok jelentik tehát a legnagyobb megmaradt költséget: a LOD ezek árát is csökkenti, de a kikapcsolásuk még mindig nagy ugrást ad.</p>
<p>Amit a LOD nem old meg: a lyukellenőrzés T-pózban fut, így nem veszi észre az extrém pózokban fellépő nyúlást. Egy ellenféltípus LOD 2-ben még túllépi a lyukhatárt. A horda mozgásának számítására a LOD nincs hatással, erről a <a href="https://smallwater.eu/hu/blog/unity-dots-horde-navigation/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/unity-lod-vertex-animation-crowd/" data-ga-destination="/hu/blog/unity-dots-horde-navigation/" data-ga-placement="inline">horda navigációja Unity DOTS-ban</a> cikk szól, a robbanásokra adott reakciókról pedig a <a href="https://smallwater.eu/hu/blog/fake-ragdoll-unity-dots/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/unity-lod-vertex-animation-crowd/" data-ga-destination="/hu/blog/fake-ragdoll-unity-dots/" data-ga-placement="inline">fake ragdoll</a>.</p>
<p>Segítségre van szüksége Unity-játéka teljesítményével vagy tömegszimulációval kapcsolatban? <a href="https://smallwater.eu/hu/#kontakt">Írjon nekünk</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Glow és 2D fények régi 2D játékba Unity URP-ben</title>
      <link>https://smallwater.eu/hu/blog/dreamica-remaster-unity-urp-glow/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/dreamica-remaster-unity-urp-glow/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Dreamica</category>
      <description>Hogyan kap egy régi 2D Unity-játék szelektív glow-t Bloommal és HDR-rel, dinamikus 2D fényeket és éles felületet URP-ben. Lépésről lépésre a Dreamica példáján.</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="Három kép a Dreamica játékból: szellem üldöz egy buborékot, mókus ágyúval és világító lézerek az erdőben" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#miert-nem-eleg-a-regi-teljes-kepernyos-glow" data-ga="blog_toc_clicked" data-ga-heading-id="miert-nem-eleg-a-regi-teljes-kepernyos-glow">Miért nem elég a régi teljes képernyős glow</a></li><li><a href="#1-lepes-allitsa-at-a-projektet-urp-re" data-ga="blog_toc_clicked" data-ga-heading-id="1-lepes-allitsa-at-a-projektet-urp-re">1. lépés: Állítsa át a projektet URP-re</a><ol><li><a href="#szurke-kep-allitson-be-camera-stacket" data-ga="blog_toc_clicked" data-ga-heading-id="szurke-kep-allitson-be-camera-stacket">Szürke kép: állítson be camera stacket</a></li><li><a href="#a-sprite-ok-figyelmen-kivul-hagyjak-a-fenyt-valtson-2d-rendererre" data-ga="blog_toc_clicked" data-ga-heading-id="a-sprite-ok-figyelmen-kivul-hagyjak-a-fenyt-valtson-2d-rendererre">A sprite-ok figyelmen kívül hagyják a fényt: váltson 2D rendererre</a></li><li><a href="#reszecskek-es-regi-pluginok" data-ga="blog_toc_clicked" data-ga-heading-id="reszecskek-es-regi-pluginok">Részecskék és régi pluginok</a></li></ol></li><li><a href="#2-lepes-szelektiv-glow-hdr-szinnel-es-1-es-bloom-kuszobbel" data-ga="blog_toc_clicked" data-ga-heading-id="2-lepes-szelektiv-glow-hdr-szinnel-es-1-es-bloom-kuszobbel">2. lépés: Szelektív glow HDR színnel és 1-es Bloom-küszöbbel</a><ol><li><a href="#emisszios-terkep-a-sprite-nak-csak-egy-resze-vilagit" data-ga="blog_toc_clicked" data-ga-heading-id="emisszios-terkep-a-sprite-nak-csak-egy-resze-vilagit">Emissziós térkép: a sprite-nak csak egy része világít</a></li><li><a href="#luktetes-expozicios-lepcsokben" data-ga="blog_toc_clicked" data-ga-heading-id="luktetes-expozicios-lepcsokben">Lüktetés expozíciós lépcsőkben</a></li></ol></li><li><a href="#3-lepes-dinamikus-2d-feny-a-jatekos-korul" data-ga="blog_toc_clicked" data-ga-heading-id="3-lepes-dinamikus-2d-feny-a-jatekos-korul">3. lépés: Dinamikus 2D fény a játékos körül</a><ol><li><a href="#ugyanaz-a-shader-az-egymashoz-illeszkedo-feluleteken" data-ga="blog_toc_clicked" data-ga-heading-id="ugyanaz-a-shader-az-egymashoz-illeszkedo-feluleteken">Ugyanaz a shader az egymáshoz illeszkedő felületeken</a></li><li><a href="#felvillanas-attunes-a-csucsra-hozzaadas-helyett" data-ga="blog_toc_clicked" data-ga-heading-id="felvillanas-attunes-a-csucsra-hozzaadas-helyett">Felvillanás: áttűnés a csúcsra hozzáadás helyett</a></li></ol></li><li><a href="#4-lepes-eles-ui-a-mai-kijelzokre" data-ga="blog_toc_clicked" data-ga-heading-id="4-lepes-eles-ui-a-mai-kijelzokre">4. lépés: Éles UI a mai kijelzőkre</a></li><li><a href="#korlatok-tomoritetlen-texturak" data-ga="blog_toc_clicked" data-ga-heading-id="korlatok-tomoritetlen-texturak">Korlátok: tömörítetlen textúrák</a></li></ol></nav><p>Egy régi 2D Unity-játék a beépített render pipeline-on laposnak hat egy mai telefonon: nincs fény, a glow vagy hiányzik, vagy a teljes képet bevilágítja a UI-jal együtt, a felület sprite-jai pedig elmosódottak. Unity URP-ben ezt három dologgal oldja meg: 1-es küszöbű Bloommal és HDR színnel csak azokon az objektumokon, amelyeknek világítaniuk kell, a 2D renderer 2D fényeivel, valamint nagyobb sprite-okból álló UI-jal, amely figyelembe veszi a kijelző biztonságos területét.</p>
<p>A folyamatot a Dreamica nevű játékunkon mutatjuk be (<a href="https://smallwater.eu/dreamica-ios">App Store</a>, <a href="https://smallwater.eu/dreamica-android">Google Play</a>). Ez egy zuhanós kalandjáték, amelyben ujjal vonalakat rajzol, és ezekkel vezet egy alvó gyereket szállító buborékot négy világon át. Eredetileg Unity 5.3-ban készült, a UI az NGUI pluginban futott, a glow-t az MK Glow plugin adta, a grafika pedig az iPhone 7 1334 × 750 pixeles kijelzőjére készült. A játékmenet ugyanaz maradt, csak a grafika változott.</p>
<p>Egy régi játékot gyakran az áruház miatt kell újra elővenni, nem a grafika miatt. A Google Play 2019 augusztusa óta <a href="https://android-developers.googleblog.com/2019/01/get-your-apps-ready-for-64-bit.html" target="_blank" rel="noopener">64 bites verziókat követel meg</a>, és a csak 32 bites builddel rendelkező játék kikerül belőle. Pontosan ez történt a Dreamicával is. Ha a projektet úgyis átviszi Unity 6-ra, a grafika viszonylag olcsó bónusz.</p>
<h2 id="miert-nem-eleg-a-regi-teljes-kepernyos-glow"><a class="heading-anchor" href="#miert-nem-eleg-a-regi-teljes-kepernyos-glow" aria-hidden="true" tabindex="-1">#</a>Miért nem elég a régi teljes képernyős glow</h2>
<p>A régebbi glow pluginok teljes képernyős effektként működnek. Mindent bevilágítanak, ami világos, a menü ikonos gombjait is. A Dreamicában az MK Glow alatt Unity 6-ban az ikonok szinte eltűntek. A hivatalos Post Processing Stack v2 csak a beépített pipeline-hoz nyújt pótlást, URP-ben már nem érvényesül. Ha anyagonként vezérelhető glow-t és mellé dinamikus 2D fényeket szeretne, váltson URP-re, és használja a beépített Bloomot.</p>
<h2 id="1-lepes-allitsa-at-a-projektet-urp-re"><a class="heading-anchor" href="#1-lepes-allitsa-at-a-projektet-urp-re" aria-hidden="true" tabindex="-1">#</a>1. lépés: Állítsa át a projektet URP-re</h2>
<p>Az URP-re váltás után egy régi 2D játék szinte biztosan két csapdába fut bele. Mindkettő úgy jelentkezik, hogy a kép hibás, vagy pontosan olyan, mint korábban, pedig a shaderek rendben vannak.</p>
<h3 id="szurke-kep-allitson-be-camera-stacket"><a class="heading-anchor" href="#szurke-kep-allitson-be-camera-stacket" aria-hidden="true" tabindex="-1">#</a>Szürke kép: állítson be camera stacket</h3>
<p>Ha az Editor Game nézete csak szürke felületet mutat, de a Scene nézet és egy egyszeri kamerarender rendben van, a hiba nem a shaderekben, hanem a kamerák összerakásában van. A régi játékoknak gyakran két kamerájuk van, egy a világnak és egy a UI-nak, és a UI-kamera csak a mélységet törli. A beépített pipeline-ban így rétegezik a kamerákat, az URP viszont ezt nem támogatja. Explicit camera stack kell neki: az egyik kamera az alap (Base), a többi a stackjébe sorolt rátét (Overlay). Állítsa be minden jelenetben, ahol a kamerák egymásra kerülnek.</p>
<h3 id="a-sprite-ok-figyelmen-kivul-hagyjak-a-fenyt-valtson-2d-rendererre"><a class="heading-anchor" href="#a-sprite-ok-figyelmen-kivul-hagyjak-a-fenyt-valtson-2d-rendererre" aria-hidden="true" tabindex="-1">#</a>A sprite-ok figyelmen kívül hagyják a fényt: váltson 2D rendererre</h3>
<p>A megvilágított shaderes sprite-ok semmilyen fényre nem reagálnak, ha a hagyományos URP renderer aktív. Az a 2D fényeket egyáltalán nem számolja. Váltson a 2D rendererre. Ám ez sem rajzol megvilágítást, amíg a jelenetben nincs legalább egy 2D fény. Ha valami egy változtatás után pontosan ugyanúgy néz ki, mint előtte, először ellenőrizze, melyik renderer fut.</p>
<h3 id="reszecskek-es-regi-pluginok"><a class="heading-anchor" href="#reszecskek-es-regi-pluginok" aria-hidden="true" tabindex="-1">#</a>Részecskék és régi pluginok</h3>
<p>A régi shaderes részecskék a 2D renderer alatt feketén vagy egyáltalán nem jelennek meg. Adjon nekik új, egyszerű shadert.</p>
<p>Vigyázzon az Asset Store-ral is. Az assetek frissítésekor az Editor magától alkalmazhatja egy régi plugin „frissítését”, amely metaadatokat ad hozzá és átnevezi a shadereket. Az anyagok név szerint keresik a shaderüket, így csendben elromlanak. Egy régi projektben minden nagyobb assetfrissítés után nézze át a verziókezelőben az összes módosított fájlt, azokat is, amelyeken épp nem dolgozik.</p>
<h2 id="2-lepes-szelektiv-glow-hdr-szinnel-es-1-es-bloom-kuszobbel"><a class="heading-anchor" href="#2-lepes-szelektiv-glow-hdr-szinnel-es-1-es-bloom-kuszobbel" aria-hidden="true" tabindex="-1">#</a>2. lépés: Szelektív glow HDR színnel és 1-es Bloom-küszöbbel</h2>
<p>A cél az, hogy csak a kiválasztott objektumok világítsanak, a Dreamicában a megrajzolt vonal, a lézerek, a szikrák, a szentjánosbogarak és a rúnák, a háttér és a UI viszont ne. Az URP Bloom elmos és hozzáad mindent, ami világosabb a <a href="https://docs.unity3d.com/6/Documentation/Manual/urp/post-processing-bloom.html" target="_blank" rel="noopener">küszöbénél</a>. Állítsa a küszöböt 1-re, és a világítani hivatott anyagok színét emelje 1 fölé.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">vonal shadere:
    szín = textúra × csúcsszín × árnyalat × intenzitás    (intenzitás = 6)

Bloom:
    küszöb = 1, erősség = 0,3
    csak az világít, aminek a számítás után 1 fölötti a fényereje</code></pre>
<p>A háttér és a gombok színe 0 és 1 között van, nem lépik át a küszöböt, és élesek maradnak. Ezzel a kifakult ikonok problémája is megszűnik. Feltétel a HDR-rel rendelkező kamera. Enélkül a szín 1-re vágódik, és semmi sem világít. A 2D renderer nem mutatja a HDR kapcsolót a kamera inspectorában, ezért kapcsolja be szkriptből.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Alacsony küszöb, teljes kép</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="Teljes képernyős glow: világít a vonal, a gomb széle és a háttér világos részei" width="1440" height="840" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">1-es küszöb, HDR anyag</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="Szelektív glow: csak a megrajzolt vonal világít, a gomb és a háttér éles" width="1440" height="840" loading="lazy" decoding="async" /></picture></div></div><figcaption>Az elv illusztrációja. Balra teljes képernyős glow alacsony küszöbbel, jobbra csak az 1 fölötti intenzitású anyag világít.</figcaption></figure>
<p>Ha a glow így kézben van, nem kell opcionálisnak lennie. A Dreamicában megszüntettük a glow kapcsolóját a beállításokban, a glow a megjelenés része.</p>
<h3 id="emisszios-terkep-a-sprite-nak-csak-egy-resze-vilagit"><a class="heading-anchor" href="#emisszios-terkep-a-sprite-nak-csak-egy-resze-vilagit" aria-hidden="true" tabindex="-1">#</a>Emissziós térkép: a sprite-nak csak egy része világít</h3>
<p>Ha a sprite-nak csak egy része világítson, adjon neki emissziós térképet (emission map): egy második, fekete-fehér textúrát, amely megjelöli, a sprite melyik része világít. Csak ez a rész kap 1 fölötti színt, így a rúnán a mag ragyog, nem a teljes körvonal.</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="Egy rúna, a fekete-fehér emissziós térképe és az eredmény világító maggal" width="1800" height="600" loading="lazy" decoding="async" /></picture><figcaption>A rúna sprite-ja, az emissziós térkép (a fehér világít) és az eredmény a Bloom után.</figcaption></figure>
<h3 id="luktetes-expozicios-lepcsokben"><a class="heading-anchor" href="#luktetes-expozicios-lepcsokben" aria-hidden="true" tabindex="-1">#</a>Lüktetés expozíciós lépcsőkben</h3>
<p>Ha a ragyogás lüktessen, ne szorozza a színt lineárisan. Egy HDR anyag fényereje 2 a nyolcadikon körül lehet, és rajta egy 0 és 10 közötti lineáris szorzó egyáltalán nem látszik. A lüktetést expozíciós lépcsőkben (EV) számolja, ugyanúgy, mint a Unity HDR színválasztójának intenzitáscsúszkája:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">szín = árnyalat × 2 ^ ev,   ev egy minimum és egy maximum között ingadozik</code></pre>
<h2 id="3-lepes-dinamikus-2d-feny-a-jatekos-korul"><a class="heading-anchor" href="#3-lepes-dinamikus-2d-feny-a-jatekos-korul" aria-hidden="true" tabindex="-1">#</a>3. lépés: Dinamikus 2D fény a játékos körül</h2>
<p>Tegyen a játékosra egy 2D pontfényt, köré pedig lágy ragyogást. A Dreamicában egyetlen szinuszgörbe vezérli egyszerre a ragyogás fényerejét, a fény sugarát és intenzitását, így együtt lüktetnek. A fény színét a környezet is meghatározhatja: az erdőben fehér, a mocsárban narancssárga, télen lila, a pokolban sárga. A hátteret és az akadályokat állítsa megvilágított shaderekre, különben nem reagálnak a fényre.</p>
<p>A rövid fények életet visznek az eseményekbe. Amikor az ágyús mókus lő, felvillan egy 3-as intenzitású borostyánszínű fény, amely 0,3 másodperc alatt kialszik, és a csőből kiáramló szikrák világítanak. Nem kell mindennek világítania: a makk eltűnésekor keletkező por szándékosan barna, és nem világít.</p>
<h3 id="ugyanaz-a-shader-az-egymashoz-illeszkedo-feluleteken"><a class="heading-anchor" href="#ugyanaz-a-shader-az-egymashoz-illeszkedo-feluleteken" aria-hidden="true" tabindex="-1">#</a>Ugyanaz a shader az egymáshoz illeszkedő felületeken</h3>
<p>Ha két háttér illeszkedik egymáshoz, a forrásképek fényereje azonos, mégis látható köztük a varrat, ellenőrizze a shadereket. A Dreamicában a kezdő háttérnek és a végtelen erdei háttérnek más-más shadere volt. Az egyik figyelmen kívül hagyta az anyag 0,733-as árnyalatát, a másik alkalmazta, és 73 %-ra sötétítette az erdőt. A megoldás ugyanaz a shader mindkét felületen.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Két shader</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="Két erdei háttér határa, az alsó rész jóval sötétebb" width="1024" height="1120" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Egy 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="Ugyanaz a határ, mindkét háttér fényereje azonos" width="1024" height="1120" loading="lazy" decoding="async" /></picture></div></div><figcaption>A kezdő háttér és az erdő határa. Balra az alsó részt sötétíti a 0,733-as árnyalat, amelyet a felső shader figyelmen kívül hagy.</figcaption></figure>
<h3 id="felvillanas-attunes-a-csucsra-hozzaadas-helyett"><a class="heading-anchor" href="#felvillanas-attunes-a-csucsra-hozzaadas-helyett" aria-hidden="true" tabindex="-1">#</a>Felvillanás: áttűnés a csúcsra hozzáadás helyett</h3>
<p>A következő világba lépéskor a fény és a ragyogás fellobban: 0,6 másodperc felfutás, rövid kitartás, 1,4 másodperc lecsengés, közben a ragyogás egyszer körbefordul. Ha a felvillanást egyszerűen hozzáadja a lüktetéshez, a csúcs attól függ, épp melyik fázisban van a szinuszgörbe, és minden felvillanás más erősségű. Ehelyett tűnjön át folyamatosan a lüktetésből egy rögzített csúcsra:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">súly = felvillanási görbe (0-tól 1-ig)
érték = lüktetés + (csúcs - lüktetés) × súly</code></pre>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Hozzáadás</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="Intenzitásgrafikon: lüktetés és két eltérő magasságú csúccsal rendelkező villanás" width="1440" height="720" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Áttűnés a csúcsra</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="Intenzitásgrafikon: lüktetés és két villanás azonos csúccsal a pontozott vonalon" width="1440" height="720" loading="lazy" decoding="async" /></picture></div></div><figcaption>Két villanás a lüktetés különböző fázisában (narancssárga és kék). Balra a csúcsok eltérnek, jobbra mindkettő ugyanazt az értéket éri el.</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">Próbálja ki</p><h2 class="app-box-title">Dreamica</h2><p>Kézzel rajzolt zuhanós kaland, amelyben fizikai vonalakat rajzolsz egy varázsbuborék irányításához. Fejlesztési jegyzetek és újdonságok.</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="4-lepes-eles-ui-a-mai-kijelzokre"><a class="heading-anchor" href="#4-lepes-eles-ui-a-mai-kijelzokre" aria-hidden="true" tabindex="-1">#</a>4. lépés: Éles UI a mai kijelzőkre</h2>
<p>Az iPhone 7-re rajzolt UI sprite-jai kicsik. A Dreamica menügombjának sprite-ja 128 × 64 pixeles volt, és háromszoros pixelsűrűségű telefonon többszörösére nyúlt. Rajzolja újra a sprite-okat nagyobb felbontásban: az új gomb 800 × 434-es, a haladásjelzők 200 × 26-ról 800 × 104-re nőttek.</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="Egy gomb elmosódott szélének közeli képe" 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="Egy gomb éles szélének közeli képe látható textúrával" width="1120" height="608" loading="lazy" decoding="async" /></picture></div></div><figcaption>Ugyanaz a gomb ugyanakkora méretben a kijelzőn. Balra az eredeti sprite, jobbra az új.</figcaption></figure>
<p>Ha a UI NGUI-ra épül, fontolja meg az újraírást. Az NGUI horgonyai darabonként, több képkockán át számolódnak ki, a hosszú horgonyláncok néha rossz helyen állnak be, a méretarány pedig eltér az Editor, a telefon és az iPad között. A Dreamicában a javítások ezt nem tették megbízhatóvá, ezért a UI-t újraírtuk uGUI-ra és TextMeshPro-ra. Haladjon képernyőről képernyőre, a régi verziót pedig tartsa mellette futva mintaként. A UI-t a magasság szerint skálázza egy referenciafelbontáshoz képest (nálunk 434 × 941 pont), és vegye figyelembe a kijelző biztonságos területét, vagyis a kivágást és a lekerekített sarkokat. Az újraírás után távolítsa el teljesen az NGUI-t a projektből, hogy ne maradjon benne két UI-rendszer.</p>
<h2 id="korlatok-tomoritetlen-texturak"><a class="heading-anchor" href="#korlatok-tomoritetlen-texturak" aria-hidden="true" tabindex="-1">#</a>Korlátok: tömörítetlen textúrák</h2>
<p>Ez a folyamat csak a grafikát változtatja meg. A Dreamicában szándékosan nem tömörítettük a textúrákat, mert teljes élességet akartunk. Az ára a méret: már az eredeti játéknak is 313 MB textúrája volt a memóriában az Editorban, az App Store-ban pedig 299 MB. Becslésünk szerint a tömörítés nagyjából 40 MB-ra csökkentené a textúrákat. Ha fontos a játék mérete, itt spóroljon először.</p>
<p>Egy másik Unity-játékot az első kódsortól a kiadásig a <a href="https://smallwater.eu/hu/blog/shmup-apple-watch-devlog/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/dreamica-remaster-unity-urp-glow/" data-ga-destination="/hu/blog/shmup-apple-watch-devlog/" data-ga-placement="inline">Space Impact Watch</a> cikkünk mutat be, a Unity teljesítményéről pedig a <a href="https://smallwater.eu/hu/blog/unity-dots-horde-navigation/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/dreamica-remaster-unity-urp-glow/" data-ga-destination="/hu/blog/unity-dots-horde-navigation/" data-ga-placement="inline">horda-navigáció Unity DOTS-ban</a> cikk szól. Ha a játék még Unity 4-ben készült, ne nyissa meg közvetlenül Unity 6-ban; a biztonságos utat az írja le, <a href="https://smallwater.eu/hu/blog/unity-4-to-unity-6-upgrade/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/dreamica-remaster-unity-urp-glow/" data-ga-destination="/hu/blog/unity-4-to-unity-6-upgrade/" data-ga-placement="inline">hogyan vigyen át egy Unity 4-es projektet Unity 6-ra</a>. Ha van egy régi Unity-játéka, amely kikerült az áruházakból, és szeretné visszahozni, <a href="https://smallwater.eu/hu/#kontakt">vegye fel velünk a kapcsolatot</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hogyan készítsen fake ragdollt ezernyi alakra Unity DOTS-ban</title>
      <link>https://smallwater.eu/hu/blog/fake-ragdoll-unity-dots/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/fake-ragdoll-unity-dots/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Tower Defense</category>
      <description>Hogyan hajítson el ezernyi ellenfelet robbanással Unity DOTS-ban csontfizika nélkül: test egy pontként, determinisztikus kilövés, járható landolás, forgás.</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="Robbanás által elhajított alakok íveken repülnek és pörögnek a levegőben" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#miert-nem-igazi-ragdoll" data-ga="blog_toc_clicked" data-ga-heading-id="miert-nem-igazi-ragdoll">Miért nem igazi ragdoll</a></li><li><a href="#a-test-mint-egyetlen-pont" data-ga="blog_toc_clicked" data-ga-heading-id="a-test-mint-egyetlen-pont">A test mint egyetlen pont</a></li><li><a href="#a-ledontott-ellenfel-fazisai" data-ga="blog_toc_clicked" data-ga-heading-id="a-ledontott-ellenfel-fazisai">A ledöntött ellenfél fázisai</a></li><li><a href="#kiloves-magasabbra-nem-messzebbre" data-ga="blog_toc_clicked" data-ga-heading-id="kiloves-magasabbra-nem-messzebbre">Kilövés: magasabbra, nem messzebbre</a></li><li><a href="#landolas-jarhato-cellan" data-ga="blog_toc_clicked" data-ga-heading-id="landolas-jarhato-cellan">Landolás járható cellán</a><ol><li><a href="#a-levegoben-falakrol-visszapattanas-nem-eleg" data-ga="blog_toc_clicked" data-ga-heading-id="a-levegoben-falakrol-visszapattanas-nem-eleg">A levegőben falakról visszapattanás nem elég</a></li><li><a href="#palyaelorejelzes-a-kiloveskor" data-ga="blog_toc_clicked" data-ga-heading-id="palyaelorejelzes-a-kiloveskor">Pályaelőrejelzés a kilövéskor</a></li></ol></li><li><a href="#a-repulesi-magassagbol-tervezett-forgas" data-ga="blog_toc_clicked" data-ga-heading-id="a-repulesi-magassagbol-tervezett-forgas">A repülési magasságból tervezett forgás</a></li><li><a href="#a-modell-a-talajra-helyezese" data-ga="blog_toc_clicked" data-ga-heading-id="a-modell-a-talajra-helyezese">A modell a talajra helyezése</a></li><li><a href="#halott-ellenfelek-test-decal-es-por" data-ga="blog_toc_clicked" data-ga-heading-id="halott-ellenfelek-test-decal-es-por">Halott ellenfelek: test, decal és por</a></li><li><a href="#csapdak-a-unity-entitiesben" data-ga="blog_toc_clicked" data-ga-heading-id="csapdak-a-unity-entitiesben">Csapdák a Unity Entitiesben</a></li><li><a href="#teljesitmeny" data-ga="blog_toc_clicked" data-ga-heading-id="teljesitmeny">Teljesítmény</a></li><li><a href="#a-megoldas-korlatai" data-ga="blog_toc_clicked" data-ga-heading-id="a-megoldas-korlatai">A megoldás korlátai</a></li></ol></nav><p>Ha egy robbanás a játékban egyszerre több száz ellenfelet repít el, mindegyiküknek igazi ragdollt adni túl drága. Fake ragdollt Unity DOTS-ban úgy készít, hogy az ellenfél teljes testét egyetlen pontként szimulálja saját egyszerű fizikával, a modellt pedig csak a test középpontja körül forgatja. Az esés és a felállás közönséges animációs klipek, csontokat egyáltalán nem szimulálunk.</p>
<p>A módszert egy PC-s tower defense játékon mutatjuk be, amelyet Unityben, ECS-sel fejlesztünk (<a href="https://smallwater.eu/hu/#referencie">bővebben a referenciáink között</a>). Egy bomba vagy fireball ott egyszerre több tucat, akár több száz ellenfelet repít el. Ha 2000 test van a levegőben, a szimuláció tickenként 0,53-0,59 ms-ba kerül (a tick a játékszimuláció egy rögzített hosszúságú lépése).</p>
<h2 id="miert-nem-igazi-ragdoll"><a class="heading-anchor" href="#miert-nem-igazi-ragdoll" aria-hidden="true" tabindex="-1">#</a>Miért nem igazi ragdoll</h2>
<p>Egy igazi ragdoll karakterenként tíz vagy több, ízületekkel összekötött fizikai test. Több ezer karakternél a képernyőn ez nem reális.</p>
<p>A másik ok a determinizmus: ugyanabból az állapotból ugyanannak az eredménynek kell kijönnie. A mi játékunkban erre épül egy elvesztett hullám újrajátszása egy mentési pontból, és ezt a szimuláció állapotának hashével ellenőrizzük 1800 tick után.</p>
<h2 id="a-test-mint-egyetlen-pont"><a class="heading-anchor" href="#a-test-mint-egyetlen-pont" aria-hidden="true" tabindex="-1">#</a>A test mint egyetlen pont</h2>
<p>Fizikai motor helyett használjon saját, egyszerű 2,5D fizikát: pozíció a rácson, magasság a talaj felett, ballisztikus repülés, visszapattanás a falakról és súrlódás. A mi játékunkban a bomba is így mozog, és a robbanás által eltalált ellenfél egy pillanatra ugyanolyan testté válik. Az ötletet az adta, ahogyan a katonák repülnek a <a href="https://store.steampowered.com/app/1272320/Diplomacy_is_Not_an_Option/" target="_blank" rel="noopener">Diplomacy is Not an Option</a> című játékban.</p>
<p>A szimuláció csak a pozíciót, a magasságot és az elforgatás szögét ismeri. A modell forgatását ne írja bele. Az az entitás kiegészítő transzformációs mátrixába tartozik, amelyet az Entities csomag (ECS a Unityben) a nem egyenletes skálázáshoz támogat (<a href="https://docs.unity3d.com/Packages/com.unity.entities@1.3/api/Unity.Transforms.PostTransformMatrix.html" target="_blank" rel="noopener">dokumentáció</a>).</p>
<h2 id="a-ledontott-ellenfel-fazisai"><a class="heading-anchor" href="#a-ledontott-ellenfel-fazisai" aria-hidden="true" tabindex="-1">#</a>A ledöntött ellenfél fázisai</h2>
<p>A ledöntött ellenfél három fázison megy át, aztán visszatér a tömegbe:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">találatkor:
    számold ki a kilövési sebességet (erő, távolság a robbanástól, véletlen a seedből)
    keress landolást járható cellán (lásd lent)
    kapcsold be a &quot;ledöntve&quot; állapotot, szabadítsd fel a helyét a támadás célpontjánál
repülés:   a test parabolán repül, a modell a test középpontja körül forog
landolás:  testhelyzet a szögből (hát vagy has), rövid ráborulás
fekvés:    rögzített számú ticket vár
felállás:  felállási klip hátról vagy hasról, aztán vissza a tömegbe</code></pre>
<p>A „ledöntve” állapotot olyan komponensként készítse el, amely ki- és bekapcsolható anélkül, hogy az entitást chunkok között kellene mozgatni (a chunk az a memóriablokk, amelyben az ECS az azonos komponenskészletű entitásokat tárolja). A navigáció, a tömegben való taszítás és a támadási döntés így a ledöntötteket egyszerűen kihagyja. Hogy hogyan működik a navigáció és a taszítás nagy tömegben, azt a <a href="https://smallwater.eu/hu/blog/unity-dots-horde-navigation/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/fake-ragdoll-unity-dots/" data-ga-destination="/hu/blog/unity-dots-horde-navigation/" data-ga-placement="inline">horda navigációjáról szóló cikk Unity DOTS-ban</a> írja le.</p>
<h2 id="kiloves-magasabbra-nem-messzebbre"><a class="heading-anchor" href="#kiloves-magasabbra-nem-messzebbre" aria-hidden="true" tabindex="-1">#</a>Kilövés: magasabbra, nem messzebbre</h2>
<p>A legegyszerűbb kilövés 45° ± 10° szögben történik. 18 m/s bombaerőnél és 20 m/s² gravitációnál a repülés csúcspontja körülbelül 4 m. A test alacsonyan repül el a tömeg felett, és a hatás elvész benne.</p>
<p>Nagyobb erő nem segít, mert vele együtt nő a távolság is. Bontsa a sebességet két összetevőre, és mindegyiket külön szorozza: 60° ± 5° szög, vízszintes összetevő × 0,85, függőleges × 1,3. A csúcspont így körülbelül 10 m, a távolság marad nagyjából 15,5 m, a repülés pedig körülbelül 2 másodpercig tart.</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="Oldalnézet: egy alacsony szaggatott ív és egy magas ív egy ház felett, a repülés közben forgó alak sziluettjeivel" width="2200" height="1520" loading="lazy" decoding="async" /></picture><figcaption>Szaggatottan a 45°-os kilövés, folytonos vonallal a 60°-os, külön szorzókkal. A ház 5 m magas.</figcaption></figure>
<p>Az irány ± 15°-kal tér el a robbanástól, az erő ± 20%-kal, a robbanás szélén pedig a felére csökken. A véletlent ne globális generátorból vegye, hanem a pálya seedjének, az ellenfél azonosítójának és a robbanás sorszámának hashéből. A ledöntési kéréseket rendezve dolgozza fel, mert a több szálon futó jobok tetszőleges sorrendben hozzák létre őket. Ugyanannak a hullámnak két futása így ugyanazokat az íveket adja.</p>
<p>A ledöntés igen vagy nem. Minden ledönthető ellenfél találatkor legalább elesik (a sebesség alulról 2 m/s-ra korlátozott), az ellenállás csak a távolságot változtatja. Az immunitást külön jelzővel adja meg, a mi játékunkban a legnagyobb ellenféltípusnak van ilyen. Ha az immunitás az ellenállásból következne (0,95-nél a sebesség 2 m/s alá esik), és a gyenge elhajítás törlődne, az ellenfél csak véletlenül lenne immunis.</p>
<h2 id="landolas-jarhato-cellan"><a class="heading-anchor" href="#landolas-jarhato-cellan" aria-hidden="true" tabindex="-1">#</a>Landolás járható cellán</h2>
<p>Az ellenfél nem landolhat szakadékban, sziklán vagy egy torony alapterületén. A járhatóságot rácscellánként és minden ellenfél-méretosztályra külön határozzuk meg.</p>
<h3 id="a-levegoben-falakrol-visszapattanas-nem-eleg"><a class="heading-anchor" href="#a-levegoben-falakrol-visszapattanas-nem-eleg" aria-hidden="true" tabindex="-1">#</a>A levegőben falakról visszapattanás nem elég</h3>
<p>Kézenfekvő ötlet a nem járható cellákat falként kezelni, amelyekről a repülő test visszapattan. Sziklánál ez természetesnek tűnik, szakadéknál nem: a szakadék felett levegő van, de a cella nem járható, így a test egy láthatatlan oszlopról pattan vissza a széle felett. Ráadásul semmi sem garantálja, hogy a visszapattanás után jó helyen ér földet.</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="Oldalnézet: egy test átrepül egy szakadék felett, repülés közben visszapattan egy láthatatlan oszlopról a széle felett, és visszaesik" width="2200" height="1640" loading="lazy" decoding="async" /></picture><figcaption>Visszapattanás nem járható celláról. A test a szakadék széle feletti levegőről pattan vissza.</figcaption></figure>
<h3 id="palyaelorejelzes-a-kiloveskor"><a class="heading-anchor" href="#palyaelorejelzes-a-kiloveskor" aria-hidden="true" tabindex="-1">#</a>Pályaelőrejelzés a kilövéskor</h3>
<p>Megbízhatóbb a landolást már a kilövéskor meghatározni. Szimulálja le előre a repülést ugyanazzal a lépéssel, mint a valódi fizikát, és ellenőrizze a landolási cellát az ellenfél méretosztályára. Ha nem járható, próbáljon további változatokat rögzített sorrendben:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">a szögekre [0, +25, -25, +50, -50, ... , +175, -175]:
    a vízszintes sebesség szorzóira [1, 0.7, 0.4, 0.1]:
        szimuláld a repülést
        ha a landolási cella járható az ellenfél osztályának: használd ezt az irányt, és vége
ha semmi sem jött be: vízszintes sebesség = 0 (helyben ugrás)</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="Felülnézet egy rácsra szakadékkal és kiemelkedő sziklával, áthúzott elvetett landolásokkal és kiemelt első járható landolással" width="2200" height="1400" loading="lazy" decoding="async" /></picture><figcaption>Robbanás, ellenfél, elvetett landolások és a kiválasztott. A szürke pontok az összes jelölt landolási hely.</figcaption></figure>
<p>A legrosszabb eset 60 repülésszimuláció ellenfelenként (15 irány × 4 szorzó). A függőleges sebesség nem változik, így a repülés magassága ugyanaz marad, csak a távolság rövidül. Ha repülés közben újabb robbanás éri az ellenfelet, futtassa újra az előrejelzést.</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="2D illusztráció: két ellenfél a szakadékba esne (keresztek), az előrejelzés lerövidíti a repülésüket."><source src="https://smallwater.eu/blog/fake-ragdoll-unity-dots/ragdoll-loop.mp4" type="video/mp4" /></video><figcaption>2D illusztráció: két ellenfél a szakadékba esne (keresztek), az előrejelzés lerövidíti a repülésüket.</figcaption></figure>
<p>Hogy a test landolás után ne csússzon és ne pattanjon, adjon neki 0 talajvisszapattanási együtthatót és nagy súrlódást. Ott marad, ahol az előrejelzés mondta.</p>
<h2 id="a-repulesi-magassagbol-tervezett-forgas"><a class="heading-anchor" href="#a-repulesi-magassagbol-tervezett-forgas" aria-hidden="true" tabindex="-1">#</a>A repülési magasságból tervezett forgás</h2>
<p>A legegyszerűbb a modellt véletlen sebességgel forgatni, például 180-540 °/s-mal, és landolás után 0,2 s alatt a legközelebbi fekvő helyzetbe billenteni. A billentés azonban látszik, ha a test szinte függőlegesen ér földet. Ha a forgási sebességet minden tickben úgy igazítja, hogy a szög landoláskor pontosan vízszintes legyen, az csak részben segít: még egy alacsony dobás is szaltóként pörög, és a forgásnak semmi köze ahhoz, milyen magasan repül a test.</p>
<p>Jobb a forgást a kilövéskor a repülési magasságból megtervezni:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">magasság = vz² / (2 g), repülési idő vz-ből és a magasságból
cél = legközelebbi vízszintes helyzet véletlen irányban (negyed fordulat állásból)
cél += fél fordulat minden 2,5 m magasságért, legfeljebb 3
forgási sebesség = (cél - aktuális szög) / repülési idő</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="Két ív: egy alacsony dobás, amelynél az alak csak eldől, és egy magas, amelynél három és negyed fordulatot tesz" width="2200" height="1200" loading="lazy" decoding="async" /></picture><figcaption>Az alacsony dobás (csúcs kb. 2 m) 90°-ot fordul, a bomba (kb. 10 m) 90°-ot és három fél fordulatot.</figcaption></figure>
<p>A landolás oldala a szögből következik: arccal a föld felé has, egyébként hát. Ez alapján választódik ki a felállási klip. A fekvő vagy felálló ellenfél újabb találatkor megtartja aktuális szögét, és nem ugrik vissza függőleges helyzetbe.</p>
<h2 id="a-modell-a-talajra-helyezese"><a class="heading-anchor" href="#a-modell-a-talajra-helyezese" aria-hidden="true" tabindex="-1">#</a>A modell a talajra helyezése</h2>
<p>A modell a test középpontja körül forog, a talaj viszont a legalsó pontja alatt van, és ez a forgással változik. Az eltolás becslése két konstansból és a szög szinuszából nem illik azokhoz a modellekhez, amelyek az esési klipben szélesre tárják a karjukat: a mi játékunkban a csontváz modellje (gyakori ellenfél) akár 1,15 m-re, a nekromantáé akár 1,73 m-re süllyedt a föld alá.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Becslés</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="Egy alak hat elforgatása, a föld alatti részek pirosak" width="2200" height="840" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Mért</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="Egy alak hat elforgatása, mindegyik a legalsó pontjával érinti a földet" width="2200" height="840" loading="lazy" decoding="async" /></picture></div></div><figcaption>Ugyanazok az elforgatások a konstansokból és a mért táblázatból számolt eltolással.</figcaption></figure>
<p>Megbízhatóbb a legalsó pontot megmérni. Az animációk sütésekor (bake) számolja ki az esési klipre a test legalsó pontját 72 elforgatásnál 5°-os lépésekben, futás közben pedig interpoláljon a táblázatból. A legmélyebb süllyedés így 4 mm-re csökken. Egyetlen minimum a klip összes képkockáján át azonban nem elég: a test landolás után 0,15-0,28 m-rel a talaj felett lebegne, amíg a felállási klip le nem húzza. A táblázatnak ezért képkockák × elforgatások méretűnek kell lennie.</p>
<p>A lassú, tervezett forgásnál még egy hatás előjön. Ha a modellt a legalsó pontjával a repülési magasságra teszi, a test középpontja az állásból vízszintesbe fordulás közben a fél magasság és a fél vastagság különbségével lesüllyed. A test ekkor a repülés elején csak lassan emelkedik, és csak utána gyorsul. Emelje meg ezért a modellt az állásbeli és az aktuális szögnél mért fél kiterjedés különbségével, legfeljebb a repülési magassággal. A test középpontja így parabolán halad.</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="A test középpontjának magassága az idő függvényében: szaggatott ideális parabola, egy behorpadó piros görbe és egy kék, amely követi a parabolát" width="2200" height="1040" loading="lazy" decoding="async" /></picture><figcaption>A test középpontjának magassága repülés közben: emelés nélkül (piros) és emeléssel (kék).</figcaption></figure>
<h2 id="halott-ellenfelek-test-decal-es-por"><a class="heading-anchor" href="#halott-ellenfelek-test-decal-es-por" aria-hidden="true" tabindex="-1">#</a>Halott ellenfelek: test, decal és por</h2>
<p>A robbanásban meghalt ellenfélnek is el kell repülnie. Hozzon létre számára repülő testet: az entitás másolatát játékmenet-komponensek nélkül (életerő, jutalom, célpont). A test elrepül, földet ér, egy ideig fekszik, majd eltűnik. A helyén holttest-decal (a talajra vetített textúra) marad, mint a Warcraft 3-ban, és egy kis porfelhő. A mi játékunkban a felső határ 1000 repülő test és 1500 decal.</p>
<p>A decal felülnézet az esési klip egy képkockájáról, ugyanúgy elforgatva, mint a fekvő test, így a test decalra cserélése nem látszik. A felállási klip első képkockája nem jó, mert azon az alak a hátáról már felül. A porfelhőket vonja össze 1,5 m-es cellán belül. Egy bomba a tömegbe így néhány nagyobb felhőt hoz létre több száz apró helyett.</p>
<h2 id="csapdak-a-unity-entitiesben"><a class="heading-anchor" href="#csapdak-a-unity-entitiesben" aria-hidden="true" tabindex="-1">#</a>Csapdák a Unity Entitiesben</h2>
<ul>
<li><strong>A bakerből származó mátrix eltűnik.</strong> Ha a kiegészítő mátrixot bake közben adja hozzá, a transzformációs rendszer egyenletes skálánál eltávolítja, és a ledöntött ellenfelek nem mozognak. Bake közben ezért kérjen nem egyenletes skálát. Ekkor viszont a prefab teljes skálája ugyanabba a mátrixba kerül, tehát ne írja felül puszta forgatással. Különben egy 2×-es méretű ledöntött ellenfél fele akkora lesz, felállás után is. A skálát tárolja külön, a mátrixot pedig skálából és forgatásból állítsa össze.</li>
<li><strong>A gyökér magassága a klipek importjakor.</strong> Humanoid klipeknél a gyökér (root) függőleges mozgása elvész, és a karakterek felálláskor átesnek a padlón. Süsse bele a gyökér magasságát a pózba. A póz lábfejhez rögzítésének opcióját ne használja, az egy méterrel lejjebb tolja az egész testet.</li>
<li><strong>Zárt alakú mátrix.</strong> Az eltolás, a forgatás és a visszatolás mint három mátrixszorzás egy jobban mérhetően lassabb, mint az eredő mátrix kézzel megírt képlete.</li>
<li><strong>Burst régi verzióval.</strong> Ha módosítja egy Burst job által olvasott komponens mezőit, az editor összeomolhat vagy értelmetlen hibákat jelezhet. Az editor újraindítása segít.</li>
</ul>
<h2 id="teljesitmeny"><a class="heading-anchor" href="#teljesitmeny" aria-hidden="true" tabindex="-1">#</a>Teljesítmény</h2>
<p>Mérve a Unity Editorban M3 Prón, 10 000 csontvázzal egy benchmark jelenetben, 18-as kilövési erővel. Az idők a Burst jobok CPU-idejének összegei az összes szálon, nem a képkocka ideje. A grafikon és a táblázat pályaelőrejelzés és landolási táblázatok nélkül készült.</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="A tickenkénti idő grafikonja a ledöntött ellenfelek száma szerint a szimulációra és a vizuális előkészítésre, 0,5 ms-os célvonallal" width="2200" height="1120" loading="lazy" decoding="async" /></picture><figcaption>Szimuláció és vizuális előkészítés pályaelőrejelzés nélkül. Cél 0,5 ms 2000 ledöntöttnél.</figcaption></figure>
<div class="table-wrap"><table>
<thead>
<tr>
<th>ledöntött</th>
<th>szimuláció (ms/tick)</th>
<th>vizuális előkészítés (ms)</th>
</tr>
</thead>
<tbody><tr>
<td>500</td>
<td>0,28-0,30</td>
<td>0,15</td>
</tr>
<tr>
<td>2000</td>
<td>0,53</td>
<td>0,30-0,55</td>
</tr>
<tr>
<td>5000</td>
<td>0,74-0,75</td>
<td>0,49-0,91</td>
</tr>
<tr>
<td>10 000</td>
<td>0,96-1,01</td>
<td>0,68-1,24</td>
</tr>
</tbody></table>
</div>
<ul>
<li>A szimuláció lineárisan nő, ezer ledöntöttenként nagyjából 0,25 ms-mal. Pályaelőrejelzéssel és landolási táblázatokkal 2000 ledöntöttnél 0,58-0,59 ms, vagyis kicsivel a 0,5 ms-os cél felett. Észszerű felső határ ezért egyszerre 3000 ledöntött ellenfél, ami körülbelül 0,8 ms.</li>
<li>Egy robbanás egy fal melletti 359 fős csoportba a ledöntési kérések rendszerében egyetlen képkockában 1,3-1,9 ms-ba kerül. Ennek nagy részét a pályaelőrejelzés teszi ki.</li>
<li>Az editor FPS-értékei nem hasonlíthatók össze: ugyanannak a konfigurációnak két futása akár 80 FPS-sel is eltért.</li>
</ul>
<h2 id="a-megoldas-korlatai"><a class="heading-anchor" href="#a-megoldas-korlatai" aria-hidden="true" tabindex="-1">#</a>A megoldás korlátai</h2>
<p>A vizuális előkészítés egy másik tesztjelenetben akár 1,3 ms-ra is nőtt, valószínűleg a landolási táblázat minden testnél történő olvasása miatt. Ez az első jelölt az optimalizálásra. Az FPS-t buildben és 1000 ellenfél egyidejű megölésének terhelését (cél: legfeljebb 1,5 ms többlet képkockánként) még nem mértük.</p>
<p>Ha hasonló tömeg- vagy effektszimuláción dolgozik Unityben, és segítségre van szüksége, <a href="https://smallwater.eu/hu/#kontakt">írjon nekünk</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hogyan készítsünk shmupot Apple Watchra</title>
      <link>https://smallwater.eu/hu/blog/shmup-apple-watch-devlog/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/shmup-apple-watch-devlog/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Space Impact Watch</category>
      <description>Útmutató shmuphoz Apple Watchra: virtuális felbontás, irányítás a koronával, téglalapos ütközés, pooling, időtáblás pályák és hullámgenerátor.</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="Pixeles jelenet zöld kijelzőn: bal oldalt egy hajó lő a jobb oldali üstökösökre, rakétákra és egy főellenségre" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#rogzitett-84-48-as-virtualis-felbontas" data-ga="blog_toc_clicked" data-ga-heading-id="rogzitett-84-48-as-virtualis-felbontas">Rögzített 84 × 48-as virtuális felbontás</a></li><li><a href="#iranyitas-a-koronaval-es-automatikus-tuzeles" data-ga="blog_toc_clicked" data-ga-heading-id="iranyitas-a-koronaval-es-automatikus-tuzeles">Irányítás a koronával és automatikus tüzelés</a></li><li><a href="#game-loop-egyetlen-jelenetben" data-ga="blog_toc_clicked" data-ga-heading-id="game-loop-egyetlen-jelenetben">Game loop egyetlen jelenetben</a></li><li><a href="#teglalapos-utkozes-fizikai-motor-helyett" data-ga="blog_toc_clicked" data-ga-heading-id="teglalapos-utkozes-fizikai-motor-helyett">Téglalapos ütközés fizikai motor helyett</a></li><li><a href="#pooling-csak-ott-ahol-megeri" data-ga="blog_toc_clicked" data-ga-heading-id="pooling-csak-ott-ahol-megeri">Pooling csak ott, ahol megéri</a></li><li><a href="#palyak-idotablakent" data-ga="blog_toc_clicked" data-ga-heading-id="palyak-idotablakent">Pályák időtáblaként</a></li><li><a href="#vegtelen-mod-egyetlen-alveletlen-szambol" data-ga="blog_toc_clicked" data-ga-heading-id="vegtelen-mod-egyetlen-alveletlen-szambol">Végtelen mód egyetlen álvéletlen számból</a></li><li><a href="#a-megkozelites-korlatai" data-ga="blog_toc_clicked" data-ga-heading-id="a-megkozelites-korlatai">A megközelítés korlátai</a></li></ol></nav><p>Egy shmup (shoot &#39;em up, lövöldözős játék, amelyben a hajó ellenséghullámok ellen repül) Apple Watchon három akadályba ütközik: kicsi a kijelző, az ujj eltakarja a játék felét, és az órák különböző méretűek. Ezt három döntés oldja meg: az egész játék egy rögzített 84 × 48 pontos rácson fut, az ütközéseket kézzel, téglalapokkal vizsgáljuk, a pályák pedig időpontokat tartalmazó táblázatok, nem kód. A hajót a Digital Crown korona irányítja, és magától lő.</p>
<p>A példák a <a href="https://smallwater.eu/space-impact-watch">Space Impact Watch</a> játékból származnak, amely a Nokia 3310 telefonon 2000-ben megjelent Space Impact lövöldözős játék remake-je, és az órán kívül iPhone-on és iPaden is fut. A játék a SpriteKitre, az Apple 2D-s játékkeretrendszerére épül, de az elvek bármilyen kis képernyős shmupra érvényesek.</p>
<h2 id="rogzitett-84-48-as-virtualis-felbontas"><a class="heading-anchor" href="#rogzitett-84-48-as-virtualis-felbontas" aria-hidden="true" tabindex="-1">#</a>Rögzített 84 × 48-as virtuális felbontás</h2>
<p>Először döntse el, milyen egységekben számol a játék. Ha képernyőpontokat használ, a világ minden óraméreten másképp lesz nagy. Jobb egy kis virtuális felbontást választani, és mindent abban számolni. A mi játékunkban ez 84 × 48, pontosan akkora, mint a <a href="https://www.adafruit.com/products/338" target="_blank" rel="noopener">Nokia 3310 kijelzője</a> volt. A pozíciók, sebességek, sprite-méretek és az ellenségek útvonalai ezekben az egységekben vannak megadva. A jelenet indulásakor egyszer kiszámoljuk, hány képernyőpont egy virtuális pixel (a jelenet szélessége osztva 84-gyel), és mindent csak ezzel szorzunk.</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="Játékjelenet látható 84 × 48 virtuális pixeles ráccsal" width="1008" height="576" loading="lazy" decoding="async" /></picture><figcaption>Egy pálya a 84 × 48-as rácson. Minden négyzet egy virtuális pixel, amelyet az óra több valódi pontból álló blokként rajzol ki.</figcaption></figure>
<p>Amit ezzel nyer:</p>
<ul>
<li><strong>Ugyanaz a megjelenés minden eszközön.</strong> A játék minden óraméreten és iPaden is ugyanúgy néz ki, csak a blokk mérete változik.</li>
<li><strong>Level design kis egész számokkal.</strong> Az ellenség 13-as magasságban repül, a rakéta az x = 20 pontban fordul.</li>
<li><strong>Egyszerű grafika.</strong> A sprite-ok egyszínűek, tízszeres méretben rajzolva, így egy virtuális pixel a forrásképen egy 10 × 10-es blokk. A színüket csak a játékban kapják meg: világos pályákon fekete a zöld háttéren, sötét pályákon fordítva.</li>
</ul>
<h2 id="iranyitas-a-koronaval-es-automatikus-tuzeles"><a class="heading-anchor" href="#iranyitas-a-koronaval-es-automatikus-tuzeles" aria-hidden="true" tabindex="-1">#</a>Irányítás a koronával és automatikus tüzelés</h2>
<p>Az órán az ujj eltakarja a játék felét, ezért a hajót nem érintéssel irányítjuk. A mozgást a Digital Crown végzi: a korona elforgatása fel vagy le mozdítja a hajót. Adjon hozzá érzékenységi beállítást és az irány megfordításának lehetőségét.</p>
<p>A tüzelés automatikus, a mi játékunkban 0,4 másodpercenként, a negyedik pályától 0,3 másodpercenként. A kijelző csak egyetlen gesztusra szolgál: jobbra húzással a játékos bevet egyet a három különleges fegyver közül.</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="Három különleges fegyver: függőleges vonal, célkövető rakéta és egy főellenséget eltaláló lézer" width="1104" height="240" loading="lazy" decoding="async" /></picture><figcaption>Három különleges fegyver: a játéktér teljes magasságán végigfutó vonal, célkövető rakéta és lézer.</figcaption></figure>
<p>Az órán a találatkor érkező rezgés ugyanolyan fontos, mint az animáció, egy kis robbanást a kijelzőn könnyű nem észrevenni. A haptikát egyetlen felületen keresztül hívja meg (találat, ellenség megsemmisítése, legyőzött főellenség), amelyet minden platform a maga módján valósít meg: az óra rezgéssel, az iPhone a saját módján. A telefonos verzióban nincs korona, helyette a kijelzőn lévő gombok vannak, ugyanazzal az érzékenységi és iránybeállítással.</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="Képernyőkép a játékról az Apple Watch szimulátorában, a hajó egy főellenséggel szemben" width="368" height="448" loading="lazy" decoding="async" /></picture><figcaption>A játék az Apple Watch szimulátorában: a hajó egy főellenséggel szemben.</figcaption></figure>
<h2 id="game-loop-egyetlen-jelenetben"><a class="heading-anchor" href="#game-loop-egyetlen-jelenetben" aria-hidden="true" tabindex="-1">#</a>Game loop egyetlen jelenetben</h2>
<p>Az egész játék lehet egyetlen jelenet egyetlen ciklussal:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">minden képkockában:
    mozgass minden objektumot
    vizsgáld az ütközéseket
    a pályán eltelt idő alapján adj hozzá új ellenségeket</code></pre>
<p>Az elemeket abban a sorrendben adja hozzá, amelyben a legkorábban tesztelhetők:</p>
<ul>
<li><strong>Lövés és a játékos mozgása.</strong> Nélkülük semmit sem lehet tesztelni.</li>
<li><strong>Ellenségek és ütközések.</strong> Az első ellenségnek elég egyenesen balra repülnie és egy találattól elpusztulnia.</li>
<li><strong>Rezgés és találati animációk.</strong> Nélkülük a kis kijelzőn a játékos nem tudja, hogy talált.</li>
<li><strong>Háttér és HUD.</strong> Életek, pontszám, a különleges fegyverek száma. Ha a HUD valamelyik pályán zavar, tegye alulra.</li>
</ul>
<p>A végén játszható ciklusa lesz életekkel, pontszámmal, robbanásokkal, háttérrel és a játékos respawnjával. Ha a játéklogikát külön tartja a megjelenítéstől és a bemenettől, az órás és a telefonos verzió közösen használhatja.</p>
<h2 id="teglalapos-utkozes-fizikai-motor-helyett"><a class="heading-anchor" href="#teglalapos-utkozes-fizikai-motor-helyett" aria-hidden="true" tabindex="-1">#</a>Téglalapos ütközés fizikai motor helyett</h2>
<p>A SpriteKitnek van fizikai motorja, és kézenfekvőnek tűnik az ütközésekhez is használni. Minden objektum kap egy sprite-ja körvonalából készült fizikai testet, pontos ütközésérzékelést és egy kategóriát (játékos, ellenség, lövedék, különleges fegyver), a motor pedig magától jelzi az egymáshoz ért párokat. Az Apple a <a href="https://developer.apple.com/documentation/spritekit/getting-started-with-physics-bodies" target="_blank" rel="noopener">fizikai testekről szóló dokumentációban</a> azt írja, hogy a textúra alfa-csatornájából készült testek a legpontosabbak, de egyben a legszámításigényesebbek is. A mi játékunkban kipróbáltuk, és visszatértünk a kézi vizsgálathoz.</p>
<p>A kézi vizsgálat egyetlen függvény, amely sorban végigmegy a csoportpárokon, és téglalapokat hasonlít össze:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">minden csoportpárra:
    (játékos és ellenségek, játékos és lövedékek, lövedékek és ellenségek, lövedékek és lövedékek)
    az első csoport minden A objektumára és a második minden B objektumára:
        ha az A téglalap fedi a B téglalapot:
            kezeld a találatot</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="Két sprite-pár bejelölt ütközési téglalapokkal, bal oldalt csak a téglalapok sarkai fedik egymást" width="960" height="352" loading="lazy" decoding="async" /></picture><figcaption>Balra: a téglalapok fedik egymást, a pixelek nem, a játék mégis találatnak veszi. Jobbra: valódi találat.</figcaption></figure>
<p>Az előnyök: a vizsgálatok sorrendje az Ön kezében van, és a teljes „mi történik találatkor” szabály egy helyen található. A pontatlanság kicsi, mert a 84 × 48-as rácson a sprite-ok olyan aprók, hogy a téglalap és a valódi körvonal csak néhány virtuális pixellel tér el.</p>
<h2 id="pooling-csak-ott-ahol-megeri"><a class="heading-anchor" href="#pooling-csak-ott-ahol-megeri" aria-hidden="true" tabindex="-1">#</a>Pooling csak ott, ahol megéri</h2>
<p>A pooling azt jelenti, hogy egy objektumot a megszűnése után nem töröl, hanem elrejti, és legközelebb újra felhasználja. Csábító mindent poolozni, a robbanásokat és a power-upokat is. Ennek azonban ára van: minden ütközésnek és minden frissítésnek először ellenőriznie kell, hogy az objektum egyáltalán él-e. Elég egy elfelejtett ellenőrzés, és egy rejtett ellenség megöli a játékost, vagy egy rejtett lövedék lelő egy rakétát. Amikor a mi játékunkban mindenhonnan eltávolítottuk a poolingot, főleg ezeket az ellenőrzéseket töröltük.</p>
<p>Egyszerű szabály: azt poolozza, amiből sok van, és ami mindig ugyanúgy jön létre. A játékos induláskor létrehoz 21 lövedéket, és utána csak újrahasznosítja őket. Az ellenségeket és lövedékeiket is újra felhasználjuk, a robbanások és a power-upok viszont normálisan jönnek létre és tűnnek el.</p>
<h2 id="palyak-idotablakent"><a class="heading-anchor" href="#palyak-idotablakent" aria-hidden="true" tabindex="-1">#</a>Pályák időtáblaként</h2>
<p>A pályának nem kell kódnak lennie, elég egy bejegyzéslista. Minden bejegyzés megmondja, mi jelenik meg, hányadik másodpercben, milyen magasságban és hogyan mozog. A mozgás pontok listája, amelyeken az ellenség állandó sebességgel repül át. Opcionálisan jöhetnek lövési időpontok, a főellenség rohama vagy kisebb ellenségeket kibocsátó generátor.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">minden képkockában:
    idő = most - pálya kezdete - szünetben töltött idő
    a pályatáblázat minden bejegyzésére:
        ha a bejegyzés még nincs felhasználva és idő &gt; bejegyzés.idő:
            hozz létre objektumot a bejegyzés szerint
            jelöld a bejegyzést felhasználtnak</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="Az első pálya idővonala: balról jobbra másodpercek, az ikonok magassága az ellenség megjelenési magasságának felel meg" width="1004" height="120" loading="lazy" decoding="async" /></picture><figcaption>Az első pálya: 39 bejegyzés 67 másodperc alatt. Üstökösök, aztán rakétahullámok, power-upok (piros) és a végén a főellenség.</figcaption></figure>
<p>Az így megírt pálya könnyen hangolható: egy hullámot fél másodperccel eltolni egyetlen szám módosítását jelenti. Az idővonalon látszik a ritmus is, amelyre törekedni érdemes: rövid hullám, szünet, másfajta ellenség, power-up egy nehezebb szakasz előtt.</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="Cikcakkban repülő rakéta a játéktéren, az útvonal töréspontjai bejelölve" width="1008" height="576" loading="lazy" decoding="async" /></picture><figcaption>Egy rakéta útvonala az első pályáról: hat pont, amelyeken állandó sebességgel repül át.</figcaption></figure>
<p>A hátrány nagyobb változtatásoknál jön elő. A játékunk legsűrűbb pályája 71 soros, és egy ilyen táblázat szerkesztő nélkül nehezen olvasható. Jobb a pályákat hullámokként leírni (egy közös mozgásmintájú ellenségcsoport), nem egyes hajókként.</p>
<h2 id="vegtelen-mod-egyetlen-alveletlen-szambol"><a class="heading-anchor" href="#vegtelen-mod-egyetlen-alveletlen-szambol" aria-hidden="true" tabindex="-1">#</a>Végtelen mód egyetlen álvéletlen számból</h2>
<p>Egy ranglistához (nálunk Game Center) olyan mód kell, amely nem ér véget. Ebben a kézzel írt táblázatot egy generátor váltja fel, amely játék közben állítja elő. Egy 20 jegyű álvéletlen számot generál, és az egyes számjegypárok döntenek egy hullámról:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">szám = következő 20 jegyű álvéletlen szám
típus = power-up, ha a 4-5. számjegy &gt; 90, különben ellenség
sprite = 1-2. számjegy modulo ellenségfajták száma
szünet a hullám előtt = 3. számjegy / 2 másodperc (legalább 1)
magasság = 7. számjegy, a játéktér magasságára átszámolva
mozgásminta = 10-11. számjegy modulo 10
hajók a hullámban = 17. számjegy / 2 + 1
sebesség = 13. számjegy × (1 + a hullám sorszáma / 10), 20 és 50 között
minden harmincadik hullám = főellenség, a generálás áll, amíg él</code></pre>
<p>A generátor seedjét (kezdőértékét) hagyja rögzítve a játékban. Így minden játékos ugyanazt a hullámsorrendet kapja, és a ranglista pontszámai összehasonlíthatók.</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="Tíz kis játéktér, mindegyiken egy ellenséges mozgásminta szaggatott vonallal" width="912" height="228" loading="lazy" decoding="async" /></picture><figcaption>A generátor tíz mozgásmintája. A pontos alak attól függ, milyen magasságban jelenik meg a hullám.</figcaption></figure>
<p>Hogy hogyan alakul egy ilyen mód, azt a generátor játékon kívüli szimulációja mutatja (300 hullám, ugyanaz a seed, mint a játékban). Az első főellenség 29 hullám után érkezik, vagyis 74 ellenség és 3 power-up után, nagyjából 130 másodpercnél. Az első 30 hullám a 20-as minimális sebességgel repül, mert egyetlen számjegy még az alsó határ alatt van. Az átlagsebesség aztán 28,5-re, 37,5-re és 41-re nő, a 120. hullámtól a legtöbb ellenség a maximumon repül. Nagyjából minden tizenegyedik hullám power-up.</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="A végtelen mód első 135 másodpercének idővonala ellenségikonokkal és a végén egy főellenséggel" width="1104" height="120" loading="lazy" decoding="async" /></picture><figcaption>A végtelen mód első 135 másodperce a generátor szimulációja szerint. A hullámok hosszabbak és szabálytalanabbak, mint egy kézzel írt pályán.</figcaption></figure>
<p>A gyengeség az, hogy a generátor nem ismeri a ritmust. A kézzel írt pálya váltogatja a nyomást és a pihenőt, a generátor nem. A főellenség alatti szünet és a növekvő sebesség részben segít, de a kézzel írt pályák jobban játszhatók.</p>
<h2 id="a-megkozelites-korlatai"><a class="heading-anchor" href="#a-megkozelites-korlatai" aria-hidden="true" tabindex="-1">#</a>A megközelítés korlátai</h2>
<ul>
<li><strong>Nincsenek automatikus tesztek.</strong> Mindent kézzel ellenőrzünk a szimulátorban és az órán.</li>
<li><strong>Globális állapot.</strong> A játékállapot nagy része egyetlen jelenet globális változója. Csak órás játéknál ez kényelmes, iPhone-verzió hozzáadásakor már nem.</li>
<li><strong>Pályák szerkesztő nélkül.</strong> Az időtábla kis lépésekben jól hangolható, nagyobb változtatásokhoz szerkesztő kellene.</li>
</ul>
<p>Ha órára vagy más kis képernyőre készít játékot, és segítségre van szüksége, <a href="https://smallwater.eu/hu/#kontakt">írjon nekünk</a>. Arról, hogyan generálunk rejtvényeket egy másik alkalmazásunkban, a <a href="https://smallwater.eu/hu/blog/sudoku-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/shmup-apple-watch-devlog/" data-ga-destination="/hu/blog/sudoku-generator/" data-ga-placement="inline">sudokugenerátorról</a> szóló cikkünkben írtunk.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hogyan generáljunk egyértelmű megoldású sudokut</title>
      <link>https://smallwater.eu/hu/blog/sudoku-generator/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/sudoku-generator/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Keresztrejtvények</category>
      <description>Sudoku-generátor lépésről lépésre: gyors solver bitmaszkokkal, véletlen teli rács, mezők törlése az egyértelműség ellenőrzésével és a nehézség.</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="Félig megoldott sudoku apró jelöltszámokkal és egy kiemelt mezővel, amelynek egyetlen jelöltje van" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#miert-pontosan-egy-megoldas" data-ga="blog_toc_clicked" data-ga-heading-id="miert-pontosan-egy-megoldas">Miért pontosan egy megoldás</a></li><li><a href="#1-lepes-irjon-gyors-solvert" data-ga="blog_toc_clicked" data-ga-heading-id="1-lepes-irjon-gyors-solvert">1. lépés: Írjon gyors solvert</a></li><li><a href="#2-lepes-toltse-ki-az-egesz-racsot" data-ga="blog_toc_clicked" data-ga-heading-id="2-lepes-toltse-ki-az-egesz-racsot">2. lépés: Töltse ki az egész rácsot</a></li><li><a href="#miert-nem-eleg-veletlen-mezoket-torolni" data-ga="blog_toc_clicked" data-ga-heading-id="miert-nem-eleg-veletlen-mezoket-torolni">Miért nem elég véletlen mezőket törölni</a></li><li><a href="#3-lepes-toroljon-az-egyertelmuseg-ellenorzesevel" data-ga="blog_toc_clicked" data-ga-heading-id="3-lepes-toroljon-az-egyertelmuseg-ellenorzesevel">3. lépés: Töröljön az egyértelműség ellenőrzésével</a></li><li><a href="#4-lepes-allitsa-be-a-nehezseget-az-ures-mezok-szamaval" data-ga="blog_toc_clicked" data-ga-heading-id="4-lepes-allitsa-be-a-nehezseget-az-ures-mezok-szamaval">4. lépés: Állítsa be a nehézséget az üres mezők számával</a></li><li><a href="#a-generator-sebessege-es-megbizhatosaga" data-ga="blog_toc_clicked" data-ga-heading-id="a-generator-sebessege-es-megbizhatosaga">A generátor sebessége és megbízhatósága</a></li><li><a href="#amikor-az-ures-mezok-szama-nem-eleg" data-ga="blog_toc_clicked" data-ga-heading-id="amikor-az-ures-mezok-szama-nem-eleg">Amikor az üres mezők száma nem elég</a></li></ol></nav><p>Egyértelmű megoldású sudokut két lépésben generálhat. Először töltsön ki teljesen egy üres 9×9-es rácsot backtrackinggel, a számjegyeket véletlen sorrendben próbálva. Ezután egyenként, véletlen sorrendben törölje a mezőket, és minden törlés után egy solverrel ellenőrizze, hogy a feladványnak még mindig pontosan egy megoldása van-e. Ha nem, tegye vissza a számjegyet. Egy átlagos processzoron a medián futásidő kevesebb mint egy milliszekundum.</p>
<p>A módszer minden olyan játékhoz és alkalmazáshoz illik, amelynek korlátlan számú új feladványra van szüksége kézi előkészítés nélkül. Gyakorlati példa a sudoku a keresztrejtvényes és fejtörős alkalmazásunkban (<a href="https://smallwater.eu/crosswords-ios">App Store</a>, <a href="https://smallwater.eu/crosswords-android">Google Play</a>).</p>
<h2 id="miert-pontosan-egy-megoldas"><a class="heading-anchor" href="#miert-pontosan-egy-megoldas" aria-hidden="true" tabindex="-1">#</a>Miért pontosan egy megoldás</h2>
<p>A szabályokat mindenki ismeri: minden sorban, oszlopban és 3×3-as blokkban minden számjegy 1-től 9-ig pontosan egyszer szerepel. Ritkábban esik szó arról, hogy egy jó feladványnak <strong>egyetlen</strong> megoldása van. Csak így oldható meg tisztán logikával, találgatás nélkül.</p>
<p>Ennek gyakorlati következménye is van. A játék általában egy megoldást tárol, ehhez ellenőrzi az eredményt, és ebből ad tippeket. Ha a feladványnak két megoldása van, a játékos eljuthat a másikhoz, amely ugyanolyan helyes, a játék mégsem fogadja el.</p>
<h2 id="1-lepes-irjon-gyors-solvert"><a class="heading-anchor" href="#1-lepes-irjon-gyors-solvert" aria-hidden="true" tabindex="-1">#</a>1. lépés: Írjon gyors solvert</h2>
<p>A solver olyan program, amely megoldja a sudokut. Backtrackinggel (visszalépéses kereséssel) dolgozik: egy mezőbe beír egy lehetséges számjegyet, továbblép a következő mezőre, és ha ellentmondásba ütközik, visszalép, és másik számjegyet próbál. Kétszer lesz rá szükség: az üres rács kitöltéséhez és az egyértelműség ellenőrzéséhez. Az ellenőrzés minden törölt mező után lefut, feladványonként 40-70-szer, ezért gyorsnak kell lennie. Két dolog számít:</p>
<ul>
<li><strong>Bitmaszkok.</strong> Minden sorhoz, oszlophoz és blokkhoz tartson egy 9 bites számot a már használt számjegyekkel. Egy mező jelöltjei, vagyis azok a számjegyek, amelyek még kerülhetnek bele, így egyetlen művelettel megvannak: minden számjegy, kivéve azokat, amelyek már szerepelnek a sorában, oszlopában vagy blokkjában. A jelöltek száma a beállított bitek száma, ez előre kiszámolható egy 512 elemű táblázatba.</li>
<li><strong>Először a legkevesebb jelöltű mező.</strong> Ne az első üres mezőt válassza balról, hanem azt, amelyiknél a legkevesebb a lehetőség (a szakirodalomban MRV-heurisztika, minimum remaining values). Az egyetlen jelöltű mezőt töltse ki azonnal, a jelölt nélküli mező halott ágat jelent, és vissza kell lépni.</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="Félig kitöltött rács apró jelöltszámokkal az üres mezőkben, egy egyetlen jelöltű mező kiemelve" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Jelöltek az üres mezőkben. A kiemelt mezőnek egyetlen jelöltje van, a solver ezt tölti ki először.</figcaption></figure>
<p>Nehéz feladványokon az egyértelműség ellenőrzéséhez ezzel a szabállyal mediánban 311 lépés kell. Ha a solver balról jobbra sorban veszi a mezőket, mediánban 28 579 lépés, a legrosszabb 5%-ban pedig több mint 300 000. A különbség nagyjából 90-szeres.</p>
<h2 id="2-lepes-toltse-ki-az-egesz-racsot"><a class="heading-anchor" href="#2-lepes-toltse-ki-az-egesz-racsot" aria-hidden="true" tabindex="-1">#</a>2. lépés: Töltse ki az egész rácsot</h2>
<p>Teli rácsot ugyanaz a solver ad, ha üres rácson futtatja. Az egyetlen különbség, hogy a mező jelöltjeit véletlen sorrendben próbálja. Az első talált megoldás egy véletlen, érvényes, teljesen kitöltött rács.</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="Teljesen kitöltött 9×9-es sudokurács" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Véletlen teli rács. Ebből lesz a feladvány.</figcaption></figure>
<h2 id="miert-nem-eleg-veletlen-mezoket-torolni"><a class="heading-anchor" href="#miert-nem-eleg-veletlen-mezoket-torolni" aria-hidden="true" tabindex="-1">#</a>Miért nem elég véletlen mezőket törölni</h2>
<p>A legegyszerűbb ötlet az, hogy a teli rácsból N véletlen mezőt töröljön, például nehézségtől függően 40, 50 vagy 55 mezőt. A feladvány rendben lévőnek tűnik, csak gyakran nem egyértelmű a megoldása.</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="Sudoku-feladvány, amelyben egy téglalap négy sarkában lévő mezőkbe két számpár is beillik" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Véletlen törléssel készült feladvány. A kiemelt mezőkbe mindkét számpár beillik, a feladványnak két megoldása van.</figcaption></figure>
<p>A képen a tipikus eset látható. Egy két blokkra kiterjedő téglalap négy sarkában lévő mezőket kétféleképpen lehet kitölteni, és a rács többi része ezt semmivel sem dönti el. A sudokusok ezt a mintát deadly patternként ismerik. Nehézségenként 1000 feladványon az eredmény így néz ki:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Nehézség</th>
<th align="right">Törölt mezők</th>
<th align="right">Több megoldású feladványok</th>
</tr>
</thead>
<tbody><tr>
<td>Könnyű</td>
<td align="right">40</td>
<td align="right">59%</td>
</tr>
<tr>
<td>Közepes</td>
<td align="right">50</td>
<td align="right">96%</td>
</tr>
<tr>
<td>Nehéz</td>
<td align="right">55</td>
<td align="right">100%</td>
</tr>
</tbody></table>
</div>
<p>A véletlen törléssel készült nehéz feladványnak átlagosan több száz megoldása van. Ezért minden törlés után szükség van az egyértelműség ellenőrzésére.</p>
<h2 id="3-lepes-toroljon-az-egyertelmuseg-ellenorzesevel"><a class="heading-anchor" href="#3-lepes-toroljon-az-egyertelmuseg-ellenorzesevel" aria-hidden="true" tabindex="-1">#</a>3. lépés: Töröljön az egyértelműség ellenőrzésével</h2>
<p>Járja be mind a 81 mezőt véletlen sorrendben. Mindegyiket próbálja kiüríteni, és számolja meg a megoldásokat a solverrel, de kettőnél álljon meg. Ennél többet nem kell tudni: egy megoldás azt jelenti, hogy a mező üres maradhat, kettő azt, hogy vissza kell tenni.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">feladvány = a teli rács másolata
törölt = 0
minden mezőre véletlen sorrendben:
    ha törölt == cél: vége
    számjegy = feladvány[mező]
    feladvány[mező] = üres
    ha megoldások_száma(feladvány, legfeljebb 2) == 1:
        törölt = törölt + 1
    különben:
        feladvány[mező] = számjegy</code></pre>
<p>Ha egy mezőt egyszer visszatett, ne próbálja újra. A további törlés csak lazítja a feladványt, így ha egy mezőt most nem lehetett kiüríteni, később sem lehet.</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="Félkész nehéz feladvány 37 üres mezővel és egy kiemelt számjeggyel, amelyet vissza kellett tenni" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>37 törölt mező után. A kiemelt számjegyet nem lehetett törölni, a feladványnak két megoldása lenne, ezért visszakerült.</figcaption></figure>
<h2 id="4-lepes-allitsa-be-a-nehezseget-az-ures-mezok-szamaval"><a class="heading-anchor" href="#4-lepes-allitsa-be-a-nehezseget-az-ures-mezok-szamaval" aria-hidden="true" tabindex="-1">#</a>4. lépés: Állítsa be a nehézséget az üres mezők számával</h2>
<p>A nehézség legegyszerűbb mértéke a törölt mezők száma. A játékunkban használt három szint:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Nehézség</th>
<th align="right">Üres mezők</th>
<th align="right">Megmaradó számjegyek</th>
<th align="right">Átlagosan visszatett mezők</th>
</tr>
</thead>
<tbody><tr>
<td>Könnyű</td>
<td align="right">40</td>
<td align="right">41</td>
<td align="right">0,9</td>
</tr>
<tr>
<td>Közepes</td>
<td align="right">50</td>
<td align="right">31</td>
<td align="right">4,5</td>
</tr>
<tr>
<td>Nehéz</td>
<td align="right">55</td>
<td align="right">26</td>
<td align="right">12,7</td>
</tr>
</tbody></table>
</div>
<p>Minél kevesebb számjegy marad, annál gyakrabban rontaná el a következő törlés az egyértelműséget, és a mezőt vissza kell tenni. Nehéz szinten egy menet néha megáll, mielőtt elérné az 55 üres mezőt. Ilyenkor generáljon új teli rácsot, és próbálja újra. 1000 nehéz feladványon mérve az esetek 97,6%-ában elég volt az első menet, és egyszer sem kellett 3-nál több próbálkozás.</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="Kész nehéz sudoku-feladvány 26 számjeggyel" width="1024" height="1034" loading="lazy" decoding="async" /></picture><figcaption>Kész nehéz feladvány: 26 számjegy és egyetlen megoldás.</figcaption></figure>
<h2 id="a-generator-sebessege-es-megbizhatosaga"><a class="heading-anchor" href="#a-generator-sebessege-es-megbizhatosaga" aria-hidden="true" tabindex="-1">#</a>A generátor sebessége és megbízhatósága</h2>
<p>A generátort nehézségenként 1000 feladványon futtattuk (Node.js 22 szerverprocesszoron, a telefon lassabb lesz):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Nehézség</th>
<th align="right">Medián idő</th>
<th align="right">A feladványok 95%-a ennyi alatt</th>
<th align="right">Leghosszabb</th>
<th align="right">Több megoldású feladványok</th>
</tr>
</thead>
<tbody><tr>
<td>Könnyű</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>Közepes</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>Nehéz</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>Emellett minden generált feladványt külön is ellenőriztünk a solverrel. Mind a 3000-nek egyetlen megoldása volt.</p>
<h2 id="amikor-az-ures-mezok-szama-nem-eleg"><a class="heading-anchor" href="#amikor-az-ures-mezok-szama-nem-eleg" aria-hidden="true" tabindex="-1">#</a>Amikor az üres mezők száma nem elég</h2>
<p>A megadott számjegyek száma a nehézség durva mértéke. Két 26 számjegyes feladvány különböző nehézségű lehet attól függően, milyen logikai technikákat igényel. Pontosabb besorolás az lenne, ha a program emberi módszerekkel oldaná meg a feladványt, és feljegyezné a legnehezebb szükséges technikát. Egy mobiljátékhoz, ahol a játékos három szint közül választ, a számjegyek száma elég.</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">Próbálja ki</p><h2 class="app-box-title">Keresztrejtvények</h2><p>Keresztrejtvény, szókereső, sudoku és nonogram egyetlen offline alkalmazásban. Hogyan készülnek a rejtvények, és mi az újdonság.</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>Ha hasonló problémán dolgozik, rejtvényeket generál, vagy mobilalkalmazásra van szüksége, <a href="https://smallwater.eu/hu/#kontakt">írjon nekünk</a>. Hogy hogyan lehet szókeresőt generálni rejtett megfejtéssel, azt egy <a href="https://smallwater.eu/hu/blog/word-search-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/sudoku-generator/" data-ga-destination="/hu/blog/word-search-generator/" data-ga-placement="inline">külön útmutató</a> írja le. A megfejtéses svéd rejtvény generálásáról <a href="https://smallwater.eu/hu/blog/swedish-crossword-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/sudoku-generator/" data-ga-destination="/hu/blog/swedish-crossword-generator/" data-ga-placement="inline">egy másik útmutató</a> szól.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hogyan generáljunk svéd rejtvényt megfejtéssel</title>
      <link>https://smallwater.eu/hu/blog/swedish-crossword-generator/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/swedish-crossword-generator/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Keresztrejtvények</category>
      <description>Útmutató svéd rejtvény generátorhoz: maszk hill climbinggel, kitöltés CSP-ként backtrackinggel, a megfejtést pedig csak a kész rácsban keressük.</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="Két 10×10-es svéd rejtvény: balra véletlen maszk büntetett mezőkkel, jobbra kitöltött rács a megfejtés korallszínű betűivel" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#minek-kell-megfelelnie-egy-sved-rejtvenynek" data-ga="blog_toc_clicked" data-ga-heading-id="minek-kell-megfelelnie-egy-sved-rejtvenynek">Minek kell megfelelnie egy svéd rejtvénynek</a></li><li><a href="#miert-nem-eleg-szorol-szora-kitolteni-a-racsot" data-ga="blog_toc_clicked" data-ga-heading-id="miert-nem-eleg-szorol-szora-kitolteni-a-racsot">Miért nem elég szóról szóra kitölteni a rácsot</a></li><li><a href="#1-lepes-a-maszk-hill-climbinggel" data-ga="blog_toc_clicked" data-ga-heading-id="1-lepes-a-maszk-hill-climbinggel">1. lépés: a maszk hill climbinggel</a></li><li><a href="#2-lepes-kitoltes-szavakkal-csp-kent" data-ga="blog_toc_clicked" data-ga-heading-id="2-lepes-kitoltes-szavakkal-csp-kent">2. lépés: kitöltés szavakkal CSP-ként</a></li><li><a href="#3-lepes-a-megfejtes-a-kesz-racsban" data-ga="blog_toc_clicked" data-ga-heading-id="3-lepes-a-megfejtes-a-kesz-racsban">3. lépés: a megfejtés a kész rácsban</a></li><li><a href="#miert-ne-helyezzuk-el-elore-a-megfejtest-mint-az-ujsagokban" data-ga="blog_toc_clicked" data-ga-heading-id="miert-ne-helyezzuk-el-elore-a-megfejtest-mint-az-ujsagokban">Miért ne helyezzük el előre a megfejtést, mint az újságokban</a></li><li><a href="#a-generator-teljesitmenye-es-miert-nem-fut-a-telefonon" data-ga="blog_toc_clicked" data-ga-heading-id="a-generator-teljesitmenye-es-miert-nem-fut-a-telefonon">A generátor teljesítménye, és miért nem fut a telefonon</a></li><li><a href="#generalas-nagy-mennyisegben" data-ga="blog_toc_clicked" data-ga-heading-id="generalas-nagy-mennyisegben">Generálás nagy mennyiségben</a></li><li><a href="#amit-a-megoldas-nem-fed-le" data-ga="blog_toc_clicked" data-ga-heading-id="amit-a-megoldas-nem-fed-le">Amit a megoldás nem fed le</a></li></ol></nav><p>Svéd rejtvényt megfejtéssel két fázisban generálhat: először lokális kereséssel keresse meg a maszkot, vagyis a kérdésmezők és a betűmezők elrendezését, aztán töltse ki a szótár szavaival kényszerkielégítési feladatként (CSP), backtrackinggel. A megfejtést ne tegye be előre a rácsba. Csak amikor a rejtvény kész, keressen egy viccet, amelynek a csattanója balról jobbra és fentről lefelé olvasva kiolvasható belőle.</p>
<p>Így készülnek a svéd rejtvények a keresztrejtvényeket és szókeresőket tartalmazó alkalmazásunkban (<a href="https://smallwater.eu/crosswords-ios">App Store</a>, <a href="https://smallwater.eu/crosswords-android">Google Play</a>). Egy rács néhány másodperctől néhány tíz másodpercig tartó processzoridőbe kerül, ezért a rejtvényeket előre, erősebb számítógépen generáljuk, és az alkalmazással együtt szállítjuk. A szókeresőt egészen másképp és egyszerűbben lehet generálni, erről a <a href="https://smallwater.eu/hu/blog/word-search-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/swedish-crossword-generator/" data-ga-destination="/hu/blog/word-search-generator/" data-ga-placement="inline">Hogyan generáljunk szókeresőt rejtett megfejtéssel</a> című cikk szól.</p>
<h2 id="minek-kell-megfelelnie-egy-sved-rejtvenynek"><a class="heading-anchor" href="#minek-kell-megfelelnie-egy-sved-rejtvenynek" aria-hidden="true" tabindex="-1">#</a>Minek kell megfelelnie egy svéd rejtvénynek</h2>
<p>A svéd rejtvényben nincsenek fekete mezők. Minden mező vagy betű, vagy jobbra, illetve lefelé mutató nyíllal ellátott kérdésmező. Ebből kemény feltételek következnek:</p>
<ul>
<li>Minden betű egy vízszintes és egy függőleges szóhoz is tartozik. Az a betű, amelyet csak egy szó keresztez, nem ellenőrizhető, és hibának hat.</li>
<li>Minden szó legalább kétbetűs, és kérdésmező áll előtte. Ezért az első sor és az első oszlop mindig csupa kérdésmező.</li>
<li>A betűk egyetlen összefüggő területet alkotnak, leszakadt szigetek nélkül.</li>
<li>Minden szónak szerepelnie kell a szótárban, és legalább egy meghatározással kell rendelkeznie. Egy szó sem ismétlődik a rácsban.</li>
</ul>
<p>Ehhez jönnek a lágy minőségi szempontok: kevés kétbetűs szó, ne legyen sehová sem mutató kérdésmező, és minél kevesebb nagyon hosszú szó, mert ezeket nehéz kitölteni. A feladat számításilag nehéz. <a href="https://link.springer.com/chapter/10.1007/978-3-642-30347-0_15" target="_blank" rel="noopener">Engel és társai (2012)</a> megmutatták, hogy már a maszk szavakkal való kitöltése is NP-teljes.</p>
<h2 id="miert-nem-eleg-szorol-szora-kitolteni-a-racsot"><a class="heading-anchor" href="#miert-nem-eleg-szorol-szora-kitolteni-a-racsot" aria-hidden="true" tabindex="-1">#</a>Miért nem elég szóról szóra kitölteni a rácsot</h2>
<p>Az első ötlet az, hogy úgy járjunk el, mint egy ember ceruzával: a megfejtést szavakra bontjuk, újságszerűen egy sorba rakjuk, mindegyik betűjén átvezetünk egy keresztező szót, a rács többi részét pedig greedy algorithm tölti ki. A greedy algorithm minden lépésben azt választja, ami éppen akkor a legjobbnak látszik, és a döntést soha nem vizsgálja felül. Ahol semmi sem illik, lyuk marad. Mivel a greedy kitöltés nem tud visszalépni, ha az első tíz szó rossz, a rács többi része ezt már nem hozza helyre.</p>
<p>Egy jobb szóválasztási heurisztika csak keveset segít. Kipróbálhatja a keresztező szavak hosszát a középsőktől, nagyjából öt betűtől haladva a szélsők felé. Vagy választhat evolúciós megközelítést: félkész rejtvények populációja, mindegyikből öt utód egy-egy újabb szóval, értékelés a kitöltött mezők, a magánhangzók száma és a betűk egyenletessége alapján, és csak a legjobbak jutnak a következő generációba. Az eredmények valamivel sűrűbbek lesznek, de a lyukak maradnak, mert a heurisztika csak a következő lépést látja.</p>
<p>A kiutat Jakob Engel 2009-ben, a Müncheni Műszaki Egyetemen írt szakdolgozata mutatja meg; ő a fent említett cikk első szerzője is. Bontsa a feladatot két független fázisra: előbb keressen jó maszkot a szavakra való tekintet nélkül, aztán töltse ki. Engel a maszkhoz evolúciós algoritmust használt. Mi egyszerűbb, véletlen rázásokkal kiegészített lokális keresést használunk, ezt a következő lépés írja le.</p>
<h2 id="1-lepes-a-maszk-hill-climbinggel"><a class="heading-anchor" href="#1-lepes-a-maszk-hill-climbinggel" aria-hidden="true" tabindex="-1">#</a>1. lépés: a maszk hill climbinggel</h2>
<p>A maszk olyan rács, amelyben minden mező vagy kérdésmező, vagy betű. A megtalálásához elég a hill climbing: olyan lokális keresés, amely apró változtatásokat végez, és azokat tartja meg, amelyek nem rontanak az eredményen.</p>
<p>Induljon véletlenszerűen, a belső mezők nagyjából 22%-án kérdésmezővel. Minden maszkot pontozzon büntetőpontokkal. A kemény szabálysértések (túl rövid szó, keresztező szó nélküli betű, leszakadt sziget) többnyire 1000 pontba kerüljenek, a lágy hiányosságok (a megengedett számon felüli kétbetűs szavak, semmilyen szóra nem mutató kérdésmező, egymás mellé zsúfolt kérdésmezők) 50 és 500 pont közé.</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="Véletlen 10×10-es maszk sok pirossal és narancssárgával kiemelt problémás mezővel" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>Véletlen maszk. A piros mezők szabálysértő betűk, a narancssárgák sehová sem mutató kérdésmezők. Büntetés: 35 500.</figcaption></figure>
<p>Ezután ciklusban alakítson egy mezőt kérdésmezőből betűvé vagy fordítva, esetleg toljon egy kérdésmezőt átlósan egy mezővel arrébb. A változtatást tartsa meg, ha a büntetés nem nőtt, különben vonja vissza. A változtatások felét irányítsa a legnagyobb büntetésű mezőkre, így a durva hibák gyorsan eltűnnek:</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="Ugyanaz a maszk 150 lépés után, már csak néhány problémás mező maradt" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>150 lépés után. Büntetés: 1770.</figcaption></figure>
<p>A hill climbing könnyen elakad egy lokális minimumban. Ha 2000 lépésen át semmi sem javul, „rázza meg” a rácsot: az eddigi legjobb maszkban fordítson át öt véletlen mezőt, és keressen tovább. A grafikonon ezek a csúcsok. Ha három rázás után sincs jobb maszk, és nincs benne kemény szabálysértés, álljon le.</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="Logaritmikus skálájú büntetésgörbe 8300 lépésen át: gyors esés 35 500-ról 200-ra és három rövid csúcs a rázások után" width="1800" height="850" loading="lazy" decoding="async" /></picture><figcaption>A büntetés egy 10×10-es maszk generálása közben. A pontok a két előző pillanatképet jelölik.</figcaption></figure>
<p>A kész maszk maradék büntetése 200 a lágy hiányosságokért, de kemény szabálysértés nincs benne:</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="Kész 10×10-es maszk nyilakkal a kérdésmezőkben és üres betűmezőkkel" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>A kész maszk. A nyilak a szavak irányát mutatják.</figcaption></figure>
<p>A szó nélküli kérdésmezőért és a leszakadt szigetért járó büntetés legyen a maszkban kezdettől fogva. Egy nagy, kész rejtvényekből álló gyűjteményben ezek nélkül a kérdésmezők 13,2%-a nem mutatott egyetlen szóra sem, és a rácsok 23,5%-ában volt leszakadt betűsziget. Velük ezek az értékek 5,0% és 0%.</p>
<h2 id="2-lepes-kitoltes-szavakkal-csp-kent"><a class="heading-anchor" href="#2-lepes-kitoltes-szavakkal-csp-kent" aria-hidden="true" tabindex="-1">#</a>2. lépés: kitöltés szavakkal CSP-ként</h2>
<p>A maszkból slotok listája lesz, vagyis ismert hosszúságú és ismert keresztezésű szóhelyek. A kitöltés klasszikus kényszerkielégítési feladat (constraint satisfaction problem, CSP): minden slot egy változó, a tartománya a belé illő szavak, a kényszerek pedig az egyező betűk a keresztezéseknél. Backtrackinggel oldható meg, vagyis olyan kereséssel, amely zsákutcás ágban egy lépést visszalép, és más lehetőséget próbál:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">minden slotra: tartomány = az adott hosszúságú összes szó
amíg nincs minden slot kitöltve:
    válaszd a legkisebb tartományú slotot (egyezésnél a legtöbb keresztezésűt)
    a tartomány minden szavára, a legjobbtól kezdve:
        írd be a szót
        szűkítsd a keresztező slotok tartományát az egyező betűjű szavakra
        ha valamelyik tartomány kiürült: vond vissza, próbáld a következő szót
        különben folytasd a következő slottal
    ha egyik szó sem jó: lépj vissza egy slotot
50 000 lépés után add fel</code></pre>
<p>A szakirodalom ezeket a heurisztikákat MRV-nek (minimum remaining values, először a legkevesebb lehetőséggel rendelkező slot) és forward checkingnek nevezi (minden beírás után szűkítsük a szomszédok tartományát, hogy a zsákutcás ág azonnal kiderüljön, ne tíz szóval később). Indexelje a szótárt előre hossz, pozíció és betű szerint. Így „a harmadik helyen Q betűt tartalmazó szavak” egyetlen halmazmetszetet jelentenek, nem a teljes szótár átnézését.</p>
<p>A szavak sorrendje is számít. Részesítse előnyben azokat a szavakat, amelyekhez a szótárban több különböző meghatározás tartozik. Ez egyszerű jele annak, hogy gyakori rejtvényszóról van szó. Enélkül a rács ismeretlen rövidítésekkel telne meg.</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="Angol szavakkal kitöltött 10×10-es maszk" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>A kitöltött rács. A cikk ábrái az angol szótárt használják, a táblázatok számai a szlovákból származnak.</figcaption></figure>
<h2 id="3-lepes-a-megfejtes-a-kesz-racsban"><a class="heading-anchor" href="#3-lepes-a-megfejtes-a-kesz-racsban" aria-hidden="true" tabindex="-1">#</a>3. lépés: a megfejtés a kész rácsban</h2>
<p>Kitöltés közben még nem tudja, mi lesz a megfejtés. Olvassa ki a kész rács betűit balról jobbra és fentről lefelé, és keressen olyan viccet, amelynek csattanója ennek a karakterláncnak részsorozata. A betűk tehát a helyes sorrendben követik egymást, de nem kell egymás mellett lenniük. Ha talál ilyet, a megfejtés betűit ossza el a lehető legegyenletesebben a rácsban, hogy a csattanót ne lehessen egyetlen szóból kitalálni.</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="A kitöltött rács tíz korallszínű betűvel: S, P, A, C, E, S, U, I, T, S, olvasási sorrendben számozva" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>A SPACESUITS megfejtés olvasási sorrendben. Ebbe a rácsba angol viccgyűjteményünk 111 különböző csattanója férne bele.</figcaption></figure>
<p>Ez a sorrend (előbb a rács, aztán a vicc) jelentősen megnöveli a kész rejtvények számát. Méréseink szerint egyetlen 12×12-es rácsba átlagosan 86 vicc illik a szlovák gyűjteményből (5960 vicc). Így mindig akad olyan, amelyet az adott méretben még nem használtunk.</p>
<h2 id="miert-ne-helyezzuk-el-elore-a-megfejtest-mint-az-ujsagokban"><a class="heading-anchor" href="#miert-ne-helyezzuk-el-elore-a-megfejtest-mint-az-ujsagokban" aria-hidden="true" tabindex="-1">#</a>Miért ne helyezzük el előre a megfejtést, mint az újságokban</h2>
<p>Az újságokban a megfejtés általában egy helyen, egy sorban és szavakra bontva áll. Ezt kétféleképpen lehet elérni, és mindkettő nagyon kevés eredményt ad.</p>
<p>Az első módszer, hogy a megfejtést előre elhelyezzük a rácsban. Ekkor bele kell férnie. A csattanóink átlagosan 10,3 betűből és 2 szóból állnak. Ekkora a szlovák gyűjtemény azon vicceinek aránya, amelyek egyáltalán elhelyezhetők az adott méretben:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Rács</th>
<th align="right">Teljes csattanó egy sorban</th>
<th align="right">Szavakra bontott csattanó (legfeljebb 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="10×10-es rács, amelyben a KNOW és RULES megfejtésszavak előre, egész vízszintes szavakként, korallszínnel vannak elhelyezve" width="968" height="978" loading="lazy" decoding="async" /></picture><figcaption>Előre, egész szavakként sorokba helyezett megfejtés, ahogy az újságokban. Ugyanazzal a generátorral készült.</figcaption></figure>
<p>Előre elhelyezett megfejtéssel a kis rácsok kitöltése nagyjából ugyanolyan gyakran sikerül, mint nélküle. 15×15-nél 60 próbálkozásból 5 sikerült a megfejtés nélküli 8-cal szemben, 18×18-nál 60-ból egy sem. Ráadásul minden ilyen rács egyetlen vicchez tartozik, és kis méretekben a viccek többsége el sem helyezhető.</p>
<p>A második módszer, hogy a rács szabadon alakul ki, és a csattanót összefüggő szakaszként keressük benne. 2200 kész szlovák rejtvényből álló mintán (mind a 8×8-tól 18×18-ig terjedő méretekből 200-200) a teljes csattanó összefüggő szakaszként egy sorban vagy oszlopban mindössze 81 rácsban fordult elő (3,7%). Ha a csattanó minden szava máshol lehet, 443 rácsban (20%), és ez még a szavak egymás mellett állásának feltétele nélkül. Olvasási sorrendben mindegyikben akadt vicc. A rácsban szétszórt megfejtés árán nagyságrendekkel több rejtvényt kap.</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">Próbálja ki</p><h2 class="app-box-title">Keresztrejtvények</h2><p>Keresztrejtvény, szókereső, sudoku és nonogram egyetlen offline alkalmazásban. Hogyan készülnek a rejtvények, és mi az újdonság.</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="a-generator-teljesitmenye-es-miert-nem-fut-a-telefonon"><a class="heading-anchor" href="#a-generator-teljesitmenye-es-miert-nem-fut-a-telefonon" aria-hidden="true" tabindex="-1">#</a>A generátor teljesítménye, és miért nem fut a telefonon</h2>
<p>A generátor méretenként 60 próbálkozást futtatott a szlovák szótárral, egy szálon (JavaScript, Node 22, Intel Xeon 2,8 GHz-es szerverprocesszor a felhőben). Egy próbálkozás egy maszk és egy kitöltés. A „sikeres kitöltés” azt jelenti, hogy a CSP 50 000 lépésen belül megoldást talált.</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Rács</th>
<th align="right">Maszk</th>
<th align="right">Kitöltés</th>
<th align="right">Sikeres kitöltés</th>
<th align="right">Szavak a rácsban</th>
<th align="right">Vicc rácsonként</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 (3 rácsból)</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>Mit mutatnak a mérések:</p>
<ul>
<li><strong>A kis rács nem könnyű rács.</strong> A 8×8-as rácsban sok a rövid, sokszorosan keresztezett slot, és kevés a mozgástér, a kitöltés a próbálkozások mindössze 5%-ában sikerül. Egy kitöltött rács átlagosan körülbelül 10 másodperc processzoridőbe kerül 8×8-nál, 4 másodpercbe 12×12-nél és 18 másodpercbe 18×18-nál.</li>
<li><strong>A legtöbb próbálkozás kudarccal végződik.</strong> Ezért futtassa a próbálkozásokat párhuzamosan az összes magon, és vegye az első elég jó eredményt.</li>
<li><strong>A telefonon ennek nincs értelme.</strong> A játékos nem akar több tíz másodpercet várni a képernyő megnyitásakor, és az alkalmazásnak a teljes, meghatározásokat tartalmazó szótárt magával kellene vinnie. Az alkalmazáson belüli generátornak ráadásul gyakran át kell adnia a vezérlést a felhasználói felületnek, különben az alkalmazás lefagy.</li>
</ul>
<h2 id="generalas-nagy-mennyisegben"><a class="heading-anchor" href="#generalas-nagy-mennyisegben" aria-hidden="true" tabindex="-1">#</a>Generálás nagy mennyiségben</h2>
<p>A generátor helye ezért egy többmagos processzorú számítógép. Nálunk minden mag saját szálat kap az indításkor egyszer betöltött szótárral. A rács akkor kész, amikor valamelyik szál elég kevés kihasználatlan mezőt tartalmazó eredményt ad vissza. Folyamatos üzemmódban körbe-körbe generál egy-egy rejtvényt minden méretben és minden nyelven. Így készült több mint 14 000 rejtvény négy nyelven (szlovák, cseh, angol, német) és tizenegy méretben 8×8-tól 18×18-ig. Minden vicc méretenként és nyelvenként legfeljebb egyszer szerepel.</p>
<p>Ekkora mennyiségnél két dolog számít:</p>
<ul>
<li><strong>Formátum.</strong> A teljes rekord rejtvényenként körülbelül 28 kB, a teljes gyűjtemény 390 MB. Ennek nagy része kiszámítható (koordináták, mezőtípusok, kérdésmezők számai). A rácsot egyetlen karakterláncként tároló tömör formátum körülbelül 2 kB, a gyűjtemény 15 MB, az alkalmazás telepítőcsomagjában pedig nagyjából 7 MB.</li>
<li><strong>Minőségmérés.</strong> Minden generálás után mérje a szó nélküli kérdésmezők és a szigetes rácsok arányát, és az ellenőrzés jelezzen hibát, ha a számok romlanak.</li>
</ul>
<h2 id="amit-a-megoldas-nem-fed-le"><a class="heading-anchor" href="#amit-a-megoldas-nem-fed-le" aria-hidden="true" tabindex="-1">#</a>Amit a megoldás nem fed le</h2>
<ul>
<li><strong>Szó nélküli kérdésmezők.</strong> Az átlagos rejtvényben még mindig 2,7 kérdésmező mutat a semmibe. A bal felső sarok mindig ilyen.</li>
<li><strong>Gyakorisági adatok nélküli szótárak.</strong> A Wikiszótárból származó német szótárban a legtöbb szóhoz egyetlen meghatározás tartozik, így a meghatározások száma szerinti rangsor alig különböztet, és ritka szavak kerülnek a rácsba. Egy gyakorisági lista segítene, egyelőre nincs ilyenünk.</li>
<li><strong>A megfejtés nem egy helyen áll.</strong> Aki az újságokhoz szokott, egy sorban keresheti. Ez kompromisszum, nem ideális megoldás.</li>
</ul>
<p>Ha hasonló, tartalomgenerálással kapcsolatos problémán dolgozik, vagy mobilalkalmazásra van szüksége, <a href="https://smallwater.eu/hu/#kontakt">írjon nekünk</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hogyan generáljunk 3D telefonmodellt a böngészőben</title>
      <link>https://smallwater.eu/hu/blog/threejs-csg-3d-phone-model/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/threejs-csg-3d-phone-model/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Ecranify</category>
      <description>Útmutató 3D telefonmodellhez a böngészőben Three.js-szel és CSG-vel: bevel élű ház, gomb- és hangszórókivágások, Web Worker és gyors lyukkivonás.</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="Két generált 3D telefonmodell, az egyik elölről sötét kijelzővel, a másik hátulról" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#amire-szuksege-lesz" data-ga="blog_toc_clicked" data-ga-heading-id="amire-szuksege-lesz">Amire szüksége lesz</a></li><li><a href="#1-lepes-huzza-ki-a-teglalapot-3d-be" data-ga="blog_toc_clicked" data-ga-heading-id="1-lepes-huzza-ki-a-teglalapot-3d-be">1. lépés: Húzza ki a téglalapot 3D-be</a></li><li><a href="#2-lepes-vagjon-melyedest-a-kijelzonek" data-ga="blog_toc_clicked" data-ga-heading-id="2-lepes-vagjon-melyedest-a-kijelzonek">2. lépés: Vágjon mélyedést a kijelzőnek</a></li><li><a href="#3-lepes-keszitse-elo-az-oldalso-elemeket-furokent" data-ga="blog_toc_clicked" data-ga-heading-id="3-lepes-keszitse-elo-az-oldalso-elemeket-furokent">3. lépés: Készítse elő az oldalsó elemeket fúróként</a></li><li><a href="#4-lepes-az-elolap-reszleteit-ne-furja" data-ga="blog_toc_clicked" data-ga-heading-id="4-lepes-az-elolap-reszleteit-ne-furja">4. lépés: Az előlap részleteit ne fúrja</a></li><li><a href="#5-lepes-tegye-at-a-szamitast-web-workerbe" data-ga="blog_toc_clicked" data-ga-heading-id="5-lepes-tegye-at-a-szamitast-web-workerbe">5. lépés: Tegye át a számítást Web Workerbe</a></li><li><a href="#6-lepes-vonja-ki-az-osszes-lyukat-egyszerre" data-ga="blog_toc_clicked" data-ga-heading-id="6-lepes-vonja-ki-az-osszes-lyukat-egyszerre">6. lépés: Vonja ki az összes lyukat egyszerre</a></li><li><a href="#7-lepes-a-szegmensszamot-tartsa-parameterkent" data-ga="blog_toc_clicked" data-ga-heading-id="7-lepes-a-szegmensszamot-tartsa-parameterkent">7. lépés: A szegmensszámot tartsa paraméterként</a></li><li><a href="#rendereles-szerveren-videokartya-nelkul" data-ga="blog_toc_clicked" data-ga-heading-id="rendereles-szerveren-videokartya-nelkul">Renderelés szerveren videokártya nélkül</a></li></ol></nav><p>3D telefonmodellt úgy generálhat a böngészőben, hogy egy lekerekített sarkú téglalapot kihúz a készülék mélységére, majd logikai műveletekkel (CSG) kivágja belőle az előlapot, a gombokat, a hangszórókat és a csatlakozót. Az egész készüléket ezután néhány milliméterben megadott szám írja le, és egy új modell ezeknek a számoknak a módosítását jelenti, nem modellezést egy 3D programban.</p>
<p>Ez mindenhol hasznos, ahol sok hasonló készülékre van szükség, vagy a felhasználónak kell őket módosítania: konfigurátorok, mockupok, termékelőnézetek. Gyakorlati példa az <a href="https://ecranify.com" target="_blank" rel="noopener">Ecranify</a>, egy eszköz 3D mockupokhoz App Store és Google Play képernyőképekhez, amelyben egyetlen kész 3D modell sincs. Alább lépésről lépésre olvashatja a folyamatot, egy méréssel együtt, amely megmutatja, hol lehet a számításon a legtöbbet spórolni.</p>
<h2 id="amire-szuksege-lesz"><a class="heading-anchor" href="#amire-szuksege-lesz" aria-hidden="true" tabindex="-1">#</a>Amire szüksége lesz</h2>
<ul>
<li><a href="https://threejs.org" target="_blank" rel="noopener">Three.js</a> a jelenethez, kamerákhoz, fényekhez és főleg a geometriához. A geometria a Three.js-ben mesh, vagyis háromszögek halmaza.</li>
<li>A <a href="https://github.com/gkjohnson/three-bvh-csg" target="_blank" rel="noopener">three-bvh-csg</a> könyvtár a mesheken végzett logikai műveletekhez. Térbeli indexszel (BVH, bounding volume hierarchy) gyorsítja őket, így nem vet össze minden háromszöget minden másikkal.</li>
<li>A készülék leírása adatként, például JSON a ház méreteivel, a sarkok sugarával, a bevel méretével és az oldalakon lévő elemek listájával. Minden pozíciót a készülék közepétől mért milliméterben adjon meg.</li>
</ul>
<h2 id="1-lepes-huzza-ki-a-teglalapot-3d-be"><a class="heading-anchor" href="#1-lepes-huzza-ki-a-teglalapot-3d-be" aria-hidden="true" tabindex="-1">#</a>1. lépés: Húzza ki a téglalapot 3D-be</h2>
<p>Rajzoljon egy lekerekített sarkú 2D téglalapot valós méretben, például 163,4 × 78 mm-eset, és húzza ki (extrudálás) 8,75 mm mélységre. Éles élű lapot kap. Ilyen telefon nincs, ezért a kihúzáskor kapcsolja be a bevelt is, vagyis az él lekerekítését. A Three.js mindkettőt egy lépésben elvégzi.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Csak extrudálás</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="Szögletes lap lekerekített sarkok és élek nélkül" width="1200" height="800" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">+ sarkok és 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="Ugyanaz a lap lekerekített sarkokkal és élekkel" width="1200" height="800" loading="lazy" decoding="async" /></picture></div></div><figcaption>Ugyanaz a forma, két másik szám: a sarok sugara és a bevel mérete.</figcaption></figure>
<p>Egy dologra figyeljen: a bevel kifelé adódik hozzá. Ha pontos külső méretre van szüksége, csökkentse a 2D formát és a mélységet a bevel méretével.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">b = min(bevel, mélység / 2)
forma = lekerekített téglalap(szélesség - 2b, magasság - 2b, saroksugár - b)
ház = kihúz(forma, mélység - 2b, bevel b)</code></pre>
<p>Az ívek simaságát (tesszelláció) az határozza meg, hány szegmensre bontjuk az ívet. Több szegmens simább formát és több háromszöget jelent. Ez a szám később az egész számítás sebességét meghatározza, ezért a 7. lépésben visszatérünk rá.</p>
<h2 id="2-lepes-vagjon-melyedest-a-kijelzonek"><a class="heading-anchor" href="#2-lepes-vagjon-melyedest-a-kijelzonek" aria-hidden="true" tabindex="-1">#</a>2. lépés: Vágjon mélyedést a kijelzőnek</h2>
<p>A CSG (constructive solid geometry) egyszerűbb testekből rak össze testeket három művelettel: unió, különbség és metszet. A különbség úgy működik, mint egy fúró: az egyik test anyagot vesz el a másikból.</p>
<p>A telefon kijelzője nem egy síkban van a kerettel, hanem egy sekély mélyedésben ül. Ehhez készítsen a házról egy második, 1,4 mm-rel kisebb másolatot, hagyja meg belőle csak a 0,7 mm vastag elülső részt (metszet egy nagy téglatesttel), és vonja ki ezt a részt a házból. A keletkezett mélyedésbe ezután tegyen egy sötét kijelzőfelületet.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Vágótest</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="Telefonház kék, áttetsző vágótesttel az előlapon" width="1200" height="800" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Kivonás után</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="Ház kivágott mélyedéssel és benne sötét kijelzővel" width="1200" height="800" loading="lazy" decoding="async" /></picture></div></div><figcaption>A kék test az, amit kivonunk. Jobbra a kijelző már a mélyedésben ül.</figcaption></figure>
<p>Két gyakorlati részlet:</p>
<ul>
<li><strong>A metszethez használt téglatestnek tényleg nagynak kell lennie.</strong> Elég, ha a mélységet vágja, de szélességben és magasságban túl kell nyúlnia az egész készüléken. Ne a mélységből számolja: például a mélység 20-szorosa egy vékony, magas telefonnál kisebb, mint a magassága, és fent meg lent vágatlan keret marad.</li>
<li><strong>Nagyítsa meg a vágótestet néhány ezrelékkel.</strong> Ha két testnek pontosan azonos lapja van, a CSG nem tudja eldönteni, melyik oldal van belül. Az 1,002-es lépték ezt megelőzi, és szemmel nem látszik.</li>
</ul>
<h2 id="3-lepes-keszitse-elo-az-oldalso-elemeket-furokent"><a class="heading-anchor" href="#3-lepes-keszitse-elo-az-oldalso-elemeket-furokent" aria-hidden="true" tabindex="-1">#</a>3. lépés: Készítse elő az oldalsó elemeket fúróként</h2>
<p>Minden oldalsó elem ugyanaz a recept más fúróval. Egy gomb például három testből áll: egy a gombnál kicsit mélyebb lekerekített téglalapot kivonunk a keretből, a nyílás széle kis letörést kap, a nyílásba pedig egy valamivel kisebb test kerül, amely kissé kiáll a keretből.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Fúrók</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="Telefon oldala két kék gombvágó testtel" width="1200" height="800" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Kivágás és gomb</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="Telefon oldala kivágott nyílásokkal és behelyezett gombokkal" width="1200" height="800" loading="lazy" decoding="async" /></picture></div></div><figcaption>Két gomb a jobb oldalon kivonás előtt és után.</figcaption></figure>
<p>A hangszórók kis hengerek sorai, a csatlakozó egy lekerekített téglalap. Minden nyílásba tegyen valami sötétet: vékony korongot a rácsba, lapkát a csatlakozóba. Egy üres lyuk a vékony héjban hibának tűnik, a sötét kitöltésű lyuk hardvernek.</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="Telefon alsó éle két sor hangszórólyukkal és középen csatlakozóval" width="1200" height="800" loading="lazy" decoding="async" /></picture><figcaption>Hangszórólyukak és csatlakozó. Mind kivont hengerek és lekerekített téglalapok.</figcaption></figure>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">az oldalsó elemek listájának minden elemére:
    fúró = az elem formája (henger, lekerekített téglalap, ...)
    helyezd a fúrót az élre a mm-ben megadott pozíció szerint
    add hozzá a fúrót a vágások listájához
    ha az elemnek van kitöltése: add hozzá a kitöltést a jelenethez</code></pre>
<p>Egy gomb hozzáadása így csak egy új bejegyzés az adatokban. Az elemek gyorsan szaporodnak: egy iPad Pro 13″ 58 hangszórólyukkal, 4 gombbal, 2 antennával és egy csatlakozóval rendelkezik, ez több mint 60 kivonás egyetlen házból.</p>
<h2 id="4-lepes-az-elolap-reszleteit-ne-furja"><a class="heading-anchor" href="#4-lepes-az-elolap-reszleteit-ne-furja" aria-hidden="true" tabindex="-1">#</a>4. lépés: Az előlap részleteit ne fúrja</h2>
<p>A kamera kivágása, a kijelző tetején lévő sziget és a kezdőképernyő-jelző lapos. Geometriát vágni hozzájuk felesleges munka. Helyezze fel őket decalként, vagyis felületre vetített képként. Vetítse egy rejtett síkra, amely az egész előlapot lefedi, és a pozíciót ugyanabban a milliméterben adja meg, mint minden mást. Kivétel a régebbi iPhone-ok kerek kezdőgombja. Ennek valódi mélysége van, ami oldalról látszik, ezért CSG-n megy keresztül.</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">Próbálja ki</p><h2 class="app-box-title">Ecranify</h2><p>Böngészős eszköz fotorealisztikus 3D eszközmockupokhoz App Store- és Google Play-képernyőképekhez. WebGL-jegyzetek és újdonságok.</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="5-lepes-tegye-at-a-szamitast-web-workerbe"><a class="heading-anchor" href="#5-lepes-tegye-at-a-szamitast-web-workerbe" aria-hidden="true" tabindex="-1">#</a>5. lépés: Tegye át a számítást Web Workerbe</h2>
<p>A böngészőben a JavaScript egyetlen szálon fut. Ugyanez a szál számol, rajzolja az oldalt és reagál a kattintásokra. Amíg a CSG fut, az oldal megáll, és egy spinner sem segít, mert annak is ugyanez a szál kell.</p>
<p>Ezért az egész készülék összeállítását futtassa <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API" target="_blank" rel="noopener">Web Workerben</a>, vagyis egy külön háttérszálon. A worker megkapja a készülék leírását, kész csúcstömböket ad vissza, és az oldal folyamatos marad. Ha egy paraméter megváltozik, állítsa le a régi workert, és indítson újat, hogy ne számoljon végig egy elavult állapotot.</p>
<p>A worker eredménye eltart egy ideig, ezért adjon hozzá azonnali előnézetet. A szerkesztett elemet mutassa kék, áttetsző „szellemként”: egyszerű téglatestként vagy hengerként néhány tucat háromszöggel, amely a fő szálon azonnal felépíthető. Amikor a worker végez, a szellem eltűnik.</p>
<h2 id="6-lepes-vonja-ki-az-osszes-lyukat-egyszerre"><a class="heading-anchor" href="#6-lepes-vonja-ki-az-osszes-lyukat-egyszerre" aria-hidden="true" tabindex="-1">#</a>6. lépés: Vonja ki az összes lyukat egyszerre</h2>
<p>A legegyenesebb út az elemek egymás utáni kivonása: az 1. lyukat a házból, aztán a 2.-at az eredményből és így tovább. Minden kivonás azonban újraépíti az egész ház térbeli indexét, ami a számítás legdrágább része. Több tucat elemnél az idő nagyon gyorsan nő.</p>
<p>Jobb, ha előbb összegyűjti az adott test összes fúróját, egyetlen mesh-be egyesíti őket, és egyszer vonja ki.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">vágások = []
minden elemre: add hozzá a fúróját a vágásokhoz
egyesített = egyesítsd az összes vágást egy mesh-be
ház = ház mínusz egyesített
ha az egyesítés nem sikerül: vond ki a vágásokat egyenként</code></pre>
<p>Mérés a folyamat egyszerűsített másolatán (Three.js 0.170, three-bvh-csg 0.0.17, Node 22, felhőszerver 2 Intel Xeon 2,1 GHz-es maggal, 5 futás mediánja):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Test</th>
<th>Lyukak</th>
<th>Egyenként</th>
<th>Egyszerre</th>
</tr>
</thead>
<tbody><tr>
<td>telefon, 17 676 háromszög</td>
<td>25</td>
<td>2,47 s</td>
<td>0,26 s</td>
</tr>
<tr>
<td>tablet, 17 676 háromszög</td>
<td>58</td>
<td>5,55 s</td>
<td>0,89 s</td>
</tr>
<tr>
<td>tablet, 68 108 háromszög</td>
<td>58</td>
<td>22,8 s</td>
<td>3,1 s</td>
</tr>
</tbody></table>
</div>
<p>Egy feltétel: az egyesítés és egyetlen kivonás csak akkor adja ugyanazt az eredményt, mint a fokozatos kivonás, ha a fúrók nem fedik egymást. Az elemek különböző helyeken vannak, így ez általában teljesül. Ha viszont a felhasználó két elemet egymásra tol, az átfedés helyén apró hiba keletkezhet.</p>
<h2 id="7-lepes-a-szegmensszamot-tartsa-parameterkent"><a class="heading-anchor" href="#7-lepes-a-szegmensszamot-tartsa-parameterkent" aria-hidden="true" tabindex="-1">#</a>7. lépés: A szegmensszámot tartsa paraméterként</h2>
<p>A megjelenítéshez elég tesszelláció a CSG-hez gyakran feleslegesen sűrű. Saroknként 64 és a bevelen 32 szegmenssel a ház 68 108 háromszögből áll. A felét (32 és 16) egy 2 mm-es élen nem látni, de a ház így csak 17 676 háromszög, és a táblázat harmadik sora mutatja, mit jelent ez az időben.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">64 és 32 szegmens</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="Telefonsarok drótvázas modellje nagyon sűrű háromszöghálóval" width="1200" height="800" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">32 és 16 szegmens</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="Ugyanannak a saroknak a drótvázas modellje fele annyi szegmenssel" width="1200" height="800" loading="lazy" decoding="async" /></picture></div></div><figcaption>A ház sarka felülről. Jobbra a háromszögek negyede.</figcaption></figure>
<p>A legfeljebb 2 mm sugarú kis elemek (hangszórólyukak, csavarok) kapják a szegmensek felét, a galéria előnézetei még kevesebbet. A minőséget ezért ne konstansként írja a kódba, hanem az összeállítás paramétereként adja meg.</p>
<h2 id="rendereles-szerveren-videokartya-nelkul"><a class="heading-anchor" href="#rendereles-szerveren-videokartya-nelkul" aria-hidden="true" tabindex="-1">#</a>Renderelés szerveren videokártya nélkül</h2>
<p>Ha ugyanazt a jelenetet szerveren is renderelni kell, például az exportok tartalékaként, az képernyő nélküli (headless) Chrome-ban fut, amely videokártya híján szoftveresen rajzol. Ott minden pazarlás megsokszorozódik. Az Ecranifyban a legnehezebb tablet a fenti módosítások nélkül elérte a 15 perces időkorlátot. Egyesített vágásokkal, kisebb textúrákkal, alacsonyabb tesszellációval és a kész geometria cache-ével, amelynek kulcsa a készülékleírás hashe, egy renderelés nagyjából fél percig tart, egy ismételt pedig körülbelül 5 másodpercig. A részletek interaktív példákkal az <a href="https://ecranify.com/guides/how-we-build-3d-device-mockups" target="_blank" rel="noopener">ecranify.com angol nyelvű cikkében</a> olvashatók.</p>
<p>Ha hasonló 3D-s problémát old meg a böngészőben, vagy egyedi alkalmazásra van szüksége, <a href="https://smallwater.eu/hu/#kontakt">írjon nekünk</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hogyan vigyen át egy Unity 4-es projektet Unity 6-ra</title>
      <link>https://smallwater.eu/hu/blog/unity-4-to-unity-6-upgrade/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/unity-4-to-unity-6-upgrade/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Onager</category>
      <description>Unity 4-es projekt átvitele Unity 6-ra jelenetvesztés nélkül: szöveggé alakítás Unity 2019.4-ben, C#-port azonos GUID-dal és a régi HUD visszaállítása.</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="Blokkokból épült építmény egy Onager-pályán, tejüveg padlón" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#mi-tortenik-ha-unity-6-ban-nyitja-meg-a-projektet" data-ga="blog_toc_clicked" data-ga-heading-id="mi-tortenik-ha-unity-6-ban-nyitja-meg-a-projektet">Mi történik, ha Unity 6-ban nyitja meg a projektet</a></li><li><a href="#1-lepes-rogzitse-az-eredeti-allapotot" data-ga="blog_toc_clicked" data-ga-heading-id="1-lepes-rogzitse-az-eredeti-allapotot">1. lépés: Rögzítse az eredeti állapotot</a></li><li><a href="#2-lepes-szovegge-alakitas-unity-2019-4-gyel" data-ga="blog_toc_clicked" data-ga-heading-id="2-lepes-szovegge-alakitas-unity-2019-4-gyel">2. lépés: Szöveggé alakítás Unity 2019.4-gyel</a></li><li><a href="#3-lepes-az-eredmenyt-a-szerkesztoben-ellenorizze-ne-a-yaml-olvasasaval" data-ga="blog_toc_clicked" data-ga-heading-id="3-lepes-az-eredmenyt-a-szerkesztoben-ellenorizze-ne-a-yaml-olvasasaval">3. lépés: Az eredményt a szerkesztőben ellenőrizze, ne a YAML olvasásával</a></li><li><a href="#4-lepes-unityscriptrol-c-ra-ugyanazzal-a-guid-dal" data-ga="blog_toc_clicked" data-ga-heading-id="4-lepes-unityscriptrol-c-ra-ugyanazzal-a-guid-dal">4. lépés: UnityScriptről C#-ra ugyanazzal a GUID-dal</a></li><li><a href="#5-lepes-allitsa-vissza-a-hud-ot-az-eredeti-binaris-jelenetekbol" data-ga="blog_toc_clicked" data-ga-heading-id="5-lepes-allitsa-vissza-a-hud-ot-az-eredeti-binaris-jelenetekbol">5. lépés: Állítsa vissza a HUD-ot az eredeti bináris jelenetekből</a></li><li><a href="#az-atalakitas-utan-cserelje-le-a-regi-kerulomegoldasokat" data-ga="blog_toc_clicked" data-ga-heading-id="az-atalakitas-utan-cserelje-le-a-regi-kerulomegoldasokat">Az átalakítás után: cserélje le a régi kerülőmegoldásokat</a></li><li><a href="#amit-ez-a-modszer-nem-old-meg" data-ga="blog_toc_clicked" data-ga-heading-id="amit-ez-a-modszer-nem-old-meg">Amit ez a módszer nem old meg</a></li></ol></nav><p>Egy Unity 4-es projektet ne nyisson meg közvetlenül Unity 6-ban, és főleg ne mentsen benne semmit. A Unity 6 a régi bináris jeleneteket csak részben tölti be, a mentés pedig véglegessé teszi a veszteséget; a biztonságos út a Unity 2019.4-en át vezet, amely a régi formátumot olvasni és szövegként visszaírni is tudja, és csak utána jön a Unity 6.</p>
<p>Ez azokra a projektekre vonatkozik, amelyek jeleneteit és prefabjait bináris formátumban tárolták, szkriptjeit pedig UnityScriptben írták, abban a JavaScripthez hasonló nyelvben, amelyet a Unity egykor a C# mellett támogatott. Példaként az Onagert használjuk, egy katapultos fizikai játékot, amely Unity 4.1.2-ben, UnityScriptben készült. A projektben 126 szkript, 75 jelenet és 78 prefab volt. Az átvitel után Unity 6000.5-ön fut URP-vel (Universal Render Pipeline), és ingyen elérhető az <a href="https://smallwater.eu/onager-ios">App Store-ban</a> és a <a href="https://smallwater.eu/onager-android">Google Playen</a>.</p>
<h2 id="mi-tortenik-ha-unity-6-ban-nyitja-meg-a-projektet"><a class="heading-anchor" href="#mi-tortenik-ha-unity-6-ban-nyitja-meg-a-projektet" aria-hidden="true" tabindex="-1">#</a>Mi történik, ha Unity 6-ban nyitja meg a projektet</h2>
<p>A legegyszerűbb megközelítés megnyitni a projektet Unity 6-ban, és a szerializációt Force Textre állítani, vagyis az asseteket olvasható YAML-ként menteni. Ez azonban nem a fájlok fordítása. A Unity minden assetet a saját importerén keresztül tölt be, és YAML-ként írja vissza, így minden átmegy a Unity 6 betöltőjén. Ez minden jelenetnél egy GameObjecthez nem rendelt Transformot jelez, és törli a „lógó” komponenst. A mentés ezt véglegessé teszi.</p>
<p>A veszteség a binary2text eszközzel mérhető, amely a szerkesztővel együtt érkezik, és importer nélkül olvassa a szerializált fájlt. Az Onagernél a kimenetét összevetettük a keletkezett YAML-lel, és csak azokat a komponenseket számoltuk, amelyek a megmaradt GameObjectekről tűntek el:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Komponens</th>
<th align="right">Elveszett</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>A 75 jelenetből egy sem úszta meg veszteség nélkül. Teljes HUD-kamerák tűntek el, a fő kamera helyére pedig egy új, alapértelmezett kamera került. A lényeg: a kár már betöltéskor keletkezik, nem mentéskor. Amíg a projekt nincs átalakítva, a Unity 6 nem mentheti el, egyetlen jelenetet és egyetlen prefabot sem.</p>
<h2 id="1-lepes-rogzitse-az-eredeti-allapotot"><a class="heading-anchor" href="#1-lepes-rogzitse-az-eredeti-allapotot" aria-hidden="true" tabindex="-1">#</a>1. lépés: Rögzítse az eredeti állapotot</h2>
<p>Mielőtt bármihez hozzányúlna, készítsen képernyőképet minden jelenetről, és minden eredeti jelenetfájlról mentsen egy kiírást a binary2text eszközzel. Ezekhez a kiírásokhoz hasonlítja a kamerákat a 3. lépésben, és ezekből állítja vissza a HUD-ot az 5. lépésben. Az eredeti bináris fájlokat hagyja a git előzményeiben, ne törölje őket.</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="Az első pálya Unity 6-ban a remaster előtt" width="1280" height="720" loading="lazy" decoding="async" /></picture><figcaption>Az Onager első pályája Unity 6-ban, még a régi renderelővel és az eredeti anyagokkal. Minden jelenetet így rögzítsen, mielőtt bármihez hozzányúlna.</figcaption></figure>
<h2 id="2-lepes-szovegge-alakitas-unity-2019-4-gyel"><a class="heading-anchor" href="#2-lepes-szovegge-alakitas-unity-2019-4-gyel" aria-hidden="true" tabindex="-1">#</a>2. lépés: Szöveggé alakítás Unity 2019.4-gyel</h2>
<p>A Unity 2019.4 az utolsó szerkesztő, amely a Unity 4.1 formátumát olvasni és írni is tudja. Az átalakítást zárt szerkesztővel, a repó külön másolatában végezze:</p>
<ul>
<li><strong>Projektverzió.</strong> A projekt verzióját tároló fájlban állítsa be az eredeti verziót (az Onagernél 4.1.2), hogy a szerkesztő a régi projektekre szolgáló utat válassza. A Unity megnyitáskor felülírja, elég, ha az első importnál érvényes.</li>
<li><strong>Üres csomagok.</strong> Ürítse ki a csomagfüggőségek listáját. Egyetlen fordítási hiba azt jelenti, hogy a szerkesztőszkriptek egyik menüpontja sem működik, és ez úgy néz ki, mintha a menü meg sem jelent volna.</li>
<li><strong>Import a nulláról.</strong> Törölje a Library mappát, és hagyja a projektet importálni.</li>
<li><strong>Force Text és kényszerített újraírás.</strong> A Force Textre váltás után a Unity 2019.4 magától nem írja újra a meglévő fájlokat. Ezt az assetek újraszerializálására szolgáló függvénnyel kell kiváltani, mégpedig csak az assetekre, a meta fájljaikra nem. Az összes meta fájl újraírása után a Unity 6 újra és újra importál.</li>
</ul>
<p>Egy csapda a gitben van. Ha a git attribútumai a jeleneteket LF sorvégű szövegként jelölik, a git a repóba való felvételkor kiveszi a bináris jelenetekből a sorvégnek látszó bájtokat. Az Onagernél ez nyolc jelenetet rongált meg. A Unity assetjeire ne tegyen sorvég-attribútumot, a Unity úgyis LF-et ír.</p>
<h2 id="3-lepes-az-eredmenyt-a-szerkesztoben-ellenorizze-ne-a-yaml-olvasasaval"><a class="heading-anchor" href="#3-lepes-az-eredmenyt-a-szerkesztoben-ellenorizze-ne-a-yaml-olvasasaval" aria-hidden="true" tabindex="-1">#</a>3. lépés: Az eredményt a szerkesztőben ellenőrizze, ne a YAML olvasásával</h2>
<p>Az átalakított fájlok olvasása magabiztos, de hibás következtetésekhez vezet. A prefabpéldányok rövidített bejegyzései veszteségnek látszanak, ahogy az a kamera is, amely csak átkerült egy prefabba. Az számít, amit a Unity a memóriában felépít. Minden átalakított jelenetet nyisson meg, és a kameráit vesse össze az eredeti 1. lépésben mentett kiírásával.</p>
<p>Az Onagernél az átalakítás után mind a 75 jelenetben ugyanazok a kamerák maradtak, ugyanabban a sorrendben. Csak az veszett el, amit ma már egyetlen szerkesztő sem ismer: 569 GUITexture, 179 GUIText és a régi részecskerendszerek, amelyeket a Unity 2018-ban eltávolított.</p>
<h2 id="4-lepes-unityscriptrol-c-ra-ugyanazzal-a-guid-dal"><a class="heading-anchor" href="#4-lepes-unityscriptrol-c-ra-ugyanazzal-a-guid-dal" aria-hidden="true" tabindex="-1">#</a>4. lépés: UnityScriptről C#-ra ugyanazzal a GUID-dal</h2>
<p>A Unity <a href="https://blog.unity.com/community/unityscripts-long-ride-off-into-the-sunset" target="_blank" rel="noopener">a 2017.2-es verziótól fokozatosan vezette ki a UnityScriptet</a>, így a kézi port C#-ra az egyetlen út. A jelenetek a szkriptekre a GUID-dal hivatkoznak, ez egy egyedi azonosító, amelyet a Unity minden asset meta fájljában tárol, a mezők értékét pedig a mező neve szerint tárolják. Ha a port során mindkettőt megtartja, minden hivatkozás magától visszakapcsolódik:</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">minden X UnityScript-szkripthez:
  hozz létre egy C#-szkriptet pontosan X nevű osztállyal
  másold át a GUID-ot a régi szkript meta fájljából az újéba
  publikus mezők: ugyanaz a név, ugyanaz a típus, elírásokkal együtt
  semmit ne nevezz át, amíg a projekt nem fut</code></pre>
<p>Az Onagernél így mind a 126 szkript portja után 12 935 komponens kapcsolódott vissza 73 jelenetben, és az Inspectorban beállított összes érték megmaradt.</p>
<p>Szöveges kereséssel nem deríthető ki, mely szkriptek vannak használatban. A bináris prefab a GUID-ot bájtokként tárolja, nem szövegként. Az Onagernél így huszonegy szkript tűnt használatlannak, és mindegyik élt. Figyeljen a korábbi portkísérletek maradványaira is: egy fakocka prefabja egy ilyen kísérlet két szkriptjére mutatott, és egyedül miatta hiányzott 1140 komponens. Ha egy bináris fájlban GUID-ot kell cserélnie, cserélje közvetlenül a bájtokban, tizenhatot tizenhatra, hogy a fájl ne csússzon el.</p>
<h2 id="5-lepes-allitsa-vissza-a-hud-ot-az-eredeti-binaris-jelenetekbol"><a class="heading-anchor" href="#5-lepes-allitsa-vissza-a-hud-ot-az-eredeti-binaris-jelenetekbol" aria-hidden="true" tabindex="-1">#</a>5. lépés: Állítsa vissza a HUD-ot az eredeti bináris jelenetekből</h2>
<p>A GUITexture és a GUIText nincs meg a Unity 6-ban, és velük együtt eltűnik a rájuk épülő teljes HUD. Újrarajzolni nem kell. A binary2text a git előzményeiből származó eredeti bináris jeleneteket akkor is beolvassa, ha az osztályok már nem léteznek. Nyerjen ki belőlük minden HUD-képet és -szöveget textúrával, színnel, pozícióval és igazítással, és szkripttel tegye vissza őket a jelenetek ugyanazon objektumaira. Az Onagernél ez 577 kép és 180 szöveg volt.</p>
<p>Így azok a szövegek is megmenekülnek, amelyek sehol máshol nincsenek meg. Az Onagernél ez 111 szöveg volt: minden tutorialfelirat és a teljes stáblista. Ha a HUD-ot a nulláról írná meg, ki kellene találnia őket. A színek átvitelénél számoljon valamivel, amiről a dokumentáció hallgat: a régi GUITexture rajzoláskor megduplázta a színt. Mind az 577 kép színe pontosan 0,5 volt, vagyis a szürke fehéret jelentett.</p>
<h2 id="az-atalakitas-utan-cserelje-le-a-regi-kerulomegoldasokat"><a class="heading-anchor" href="#az-atalakitas-utan-cserelje-le-a-regi-kerulomegoldasokat" aria-hidden="true" tabindex="-1">#</a>Az átalakítás után: cserélje le a régi kerülőmegoldásokat</h2>
<p>A régi projektek gyakran megkerülték az akkori Unity korlátait, és ezeket a kerülőmegoldásokat az átalakítás után érdemes újraépíteni:</p>
<ul>
<li><strong>Tükröződés a padlón.</strong> Az Onagerben fényes padló volt valódi tükröződés nélkül. A padló alatt egy második, tükrözött világ állt: minden objektum másolata, amelynek pozícióját egy szkript minden harmadik képkockában átmásolta az eredetiről, egy pályán 50-70 klón. Ez helyettesíthető egyetlen további kamerával, amely a tükröződést fél felbontásban rajzolja. Ekkor a részecskék és a törmelék is tükröződnek, amire a klónozott világ nem volt képes.</li>
<li><strong>Célzás.</strong> Ha a röppálya előnézete azt próbálja megjósolni, hová küldi a lövést az első fizikai lépésben hozzáadott erő, a lövés máshová esik, mint a jelölő. Megbízhatóbb egyszer kiszámolni a sebességet, az előnézetet pontosan abból rajzolni, és közvetlenül a lövéshez rendelni.</li>
<li><strong>Teljesítmény.</strong> Gyenge telefonon mérjen. Egy Motorola G52-n az Onager csak a falak shadere miatt 20-ról 6 fps-re esett, ez több volt, mint a felbontás, az árnyékok és a tükröződés együtt. Ezért alacsony és közepes részletességnél a falak egyszerűbb anyagot kapnak.</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="A gravitációs lövés az átalakított Onagerben: egy gömb felemeli a blokkokat, körülötte örvény és kék fény."><source src="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/gravity-shot.mp4" type="video/mp4" /></video><figcaption>A gravitációs lövés az átalakított Onagerben: egy gömb felemeli a blokkokat, körülötte örvény és kék fény.</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">Próbálja ki</p><h2 class="app-box-title">Onager</h2><p>3D fizikai katapultos fejtörő: 60 pályányi rombolás. Fizika, pályatervezés és kiadási jegyzetek.</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="A szivárványlövés: fehér pontok lépnek ki közvetlenül az eltalált blokk felületéből."><source src="https://smallwater.eu/blog/unity-4-to-unity-6-upgrade/rainbow-shot.mp4" type="video/mp4" /></video><figcaption>A szivárványlövés: fehér pontok lépnek ki közvetlenül az eltalált blokk felületéből.</figcaption></figure>
<p>Ha a mai Unityben több ezer objektum fizikáját kell megoldania, nézze meg a <a href="https://smallwater.eu/hu/blog/fake-ragdoll-unity-dots/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/unity-4-to-unity-6-upgrade/" data-ga-destination="/hu/blog/fake-ragdoll-unity-dots/" data-ga-placement="inline">több ezer ellenségre szabott hamis ragdollt a Unity DOTS-ban</a> is. Hogy átállás után hogyan kaphat egy régi 2D játék glow-t és 2D fényeket URP-ben, azt egy <a href="https://smallwater.eu/hu/blog/dreamica-remaster-unity-urp-glow/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/unity-4-to-unity-6-upgrade/" data-ga-destination="/hu/blog/dreamica-remaster-unity-urp-glow/" data-ga-placement="inline">külön útmutató</a> írja le.</p>
<h2 id="amit-ez-a-modszer-nem-old-meg"><a class="heading-anchor" href="#amit-ez-a-modszer-nem-old-meg" aria-hidden="true" tabindex="-1">#</a>Amit ez a módszer nem old meg</h2>
<p>Azok a komponensek, amelyeket a mai Unity nem ismer, nem kapcsolódnak vissza. A GUITexture-t és a GUIText-et az 5. lépés szerint kell visszaállítani, a régi részecskerendszerek elvesznek. Az osztályok és mezők neve pontosan olyan marad, mint az eredeti projektben, elírásokkal együtt és azon a nyelven, amelyen valaki egykor megírta őket. A jelenetek név szerint kötődnek hozzájuk, ezért átnevezni csak a szerkesztőn keresztül érdemes, amely maga írja át a hivatkozásokat.</p>
<p>Ha van egy régi Unity-projektje, amelyet egy mai verzióra kell átvinni, <a href="https://smallwater.eu/hu/#kontakt">írjon nekünk</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hogyan navigáljunk több ezer ellenséget Unity DOTS-ban</title>
      <link>https://smallwater.eu/hu/blog/unity-dots-horde-navigation/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/unity-dots-horde-navigation/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Tower Defense</category>
      <description>Útmutató több ezer ellenség mozgatásához Unity DOTS-ban: flow field, taszítás a szomszédoktól és falaktól, sűrűségfüggő költség, dugók és teljesítmény.</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="Világos pontokból álló ellenséghorda kerüli meg az akadályokat a cél felé, alatta a flow field nyilai és a tömegsűrűség piros térképe" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#1-lepes-flow-field-egysegenkenti-utvonal-helyett" data-ga="blog_toc_clicked" data-ga-heading-id="1-lepes-flow-field-egysegenkenti-utvonal-helyett">1. lépés: Flow field egységenkénti útvonal helyett</a></li><li><a href="#2-lepes-kulon-mezo-minden-ellensegmerethez" data-ga="blog_toc_clicked" data-ga-heading-id="2-lepes-kulon-mezo-minden-ellensegmerethez">2. lépés: Külön mező minden ellenségmérethez</a></li><li><a href="#3-lepes-taszitas-a-szomszedoktol" data-ga="blog_toc_clicked" data-ga-heading-id="3-lepes-taszitas-a-szomszedoktol">3. lépés: Taszítás a szomszédoktól</a></li><li><a href="#4-lepes-taszitas-a-falaktol" data-ga="blog_toc_clicked" data-ga-heading-id="4-lepes-taszitas-a-falaktol">4. lépés: Taszítás a falaktól</a></li><li><a href="#5-lepes-surusegfuggo-koltseg-es-lassitas" data-ga="blog_toc_clicked" data-ga-heading-id="5-lepes-surusegfuggo-koltseg-es-lassitas">5. lépés: Sűrűségfüggő költség és lassítás</a></li><li><a href="#6-lepes-dugo-a-szukuletben-varni-nem-tolni" data-ga="blog_toc_clicked" data-ga-heading-id="6-lepes-dugo-a-szukuletben-varni-nem-tolni">6. lépés: Dugó a szűkületben: várni, nem tolni</a></li><li><a href="#7-lepes-nagy-ellensegek-megelozese" data-ga="blog_toc_clicked" data-ga-heading-id="7-lepes-nagy-ellensegek-megelozese">7. lépés: Nagy ellenségek megelőzése</a></li><li><a href="#8-lepes-slotok-a-tamadas-celpontja-korul" data-ga="blog_toc_clicked" data-ga-heading-id="8-lepes-slotok-a-tamadas-celpontja-korul">8. lépés: Slotok a támadás célpontja körül</a></li><li><a href="#a-szimulacio-es-a-rendereles-teljesitmenye" data-ga="blog_toc_clicked" data-ga-heading-id="a-szimulacio-es-a-rendereles-teljesitmenye">A szimuláció és a renderelés teljesítménye</a></li><li><a href="#a-modszer-korlatai" data-ga="blog_toc_clicked" data-ga-heading-id="a-modszer-korlatai">A módszer korlátai</a></li></ol></nav><p>Több ezer ellenségből álló hordát Unity DOTS-ban egyetlen, az egész pályára kiterjedő flow fielddel és néhány olcsó helyi szabállyal irányítson: taszítás a szomszédoktól és a falaktól, a tömegsűrűségtől függő útköltség és egy „várok” állapot a dugóban. A Unity Editorban egy M3 Pro-s MacBookon ez nagyjából 9600 ellenséget bír egyszerre 90 FPS mellett.</p>
<p>A probléma tower defense, stratégiai és survival játékokban jelentkezik, ahol egyszerre több ezer egység tart egy cél felé. A klasszikus, egységenkénti útkeresés túl drága, helyi szabályok nélkül pedig a tömeg átfedi egymást, a falakhoz tapad és elakad a szűkületekben. A Unity DOTS (Data-Oriented Technology Stack, azaz Entities, ECS és Job System) megadja a teljesítményt több ezer entityhez, a navigációt viszont nem oldja meg. Az alábbi módszert a PC-re készülő tower defense játékunkban használjuk (szerepel a <a href="https://smallwater.eu/hu/#referencie">referenciáink</a> között). A képek egy egyszerűsített, ugyanazokkal a szabályokkal futó 2D szimulációból származnak, nem a játékból.</p>
<h2 id="1-lepes-flow-field-egysegenkenti-utvonal-helyett"><a class="heading-anchor" href="#1-lepes-flow-field-egysegenkenti-utvonal-helyett" aria-hidden="true" tabindex="-1">#</a>1. lépés: Flow field egységenkénti útvonal helyett</h2>
<p>A flow field egy az egész pályát lefedő rács, amelyben minden cella tudja, merre kell belőle a cél felé menni. Az ellenség nem keres utat, csak leolvassa az irányt azon a helyen, ahol áll. A költség 10 és 10 000 ellenségnél is ugyanannyi.</p>
<p>Ossza fel a pályát 1 m-es cellákra (a tesztpályánk 128 × 128-as), és minden célhoz egyszer számolja ki minden cella távolságát a céltól. A statikus mezőt elég a szerkesztőben, a pálya előkészítésekor kiszámolni és egy assetben tárolni.</p>
<p>A legegyszerűbb változat a Dijkstra 8 szomszéddal, minden cellában a 8 irány egyikével. Működik, de az ellenségek átlósan mennek, és libasorban a falakhoz tapadnak.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">8 irány</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="Nyolcirányú flow field, az ellenségek útvonalai átlósan haladnak és az akadály széléhez tapadnak" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Sima mező</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="Sima flow field, az útvonalak ívben futnak össze az akadály sarka körül, távol a faltól" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div></div><figcaption>Ugyanaz a pálya, magányos ellenségek útvonalai. Balra 8 irány, jobbra sima mező, falak mellett felárral.</figcaption></figure>
<p>Jobb eredményt ad a sima mező. A távolságot az <a href="https://en.wikipedia.org/wiki/Eikonal_equation" target="_blank" rel="noopener">eikonál-egyenlet</a> megoldásával számolja ki (hullámfront-egyenlet, amelynek megoldása a folytonos térben mért legrövidebb távolság, nem csak a rács élei mentén), a fast sweeping módszerrel, amely a rácsot ismételten mind a négy sarokból bejárja. Az irány így nem a cella iránya, hanem a távolság gradiense az ellenség pontos helyén. A cella költségéhez adjon felárat a fal közelségéért, így a mező magától távol tartja a tömeget az akadályoktól.</p>
<h2 id="2-lepes-kulon-mezo-minden-ellensegmerethez"><a class="heading-anchor" href="#2-lepes-kulon-mezo-minden-ellensegmerethez" aria-hidden="true" tabindex="-1">#</a>2. lépés: Külön mező minden ellenségmérethez</h2>
<p>A nagy ellenség nem fér be mindenhová, ahová a kicsi. Vezessen be méretosztályokat: mindegyiknek saját járhatósága van (a cellának elég messze kell lennie a faltól), és minden célhoz saját mezője. Az osztályokat pályaadatként tartsa, ne a kódban. Nálunk három van.</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="Pálya keskeny folyosóval: a kis ellenségek átmennek rajta, a nagyobb narancssárgák alul kerülik meg" width="1792" height="1008" loading="lazy" decoding="async" /></picture><figcaption>A kis ellenségek a keskeny folyosón mennek, a nagyok (narancssárgák) nem férnek be, ezért megkerülik.</figcaption></figure>
<h2 id="3-lepes-taszitas-a-szomszedoktol"><a class="heading-anchor" href="#3-lepes-taszitas-a-szomszedoktol" aria-hidden="true" tabindex="-1">#</a>3. lépés: Taszítás a szomszédoktól</h2>
<p>A flow field megadja az irányt, de a szomszédokról semmit sem tud. Az alap egy egyszerűsített boids modell (a raj klasszikus szabályai): minden ellenség taszítja a 0,9 m-en belüli szomszédait. A szomszédokat spatial hashben keresse, vagyis egy térbeli rácsban, ahol minden cella a benne lévő egységek listáját tartja. A hasht minden tickben építse fel újra és rendezze, hogy a sorrend determinisztikus legyen.</p>
<p>A taszítás önmagában nem elég. 3000 ellenségnél 83,7 %-uk legközelebbi szomszédja 0,8 m-nél közelebb volt, a legkisebb távolság pedig 4 cm, vagyis két ellenség szinte egymásban állt.</p>
<p>A taszítás súlyának növelése nem oldja meg. 1,5 helyett 6-os súlynál a túl szorosan állók aránya 54,6 %-ra esett, 12-nél 44,3 %-ra, 24-nél már nem javult (48,2 %). Ráadásul a tömeg ugrálni kezd, mert sok szomszéd korlátlan lökései egyszerre adódnak össze.</p>
<p>A pozíciókorrekció segít. Mozgás után minden átfedő párt toljon szét, mindkettőt az átfedés felével, és tickenként korlátozza az eltolást 0,15 m-re. Korlát nélkül a korrekció ugyanúgy ugrál, mint a nagy súly. Vele a túl szorosak aránya 60,8 % marad, de a legkisebb távolság 28 cm-re nő, és a tömeg nem ugrál.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Csak taszítás</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="Tömeg egy szűkületnél, az ellenségek nagy része piros, átfedik egymást" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Átfedéskorrekció</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="Ugyanaz a tömeg átfedéskorrekcióval, pirosak csak közvetlenül a szűkület előtt maradtak" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div></div><figcaption>A pirosak több mint 20 cm-rel fedik át a szomszédjukat. Nyílt terepen a korrekció segít, a szűkület előtt nem.</figcaption></figure>
<h2 id="4-lepes-taszitas-a-falaktol"><a class="heading-anchor" href="#4-lepes-taszitas-a-falaktol" aria-hidden="true" tabindex="-1">#</a>4. lépés: Taszítás a falaktól</h2>
<p>A falnál az ellenségek közötti korrekció csődöt mond. A falba mozgás tilos (az eltolás tengelyenként bomlik fel, a falba mutató összetevő elvész), így a falhoz szorult ellenség nem tud kitérni, és a szomszédtól jövő korrekció elvész. 938 átfedő ellenségből csak 35-nél volt egyáltalán nyomás a fal felől.</p>
<p>Ezért adjon hozzá taszítást a legközelebbi járhatatlan cellák felől, 4 irányban és átlósan is. Két hiba, amelyet elkerülhet:</p>
<ul>
<li><strong>Belső sarok.</strong> A két él és a sarok nyomása összeadódik, és elhajítja az ellenséget. Sarokból jövő nyomást csak külső saroknál adjon hozzá.</li>
<li><strong>Valódi falak.</strong> A valódi falaktól taszítson, ne a méretosztály járhatóságától. Egy nagy ellenség számára a „fal” már egy méterrel a valódi előtt kezdődik. Egy folyosó bejáratánál így a sarok nyomása kioltotta egy nagy ellenség előrehaladását, és a mögötte haladó egész csoport megállt.</li>
</ul>
<h2 id="5-lepes-surusegfuggo-koltseg-es-lassitas"><a class="heading-anchor" href="#5-lepes-surusegfuggo-koltseg-es-lassitas" aria-hidden="true" tabindex="-1">#</a>5. lépés: Sűrűségfüggő költség és lassítás</h2>
<p>Sima mezővel is az egész tömeg a legrövidebb úton, szorosan a sarok mellett megy, függetlenül attól, hányan vannak már ott. Megoldást a <a href="https://grail.cs.washington.edu/projects/continuum-crowds" target="_blank" rel="noopener">Continuum Crowds</a> (Treuille és mtsai, 2006) kínál: a cella költsége a tömegsűrűséggel nő. 10 tickenként számolja meg az ellenségeket cellánként, simítsa ki az eredményt, és számolja újra egy cél mezőjét. Zsúfolt saroknál a mező így körbeküldi az ellenséget, a szabadabb cellákon át.</p>
<figure class="post-compare"><div class="compare-grid"><div class="compare-item"><span class="compare-label">Sűrűség nélkül</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="A tömeg szorosan a sarok mellett, keskeny áramban kerüli meg az akadályt, a piros sűrűségtérkép koncentrált" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div><div class="compare-item"><span class="compare-label">Sűrűségfüggő költség</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="A tömeg széles ívben kerüli meg az akadályt, a sűrűség eloszlik" width="1792" height="1008" loading="lazy" decoding="async" /></picture></div></div><figcaption>Ugyanaz a tömeg ugyanazon sarok körül. A piros a kisimított tömegsűrűség.</figcaption></figure>
<p>A másik fele a lassítás. A sűrű cellában lévő ellenség lassabban megy, legfeljebb a sebessége 15 %-ára lassul. 2250 ellenségnél a ritka cellákban átlagosan a sebességük 94 %-ával haladtak, közepes sűrűségnél 71 %-kal, dugóban 20 %-kal. A lassítás kezdődjön korán: cellánként 1 helyett 0,6 ellenséges szabad sűrűségi küszöbbel a túl szorosan állók aránya a szűkületben 36,7 %-ra esett.</p>
<p>Gyakorlati tanács: a csak futásidőben létrejövő tömböket (például a sűrűségtérképet) foglalja le, mielőtt egy job először használná őket. Egy még létre nem hozott tömbbel ütemezett job natívan összeomlasztja a Unity Editort, mindenféle exception nélkül.</p>
<h2 id="6-lepes-dugo-a-szukuletben-varni-nem-tolni"><a class="heading-anchor" href="#6-lepes-dugo-a-szukuletben-varni-nem-tolni" aria-hidden="true" tabindex="-1">#</a>6. lépés: Dugó a szűkületben: várni, nem tolni</h2>
<p>Ha a tömeg sehová sem tud szétterülni, marad a szűkület. A legegyszerűbb ötlet az, hogy eldobjuk a sebesség azon összetevőjét, amely az érintett szomszéd felé mutat. A hátsók ekkor kitérés helyett megállnak, és fenntartják a dugót.</p>
<p>Oldalra kitérni, például 35°-kal a kevesebb szomszéd felé, jobb, de a tömeg villogni kezd. A fordulás simítása és alacsonyabb fordulási sebesség (360 helyett 180 °/s) csak tompítja a tünetet. Az ok máshol van: kiegyenlített sűrűségnél az ellenség minden tickben más oldalt választ. A hiszterézis segít, vagyis az előző döntés előnyben részesítése: az előző tick oldala az összehasonlításnál 3 szomszédnyi kedvezményt kap.</p>
<p>A végső megoldás érintkezési korlátot és három állapotot kapcsol össze: halad, kitér, vár. Az érintett szomszéd felé mutató sebességösszetevő elvész, az oldalirányú megmarad. Akinek nincs szabad oldala és maga előtt sincs helye, az vár, és nem tol.</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">minden ellenségre, minden tickben:
    sebesség = irány a flow fieldből × tempó × sűrűség szerinti lassítás
    sebesség += taszítás a szomszédoktól
    ha elöl szomszédhoz ér:
        ha van szabad oldal: kitér (oldal hiszterézissel)
        különben: vár
    eldobja az érintett szomszédok felé mutató sebességösszetevőt
    elmozdul, a falba mutató összetevőt eldobja
utána mindenkire:
    az átfedő párokat széttolja (az átfedés fele, legfeljebb 0,15 m)
    taszít a falaktól</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="Tömeg a szűkület előtt, a fehér pontok haladnak, a pirosak várnak, a szűkület mögött térközös sor halad" width="1792" height="1008" loading="lazy" decoding="async" /></picture><figcaption>A szűkület előtt: a fehérek haladnak, a pirosak várnak. Mögötte térközös sor halad tovább.</figcaption></figure>
<p>Három dologra figyeljen:</p>
<ul>
<li><strong>Türelem.</strong> Ha két ellenség szemből találkozik, a nagyobb azonosítójú (ID) várjon. Egy szomszédlánc azonban ezernyi tickig blokkolhatja, ezért a várakozási korlát után vészkitéréssel próbálkozik.</li>
<li><strong>Nulla sebesség.</strong> A nulla kívánt sebességű ellenség soha nem lép ki a „vár” állapotból, és egy hullám után a nyílt terepen állva marad. Aki nem akar mozogni, legyen mindig „halad” állapotban.</li>
<li><strong>Keskeny kúp.</strong> Az elöl lévő szomszédot keskeny kúpban, rövid szondával keresse. 45°-nál a beállítás nyílt terepen is a tömeg 55-60 %-át küldte várakozásba vagy kitérésbe, 30°-nál és rövidebb szondával 41 %-át.</li>
</ul>
<h2 id="7-lepes-nagy-ellensegek-megelozese"><a class="heading-anchor" href="#7-lepes-nagy-ellensegek-megelozese" aria-hidden="true" tabindex="-1">#</a>7. lépés: Nagy ellenségek megelőzése</h2>
<p>A különböző méretű és sebességű ellenségtípusok újabb eseteket hoznak. A nagy, lassú ellenségünk sugara a tömegben 1,25 m, sebessége 1,1 m/s. A mögötte haladó gyors ellenség nem tudta megelőzni: az átlósan előre néző oldalsó szondák mindig közelebb voltak a nagyhoz, mint a sugarak összege, mindkét oldal foglaltnak bizonyult, és a gyors várt. A javítás: a megkerült szomszédot a szonda csak a saját oldalán veszi figyelembe. A pontosan előtte álló szomszéd egyik oldalt sem blokkolja.</p>
<p>A nagy ellenség viszont oldalra löki útjából a kicsiket, a maga előtti ±45°-os szektorban. Ne lökje el azokat, akik gyorsabban haladnak az ő irányában, mint amennyire ő akar, különben azokat is ellöki, akik éppen megelőzik.</p>
<h2 id="8-lepes-slotok-a-tamadas-celpontja-korul"><a class="heading-anchor" href="#8-lepes-slotok-a-tamadas-celpontja-korul" aria-hidden="true" tabindex="-1">#</a>8. lépés: Slotok a támadás célpontja körül</h2>
<p>Ha az ellenségek közelharcban támadnak, hely kell nekik a célpont körül. A tornyunknak 24 slotja van az 5 × 5 m-es alapterület kerületén, minden ellenség egyet foglal el, a többiek sorban várnak.</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="Torony 24 slottal a kerületén, a legtöbb foglalt, egy sárga útvonal ívben megkerüli a tornyot egy szabad slothoz" width="1408" height="960" loading="lazy" decoding="async" /></picture><figcaption>A foglalt slotok támadnak, a szabadok szaggatottak. Egy új ellenség ívben megkerüli a tornyot egy szabad slothoz, a teli oldal mögött sor vár.</figcaption></figure>
<ul>
<li><strong>Körben kerülés.</strong> Ha csak akkor kínál fel slotot, ha egyenes vonal vezet hozzá, a hátsó slotok üresek maradnak, mert elölről a hozzájuk vezető vonal átmetszi a tornyot. Az ellenség ívben kerülje meg a tornyot a szabad slotig.</li>
<li><strong>Kiszorítás nélkül.</strong> Ne szorítsa ki a slot felé tartó ellenséget egy másik jelentkező javára, mert kiszorítások láncolatát indítja el.</li>
<li><strong>Taszítás nélkül a slotnál.</strong> A foglalt slot mellé érkező ellenséget a szomszédja ellöki, így 13 cm-re a céljától vagy teljesen mellette köt ki. A slotban lévő és tőle 0,5 m-en belüli ellenségek nem taszítják egymást.</li>
</ul>
<h2 id="a-szimulacio-es-a-rendereles-teljesitmenye"><a class="heading-anchor" href="#a-szimulacio-es-a-rendereles-teljesitmenye" aria-hidden="true" tabindex="-1">#</a>A szimuláció és a renderelés teljesítménye</h2>
<p>Egy benchmark a Unity Editorban (M3 Pro, nagyjából 9600 egyszerű ellenség, két szűkület) 1440p-n 90 FPS-t, 1080p-n 100-at adott. A felbontás alig változtat az FPS-en, az időgyorsítás viszont igen: 10×-nél 20 FPS-re esik, mert egy képkockára tíz szimulációs lépés jut. Normál sebességnél a szimulációnak van tartaléka.</p>
<p>Kapszula helyett részletes ellenségmodellel a szűk keresztmetszet a renderelésbe kerül át:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Változat</th>
<th align="right">Egyszerre</th>
<th align="right">Háromszög modellenként</th>
<th align="right">FPS medián</th>
</tr>
</thead>
<tbody><tr>
<td>Kapszula</td>
<td align="right">2612</td>
<td align="right">844</td>
<td align="right">197</td>
</tr>
<tr>
<td>Részletes modell, árnyékkal</td>
<td align="right">2976</td>
<td align="right">3578</td>
<td align="right">48</td>
</tr>
<tr>
<td>Részletes modell, árnyék nélkül</td>
<td align="right">2976</td>
<td align="right">3578</td>
<td align="right">85</td>
</tr>
<tr>
<td>Egyszerűsített modell, árnyékkal</td>
<td align="right">2945</td>
<td align="right">878</td>
<td align="right">335</td>
</tr>
</tbody></table>
</div>
<p>A szimulációban három dolog térül meg:</p>
<ul>
<li><strong>Két szomszédkeresési menet.</strong> Az olcsó (érintkezések, blokkolás) mindig fut, a drága (oldalsó szondák, oldalak sűrűsége) csak a blokkoltaknál, akik szabad áramlásban kisebbségben vannak. A távolságokat négyzetesen hasonlítsa össze, gyökvonás nélkül.</li>
<li><strong>Egyszerű rendezés.</strong> A spatial hash szinkron rendezése 3000, 10 000, 20 000 és 50 000 bejegyzésnél 0,37, 1,49, 3,16 és 8,46 ms-ig tartott, a jobos változat 0,18, 0,93, 3,12 és 15,53 ms-ig. A job nem következetesen gyorsabb, az egyszerű rendezés elég.</li>
<li><strong>Állapothash.</strong> A 600. és az 1800. tickben számoljon hasht a pozíciókból és az életerőből. Egy refaktornak ugyanazt a hasht kell adnia, a viselkedés megváltozása megváltoztatja, és meg tudja magyarázni, miért. Ez a spatial hash determinisztikus sorrendjének köszönhetően működik.</li>
</ul>
<h2 id="a-modszer-korlatai"><a class="heading-anchor" href="#a-modszer-korlatai" aria-hidden="true" tabindex="-1">#</a>A módszer korlátai</h2>
<ul>
<li>Két teli, egymással szemben haladó tömeg egy szűkületben végleg beragad. Ez a fizika nélküli modell korlátja.</li>
<li>A számok a Unity Editorból származnak, M3 Pro-n. Steam Decken és valódi buildben még nincs mérésünk, csak becslésünk nyilvános processzor-benchmarkok alapján.</li>
</ul>
<p>Ha saját tömeget szeretne kipróbálni, a <a href="https://docs.unity3d.com/Packages/com.unity.entities@6.5/manual/" target="_blank" rel="noopener">Unity Entities dokumentációja</a> jó kiindulópont. Hasonló utat a legegyszerűbb megoldástól a működőig a <a href="https://smallwater.eu/hu/blog/sudoku-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/unity-dots-horde-navigation/" data-ga-destination="/hu/blog/sudoku-generator/" data-ga-placement="inline">sudokugenerátorról</a> szóló cikkünk is bemutat. Hogy egy ilyen horda ellenségeit hogyan repítheti szét egy robbanás anélkül, hogy mindegyikükre drága ragdollt szimulálna, azt a <a href="https://smallwater.eu/hu/blog/fake-ragdoll-unity-dots/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/unity-dots-horde-navigation/" data-ga-destination="/hu/blog/fake-ragdoll-unity-dots/" data-ga-placement="inline">fake ragdoll Unity DOTS-ban</a> útmutató mutatja be. Ha hasonló szimuláción dolgozik Unityben, és segítség kellene, <a href="https://smallwater.eu/hu/#kontakt">írjon nekünk</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hogyan generáljunk szókeresőt rejtett megfejtéssel</title>
      <link>https://smallwater.eu/hu/blog/word-search-generator/</link>
      <guid isPermaLink="true">https://smallwater.eu/hu/blog/word-search-generator/</guid>
      <pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate>
      <category>Keresztrejtvények</category>
      <description>Szókereső-generátor megfejtéssel lépésről lépésre: szótár, szóhossz sorsolása, a rács kitöltése backtracking nélkül és sikerarány rácsméret szerint.</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="10×10-es szókereső színesen jelölt szavakkal és a megfejtéssel korallszínű mezőkben" width="1200" height="630" fetchpriority="high" decoding="async" /></picture><nav class="post-toc" aria-label="Tartalom"><p class="post-toc-title">Tartalom</p><ol><li><a href="#a-racs-szabalyai" data-ga="blog_toc_clicked" data-ga-heading-id="a-racs-szabalyai">A rács szabályai</a></li><li><a href="#1-lepes-keszitse-elo-a-szotart" data-ga="blog_toc_clicked" data-ga-heading-id="1-lepes-keszitse-elo-a-szotart">1. lépés: Készítse elő a szótárt</a></li><li><a href="#2-lepes-valassza-ki-a-megfejtest" data-ga="blog_toc_clicked" data-ga-heading-id="2-lepes-valassza-ki-a-megfejtest">2. lépés: Válassza ki a megfejtést</a></li><li><a href="#miert-ne-a-megfejtessel-kezdjen" data-ga="blog_toc_clicked" data-ga-heading-id="miert-ne-a-megfejtessel-kezdjen">Miért ne a megfejtéssel kezdjen</a></li><li><a href="#3-lepes-toltse-a-racsot-amig-pontosan-n-lyuk-marad" data-ga="blog_toc_clicked" data-ga-heading-id="3-lepes-toltse-a-racsot-amig-pontosan-n-lyuk-marad">3. lépés: Töltse a rácsot, amíg pontosan N lyuk marad</a></li><li><a href="#4-lepes-sorsolja-okosan-a-szohosszt" data-ga="blog_toc_clicked" data-ga-heading-id="4-lepes-sorsolja-okosan-a-szohosszt">4. lépés: Sorsolja okosan a szóhosszt</a></li><li><a href="#5-lepes-ontse-a-megfejtest-a-lyukakba" data-ga="blog_toc_clicked" data-ga-heading-id="5-lepes-ontse-a-megfejtest-a-lyukakba">5. lépés: Öntse a megfejtést a lyukakba</a></li><li><a href="#ha-a-racs-nem-sikerul" data-ga="blog_toc_clicked" data-ga-heading-id="ha-a-racs-nem-sikerul">Ha a rács nem sikerül</a></li><li><a href="#sikerarany-es-sebesseg-racsmeret-szerint" data-ga="blog_toc_clicked" data-ga-heading-id="sikerarany-es-sebesseg-racsmeret-szerint">Sikerarány és sebesség rácsméret szerint</a></li><li><a href="#tipp-ugyanaz-a-racs-minden-futtataskor" data-ga="blog_toc_clicked" data-ga-heading-id="tipp-ugyanaz-a-racs-minden-futtataskor">Tipp: ugyanaz a rács minden futtatáskor</a></li></ol></nav><p>Rejtett megfejtésű szókeresőt úgy generálhat, hogy egy üres rácsba véletlenszerű szavakat tesz a szótárból, amíg pontosan annyi szabad mező marad, ahány betűs a megfejtés. Ezeket aztán kitölti a megfejtéssel balról jobbra és fentről lefelé. Telefonon ez néhány milliszekundum, és nem kell hozzá backtracking, vagyis visszalépés korábbi döntésekhez, amikor a generátor elakad.</p>
<p>A szókereső egy betűrács, amelyben a játékos egy lista szavait keresi meg és húzza ki. A megfejtés a kihúzás után megmaradó betűkből olvasható ki. A generátornak tehát teljesen kitöltött rácsot kell összeraknia, amelyben a szavak kihúzása után pontosan a megfejtés marad. Az alábbi módszerrel készülnek a szókeresők a keresztrejtvényes és szókeresős alkalmazásunkban (<a href="https://smallwater.eu/crosswords-ios">App Store</a>, <a href="https://smallwater.eu/crosswords-android">Google Play</a>).</p>
<h2 id="a-racs-szabalyai"><a class="heading-anchor" href="#a-racs-szabalyai" aria-hidden="true" tabindex="-1">#</a>A rács szabályai</h2>
<p>Mielőtt nekikezd, döntse el, mit kell teljesítenie a kész rácsnak. Az alkalmazásunkban ezek a szabályok:</p>
<ul>
<li>A szavak nyolc irányban futnak: vízszintesen, függőlegesen és mindkét átló mentén, mindegyik irányban visszafelé is.</li>
<li>Két szónak legfeljebb egy közös betűje lehet.</li>
<li>Egyetlen szó sem haladhat át a megfejtés mezőjén.</li>
<li>A rácsnak tele kell lennie. Az üres mező hiba.</li>
<li>A megfejtést balról jobbra és fentről lefelé olvassuk az összes ki nem húzott betűből.</li>
</ul>
<h2 id="1-lepes-keszitse-elo-a-szotart"><a class="heading-anchor" href="#1-lepes-keszitse-elo-a-szotart" aria-hidden="true" tabindex="-1">#</a>1. lépés: Készítse elő a szótárt</h2>
<p>A szótárt egyszer, az elején ossza csoportokba szóhossz szerint. Így egy adott hosszúságú véletlen szó kiválasztása egyetlen véletlen index. Ha minden szónál a teljes szótárt szűri, a generálás érezhetően lelassul, mert egy rácshoz több száz szót próbál ki.</p>
<p>A szlovák szótárunk 16 713 szót tartalmaz 3 és 21 betű között, a legtöbb hét- és nyolcbetűs. Néhány gyakorlati tanács:</p>
<ul>
<li><strong>Hagyja a szótárban a tulajdonneveket és rövidítéseket is.</strong> Így a rácsban felbukkan például HOUSTON vagy MAINE. Rövid és „furcsa” szavak nélkül a rácsot nem lehet elég sűrűn kitölteni.</li>
<li><strong>A trágár szavakat külön listával szűrje</strong>, amelyet ki lehet kapcsolni, ha a játékos felnőtt tartalmat szeretne.</li>
<li><strong>Több nyelv esetén csak azt töltse be, amelyen épp játszanak.</strong> Az angol szótárban több mint 414 000 szó van. Ha az alkalmazás indulásakor mind a négy nyelvet egyszerre tölti be, egy gyengébb telefon akár 11 másodpercre is lefagy.</li>
</ul>
<h2 id="2-lepes-valassza-ki-a-megfejtest"><a class="heading-anchor" href="#2-lepes-valassza-ki-a-megfejtest" aria-hidden="true" tabindex="-1">#</a>2. lépés: Válassza ki a megfejtést</h2>
<p>A megfejtésnek legalább annyi betűből kell állnia, amekkora a rács oldala, és legfeljebb néggyel többől. A rövidebb elveszik a rácsban, a hosszabb túl sok lyukat igényel.</p>
<p>Az alkalmazásunkban a megfejtés egy vicc csattanója. A játékos kihúzza a szavakat, és a maradék betűkből olvassa ki, hogyan végződik a vicc. A megfejtést a vicc végéről állítjuk össze: hátulról teljes szavakat fűzünk hozzá, amíg a hossza ebbe a tartományba nem esik.</p>
<p>Az alábbi képek a „Two snakes walk into a bar. Turns out they were lizards.” angol viccet használják (Két kígyó bemegy egy bárba. Kiderül, hogy gyíkok voltak.), 10×10-es rácson. A megfejtés WERELIZARDS, 11 betű. A képeken szereplő szavak az angol szótárból származnak, a gyakori szavakra szűkítve, hogy olvashatók legyenek.</p>
<h2 id="miert-ne-a-megfejtessel-kezdjen"><a class="heading-anchor" href="#miert-ne-a-megfejtessel-kezdjen" aria-hidden="true" tabindex="-1">#</a>Miért ne a megfejtéssel kezdjen</h2>
<p>Az első ötlet az, amit ceruzával csinálnánk: a megfejtés betűit olvasási sorrendben véletlenszerű mezőkre szórni, aztán köréjük építeni a szavakat.</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="Üres 10×10-es rács tizenegy véletlenszerűen elszórt megfejtésbetűvel" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>A megfejtés betűi még a szavak előtt szétszórva a rácsban.</figcaption></figure>
<p>Ez ésszerűnek tűnik, amíg el nem jön a vége. A generátor kiválaszt egy üres mezőt, megpróbál rajta keresztül szót elhelyezni minden irányban és eltolással, és amikor a rács besűrűsödik, elszigetelt lyukak maradnak. Egyetlen lyukat két megfejtésbetű között semmi sem tölthet ki, mert a legrövidebb szó hárombetűs, és a megfejtésen szó nem haladhat át.</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="Majdnem teli rács három üres mezővel, amelyeket már semmi sem tölthet ki" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>10 000 próbálkozás után három lyuk maradt, amelyet egyetlen szó sem tölt ki.</figcaption></figure>
<p>200 darab 10×10-es rácson ez a megközelítés csak az esetek 28 %-ában adott teli rácsot, átlagosan 1-2 lyuk maradt. Utólag foltozni őket nehéz. Ezért a megfejtést addig ne tegye a rácsba, amíg a szavak el nem készülnek.</p>
<h2 id="3-lepes-toltse-a-racsot-amig-pontosan-n-lyuk-marad"><a class="heading-anchor" href="#3-lepes-toltse-a-racsot-amig-pontosan-n-lyuk-marad" aria-hidden="true" tabindex="-1">#</a>3. lépés: Töltse a rácsot, amíg pontosan N lyuk marad</h2>
<p>A kitöltés egy egyszerű greedy algorithm: minden szót azonnal letesz, amint elfér, és többé nem veszi ki. A generátor egyetlen számot követ: hány mező üres még. Csak akkor tesz le egy szót, ha utána nem marad kevesebb üres mező, mint ahány betűs a megfejtés (N).</p>
<pre class="code-block" data-lang="text"><code class="hljs language-plaintext">üres = sorok × oszlopok
amíg üres &gt; N és próbálkozás &lt; 2000:
    szó = véletlen szó a választott hosszal
    legfeljebb 50-szer ismételd:
        kezdet = véletlen mező, irány = véletlen a 8-ból
        ha a szó elfér a rács szabályai szerint:
            új = hány üres mezőt töltene ki a szó
            ha üres - új &gt;= N:
                tedd le a szót
                üres = üres - új
                folytasd a következő szóval
ha üres != N: dobd el a rácsot és kezdd elölről</code></pre>
<p>Az első szavak szabadon kerülnek az üres rácsba:</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="Rács az első három szóval: egy vízszintes és két függőleges" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Három szó után. Minden szónak saját színe van.</figcaption></figure>
<p>Ahogy a rács telik, a szavak egyre többször osztoznak betűkön. Ez kifejezetten jó, mert minden közös betű eggyel kevesebb kitöltendő mezőt jelent:</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="Rács tizenhárom szóval, a mezők több mint harmada üres" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Tizenhárom szó, 40 üres mező.</figcaption></figure>
<p>Amikor az üres mezők száma N-re csökken, a kitöltés véget ér. Hogy hol vannak a lyukak, mindegy. Bárhol szétszórva lehetnek, mert a megfejtésnek nem kell rajtuk keresztül összefüggő utat alkotnia:</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="Rács huszonkét szóval és tizenegy szaggatott keretes üres mezővel" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>Pontosan 11 üres mező maradt, ahány betűs a WERELIZARDS.</figcaption></figure>
<h2 id="4-lepes-sorsolja-okosan-a-szohosszt"><a class="heading-anchor" href="#4-lepes-sorsolja-okosan-a-szohosszt" aria-hidden="true" tabindex="-1">#</a>4. lépés: Sorsolja okosan a szóhosszt</h2>
<p>A hosszú szavak teszik érdekessé a szókeresőt, a rövidek segítenek befejezni. Ezért a hosszt két normális eloszlásból sorsolja (normális eloszlású véletlenszámokat például a <a href="https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform" target="_blank" rel="noopener">Box-Muller-transzformáció</a> ad):</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Valószínűség</th>
<th>Szóhossz</th>
</tr>
</thead>
<tbody><tr>
<td>50 %</td>
<td>kb. 6 betű, szórás 2</td>
</tr>
<tr>
<td>30 %</td>
<td>kb. 5 betű, szórás 1</td>
</tr>
<tr>
<td>20 %</td>
<td>3 betű</td>
</tr>
</tbody></table>
</div>
<p>A hossz mindig legalább 3 és legfeljebb a rács oldala. Ehhez két szabály kell, különben a generátor a végén elakad:</p>
<ul>
<li><strong>A végén kényszerítse ki a pontos hosszt.</strong> Ha a célig 5 vagy kevesebb mező hiányzik, pontosan a hiányzó hosszúságú szót keressen (legalább 3). Különben a generátor a végtelenségig próbálkozik hatbetűs szavakkal, amelyek már nem férnek el.</li>
<li><strong>A hárombetűs szavakat adagolja.</strong> Egy rács első három próbálkozásában egy sem lehet, a 4-6. próbálkozásban egy, a következő háromban kettő, és így tovább. A hárombetűs szótári szavak gyakran rövidítések, például DNA vagy CPU, és a rejtvényben olcsón hatnak. Csak akkor engedje őket, ha másként nem megy.</li>
</ul>
<h2 id="5-lepes-ontse-a-megfejtest-a-lyukakba"><a class="heading-anchor" href="#5-lepes-ontse-a-megfejtest-a-lyukakba" aria-hidden="true" tabindex="-1">#</a>5. lépés: Öntse a megfejtést a lyukakba</h2>
<p>Járja be a rácsot balról jobbra és fentről lefelé, és minden üres mezőbe írja a megfejtés következő betűjét. Mivel pontosan N lyuk van, a megfejtés maradék nélkül elfér.</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="Ugyanaz a rács, az üres mezőkben a megfejtés 1-től 11-ig számozott betűi" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>A megfejtés betűi olvasási sorrendben. A játékos ezeket találja meg utolsó, ki nem húzott betűkként.</figcaption></figure>
<p>A játékos csak ezt látja:</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="Kész 10×10-es szókereső jelölések nélkül" width="976" height="986" loading="lazy" decoding="async" /></picture><figcaption>A kész rács úgy, ahogy a játékos látja.</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">Próbálja ki</p><h2 class="app-box-title">Keresztrejtvények</h2><p>Keresztrejtvény, szókereső, sudoku és nonogram egyetlen offline alkalmazásban. Hogyan készülnek a rejtvények, és mi az újdonság.</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="ha-a-racs-nem-sikerul"><a class="heading-anchor" href="#ha-a-racs-nem-sikerul" aria-hidden="true" tabindex="-1">#</a>Ha a rács nem sikerül</h2>
<p>Ha a generátor 2000 próbálkozás alatt sem éri el pontosan az N üres mezőt, dobja el az egész rácsot, és kezdje elölről, legfeljebb 100-szor. Nincs lyukfoltozás. Ez durva megoldás, de 12×12-es rácsig bőven elég. Nagyobb rácsokhoz több próbálkozás kell, ahogy a lenti táblázat mutatja.</p>
<h2 id="sikerarany-es-sebesseg-racsmeret-szerint"><a class="heading-anchor" href="#sikerarany-es-sebesseg-racsmeret-szerint" aria-hidden="true" tabindex="-1">#</a>Sikerarány és sebesség rácsméret szerint</h2>
<p>A generátor méretenként 300 rácson futott, valódi viccekkel és a szlovák szótárral (Node.js 22 szerverprocesszoron, a telefon lassabb lesz; a képek az angol, a számok a szlovák szótárral készültek, mert az kisebb, így szigorúbb teszt). Az „első próbálkozásra” azt jelenti, hogy az első kitöltési kör teli ráccsal ért véget.</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Rács</th>
<th align="right">Első próbálkozásra</th>
<th align="right">Átlagos próbálkozás</th>
<th align="right">Medián idő</th>
<th align="right">Szó a rácsban</th>
<th align="right">Átlók aránya</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>Mit jelent ez egy saját generátornál:</p>
<ul>
<li><strong>Az átlagos szó 4,7 betűs</strong>, pedig a cél 6 körül van. A hosszú szavak egyszerűen gyakrabban nem férnek bele a sűrű rácsba.</li>
<li><strong>A mezők kb. 9-10 %-án két szó osztozik.</strong> Ezt nem kell beállítani, a „legfeljebb egy közös betű” szabályból adódik.</li>
<li><strong>Kevesebb átló lesz, mint amire számít.</strong> Az irányt egyenletesen sorsoljuk a nyolcból, így az átlóknak a szavak felét kellene kitenniük. Kis rácsban viszont az átlós szó sokkal gyakrabban lóg ki a szélén. 8×8-on az átlók a szavak 24 %-át adják, 15×15-ön már 33 %-át. Ha több átlót szeretne, adjon nekik nagyobb súlyt az irány sorsolásakor.</li>
</ul>
<h2 id="tipp-ugyanaz-a-racs-minden-futtataskor"><a class="heading-anchor" href="#tipp-ugyanaz-a-racs-minden-futtataskor" aria-hidden="true" tabindex="-1">#</a>Tipp: ugyanaz a rács minden futtatáskor</h2>
<p>Az áruházi képernyőképekhez minden alkalommal pontosan ugyanarra a rácsra van szükség. Ebben a módban cserélje le a szokásos véletlenszám-generátort egy egyszerű lineáris kongruens generátorra rögzített seeddel (például a nyelvből származtatva). Ha minden számítás abban a tartományban marad, amelyet a JavaScript számai pontosan ábrázolnak, az eredmény minden eszközön azonos. Ugyanezzel a trükkel készültek a cikk képei is.</p>
<p>Ha hasonló problémán dolgozik, rejtvényeket generál, vagy mobilalkalmazásra van szüksége, <a href="https://smallwater.eu/hu/#kontakt">írjon nekünk</a>. Rövid videókat a fejlesztésből a <a href="https://www.youtube.com/@smallwaterinc" target="_blank" rel="noopener">YouTube</a> csatornánkon is megosztunk. Ugyanígy lépésről lépésre olvasható az is, <a href="https://smallwater.eu/hu/blog/sudoku-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/word-search-generator/" data-ga-destination="/hu/blog/sudoku-generator/" data-ga-placement="inline">hogyan generáljunk egyértelmű megoldású sudokut</a>. Ha szókereső helyett keresztrejtvényre van szüksége, olvassa el, <a href="https://smallwater.eu/hu/blog/swedish-crossword-generator/" data-ga="blog_related_link_clicked" data-ga-source-path="/hu/blog/word-search-generator/" data-ga-destination="/hu/blog/swedish-crossword-generator/" data-ga-placement="inline">hogyan generáljunk svéd rejtvényt megfejtéssel</a>.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
