<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Personal blog of Dmytro Gladkyi]]></title><description><![CDATA[Unity & Web Developer.]]></description><link>https://gladimdim.org</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 19:43:01 GMT</lastBuildDate><atom:link href="https://gladimdim.org/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Loca Deserta: Odesa. Available on Apple and Google Stores!]]></title><description><![CDATA[We are happy to announce that we have published our 6th game in the Loca Deserta game universe!
Google Play: Loca Deserta: Odesa - Apps on Google Play

Apple App Store: Loca Deserta: Odesa on the App Store (apple.com)


This time we made our game mor...]]></description><link>https://gladimdim.org/loca-deserta-odesa-available-on-apple-and-google-stores</link><guid isPermaLink="true">https://gladimdim.org/loca-deserta-odesa-available-on-apple-and-google-stores</guid><category><![CDATA[Unity Engine]]></category><category><![CDATA[#locadesertasloboda]]></category><category><![CDATA[gamedevelopment]]></category><category><![CDATA[Games]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Sun, 16 Jul 2023 14:25:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689409054187/9130fdac-9a43-4a0f-a3ad-32d96df2a67c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We are happy to announce that we have published our 6th game in the <a target="_blank" href="https://locadeserta.com">Loca Deserta game universe</a>!</p>
<p>Google Play: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.SlobodaOdesa"><strong>Loca Deserta: Odesa - Apps on Google Play</strong></a></p>
<p><a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.SlobodaOdesa"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689525911263/4cf86643-e9ea-4e1f-910f-8dc8f8024093.png" alt class="image--center mx-auto" /></a></p>
<p>Apple App Store: <a target="_blank" href="https://apps.apple.com/us/app/loca-deserta-odesa/id6449617200"><strong>Loca Deserta: Odesa on the App Store (apple.com)</strong></a></p>
<p><a target="_blank" href="https://apps.apple.com/us/app/loca-deserta-odesa/id6449617200"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689525886020/3e9438e2-f67b-4f1e-a6bd-2b902e5f8631.png" alt class="image--center mx-auto" /></a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408902095/4dc9b7ee-7af0-4e76-b35e-b669bade8674.png" alt class="image--center mx-auto" /></p>
<p>This time we made our game more RPGish-like, with quests altering the environment, treasure hunting and artefacts gathering.</p>
<p>The previous game "<a target="_blank" href="https://gladimdim.org/loca-deserta-sloboda-2-release">Loca Deserta: Sloboda 2</a>" has been in the top paid games in 🇺🇦 App Store for 6 months straight! We hope to repeat this success and maybe we will see both our games sitting next to each other in Top-1-2 ❤️</p>
<p>Let's see what's new in this game.</p>
<h2 id="heading-huge-open-world-map">Huge open world map</h2>
<p>This time we decided not to limit our players with unlockable gates, but to allow them to discover all of the spots on them map from the very beginning! Go wherever you want, build whatever you want. Or find some NPCs who can give you advice or tell you the secrets about our treasures 😉</p>
<p>Only 1/3 of the whole available map:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408200971/a085964f-f0c7-4153-bc5b-70c7b8c4901a.jpeg" alt="Only 1/3 of the whole map" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408271748/e54192ba-5025-4786-a77b-f0a8d2b2e915.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-overhauled-the-whole-art-style">Overhauled the whole art style</h2>
<p>The new art style is cartoon, instead of the previous low-poly one. The map is dense, no running through the deserted roads or fields. Anywhere you step is either an NPC, a resource spot or some building you have to build and use for production.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689407862290/795555d3-5fef-4e19-ad0d-8c64747cb639.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408831573/1a7610c5-7907-4da0-b568-d160fecb3870.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408963790/1ef931d2-b8ed-4e88-8f18-e2d375eedeb8.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-live-as-an-npc-and-help-others">Live as an NPC and help others</h2>
<p>Speak with NPCs. They share with you knowledge about the outer world. Whom did they see at the cemetery at night with a candle looking for treasures...All quests are built into the game and map naturally, without artificial UI elements or mini-map with a compass. Listen to NPCs to get directions! Just like in good old TES III Morrowind or Gothic times ✊🏻</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408683746/8cff0d36-16db-4f19-b1a1-04831e7b4b7a.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-completing-quests-changes-the-environment">Completing quests changes the environment</h2>
<p>See the wagon with a wax? That water well in the center of the square? A horseshoe on the roof? An opened gate to the right (hint: treasure related 💰)? It was all the result of your job, completing quests, producing goods for the citizens and finishing their tasks. Each quest changes something in the game.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408323953/cb414abb-76f4-4835-a3b5-2ce9441cfaed.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-treasure-hunting">Treasure hunting</h2>
<p>NPCs, once you complete their quests, will share with you the directions to find the treasure spots. Just grab a shovel, visit the discovered location and dig. Boom. You are now a treasure hunter. Find all 5 treasures and artefacts!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408738947/34269fd4-4645-44f7-a902-42e15fa4472c.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-collecting-all-artefacts">Collecting all artefacts</h2>
<p>You have to find all the treasures on the map and bring them to the central hill. Each artefact tells you a story about Ukraine and its history.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408571292/fa536453-b5f7-4f16-9634-5a3026d8fa60.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-see-you">See you!</h2>
<p>See you in the Loca Deserta: Odesa! Have fun and play our game! And in the meantime, we start preparing the next games!</p>
]]></content:encoded></item><item><title><![CDATA[Дике Поле: Одеса. Доступна на iOS та Android]]></title><description><![CDATA[Раді представити вам нашу шосту гру з всесвіту Дикого Поля!
В цей раз події відбуваються на півдні України в часи козаччини: в околицях Хаджибея-Одеси.
Google Play: Loca Deserta: Odesa - Apps on Google Play

Apple App Store: Loca Deserta: Odesa on th...]]></description><link>https://gladimdim.org/dike-pole-odesa-dostupna-na-ios-ta-android</link><guid isPermaLink="true">https://gladimdim.org/dike-pole-odesa-dostupna-na-ios-ta-android</guid><category><![CDATA[#locadesertasloboda]]></category><category><![CDATA[GameDev]]></category><category><![CDATA[Unity Engine]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Sun, 16 Jul 2023 14:24:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689419716016/e86da583-6f11-48f1-afdb-4f9624d267a8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Раді представити вам нашу шосту гру з <a target="_blank" href="https://locadeserta.com">всесвіту Дикого Поля!</a></p>
<p>В цей раз події відбуваються на півдні України в часи козаччини: в околицях Хаджибея-Одеси.</p>
<p>Google Play: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.SlobodaOdesa">Loca Deserta: Odesa - Apps on Google Play</a></p>
<p><a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.SlobodaOdesa"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689525791736/19d9e65c-96ee-4e12-910a-5378d34adbb6.png" alt class="image--center mx-auto" /></a></p>
<p>Apple App Store: <a target="_blank" href="https://apps.apple.com/ua/app/loca-deserta-odesa/id6449617200">Loca Deserta: Odesa on the App Store (</a><a target="_blank" href="http://apple.com">apple.com</a><a target="_blank" href="https://apps.apple.com/ua/app/loca-deserta-odesa/id6449617200">)</a></p>
<p><a target="_blank" href="https://apps.apple.com/ua/app/loca-deserta-odesa/id6449617200"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689525616601/6a666c3c-88d8-46c5-ad21-e9c5dcf1f986.png" alt class="image--center mx-auto" /></a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408902095/4dc9b7ee-7af0-4e76-b35e-b669bade8674.png" alt class="image--center mx-auto" /></p>
<p>Попередня гра <a target="_blank" href="https://gladimdim.org/dike-pole-sloboda-2-dostupna-na-apple-ta-android">Дике Поле: Слобода 2</a> займала топ продажів в українському App Store 7 місяців поспіль! Сподіваємося, що побачимо обидві наші гри поруч на перших двох місцях ❤️</p>
<p>Давайте глянемо, що нового ми привнесли нового в цій грі!</p>
<h2 id="heading-povna-ukrayinska-ozvuchka">Повна українська озвучка!</h2>
<p>Ми озвучили всіх персонажів, а також виробництва. Сам герой теж балакає українською! Зацінити озвучку можна в трейлері:</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=HxJZJs9TPzk">https://www.youtube.com/watch?v=HxJZJs9TPzk</a></p>
<h2 id="heading-velichezna-mapa-z-vidkritim-svitom">Величезна мапа з відкритим світом</h2>
<p>В цей раз ми не стали обмежувати гравців на мапі. Не треба відкривати нові зони за гроші чи ресурси. Для вас доступна вся мапа з перших хвилин гри. Хочете допомагати козакам? Шукайте їх табір! Хочете зробити добро ремісникам на сході? Теж варіант! Вибирайте, що хочете!</p>
<p>Приблизно одна третина свієї мапи:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408200971/a085964f-f0c7-4153-bc5b-70c7b8c4901a.jpeg" alt="Only 1/3 of the whole map" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408271748/e54192ba-5025-4786-a77b-f0a8d2b2e915.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-povnistyu-onovlena-grafika">Повністю оновлена графіка</h2>
<p>Ми перейшли з лоуполі стилю на мультяшний стиль. Абсолютно всі виробництва мають свої власні звуки та анімації. Як завжди, доступно три рівня апгрейдів для більшості будівель!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689407862290/795555d3-5fef-4e19-ad0d-8c64747cb639.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408831573/1a7610c5-7907-4da0-b568-d160fecb3870.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689421207376/eb56c13c-e6cf-4fd6-a1dc-693411a96b63.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-nova-kvestova-sistema">Нова квестова система</h2>
<p>Ми додали більше рольових елементів в гру. Тепер кожен НІП розповість вам якийсь секрет про інших, або де заховані скарби, або хто їх заховав. Виконуючи квести для різних НІП ви все більше занурюєтесь в гру і відкриваєте всі її секрети.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689420729188/f7fc6d49-6c26-48d3-9292-1b749a4eed81.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-kvesti-zminyut-svit">Квести змінють світ</h2>
<p>Козаки та козачки будуть просити вам полагодити воза, викопати криницю, принести їжі. І всі вони знаходять своє відображення прямо на мапі!</p>
<p>Бачите віз з віском зліва? А підкову на даху? Криницю біля дороги, та відкриту хвіртку в самому низу справа? Це все зробив гравець, коли виконував завдання.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689408323953/cb414abb-76f4-4835-a3b5-2ce9441cfaed.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-poshuk-skarbiv">Пошук скарбів</h2>
<p>Деякі персонажі знають, де заховані скарби Хаджибея. Виконуй їх завдання і вони тебе приведуть до них!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689421013466/4a537ca1-6fd5-424f-b784-9ed94345e5b3.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-zberi-vsi-artefakti">Збери всі артефакти</h2>
<p>Знайди всі 5 скарбів та віднеси їх на могилу артефактів! Кожен скароб розповість якусь історію з героїчних часів України.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689421051703/f10ea603-7a19-4fac-a4c0-1fdf3d9de318.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-garnoyi-gri">Гарної гри!</h2>
<p>Грай в українське про Україну! Ну а ми уже розробляємо нашу наступну гру!</p>
<p>Підписуйся на телеграм канал: <a target="_blank" href="http://t.me/locadesertachumaki">t.me/locadesertachumaki</a> та тікток: <a target="_blank" href="https://tiktok.com/@locadeserta">https://tiktok.com/@locadeserta</a></p>
]]></content:encoded></item><item><title><![CDATA[Loca Deserta: Sloboda 2. Big January Update]]></title><description><![CDATA[Hey!
We just published the second content update for our game Sloboda 2.
This update is free for anyone who already purchased our game.
If you want to purchase it, then pick your platforms:
Apple App Store: https://apps.apple.com/us/app/loca-deserta-...]]></description><link>https://gladimdim.org/loca-deserta-sloboda-2-big-january-update</link><guid isPermaLink="true">https://gladimdim.org/loca-deserta-sloboda-2-big-january-update</guid><category><![CDATA[GameDev]]></category><category><![CDATA[sloboda]]></category><category><![CDATA[#locadesertasloboda]]></category><category><![CDATA[unity]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Tue, 24 Jan 2023 13:52:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674568006781/61435cdc-a9d1-4203-8228-29c543b74746.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey!</p>
<p>We just published the second content update for our game <a target="_blank" href="https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id1642505646">Sloboda 2</a>.</p>
<p>This update is free for anyone who already purchased our game.</p>
<p>If you want to purchase it, then pick your platforms:</p>
<p>Apple App Store: <a target="_blank" href="https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646">https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646</a></p>
<p>And Google Play Store: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda">https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda</a></p>
<p>If you already completed the game, you can still load your old save and find new quests and the new bard. We have dozen other slight changes, so even replay is an option :)</p>
<h2 id="heading-some-news">Some News</h2>
<p>This content update was published because we saw enormous interest in our game among Ukrainian players. We were holding Top positions in Casual and RPG categories for 9 weeks. And even tried to fight with Minecraft in Top Paid Games.</p>
<p>Don't miss news about our game: <a target="_blank" href="http://tiktok.com/@locadeserta">http://tiktok.com/@locadeserta</a>, and telegram: <a target="_blank" href="https://t.me/locadesertachumaki">https://t.me/locadesertachumaki</a> . Or twitter: <a target="_blank" href="https://twitter.com/locadeserta">https://twitter.com/locadeserta</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674247215499/6d52fcdf-9040-49df-93f3-acdf9dac9f4d.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-whats-included">What's included?</h1>
<h2 id="heading-new-quests">New Quests</h2>
<p>This was one of the most common requests from our players. We have added three new NPCs with 3 new quests each. Explore the map and find these new lovely scenes!</p>
<ul>
<li>A treasure hunter:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246188426/14416494-7d22-402f-85e6-f6d4a3cff250.png" alt class="image--center mx-auto" /></p>
<p>A veteran:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246357858/ebd5c47e-f358-4af6-886a-b8487fc96ca8.png" alt class="image--center mx-auto" /></p>
<ul>
<li>And a hard-working woman:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246382106/cff6b6d6-fed0-44ca-b11f-1ed057f7ecb9.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-new-bard">New Bard</h2>
<p>We have recorded another track for our new bard. This is our fourth unique track so far!</p>
<p>Find him near the sea. <strong>Or join our Telegram channel and get all the news and secrets before the release:</strong> <a target="_blank" href="https://t.me/locadesertachumaki"><strong>https://t.me/locadesertachumaki</strong></a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246548994/85f30ec1-6133-48cb-a879-86f4b72918c6.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-new-effects">New Effects</h2>
<p>We have added a camera shader that slightly changes the picture. The objects become sharper and more focused.</p>
<p>Compare this scene with the shader on:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246635239/7defe908-646b-4dc2-8cce-913d39685d29.png" alt class="image--center mx-auto" /></p>
<p>And off:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246663783/0193d52e-7075-4b10-94d3-b626253b9e76.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-more-animations">More Animations</h2>
<p>Pigs, rabbits, horses: they all get their animations! No more static objects!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246717332/f0bddf2c-7f44-4dff-80ed-989e74079d31.png" alt class="image--center mx-auto" /></p>
<p>When you build/upgrade the funny animation is played:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246954529/8dcb54ac-0d5d-462b-a0ff-50449493ee55.gif" alt class="image--center mx-auto" /></p>
<p>And much more!</p>
]]></content:encoded></item><item><title><![CDATA[Дике Поле: Слобода 2. Січневе Велике Оновлення]]></title><description><![CDATA[Привіт, компана!
Ось і настав час Січневого оновлення для нашої гри Слобода 2!
Це оновлення безкоштовне для всіх, хто придбав гру.
Якщо ж ще не придбали, то можете це зробити по цих посиланнях:
Apple App Store: https://apps.apple.com/ua/app/loca-dese...]]></description><link>https://gladimdim.org/dike-pole-sloboda-2-sichneve-velike-onovlennya</link><guid isPermaLink="true">https://gladimdim.org/dike-pole-sloboda-2-sichneve-velike-onovlennya</guid><category><![CDATA[#україна]]></category><category><![CDATA[Дикеполе ]]></category><category><![CDATA[Козаки]]></category><category><![CDATA[Ігророб]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Mon, 23 Jan 2023 20:20:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674248274202/ab57c57e-7374-4bb2-8a12-3e8101bad6c2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Привіт, компана!</p>
<p>Ось і настав час Січневого оновлення для нашої гри Слобода 2!</p>
<p>Це оновлення безкоштовне для всіх, хто придбав гру.</p>
<p>Якщо ж ще не придбали, то можете це зробити по цих посиланнях:</p>
<p>Apple App Store: <a target="_blank" href="https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id1642505646">https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id1642505646</a></p>
<p>Та Google Play Store: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda">https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda</a></p>
<p>Навіть якщо уже пройшли гру - просто завантажуєте сейв і граєте! НІП будуть доступні для вас. Або ж пройдіть заново, адже невеличкі покращення є майже скрізь, хоча і мало помітні :)</p>
<h2 id="heading-trohi-novin">Трохи новин</h2>
<p>Цей патч вийшов завдяки шаленій підтримці українських гравців. Ми тримаємося в Топ чартах українського App Store уже восьмий тиждень поспіль! Уже працюємо над новою грою з відкритим світом. Дике Поле: Одеса.</p>
<p>Щоб не пропустити новин, заходьте на наш тікток: <a target="_blank" href="http://tiktok.com/@locadeserta">http://tiktok.com/@locadeserta</a>, та телеграм: <a target="_blank" href="https://t.me/locadesertachumaki">https://t.me/locadesertachumaki</a> . Або ж твітор: <a target="_blank" href="https://twitter.com/locadeserta">https://twitter.com/locadeserta</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674247215499/6d52fcdf-9040-49df-93f3-acdf9dac9f4d.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-sho-v-patchi">Що в патчі?</h1>
<h2 id="heading-novi-kvesti">Нові Квести!</h2>
<p>Це було, мабуть, одне з найпоширеніших прохань: більше завдань! То ж ми додали 3 нових квестових НІП, кожен має по 3 завдання. Шукайте НІП з окличними знаками над головою!</p>
<ul>
<li>Козак-шукач скарбу Полуботка:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246188426/14416494-7d22-402f-85e6-f6d4a3cff250.png" alt class="image--center mx-auto" /></p>
<p>Обшитований (ветеран) козак:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246357858/ebd5c47e-f358-4af6-886a-b8487fc96ca8.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Та жінка-батрачка:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246382106/cff6b6d6-fed0-44ca-b11f-1ed057f7ecb9.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-novij-kobzar">Новий Кобзар</h2>
<p>Спеціально для нашої гри записали ще один четвертий(!) унікальний трек.</p>
<p>Що за пісня - не скажемо. Проходьте гру до долини з морем і там знайдете відповідь на запитання! <strong>Або ж заходьте в наш ТГ канал, там ділимося усіма секретами:</strong> <a target="_blank" href="https://t.me/locadesertachumaki"><strong>https://t.me/locadesertachumaki</strong></a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246548994/85f30ec1-6133-48cb-a879-86f4b72918c6.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-novi-efekti">Нові ефекти</h2>
<p>Додали шейдер для камери, який підкреслює об'єкти і робить їх більш мультяшними.</p>
<p>Порівняйте кобзаря і каміння з ефектом:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246635239/7defe908-646b-4dc2-8cce-913d39685d29.png" alt class="image--center mx-auto" /></p>
<p>Та без:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246663783/0193d52e-7075-4b10-94d3-b626253b9e76.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-bilshe-animacij">Більше анімацій</h2>
<p>Свинки, коні та інші тварини - всі анімовані. Більше немає статичних створінь взагалі!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246717332/f0bddf2c-7f44-4dff-80ed-989e74079d31.png" alt class="image--center mx-auto" /></p>
<p>При побудові чи апгрейду будівель граються невеличкі анімашки:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674246954529/8dcb54ac-0d5d-462b-a0ff-50449493ee55.gif" alt class="image--center mx-auto" /></p>
<p>Та багато всього іншого!</p>
<h2 id="heading-nu-i-na-ostanok">Ну і на останок</h2>
<p>Підтримуйте українських ігроробів. Поширюйте інформацію серед друзів. Наразі наші ютюбери не дуже хочуть поширювати інформацію про своїх же власних ігроробів. Окрема подяка <a target="_blank" href="https://www.youtube.com/@Nikattica">https://www.youtube.com/@Nikattica</a> за додавання нашої гри в свій випуск Нюдсів! Тому підтримуйте ютюберів, які підтримують ігроробів!</p>
]]></content:encoded></item><item><title><![CDATA[Дике Поле: Слобода 2. Грудневий Патч.]]></title><description><![CDATA[Статистика запуску гри
Трохи більше двох тижнів тому ми випустили гру на всіх магазинах. І одразу ж попали в Топ платних ігор 🇺🇦українського App Store!

Купити можна на iOS по посиланню: https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id164250...]]></description><link>https://gladimdim.org/dike-pole-sloboda-2-grudnevij-patch</link><guid isPermaLink="true">https://gladimdim.org/dike-pole-sloboda-2-grudnevij-patch</guid><category><![CDATA[#україна]]></category><category><![CDATA[#locadesertasloboda]]></category><category><![CDATA[lowpoly]]></category><category><![CDATA[unity]]></category><category><![CDATA[GameDev]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Mon, 26 Dec 2022 19:09:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672081118102/0ae35265-2b28-4083-9c34-2d9700425b4b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-statistika-zapusku-gri">Статистика запуску гри</h1>
<p>Трохи більше двох тижнів тому ми випустили гру на всіх магазинах. І одразу ж попали в <strong>Топ платних ігор 🇺🇦українського App Store</strong>!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672081245935/5207f699-8a8b-4817-9f91-3a4d33b36764.jpeg" alt class="image--center mx-auto" /></p>
<p>Купити можна на iOS по посиланню: <a target="_blank" href="https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id1642505646"><strong>https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id1642505646</strong></a></p>
<p>Або для Android: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda"><strong>https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda</strong></a></p>
<p>Ще раз дякуємо вам за таку шалену підтримку і інтерес до гри!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672081531057/507191f9-e62a-479d-979b-aa2050fa2483.jpeg" alt class="image--center mx-auto" /></p>
<h1 id="heading-zmini-u-grudnevomu-onovlenni">Зміни у грудневому оновленні</h1>
<p>Перший патч готовий! В нього ми додали набагато більше, ніж планували.</p>
<h2 id="heading-android-10">Android 10!</h2>
<p>Ми понизили вимогу щодо версії Андроїд. Замість 11 тепер можна 10. Сподіваємося, наша гра буде працювати на них. Адже ця версія доволі стара - 2018 рік. Але через великий попит на гру та на 10-ий андроїд вирішили ризикнути!</p>
<h2 id="heading-novi-fichi-igroladu"><strong>Нові фічі ігроладу:</strong></h2>
<p>◦ Звуки ходи. Залежать від поверхні, по якій іде гравець.</p>
<h2 id="heading-zmini-na-mapi"><strong>Зміни на мапі:</strong></h2>
<p>◦ 💹Новий Базар</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672081215144/690ad4b9-95e7-4bdc-865d-c87db8782be9.png" alt class="image--center mx-auto" /></p>
<p>◦ 🎡Новий млин</p>
<p>◦ 🌊Переробили куток з полем в долині біля моря. Тепер там відпочивають козаки і є трошку луту.</p>
<p>◦ 🎁Замінили всі старі воксельні скрині на нову лоуполі.</p>
<p>◦ 🐟Нова анімація продажу риби на Базарі.</p>
<p>◦ 👝Додали мішки з борошном та зерном на мапі в різні місця.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672081210036/63b99bb9-6fc5-4e81-a4bc-6778ac38a1ef.png" alt class="image--center mx-auto" /></p>
<p>◦ 🔥 Козак на сцені виробництва курить справжню козацьку люльку.</p>
<p>◦ 💨Вітер дме на млин відповідно до позиції лопастей.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672081208670/b321c9e8-7d75-4118-8bda-3f28481e3662.png" alt class="image--center mx-auto" /></p>
<p>◦ 🏉Більше деталей на сцену головного меню.</p>
<h2 id="heading-balansuvannya"><strong>Балансування</strong></h2>
<p>◦ 🕊️Змінили гучності пташок. Трошки тихше стало.</p>
<p>◦ 🎤Кобзар біля церкви дістає своїм співом до видавача квестів.</p>
<p>◦ 🏠Курінь коштує 3 дерева, 3 каміння, 3 дошки для побудови.</p>
<p>◦ ⚓️Човнярня коштує 1 золотий, 3 дошки та 3 дерева.</p>
<p>◦ 🔫Гарматня коштує 3 дерева, 3 каменя, 3 дошки та 1 метал для побудови.</p>
<p>◦ 🪚Більше немає безкоштовної лісопилки на верхній долині.</p>
<p>◦ 🛞Млин біля моря треба будувати.</p>
<h2 id="heading-dodatkovo"><strong>Додатково</strong></h2>
<p>◦ 🇩🇪Додали німецьку мову.</p>
<h1 id="heading-sho-dali"><strong>Що далі?</strong></h1>
<p>Ми плануємо випустити величезне розширення до гри 1 квітня. <strong>Поки що робоча назва "Одеса-Хаджибей"</strong>: 12 нових будівель, нові виробничі ланцюги та 12 нових товарів.</p>
<p>Наш офіційний міні-сайт: <a target="_blank" href="https://locadeserta.com"><strong>https://locadeserta.com</strong></a> та група в Телеграм: <a target="_blank" href="https://t.me/locadesertachumaki"><strong>https://t.me/locadesertachumaki</strong></a></p>
<p>Слава Україні 🇺🇦!</p>
]]></content:encoded></item><item><title><![CDATA[Loca Deserta: Sloboda 2. December Patch.]]></title><description><![CDATA[Release Stats
Before we tell show you our awesome December Patch Changelog we want to share some happy news! After two weeks we've gotten into the Top Paid Games categories and keep rocking it! Tremendeous result for such a tiny team as ours 🥹

Down...]]></description><link>https://gladimdim.org/loca-deserta-sloboda-2-december-patch</link><guid isPermaLink="true">https://gladimdim.org/loca-deserta-sloboda-2-december-patch</guid><category><![CDATA[Game Development]]></category><category><![CDATA[Games]]></category><category><![CDATA[C#]]></category><category><![CDATA[release notes]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Mon, 26 Dec 2022 16:42:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671912856236/427052dd-74ad-4386-a47b-911e0b72095f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-release-stats">Release Stats</h1>
<p>Before we tell show you our awesome December Patch Changelog we want to share some happy news! After two weeks we've gotten into the <strong>Top Paid Games categories and keep rocking it</strong>! Tremendeous result for such a tiny team as ours 🥹</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671913044796/ad6ac637-abec-4ccc-90a3-4bdd3af00408.jpeg" alt class="image--center mx-auto" /></p>
<p>Download it now at the App Store: <a target="_blank" href="https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646"><strong>https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646</strong></a></p>
<p>or at Play Store: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda"><strong>https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda</strong></a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671913089650/037762f2-c6ec-4e84-a151-5a7c4f0b760f.jpeg" alt class="image--center mx-auto" /></p>
<h1 id="heading-december-patch-changelog">December Patch Changelog</h1>
<p>We added to this patch much more than we initially planned.</p>
<p><strong>🎉New gameplay features:</strong></p>
<ul>
<li>🎧Footsteps sound. They depend on the surface the player walks. Check it on our Tiktok: <a target="_blank" href="https://www.tiktok.com/@locadeserta/video/7179955367961496838">https://www.tiktok.com/@locadeserta/video/7179955367961496838</a></li>
</ul>
<p><strong>🗺️Changes to the map:</strong></p>
<ul>
<li><p>💵 New Market</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671913187247/9d257d84-4a44-4b74-a023-ebd818fe0ac1.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>🎡New Mill (all 3 levels)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671913197521/55648880-e8ef-4273-abc3-19eb66d12e96.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Redesigned the corner in the Sea Valley. Added some loot there.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671913205733/03b87b53-6d6f-43d9-bf03-9347489f400d.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>⭐️Replaced old voxel chests with lowpoly variants.</p>
</li>
<li><p>🎏New animation of selling Fish.</p>
</li>
<li><p>🌾New environment object: bags with flour or grains.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671913224610/e8c032ed-16d5-4345-8035-5c2aadd73456.png" alt class="image--center mx-auto" /></p>
<ul>
<li>The wind blows directly onto the mill's wheels.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671913237703/37667080-0661-4433-8748-4562d99f1de8.png" alt class="image--center mx-auto" /></p>
<ul>
<li>More details in the main menu scene.</li>
</ul>
<p><strong>Rebalance</strong></p>
<ul>
<li><p>↘️Lowered the volume of birds.</p>
</li>
<li><p>🎤The NPC Bard near the Church is louder.</p>
</li>
<li><p>🏠The recruiter's house costs 3 pieces of wood, 3 stones and 3 planks.</p>
</li>
<li><p>💥The cannon maker costs 3 logs, 3 stones, 3 planks and 1 metal.</p>
</li>
<li><p>⚓️The Shipyard costs 1 gold, 3 planks and 3 wood.</p>
</li>
<li><p>🎡The Mill near the Sea is no longer auto-built.</p>
</li>
<li><p>🛑No more free Sawmill in the northern valley.</p>
</li>
</ul>
<p><strong>Miscellaneous</strong></p>
<ul>
<li>🇩🇪Added German translation</li>
</ul>
<h1 id="heading-downloads">Downloads</h1>
<p>Download it now at the App Store: <a target="_blank" href="https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646"><strong>https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646</strong></a></p>
<p>or at Play Store: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda"><strong>https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda</strong></a></p>
<p>Watch the release trailer on youtube:</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=rt2rBLewA6g"><strong>https://www.youtube.com/watch?v=rt2rBLewA6g</strong></a></p>
<p>Or check our official website: <a target="_blank" href="https://locadeserta.com"><strong>https://locadeserta.com</strong></a> and Telegram Group: <a target="_blank" href="https://t.me/locadesertachumaki"><strong>https://t.me/locadesertachumaki</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Дике Поле: Слобода 2. Доступна на Apple та Android!]]></title><description><![CDATA[Ми раді виходу нашої гри "Дике Поле: Слобода 2" на платформі Apple App Store та Google Play Store!
Купити можна на iOS по посиланню: https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id1642505646
Або для Android: https://play.google.com/store/apps...]]></description><link>https://gladimdim.org/dike-pole-sloboda-2-dostupna-na-apple-ta-android</link><guid isPermaLink="true">https://gladimdim.org/dike-pole-sloboda-2-dostupna-na-apple-ta-android</guid><category><![CDATA[unity]]></category><category><![CDATA[sloboda]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[app store]]></category><category><![CDATA[lowpoly]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Wed, 07 Dec 2022 12:05:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670441112735/2cpJCpxvP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ми раді виходу нашої гри "Дике Поле: Слобода 2" на платформі Apple App Store та Google Play Store!</p>
<p>Купити можна на iOS по посиланню: <a target="_blank" href="https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id1642505646">https://apps.apple.com/ua/app/loca-deserta-sloboda-2/id1642505646</a></p>
<p>Або для Android: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda">https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda</a></p>
<p>Перегляньте релізний трейлер:</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=ECU41vN7dg4">https://www.youtube.com/watch?v=ECU41vN7dg4</a></p>
<p>Офіційний сайт: <a target="_blank" href="https://locadeserta.com">https://locadeserta.com</a></p>
<p>Приєднуйтесь до Телеграм каналу: <a target="_blank" href="https://t.me/locadesertachumaki">https://t.me/locadesertachumaki</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670414454462/-gLXaly4E.png" alt class="image--center mx-auto" /></p>
<p>Якщо вам сподобалась гра Unrailed! за її простоту в керування, але одночасно зі складним ігроладом, то Слобода 2 - це те, що вам треба! Ми спеціально розробили цю гру для мобільних платформ, бо мобілки є у всіх і завжди. На відміну від комп'ютерів чи приставок. Ми поєднали Unrailed!, Minecraft та Settlers в фантастичну, веселу, добру та цікаву гру! Повне проходження гри займає 3-4 години. Але і після її закінчення можете грати далі. Гра вас ніяк не обмежує.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670414471996/_95sEfvOb.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-trohi-istoriyi"><strong>Трохи історії</strong></h1>
<p>Ми - це команда двох людей. І навіть більше: справжня сім'я 👦👧.</p>
<p>Розробка почалась рівно рік тому, в грудні 2021 року. Коли ми вирішили переробити нашу стару гру Слобода 1, яка була розроблена на Flutter.</p>
<p>Ми почали проходити офіційни Unity курси <a target="_blank" href="http://learn.unity.com">http://learn.unity.com</a> і вже за декілька тижнів почалась розробка гри. Кожен вечір, крок за кроком, задачею за задачею ми дійшли до цього дня. Паралельно прокачали наші вміння в Blender та самі собі розробляли арт, який був необхідним.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670414485295/jHFKLQikz.png" alt class="image--center mx-auto" /></p>
<p>Влітку весь арт був перероблений зі стиля voxel на low poly.</p>
<p>Потім додали🌟 21 Game Center досягнення,🌟 підтримку Apple TV 📺. Беріть будь-який геймпад 🎮 під'єднуйте до iPhone/Apple TV та грайте!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670342432201/5S6pALcED.jpg" alt /></p>
<h2 id="heading-chisti-fakti">Чисті факти:</h2>
<ul>
<li><p>🤯1105 комітів</p>
</li>
<li><p>😳більше ніж 300 лоу-полі арту для інструментів, будівель, дерев, оточення, башт, ресурсів та товарів.</p>
</li>
<li><p>🎧 3 козацькі пісні записані для гри</p>
</li>
<li><p>🙀100+ анімацій</p>
</li>
<li><p>🫢Переклад на 5 мов: 🇺🇸🇺🇦🇵🇱🇪🇸🇫🇷</p>
</li>
</ul>
<p>Наш проект в Github завершений! 👏🏻</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670342463612/tqRgOHhqa.png" alt /></p>
<h1 id="heading-sho-dali">Що далі?</h1>
<p>Ми плануємо випустити перший патч в грудні з новою піснею та новим артом. <strong>В березні - перше велике оновлення з новою мапою "Одеса-Хаджибей"</strong>, 12 нових будівель, нові виробничі ланцюги та 12 нових товарів.</p>
<p>Слава Україні 🇺🇦!</p>
]]></content:encoded></item><item><title><![CDATA[Loca Deserta: Sloboda 2 Release!]]></title><description><![CDATA[We are glad to announce that our game Loca Deserta: Sloboda 2 is available on the App Store and Google Play Store!
Download it now at the App Store: https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646
or at Play Store: https://play.goog...]]></description><link>https://gladimdim.org/loca-deserta-sloboda-2-release</link><guid isPermaLink="true">https://gladimdim.org/loca-deserta-sloboda-2-release</guid><category><![CDATA[lowpoly]]></category><category><![CDATA[sloboda]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Unity3D]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Tue, 06 Dec 2022 16:11:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670441025493/9xDZ1SBUQ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We are glad to announce that our game Loca Deserta: Sloboda 2 is available on the App Store and Google Play Store!</p>
<p>Download it now at the App Store: <a target="_blank" href="https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646">https://apps.apple.com/us/app/loca-deserta-sloboda-2/id1642505646</a></p>
<p>or at Play Store: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda">https://play.google.com/store/apps/details?id=com.dmytrogladkyi.Sloboda</a></p>
<p>Watch the release trailer on youtube:</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=rt2rBLewA6g">https://www.youtube.com/watch?v=rt2rBLewA6g</a></p>
<p>Or check our official website: <a target="_blank" href="https://locadeserta.com">https://locadeserta.com</a> and Telegram Group: <a target="_blank" href="https://t.me/locadesertachumaki">https://t.me/locadesertachumaki</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670342355053/RDHxBNNEK.png" alt /></p>
<p>If you liked <em>Unrailed!</em> for its simplicity in controls but sophisticated gameplay then <em>Sloboda 2</em> is for you as well! We specifically targeted the mobile platforms for their broad access to all persons and their availability any time you need them. The smartphone is always on you. <strong>We merged Unrailed!, Minecraft and Settlers all into one awesome, gorgeous and fun bundle.</strong> The core gameplay requires 3-4 hours to complete. Then you can do whatever you want. Or you can do whatever you want from the first seconds, the game does not limit you in any way.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670342348180/OmXsOrSBe.png" alt /></p>
<h1 id="heading-some-dev-stats"><strong>Some dev stats</strong></h1>
<p>We are a team of two people. And even more: a family 👦👧.</p>
<p>The journey started a year ago in Dec 2021 when we decided to remake our old game all done with Flutter.</p>
<p>We did official Unity courses at: <a target="_blank" href="http://learn.unity.com">http://learn.unity.com</a> and after a couple of weeks started the work on our game. Each evening step by step, task by task we moved the needle towards this day. In parallel, we learnt the skills required to use Blender to make the art we wanted.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670342387144/h4EfKC8Rn.png" alt /></p>
<p>In the summer we redesigned almost all art from voxel to low-poly style. Then added 21 Game Center Achievements🌟 and Apple TV 📺 support. Together with ANY gamepad 🎮you have connected to your iPhone/Apple TV!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670342432201/5S6pALcED.jpg" alt /></p>
<h2 id="heading-pure-facts">Pure facts:</h2>
<ul>
<li><p>🤯1105 commits</p>
</li>
<li><p>😳more than 300 new low-poly arts for tools, buildings, trees, environment, towers, resources, goods</p>
</li>
<li><p>🎧three custom audio tracks</p>
</li>
<li><p>🙀100+ animations</p>
</li>
<li><p>🫢Translations to 5 languages: 🇺🇸🇺🇦🇵🇱🇪🇸🇫🇷</p>
</li>
</ul>
<p>Our Github project looks like this. Yup. It is all done 👏🏻</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670342463612/tqRgOHhqa.png" alt /></p>
<h1 id="heading-whats-next">What's next?</h1>
<p>We will issue a small update next month with new sound track and enhanced art for some buildings. In March we plan to publish Major Content Update #1 with new map, 12 new buidings, new manufacturing production chains for 12 new goods.</p>
<p>Stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[Слобода 2. Завершення справи та наступні плани]]></title><description><![CDATA[Ось уже понад рік, як я працюю над своєю грою Дике Поле: Слобода 2.

Почав її, бо більше не можна так жити, кругом якась...Мобільних ігор з дійсно цікавим гейм-плеєм і не туалетним терміном гри (приблизно 2 хвилини) дуже мало. Крім Kingdom of Two Cro...]]></description><link>https://gladimdim.org/sloboda-2-zavershennya-spravi-ta-nastupni-plani</link><guid isPermaLink="true">https://gladimdim.org/sloboda-2-zavershennya-spravi-ta-nastupni-plani</guid><category><![CDATA[sloboda]]></category><category><![CDATA[GameDev]]></category><category><![CDATA[unity]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Tue, 22 Nov 2022 10:08:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1669111646012/zqpi36n19.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ось уже понад рік, як я працюю над своєю грою Дике Поле: Слобода 2.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669111573319/GXRYXkuWb.png" alt="panoramic_view_2.png" /></p>
<p>Почав її, бо більше не можна так жити, кругом якась...Мобільних ігор з дійсно цікавим гейм-плеєм і не туалетним терміном гри (приблизно 2 хвилини) дуже мало. Крім Kingdom of Two Crowns і згадати важко, в яку гру можна пограти, щоб мала під собою якусь канву, розвиток сюжету, зміну ігроладу під час проходження і головне: хотілося без перерви грати 30-40 хв. Всі ж мобільні ігри - це сесійна гра на 2 хвилин, поки сидиш в туалеті. І щоб кожна сесія гри це не заробляння монеток чи діамантів для купівлі наступного айтема.</p>
<p>А ще ігор про Україну немає. Є Острів. Стоїть один в полі. Уже років 8. Я одразу ж його придбав, коли він на стім забетився. І все. Повна пустка. На 50 мільйон людей немає нікого, хто б вніс щось корисне в цей відділ нашої культури. А це надвзичайно важливий і впливовий відділ. Подивіться на русню: у них всі ігрові гіганти мають бюджетування або з держави, або з газпрому чи інших нафтових доїлок. Бо через гру ти несеш свою ідею, збагачуєш свій власний культурний шар. Всі діти грають в мобільні чи стільникові ігри. Ось заходить маленький українець в стім вибрати гру. Він в щось зможе пограти про свою власну країну? Чи про її історію? У русні є танки, якась РПГ про слов'ян і махачі з німотою (як раз їх фішка, все життя штурмувати рейхстаг), є вар сандер...Встановить Острів, але гра дуже специфічна, не всі потягнуть. Візьме дитина мобілку, а там...ГК і айдл ферма. І все, росте дитина відірвана від культурного шару. Батьки можуть бути 100 разів патріотами, волонтерами та воїнами, але ось такого практичного впливу мало. Та його не існує просто. Буде роблокс і три кульки в ряд. У русні дитина бігає в пост радянський біошок, де СРСР не розвалився, а навпаки, став потужним і красивим. На мобілочці в танки за дідов...А українська дитина...популяє байрактаром в грі-пердушці й все. Так і запам'ятає: наші не можуть нічого зробити, а он русаки та танки й вар сандер...Бачите, як програмується все через ігри...</p>
<p>Повернемося до мене. Ще у 2013 році я зробив покрокову мультикористувацьку гру "Битва Націй". Щось схоже на шахи, де по черзі ходиш воїнами й вони б'ються з ворогом. В грі були поляки, турки, українці (козаки) та татари. Але треба було робити арт, і я все закинув. Пройшло 6 років, я зрозумів, що уже в принципі можна було б і універ ще один закінчити...не те щоб намалювати й навчитися робити арт для ігор. І вирішив в будь-якому випадку не зупинятися. Так виник всесвіт "Дике Поле"</p>
<h1 id="heading-dike-pole">Дике Поле</h1>
<p>Дике Поле почалося з інтерактивних історій про козаків. <a target="_blank" href="https://locadeserta.com/interactive/">Інтерактивні історії</a>. На диво, читачі самі пишуть уже інтерактивні історії та додають їх в каталог. Дуже вражений. Бо грою я більше не займаюсь.</p>
<p>Далі були: Слобода (перша, не ця :) ), Чумаки, Гекс: <a target="_blank" href="https://locadeserta.com/all_games_uk.html">Всі наявні ігри</a>. Все в одному стилі та все зі зв'язком з Україною. А саме з козацькими часами. На цих іграх я підготував близько 200 різних 3-Д моделей у воксель стилі. Що дало змогу почати мою наступну гру. Ці ігри досі мають по 100 установок в місяць, для мене це ще одне диво :)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669111499880/I50jMYysr.jpg" alt="sich_view_0.jpg" /></p>
<p>Але. Це були ігри зроблені на колінці. Хоча ігроладу в десять разів більше ніж в інших мобільних іграх.. Я користувався флатером для випуску на всіх платформах. Але ж це не ігровий рушій. То восени 2021 року вирішив зробити переробку Слободи, але уже по-справжньому, як повноцінна гра. Добре, що арту і 3D воксельних моделей уже було доволі багато, я почав перероблювати примітивний "довигадуй сам" ігролад першої флатерної Слободи.</p>
<h1 id="heading-dike-pole-sloboda-2">Дике Поле: Слобода 2</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669111508924/btgv0kHEE.png" alt="camp_ua.png" /></p>
<p><a target="_blank" href="https://www.tiktok.com/@locadeserta/video/7167762541311986949?is_copy_url=1&amp;is_from_webapp=v1">Всі відео на Тіктоку</a></p>
<p>Можу з впевненістю сказати: Слобода 2, це перша гра про Україну на мобілках. В якій є сюжет, задачі, занурення в часи XVII століття. Але без пафосного бреда, як це часто роблять в іграх. А ось таке будення: ви отаман Слободи, яку треба заснувати, розбудувати, вийти до моря і допомагати Січі. Без магії, діамантів, ІАР і Ads SDK. Чиста гра. Десь 2-4 години безперервної гри, щоб закінчити її. Так, в грі є кінець. Але ви та далі можете бігати по мапі. Отримали 21-у ачівку "Кінець Гри" - вважайте ви всю гру пройшли.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669111522852/BtJxezcAs.png" alt="stats_ua.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669111593281/8r5aHsxvn.png" alt="ortho_camp.png" /></p>
<p>Для антуражу і занурення ми замовили три оригінальні звукові доріжки. Від класичних кобзарських пісень, до класики 18 століття та сучасності. Так, в грі є кобзарі, справжні НПЦ, які грають на бандурі. Підходьте до них і вони вам зіграють. В грі треба буде висилати допомогу козакам під Азовом. Чи знали ви, що наші козаки штурмували ту турецьку фортецю, а потім ще і назад не віддавали декілька років? А завдяки грі, ваша дитина про це взнає. Або ви самі навіть. А мем про Золото Полуботка? Я ще у своєму дитинстві пам'ятаю унікумів, які розповідали, що золото Полуботка в Острі зарито, і японці хочуть знайти, але наші не дають...Такі речі єднають всі націю. Бо дитина, дорослий чи підліток пограють в щось своє, і у них уже є точки дотику. Від Закарпаття до Луганська буде щось з'єднувати людей. У русні це діди воювали, яким пронизані всі ігри. У нас має бути Дике Поле: там, де сформувалась Україна, з козаками, Гетьманством.</p>
<h1 id="heading-sho-dali">Що далі</h1>
<p>В грудні 2022 випускаємо Слободу 2. Повноцінна гра. Можливо, навіть, випустимо на стімі в кінці зими. Але наразі 100% фокус це мобільні платформи. Вони є у всіх. Дивлюся по дітях в школі - про РС навіть ніхто не мріє уже...є мобілка, навіщо ще щось? Кожна з наступних наших ігор буде відштовхуватися від попередніх: переюзання арту, звуків. Взагалі легше робити, коли уже щось зроблено.</p>
<p>В січні 2023 перший патч: доробимо те, що не доробили :)</p>
<p>Лютий 2023: перший контент патч: Нова мапа. Хаджибей-Одеса. Будемо ставити козацьку слободу і там. Опа, а ось і гравці взнають, що Одесу заснувала не якась катька з потемкінім, а конкретні татари, турки і козаки...Відчуваєте, який вплив ігри роблять?</p>
<p>Грудень 2023 (через рік): Вихід наступної гри "Козаки: Гроза Чорного Моря". Будемо кататись на дубах, байдаках і липах по Чорному Морю, топити турків, щемити татар...захоплювати Кримські селища. В голові взяти ідеї та ігролад старої гри <a target="_blank" href="https://www.youtube.com/watch?v=6D77IF9lJ9g&amp;t=636s">Corsairs: Conquest at Sea</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669111608518/GDgLiiJkr.png" alt="corsairs.png" /></p>
<p>Але спочатку реліз на Апл та Гугл платформах Слободи 2.</p>
<p>Гладкий Дмитро, 20 Листопада 2022.</p>
]]></content:encoded></item><item><title><![CDATA[The Magic of Start/Update Events in Unity's MonoBehaviour]]></title><description><![CDATA[The Magic of Start/Update Events in MonoBehaviour
If you programmed at least something in the Unity then you definitely extended the 
MonoBehaviour class and defined your custom Start, Update, Awake, etc methods.
But the method is private and it is n...]]></description><link>https://gladimdim.org/the-magic-of-startupdate-events-in-unitys-monobehaviour</link><guid isPermaLink="true">https://gladimdim.org/the-magic-of-startupdate-events-in-unitys-monobehaviour</guid><category><![CDATA[unity]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Wed, 25 May 2022 06:41:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1653460705715/r2Oe--YmL.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-the-magic-of-startupdate-events-in-monobehaviour">The Magic of Start/Update Events in MonoBehaviour</h1>
<p>If you programmed at least something in the <strong>Unity</strong> then you definitely extended the <strong>
MonoBehaviour</strong> class and defined your custom <strong>Start, Update, Awake</strong>, etc methods.</p>
<p>But the method is <strong>private</strong> and it is not possible
to <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override">override private methods in C#</a>
. They have to be either public or protected...</p>
<p>And did you know that...these methods were not derived from the parent <strong>MonoBehaviour</strong> class?</p>
<pre><code class="lang-c#"> <span class="hljs-comment">// Start is called before the first frame update</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">Start</span>(<span class="hljs-params"></span>)</span>
{
    audioSource = GetComponent&lt;AudioSource&gt;();
    player = FindObjectOfType&lt;PlayerController&gt;();
    <span class="hljs-keyword">if</span> (objectPool.Count == <span class="hljs-number">0</span>)
    {
        prepareContainerObjects();
    }

    hideRequirementInfo();
}


<span class="hljs-comment">// Update is called once per frame</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">Update</span>(<span class="hljs-params"></span>)</span>
{
    processAction();
}
</code></pre>
<p>Go to <strong>MonoBehaviour.cs</strong> and check that it has no methods. Also its parent - <strong>Behaviour</strong>...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653460691960/W73C1kjO_.png" alt="mono_behaviour.png" /></p>
<p>Then how does it work? How does <strong>Unity</strong> know there is Start method that should be called on <strong>
MonoBehaviour</strong> subclasses?</p>
<h1 id="heading-event-system">Event System</h1>
<p>Easy! Unity uses its own messaging system that's
using <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection">C# reflection</a>
feature</p>
<p>The methods Start/Update/etc are magic methods that can be defined in your <strong>MonoBehaviour</strong>
subclasses. They do not override the implementation from base class. The Unity engine uses <strong>
Component.SendMessage</strong> method in order to dispatch commands to your <strong>MonoBehaviour</strong> subclass.</p>
<pre><code class="lang-c#"><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span>   <span class="hljs-doctag">&lt;para&gt;</span>Calls the method named methodName on every MonoBehaviour in this game object.<span class="hljs-doctag">&lt;/para&gt;</span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="methodName"&gt;</span>Name of the method to call.<span class="hljs-doctag">&lt;/param&gt;</span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="value"&gt;</span>Optional parameter for the method.<span class="hljs-doctag">&lt;/param&gt;</span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="options"&gt;</span>Should an error be raised if the target object doesn't implement the method for the message?<span class="hljs-doctag">&lt;/param&gt;</span></span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SendMessage</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> methodName, <span class="hljs-keyword">object</span> <span class="hljs-keyword">value</span></span>)</span> =&gt; <span class="hljs-keyword">this</span>.SendMessage(methodName, <span class="hljs-keyword">value</span>, SendMessageOptions.RequireReceiver);
</code></pre>
<p>The class hierarchy looks like this for your script:</p>
<pre><code>YourScript <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> MonoBehaviour <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> Behaviour <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> Component
</code></pre><p>So, whenever the <strong>Unity</strong> engine decides to call <strong>Update</strong> method on all objects it calls <strong>
Component.SendMessage</strong> method on the object . It checks whether <strong>Start/Update</strong>/Whatever from the
magic list of methods is defined and will call them.</p>
<h1 id="heading-so-what">So what?</h1>
<p>So, if you have these events with empty bodies - delete them from your code! Before that I thought
that defining empty method had 0 impact as it would be called anyway from parent class. But as you can
see from the investigation: the call will be ignored! So you can save another 1-2 nanoseconds on
each object as the Engine will just skip it.</p>
]]></content:encoded></item><item><title><![CDATA[Replacing Coroutines in Unity with C# Task Async Pattern (TAP)]]></title><description><![CDATA[Intro
In any game you have to code an asynchronous logic. For example: timeouts, countdowns, progress bars
or delayed interactions.
In my city-building
game Loca Deserta: Sloboda
one of the main interactions in game to build/upgrade/produce is done b...]]></description><link>https://gladimdim.org/replacing-coroutines-in-unity-with-c-task-async-pattern-tap</link><guid isPermaLink="true">https://gladimdim.org/replacing-coroutines-in-unity-with-c-task-async-pattern-tap</guid><category><![CDATA[unity]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[C#]]></category><category><![CDATA[Indie Maker]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Thu, 19 May 2022 18:42:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652985491312/0HOZytJJ_.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro">Intro</h2>
<p>In any game you have to code an asynchronous logic. For example: timeouts, countdowns, progress bars
or delayed interactions.</p>
<p><strong>In my city-building
game <a target="_blank" href="https://github.com/gladimdim/locadeserta/blob/master/sloboda2/alpha.md">Loca Deserta: Sloboda</a></strong>
one of the main interactions in game to build/upgrade/produce is done by standing near the objects.
If you stand 1 second near the table it will open its UI like this (notice green progress circle
enlarging as the progress goes on):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652985682609/qVxHCI8wk.gif" alt="ezgif.com-gif-maker (2).gif" /></p>
<p>The logic of that delayed interaction is following:</p>
<ul>
<li>Collision checks for Player object</li>
<li>Collided object launches a 1 second delay before finally opening desired UI or completing action (
like cutting trees)</li>
<li>Player object get notification to show progress bar</li>
<li>Any time player can move away. The progress should stop and collided object should 'forget' about
the player and cancel the launched task.</li>
<li>If the player still stands near the collided object and timeout ends, then the action is
executed (cutting tree, mining stone, opening build UI, etc).</li>
</ul>
<h2 id="heading-coroutines">Coroutines</h2>
<p>One of the ways to do this is by using Unity's
Coroutines: <a target="_blank" href="https://docs.unity3d.com/Manual/Coroutines.html">https://docs.unity3d.com/Manual/Coroutines.html</a></p>
<p>They give a built-in mechanism for starting and cancelling async actions.</p>
<p>The code that starts coroutine to mine a stone resource in my game:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">StartMining</span>(<span class="hljs-params"></span>)</span>
{
    actionCoroutine = StartCoroutine(processMining());
    audioSource.PlayOneShot(actionSound);
    Actions?.Invoke(MineableActions.STARTED, <span class="hljs-keyword">this</span>);
    playMiningParticle(<span class="hljs-literal">true</span>);
}

<span class="hljs-function">IEnumerator <span class="hljs-title">processMining</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> DelayedRoutine.delayBy((<span class="hljs-keyword">float</span>)miningDurationInSeconds);
    executeMining();
}
</code></pre>
<p>And in <strong>OnTriggerExit</strong>, when the player leaves the collided object, we can cancel launched
coroutine in case it was not yet finished. The game should 'forget' about the launched action:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">stopMining</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (actionCoroutine != <span class="hljs-literal">null</span>)
    {
        StopCoroutine(actionCoroutine);
        actionCoroutine = <span class="hljs-literal">null</span>;
        Actions?.Invoke(MineableActions.STOPPED, <span class="hljs-keyword">this</span>);
        audioSource.Stop();
        playMiningParticle(<span class="hljs-literal">false</span>);
    }
}
</code></pre>
<h2 id="heading-the-problem-with-performance">The problem with performance</h2>
<p>This code works perfectly fine. But the performance of Coroutines on low-end mobile phones is below
desired.</p>
<p>Here is the profile frame when the collision happens and Unity creates coroutine for you recorded
on <strong>Galaxy Note 8</strong> device:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652985428977/0Hbw4hmbU.png" alt="screen_coroutine_time.png" /></p>
<p>It takes...200-300ms to launch a coroutine! The bad thing: it was happening on each collision call.
Players could feel the lag once they wanted to open a UI or cut a tree.</p>
<p>The performance on <strong>Galaxy S21 Ultra</strong> was way better but still I could see spikes when coroutine was created.</p>
<p>You can google dozens of questions regarding the performance issues with coroutines:</p>
<ul>
<li><a target="_blank" href="https://answers.unity.com/questions/1451237/performance-of-coroutines-in-mobile.html">https://answers.unity.com/questions/1451237/performance-of-coroutines-in-mobile.html</a></li>
<li><a target="_blank" href="https://stackoverflow.com/questions/61464452/in-unity-when-should-i-use-coroutines-versus-subtracting-time-deltatime-in-upda">https://stackoverflow.com/questions/61464452/in-unity-when-should-i-use-coroutines-versus-subtracting-time-deltatime-in-upda</a></li>
</ul>
<h2 id="heading-task-based-async-pattern-tap">Task-based Async Pattern (TAP)</h2>
<p>The C# official way of dealing with asynchronous tasks is <strong>
TAP</strong>: <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap">https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap</a>
.</p>
<p>Almost all async Task methods
support <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=net-6.0">https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=net-6.0</a>
parameter. It is used to check if your task was cancelled by the source or not.</p>
<p>Read the MS docs and then return back to my article to get basic understanding what's going on.</p>
<p>The rewritten Coroutine code with <strong>TAP</strong> pattern looks like this:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">void</span> <span class="hljs-title">StartMiningTask</span>(<span class="hljs-params"></span>)</span>
{
    audioSource.PlayOneShot(actionSound);
    Actions?.Invoke(MineableActions.STARTED, <span class="hljs-keyword">this</span>);
    playMiningParticle(<span class="hljs-literal">true</span>);

    cancellationTokenSource = <span class="hljs-keyword">new</span> CancellationTokenSource();
    <span class="hljs-keyword">await</span> ProcessMining(cancellationTokenSource.Token);
}
</code></pre>
<p>We add async keyword, add <strong>Task</strong> suffix to the method name (common pattern). And instead of saving
coroutine to variable we create new <strong>CancellationTokenSource</strong> and send it to awaited method <strong>ProcessMiningTask</strong>.</p>
<p><strong>ProcessMiningTask</strong> method uses <strong>Task.Delay</strong> and sends it the same <strong>CancellationToken</strong> it
received . If the <strong>CancellationToken</strong> is activated then the <strong>Task.Delay</strong> would throw exception that we catch:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">async</span> Task <span class="hljs-title">ProcessMiningTask</span>(<span class="hljs-params">CancellationToken token</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-keyword">await</span> Task.Delay(miningDurationInSeconds * <span class="hljs-number">1000</span>, token);
        ExecuteMining();
    }
    <span class="hljs-keyword">catch</span> (TaskCanceledException _)
    {
    }
    <span class="hljs-keyword">finally</span>
    {
        cancellationTokenSource = <span class="hljs-literal">null</span>;
    }
}
</code></pre>
<p>And <strong>StopMining</strong> method checks for active source and cancels the Task. Then proceeds with standard
gameplay logic:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">StopMining</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (cancellationTokenSource == <span class="hljs-literal">null</span>)
    {
        <span class="hljs-keyword">return</span>;
    }

    cancellationTokenSource.Cancel();
    Actions?.Invoke(MineableActions.STOPPED, <span class="hljs-keyword">this</span>);
    audioSource.Stop();
    PlayMiningParticle(<span class="hljs-literal">false</span>);
}
</code></pre>
<p>That's how easy it was to rewrite using standard C# pattern!</p>
<h2 id="heading-summary">Summary</h2>
<p>In your case the Coroutines in Unity might work and perform well but definitely not in my case. Also
I prefer standard C# .NET features instead of relying on Unity-specific implementations of common
patterns.</p>
<p>After I've got rid of all of these Coroutine calls there are no longer spikes on collisions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652985447225/E4S1ZnFi1.png" alt="profile_after.png" /></p>
<p>As usual, do and use what suites you the best!</p>
<h1 id="heading-sloboda-game">Sloboda Game</h1>
<p>If you are into games like Settlers, Minecraft, Unrailed! or just any Voxel Art games, then you can
follow my progress on creating complete new game from scratch!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652985455222/a9dklfv3a.png" alt="in_game_2.png" /></p>
<ul>
<li>Use alpha builds to get the latest features I add on a weekly
basis: <a target="_blank" href="https://github.com/gladimdim/locadeserta/blob/master/sloboda2/alpha.md">https://github.com/gladimdim/locadeserta/blob/master/sloboda2/alpha.md</a></li>
<li>Follow my dev diary blog in
Telegram: <a target="_blank" href="https://t.me/locadesertachumaki">https://t.me/locadesertachumaki</a></li>
<li>Follow on Twitter: <a target="_blank" href="https://twitter.com/gladimdim">https://twitter.com/gladimdim</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Dart: How to Handle Exceptions Raised Inside Isolates]]></title><description><![CDATA[Dart team has recently published a new version of Dart compiler with huge enhancements in Isolates worlds.
So everyone is now discovering new possibilities and that you can spawn a subprocess to unlock your main thread to do heavy lifting.
Read more ...]]></description><link>https://gladimdim.org/dart-how-to-handle-exceptions-raised-inside-isolates</link><guid isPermaLink="true">https://gladimdim.org/dart-how-to-handle-exceptions-raised-inside-isolates</guid><category><![CDATA[Dart]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Wed, 09 Feb 2022 13:44:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644414176468/JRbGWKTWv.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dart team has recently <a target="_blank" href="https://medium.com/dartlang/dart-2-15-7e7a598e508a">published a new version of Dart compiler</a> with huge enhancements in Isolates worlds.</p>
<p>So everyone is now discovering new possibilities and that you can spawn a subprocess to unlock your main thread to do heavy lifting.</p>
<p>Read more about Isolates: <a target="_blank" href="https://api.dart.dev/stable/2.15.0/dart-isolate/Isolate-class.html">https://api.dart.dev/stable/2.15.0/dart-isolate/Isolate-class.html</a></p>
<p>Read my other article <a target="_blank" href="http://dmytrogladkyi.com/#/catalog/posts/flutter-unblocking-ui-thread-with-isolates-compute-function">Flutter: Unblocking UI thread with Isolates compute function</a></p>
<h1 id="heading-glad-tools">GLAD TOOLS</h1>
<p>I keep improving my <a target="_blank" href="https://github.com/gladimdim/glad_tools/">GladTools</a> tool belt from week to week.
This tooling was made for folks like me who do some basic dev tasks by using...online services.
But are you sure they dont leak or that you dont leak important data like JWT tokens, keys, password, endpoints?</p>
<p>So far my GladTools support:</p>
<ul>
<li>JSON Parser</li>
<li>JSON Beautifier</li>
<li>JWT parser</li>
<li>URL maker</li>
<li>Base64 image decoder</li>
</ul>
<p>As you see, I have to parse JSONs. What happens if you paste a huge JSON into the text field and hit Beautify?</p>
<p>My tool will call jsonDecode in main thread...which potentially can lock your UI thread for unknown amount of time.</p>
<p>This is a bad approach and we must always think possible ways of unlocking the UI thread.</p>
<p><strong>So I decided to follow the amazing article by <a target="_blank" href="https://codewithandrea.com/articles/parse-large-json-dart-isolates/">Coding with Andrea</a> and enhanced my code.</strong></p>
<h1 id="heading-basic-version-json-parser-in-isolate">Basic version JSON Parser in Isolate</h1>
<p>From the article and from my older code we can make such snippet that parses JSON in another process:</p>
<pre><code>class JsonParserIsolate {
  final String input;

  JsonParserIsolate(<span class="hljs-built_in">this</span>.input);

  Future parseJson() async {
    <span class="hljs-keyword">var</span> port <span class="hljs-operator">=</span> ReceivePort();
    await Isolate.spawn(_parse, port.sendPort);
    <span class="hljs-keyword">return</span> await port.first;
  }

  Future<span class="hljs-operator">&lt;</span>void<span class="hljs-operator">&gt;</span> _parse(SendPort p) async {
    final json <span class="hljs-operator">=</span> jsonDecode(input);
    Isolate.exit(p, json);
  }
}
</code></pre><p>The code is straightforward to understand. It runs and parses the JSON in separate process.</p>
<h2 id="heading-but-there-is-a-problem">But there is a problem</h2>
<p>What happens when you send an invalid JSON? </p>
<p>It turns out that inside the method <strong>_parse</strong> call to <strong>jsonDecode</strong> will throw exception but it never escapes the Isolate!
The exception is not rethrown into the parseJson method, nor to the points where JsonParserIsolate.parseJson was called.</p>
<p>That is why the value or even exception will never be returned.</p>
<p>This line is never executed:</p>
<pre><code><span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> port.first
</code></pre><h2 id="heading-how-to-solve">How to solve</h2>
<p>I figured out a way to inform the caller that the JSON failed to parse. We have to use two ReceivePorts and provide them to the Isolate.spawn like this:</p>
<pre><code><span class="hljs-keyword">var</span> port <span class="hljs-operator">=</span> ReceivePort();
<span class="hljs-keyword">var</span> errorPort <span class="hljs-operator">=</span> ReceivePort();
await Isolate.spawn(_parse, port.sendPort, onError: errorPort.sendPort);
</code></pre><p>Both <strong><em>port, errorPort</em></strong> are streams that we can listen:</p>
<pre><code>errorPort.<span class="hljs-keyword">listen</span>((message) {
  // <span class="hljs-keyword">exception</span> thrown INSIDE isolate. Process it!
});

port.<span class="hljs-keyword">listen</span>((message) {
  // everything <span class="hljs-keyword">is</span> good.
  // This <span class="hljs-keyword">is</span> the same <span class="hljs-keyword">as</span> <span class="hljs-keyword">return</span> await port.first; <span class="hljs-keyword">as</span> above
});
</code></pre><h2 id="heading-how-to-notify-caller-of-our-parsejson">How to notify caller of our parseJson?</h2>
<p>You see that now we have two listeners to the data/error streams. How can we notify the caller of parseJson about the data or the error?</p>
<p>Use <a target="_blank" href="https://api.flutter.dev/flutter/dart-async/Completer-class.html">Completer</a>!</p>
<p>This class is underrated by the community as everyone just used to consume Futures/Streams/async calls.</p>
<p>Completer works like this:</p>
<ol>
<li>Instantiate Completer()</li>
<li>return completer.future from your method (this is identical to deferred Promises in JavaScript, if someone remembers Q promises there).</li>
<li>Inside errorPort/port listeners call the <strong>completer.complete</strong> or <strong>completer.completeError</strong></li>
</ol>
<p>Rewritten method:</p>
<pre><code>Future parseJson() async {
final completer <span class="hljs-operator">=</span> Completer();
<span class="hljs-keyword">var</span> port <span class="hljs-operator">=</span> ReceivePort();
<span class="hljs-keyword">var</span> errorPort <span class="hljs-operator">=</span> ReceivePort();
await Isolate.spawn(_parse, port.sendPort, onError: errorPort.sendPort);

errorPort.listen((message) {
    <span class="hljs-comment">// first is Error Message</span>
    <span class="hljs-comment">// second is stacktrace which is not needed</span>
    List errors <span class="hljs-operator">=</span> message <span class="hljs-keyword">as</span> List;
    errorPort.close();
    completer.completeError(errors.first);
});

port.listen((message) {
  port.close();
  completer.complete(message);
});

<span class="hljs-keyword">return</span> completer.future;
}
</code></pre><p>I am not sure whether we need to call port.close() in listener, but to make things safer I did it :)</p>
<h1 id="heading-full-solution">Full solution</h1>
<p>Full listing for jsonDecode called in separate Isolate:</p>
<pre><code>class JsonParserIsolate {
  final String input;

  JsonParserIsolate(<span class="hljs-built_in">this</span>.input);

  Future parseJson() async {
    final completer <span class="hljs-operator">=</span> Completer();
    <span class="hljs-keyword">var</span> port <span class="hljs-operator">=</span> ReceivePort();
    <span class="hljs-keyword">var</span> errorPort <span class="hljs-operator">=</span> ReceivePort();
    await Isolate.spawn(_parse, port.sendPort, onError: errorPort.sendPort);

    errorPort.listen((message) {
        <span class="hljs-comment">// first is Error Message</span>
        <span class="hljs-comment">// second is stacktrace which is not needed</span>
        List errors <span class="hljs-operator">=</span> message <span class="hljs-keyword">as</span> List;
        errorPort.close();
        completer.completeError(errors.first);
    });

    port.listen((message) {
      port.close();
      completer.complete(message);
    });

    <span class="hljs-keyword">return</span> completer.future;
  }

  Future<span class="hljs-operator">&lt;</span>void<span class="hljs-operator">&gt;</span> _parse(SendPort p) async {
    final json <span class="hljs-operator">=</span> jsonDecode(input);
    Isolate.exit(p, json);
  }
}
</code></pre><h1 id="heading-how-to-use-it">How to use it?</h1>
<p>Just like any other method call! Try/catch it to handle errors:</p>
<pre><code>void _minify() async {
    <span class="hljs-keyword">var</span> parser <span class="hljs-operator">=</span> JsonParserIsolate(_controller.text);
    <span class="hljs-keyword">try</span> {
      dynamic input <span class="hljs-operator">=</span> await parser.parseJson();
      _controller.text <span class="hljs-operator">=</span> _minifyString(input);
    } <span class="hljs-keyword">catch</span> (e) {
      reportError(e);
    }
}
</code></pre>]]></content:encoded></item><item><title><![CDATA[Performance Gains in Unity. How I went from 15 to 60 FPS]]></title><description><![CDATA[Intro
I am doing a remake of my old game Loca Deserta: Sloboda The first version was done with Flutter but this time I picked Unity as the game engine.
I started from scratch and implemented lots of new functionality but I have noticed that even my G...]]></description><link>https://gladimdim.org/performance-gains-in-unity-how-i-went-from-15-to-60-fps-1</link><guid isPermaLink="true">https://gladimdim.org/performance-gains-in-unity-how-i-went-from-15-to-60-fps-1</guid><category><![CDATA[unity]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Mon, 07 Feb 2022 14:10:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242752441/0uP7C4nFn.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro">Intro</h2>
<p>I am doing a remake of my old game <a target="_blank" href="https://locadeserta.com/citybuilding/index_en">Loca Deserta: Sloboda</a> The first version was done with <a target="_blank" href="https://flutter.dev">Flutter</a> but this time I picked Unity as the game engine.</p>
<p>I started from scratch and implemented lots of new functionality but I have noticed that even my Galaxy S21 Ultra had lagging issues. The FPS was smooth but sometimes I had a feeling it dropped from 60 to 30 FPS.</p>
<p>I took a very old Nokia 6.1 with Android, launched my game and was shocked. It was a complete garbage. FPS was in a 0-15 range. Completely unusable.</p>
<h1 id="heading-tldr-what-was-wrong">TLDR What was wrong</h1>
<ul>
<li>Terrain layers</li>
<li>Too much of heavy logic in MonoBehaviour:Update</li>
<li>Instantiate calls</li>
<li>Destroy calls</li>
</ul>
<h2 id="heading-how-did-i-find-bottlenecks">How did I find bottlenecks</h2>
<p>Of course I launched a Profiler and checked what was choking in my Nokia 6.1/Galaxy Note 8</p>
<p>You can see that the game ran at max 30 FPS, even fewer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242276801/HqckhO9bGI.png" alt="note_8_fix_without_fix.png" /></p>
<p>Also when I have to show building/production requirements with 3D resource models:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242290566/VW1veKvnt.png" alt="ingame.png" /></p>
<p>it spiked to 40-60ms per frame and the bottleneck was my OnTriggerEnter logic (more on this later). Also pay attention to that blue scripting timings that appear when the new resources are shown on the screen. Definitely something was wrong with my code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242303612/keANvqviO.jpeg" alt="blue_spikes.jpg" /></p>
<h3 id="heading-first-victim-shadows">First victim - Shadows?</h3>
<p>At first I thought it was all about shadows. I switched them off...and nothing changed. The game choked itself.</p>
<h3 id="heading-second-victim-quality-settings">Second victim - Quality Settings?</h3>
<p>I tried to run it on low/very low. Better but still at 15-30 FPS for such a simple game. No!</p>
<h2 id="heading-divide-and-conqueror">Divide and Conqueror</h2>
<p>I decided to use a scientific method of divide, conqueror and guess what's wrong. Something in the scene definitely chocked the GPU (as 80% of my CPU frame time was spent on waiting on GPU results).</p>
<h2 id="heading-removed-terrain">Removed Terrain</h2>
<p>I started with the biggest asset: I removed Terrain layer.</p>
<p><strong>BOOM</strong> ! Steady 60 FPS on Galaxy Note 8! Haha! It was it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242321531/2c2zgyvq5.png" alt="note_8_no_terrain_layers_60fps.png" /></p>
<p>I returned back - again 15-30...It was definitely Terrain that caused me the performance issues.</p>
<h3 id="heading-how-to-fix-terrain-on-mobile">How to fix Terrain on Mobile</h3>
<p>So I read through the <a target="_blank" href="https://docs.unity3d.com/Manual/class-TerrainLayer.html">TerrainLayer</a> official docs and noticed this:</p>
<pre><code>you can <span class="hljs-keyword">use</span> four Terrain Layers per Texture pass, <span class="hljs-keyword">with</span> <span class="hljs-keyword">no</span> <span class="hljs-keyword">limit</span> <span class="hljs-keyword">on</span> the <span class="hljs-built_in">number</span> <span class="hljs-keyword">of</span> passes.
This means that although you <span class="hljs-keyword">are</span> allowed <span class="hljs-keyword">to</span> <span class="hljs-keyword">use</span> <span class="hljs-keyword">as</span> many Terrain Layers <span class="hljs-keyword">as</span> you want,
<span class="hljs-keyword">each</span> pass increases the <span class="hljs-built_in">time</span> spent rendering the Terrain.
<span class="hljs-keyword">For</span> maximum <span class="hljs-keyword">performance</span>, <span class="hljs-keyword">limit</span> <span class="hljs-keyword">each</span> <span class="hljs-keyword">of</span> your Terrain tiles <span class="hljs-keyword">to</span> four Terrain Layers.
</code></pre><p>And I had like 8 layers all mixed on the floor with different alpha and masks!</p>
<p>And for real, once I have only 4 layers like on this screen: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242336613/v0sWIfedJG.png" alt="terrain_layers.png" /></p>
<p>Then the game runs very smooth even on ancient Android like Nokia 6.1:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242350348/zmdnag2OM.png" alt="note_8_4_terrain_layers_60fps.png" /></p>
<p>Once I add even one more layer (without using it for painting!) then the performance drops.</p>
<h4 id="heading-outcome-for-terrain">Outcome for Terrain</h4>
<p>Don't set more than 4 terrain layers even if you paint only 4 still the Unity will choke processing your Terrain.</p>
<h3 id="heading-spikes-in-monobehaviourupdate">Spikes in MonoBehaviour:Update</h3>
<p>This was easy to fix, it turned out I used DateTime.now to get the current time in order to process task timers in game. It seems this can be a little painfull on mobile phones so I rewrote the logic and used Time.deltaTime in order to check how much time has passed since the Upgrade/Production task started:</p>
<pre><code>void Update()
{
    <span class="hljs-keyword">if</span> (actionStarted)
    {
        elapsedActionTime <span class="hljs-operator">+</span><span class="hljs-operator">=</span> Time.deltaTime;
        <span class="hljs-keyword">if</span> (elapsedActionTime <span class="hljs-operator">&gt;</span> actionDuration)
        {
            actionStarted <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;
            elapsedActionTime <span class="hljs-operator">=</span> <span class="hljs-number">0</span>.0f;
            finalizeAction();
        }
    }
}
</code></pre><p>Check this awesome video on how to implement timers in Unity:</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=eeY24OyLGv0">4 Easy ways to create Timers in Unity - in 2 minutes - Jason Weimann</a></p>
<p>After fix, notice that the blue scripting timings are much shorter!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242396952/esygSAtvv.png" alt="nokia_61_without_datetimenow.png" /></p>
<h2 id="heading-get-rid-of-instantiate-and-destroy">Get rid of Instantiate and Destroy</h2>
<p>When I have to show materials for build/produce requirements I instantiated prefabs and rendered them on screen. Then when the player is not near the building I destroyed them.</p>
<p>It turns out...Instantiate and Destroy are VERY resource consuming procedures.</p>
<p>See these spikes? These are Instantiate/Destroy calls. Some of them take 40-70 ms:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242386263/J-h9yq6nL.png" alt="nokia_61_object_instantiate_spikes.png" /></p>
<p>I knew how to fix this with Object Pooling: <a target="_blank" href="https://www.youtube.com/watch?v=uxm4a0QnQ9E">Object Pooling (in depth) - Game Programming Patterns in Unity &amp; C# - Jason Weimann</a></p>
<p>I reimplemented the logic to create all the objects beforehand when the scene loads. And then just pull them from Object Pool and place where needed.</p>
<p>Check the same game scenario to show new Resource objects on screen but with Object Pooling:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242411018/lVs35qcjA.png" alt="nokia_61_object_pooling_all_objects_cached.png" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<ul>
<li>Don't use more than 4 Terrain Layers</li>
<li>Don't Instantiate/Destroy too often.</li>
<li>Use Object Pooling where applicable</li>
<li>Don't do heavy tasks in Update</li>
</ul>
<p>My game now runs at 60FPS on Samsung S21 Ultra and 30-40 FPS on Galaxy Note 8 and Nokia 6.1.</p>
<p>I can finally continue on adding more buildings to the game :)</p>
<p>This is my game running on Nokia 6.1: <a target="_blank" href="https://twitter.com/DmytroGladkyi/status/1481540410098991104">https://twitter.com/DmytroGladkyi/status/1481540410098991104</a></p>
<h1 id="heading-more-details-on-my-remake-in-unity">More Details on My Remake in Unity</h1>
<p>The first version of my game has features from a 'real' full-sized game you used to have in a city building genre:</p>
<ul>
<li><p>Manufacturing lines</p>
</li>
<li><p>Create compose materials out of simpler materials</p>
</li>
<li><p>Research influences production</p>
</li>
<li><p>Building and Upgrading buildings (each type of building has 3 level with all unique voxel art!)</p>
</li>
<li><p>Local Map with random events</p>
</li>
<li><p>Global huge map</p>
</li>
<li><p>Conquering new areas</p>
</li>
<li><p>Weather events</p>
</li>
</ul>
<p>You can learn more and download my old Sloboda game here: <a target="_blank" href="https://locadeserta.com/citybuilding/index_en">Loca Deserta: Sloboda</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644242439126/xUpsvWCe6z.png" alt="sich_screen.png" /></p>
<h1 id="heading-follow-for-progress-on-the-remake">Follow for Progress on the Remake</h1>
<p>You can join my Telegram Channel: <a target="_blank" href="Loca Deserta Game Universe">https://t.me/locadesertachumaki</a>. Or <a target="_blank" href="Try alpha 3 on Android, Web and Windows">https://t.me/locadesertachumaki/467</a>.</p>
<p>Or follow me on Twitter: <a target="_blank" href="https://twitter.com/DmytroGladkyi">https://twitter.com/DmytroGladkyi</a></p>
]]></content:encoded></item><item><title><![CDATA[What Would Happen if Dash Memo Leak Had Never Happened?]]></title><description><![CDATA[So, 12 years have passed after the infamous "Dash Memo" leak was published.

For those who doesn't know: V8 team did a side gig and created a new language with its own VM as they were exhausted maintaining all the JS shortcomings. So as a side projec...]]></description><link>https://gladimdim.org/what-would-happen-if-dash-memo-leak-had-never-happened</link><guid isPermaLink="true">https://gladimdim.org/what-would-happen-if-dash-memo-leak-had-never-happened</guid><category><![CDATA[Dart]]></category><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Sat, 05 Feb 2022 12:51:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644065353154/L_ZQZk1pY.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644065353154/L_ZQZk1pY.jpeg" alt="dart_memo_leaker.jpg" /></p>
<p>So, 12 years have passed after the infamous <a target="_blank" href="https://gist.github.com/paulmillr/1208618">"Dash Memo" leak was published.</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644065303003/MFTz3tAnf.png" alt="email.png" /></p>
<p>For those who doesn't know: V8 team did a side gig and created a new language with its own VM as they were exhausted maintaining all the JS shortcomings. So as a side project someone forked V8 and created what is now known as Dart . At those times it was called Dash. Some more effort was put into this and they managed to make a BETTER language for the Web.</p>
<p>As Google controls the Chrome development it was obvious they have power to do a tectonic shift: add another default language for the web and in a long time remove all that JS legacy. Or leave it as is.</p>
<p>But, someone at the team saw the email and leaked it to the public.</p>
<p><strong>The first passage stated:</strong></p>
<pre><code>Executive Summary

Javascript has fundamental flaws that cannot be <span class="hljs-keyword">fixed</span> merely by evolving the language.
We'll adopt a two<span class="hljs-operator">-</span>pronged strategy <span class="hljs-keyword">for</span> the future of Javascript.
</code></pre><p><strong>And their proposal:</strong></p>
<pre><code>Dash (high risk/high reward):
Develop a <span class="hljs-built_in">new</span> <span class="hljs-keyword">language</span> (<span class="hljs-keyword">called</span> Dash) that aims <span class="hljs-keyword">to</span> maintain the dynamic nature <span class="hljs-keyword">of</span> Javascript but have a better performance profile
<span class="hljs-keyword">and</span> be amenable <span class="hljs-keyword">to</span> tooling <span class="hljs-keyword">for</span> <span class="hljs-keyword">large</span> projects. Push <span class="hljs-keyword">for</span> Dash <span class="hljs-keyword">to</span> become an <span class="hljs-keyword">open</span> standard <span class="hljs-keyword">and</span> be adopted <span class="hljs-keyword">by</span> other browsers.
Developers <span class="hljs-keyword">using</span> Dash tooling will be able <span class="hljs-keyword">to</span> use a <span class="hljs-keyword">cross</span>-compiler <span class="hljs-keyword">to</span> target Javascript <span class="hljs-keyword">for</span> browsers that <span class="hljs-keyword">do</span> <span class="hljs-keyword">not</span> support Dash natively.
</code></pre><p><strong>Did they want to harm web developers? <em>No</em>. Did they want to make web development better? <em>Yes!</em></strong></p>
<p>They actually wanted to add optional types, make language tooling friendly, and more performant...all of this was later promised by TypeScript. <a target="_blank" href="https://dev.to/alekseiberezkin/typescript-is-slow-what-can-we-do-about-it-30hm">That everyone wants to improve</a> because the compilation time is slow. When we added TS to our project in 2016 the compilation time jumped from 40 seconds to...8 minutes. No shit. 4 years have passed I still see articles on how someone rewrote TS Compiler in Rust or Go and got 4000x improvement compilation time. Remember that TypeScript Compiler just converts one text into the other...</p>
<h2 id="heading-what-dash-wanted-to-give-us">What Dash wanted to give us</h2>
<pre><code>Dash <span class="hljs-keyword">is</span> designed with three perspectives in mind:

Performance <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Dash <span class="hljs-keyword">is</span> designed with performance characteristics in mind,
so that it <span class="hljs-keyword">is</span> possible to create VMs that do not have the performance problems that all EcmaScript VMs must have.

Developer Usability <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Dash <span class="hljs-keyword">is</span> designed to keep the dynamic, easy<span class="hljs-operator">-</span>to<span class="hljs-operator">-</span>get<span class="hljs-operator">-</span>started, no<span class="hljs-operator">-</span>compile nature of Javascript
that has made the web platform the clear winner <span class="hljs-keyword">for</span> hobbyist developers.

Ability to be Tooled <span class="hljs-operator">-</span><span class="hljs-operator">-</span> Dash <span class="hljs-keyword">is</span> designed to be more easily tooled (e.g. with optional types) <span class="hljs-keyword">for</span> large<span class="hljs-operator">-</span>scale projects
that <span class="hljs-built_in">require</span> code<span class="hljs-operator">-</span>comprehension features such <span class="hljs-keyword">as</span> refactoring and finding callsites.
Dash, however, does not <span class="hljs-built_in">require</span> tooling to be effective<span class="hljs-operator">-</span><span class="hljs-operator">-</span>small<span class="hljs-operator">-</span>scale developers may still be satisfied with a text editor.
</code></pre><p>Remember it was 2012, <a target="_blank" href="https://devblogs.microsoft.com/typescript/announcing-typescript-1-0/#:~:text=When%20we%20introduced%20Typescript%20to,regularly%20interacting%20and%20sharing%20ideas.">Microsoft just issued TypeScript 0.8</a> which was not needed by anyone in the web at that time. React was not a thing.</p>
<p>But the web community started a hysteria saying that Google wanted to 'lock' them into some Google owned language. Read this absurd again.  So they did not want to get a default new better language and instead welcomed TypeScript that is 100% controlled by Microsoft that was known for making very bad things to devs with their dominance of IE and Windows. Then they welcomed React that is 100% controlled by Facebook. They are all vendor lock technologies. They are done in public but that does not mean you have any control of them. Even W3C is a useless org without Google and Apple. Yet nobody cries over HTML being vendor locked...</p>
<h2 id="heading-what-would-happen-instead">What would happen instead?</h2>
<p>Let's imagine they added Dash/Dart to the Chrome in 2015. Dart compiles to JS without any issues. So the backward compatibility would be saved even if Apple rejected to add DartVM to Safari. Firefox was not a thing anymore so we don't care about Mozilla at all. You could serve two bundles <em>(LIKE ACTUALLY LOTS OF WEB APPS DO TODAY)</em> : one bundle for DartVM. The other for JS VM for Safari.</p>
<h2 id="heading-missed-benefits">Missed Benefits</h2>
<h3 id="heading-live-reload">Live Reload</h3>
<p><a target="_blank" href="https://docs.flutter.dev/development/tools/hot-reload">Live reload</a>. Don't compare to web hot reload. In Flutter you can save the file and see updated app without refresh or state change. We still don't have it in current web dev.</p>
<h3 id="heading-ui-toolkithttpsflutterdevecosystem"><a target="_blank" href="https://flutter.dev/ecosystem">UI toolkit</a></h3>
<p>Finally normal UI toolkit made on tech made for...UI! Not for text documents targeting to some basement in Zurich in 1988...</p>
<h3 id="heading-same-language-for-logic-and-ui">Same language for logic and UI</h3>
<p>The same language for logic and for the UI markup. No more: Learn JS, Learn HTML, Learn CSS. <strong>Just: learn Dart</strong>. Don't switch between completely different mind stacks. Flutter Widget code is 100% Dart code.</p>
<h3 id="heading-multiplatformhttpsflutterdevmulti-platform"><a target="_blank" href="https://flutter.dev/multi-platform">Multiplatform</a></h3>
<p>Absolutely the same project now runs on : Web, macOS, Windows, Linux, iOS, Android. No more Electrons, DENOs, Ionics, god knows what else.</p>
<h3 id="heading-native-flutter-in-webhttpsflutterdevmulti-platformweb"><a target="_blank" href="https://flutter.dev/multi-platform/web">Native Flutter in Web</a></h3>
<p>Yes, we would get a 'free' Flutter UI toolkit just out of the box, for free! I am more than sure the next step for Google would be to replace the HTML/CSS garbage (they still have like 15 CSS technologies to center things) with Skia + Flutter. With natural integration into DartVM. <a target="_blank" href="https://stackoverflow.com/questions/41365453/bundle-js-too-big-in-webpack-project">No more huge 5Mb bundles</a> to load the bundle and fonts...the platform provides it for you.</p>
<h3 id="heading-real-apps-not-document-markup-hacks">Real apps, not document markup hacks</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644065337456/nTTXJfcvJ.png" alt="pwa.png" /></p>
<p>With all of the above we would finally get a normally functioning apps that can be downloaded via the browser. No more strange text input, scrolling, CSS issues between browsers...Just a regular desktop/mobile app as we all used to them, just running under the web address. <a target="_blank" href="https://docs.flutter.dev/perf/rendering/ui-performance">With 60 FPS!</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644065327269/X87KpEnv2.png" alt="code.png" /></p>
<h1 id="heading-but">But</h1>
<p>The history went as it went. We can't change it.</p>
<p>Dart survived, found a new free land called Flutter and blossoms from year to year.</p>
]]></content:encoded></item><item><title><![CDATA[Performance Gains in Unity. How I went from 15 to 60 FPS]]></title><description><![CDATA[Intro
I am doing a remake of my old game Loca Deserta: Sloboda The first version was done with Flutter but this time I picked Unity as the game engine.
I started from scratch and implemented lots of new functionality but I have noticed that even my G...]]></description><link>https://gladimdim.org/performance-gains-in-unity-how-i-went-from-15-to-60-fps</link><guid isPermaLink="true">https://gladimdim.org/performance-gains-in-unity-how-i-went-from-15-to-60-fps</guid><category><![CDATA[unity]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Fri, 14 Jan 2022 19:51:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189801268/_ANP87SQk.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro">Intro</h2>
<p>I am doing a remake of my old game <a target="_blank" href="https://locadeserta.com/citybuilding/index_en">Loca Deserta: Sloboda</a> The first version was done with <a target="_blank" href="https://flutter.dev">Flutter</a> but this time I picked Unity as the game engine.</p>
<p>I started from scratch and implemented lots of new functionality but I have noticed that even my Galaxy S21 Ultra had lagging issues. The FPS was smooth but sometimes I had a feeling it dropped from 60 to 30 FPS.</p>
<p>I took a very old Nokia 6.1 with Android, launched my game and was shocked. It was a complete garbage. FPS was in a 0-15 range. Completely unusable.</p>
<h1 id="heading-what-was-wrong">What was wrong</h1>
<ul>
<li>Terrain layers</li>
<li>Too much of heavy logic in MonoBehaviour:Update</li>
<li>Instantiate calls</li>
<li>Destroy calls</li>
</ul>
<h2 id="heading-how-did-i-find-bottlenecks">How did I find bottlenecks</h2>
<p>Off course I launched a Profiler and checked what was choking in my Nokia 6.1/Galaxy Note 8</p>
<p>You can see that the game ran at max 30 FPS, even fewer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189641253/uB7nWVybm.png" alt="note_8_fix_without_fix.png" /></p>
<p>Also when I have to show building/production requirements with 3D resource models:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189282876/T88KG_yix.png" alt="ingame.png" /></p>
<p>it spiked to 40-60ms per frame and the bottleneck was my OnTriggerEnter logic (more on this later). Also pay attention to that blue scripting timings that appear when the new resources are shown on the screen. Definitely something was wrong with my code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189334300/TE-LokDgm.jpeg" alt="blue_spikes.jpg" /></p>
<h3 id="heading-first-victim-shadows">First victim - Shadows?</h3>
<p>At first I thought it was all about shadows. I switched them off...and nothing changed. The game choked itself.</p>
<h3 id="heading-second-victim-quality-settings">Second victim - Quality Settings?</h3>
<p>I tried to run it on low/very low. Better but still at 15-30 FPS for such a simple game. No!</p>
<h2 id="heading-divide-and-conqueror">Divide and Conqueror</h2>
<p>I decided to use a scientific method of divide, conqueror and guess what's wrong. Something in the scene definitely chocked the GPU (as 80% of my CPU frame time was spent on waiting on GPU results).</p>
<h2 id="heading-removed-terrain">Removed Terrain</h2>
<p>I started with the biggest asset: I removed Terrain layer.</p>
<p><strong>BOOM</strong> ! Steady 60 FPS on Galaxy Note 8! Haha! It was it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189321764/o6_NJkBak.png" alt="note_8_no_terrain_layers_60fps.png" /></p>
<p>I returned back - again 15-30...It was definitely Terrain that caused me the performance issues.</p>
<h3 id="heading-how-to-fix-terrain-on-mobile">How to fix Terrain on Mobile</h3>
<p>So I read through the <a target="_blank" href="https://docs.unity3d.com/Manual/class-TerrainLayer.html">TerrainLayer</a> official docs and noticed this:</p>
<pre><code>you can <span class="hljs-keyword">use</span> four Terrain Layers per Texture pass, <span class="hljs-keyword">with</span> <span class="hljs-keyword">no</span> <span class="hljs-keyword">limit</span> <span class="hljs-keyword">on</span> the <span class="hljs-built_in">number</span> <span class="hljs-keyword">of</span> passes.
This means that although you <span class="hljs-keyword">are</span> allowed <span class="hljs-keyword">to</span> <span class="hljs-keyword">use</span> <span class="hljs-keyword">as</span> many Terrain Layers <span class="hljs-keyword">as</span> you want,
<span class="hljs-keyword">each</span> pass increases the <span class="hljs-built_in">time</span> spent rendering the Terrain.
<span class="hljs-keyword">For</span> maximum <span class="hljs-keyword">performance</span>, <span class="hljs-keyword">limit</span> <span class="hljs-keyword">each</span> <span class="hljs-keyword">of</span> your Terrain tiles <span class="hljs-keyword">to</span> four Terrain Layers.
</code></pre><p>And I had like 8 layers all mixed on the floor with different alpha and masks!</p>
<p>And for real, once I have only 4 layers like on this screen: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189682299/nI3jOYQyz.png" alt="terrain_layers.png" /></p>
<p>Then the game runs very smooth even on an ancient Android like Nokia 6.1:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189363042/Cldo0cXnx.png" alt="note_8_4_terrain_layers_60fps.png" /></p>
<p>Once I add even one more layer (without using it for painting!) then the performance drops.</p>
<h4 id="heading-outcome-for-terrain">Outcome for Terrain</h4>
<p>Don't set more than 4 terrain layers even if you paint only 4 still the Unity will choke processing your Terrain.</p>
<h3 id="heading-spikes-in-monobehaviourupdate">Spikes in MonoBehaviour:Update</h3>
<p>This was easy to fix, it turned out I used DateTime.now to get the current time in order to process task timers in game. It seems this can be a little painfull on mobile phones so I rewrote the logic and used Time.deltaTime in order to check how much time has passed since the Upgrade/Production task started:</p>
<pre><code>    void Update()
    {
        <span class="hljs-keyword">if</span> (actionStarted)
        {
            elapsedActionTime <span class="hljs-operator">+</span><span class="hljs-operator">=</span> Time.deltaTime;
            <span class="hljs-keyword">if</span> (elapsedActionTime <span class="hljs-operator">&gt;</span> actionDuration)
            {
                actionStarted <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;
                elapsedActionTime <span class="hljs-operator">=</span> <span class="hljs-number">0</span>.0f;
                finalizeAction();
            }
        }
    }
</code></pre><p>Check this awesome video on how to implement timers in Unity:</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=eeY24OyLGv0">4 Easy ways to create Timers in Unity - in 2 minutes - Jason Weimann</a></p>
<p>After fix, notice that the blue scripting timings are much shorter!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189389041/ydxbDquaT.png" alt="nokia_61_without_datetimenow.png" /></p>
<h2 id="heading-get-rid-of-instantiate-and-destroy">Get rid of Instantiate and Destroy</h2>
<p>When I have to show materials for build/produce requirements I instantiated prefabs and rendered them on screen. Then when the player is not near the building I destroyed them.</p>
<p>It turns out...Instantiate and Destroy are VERY resource consuming procedures.</p>
<p>See these spikes? These are Instantiate/Destroy calls. Some of them take 40-70 ms:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189454099/dbRTIS0H7v.png" alt="nokia_61_object_instantiate_spikes.png" /></p>
<p>I knew how to fix this with Object Pooling: <a target="_blank" href="https://www.youtube.com/watch?v=uxm4a0QnQ9E">Object Pooling (in depth) - Game Programming Patterns in Unity &amp; C# - Jason Weimann</a></p>
<p>I reimplemented the logic to create all the objects beforehand when the scene loads. And then just pull them from Object Pool and place where needed.</p>
<p>Check the same game scenario to show new Resource objects on screen but with Object Pooling:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189529280/mgXa__WRae.png" alt="nokia_61_object_pooling_all_objects_cached.png" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<ul>
<li>Don't use more than 4 Terrain Layers</li>
<li>Don't Instantiate/Destroy too often.</li>
<li>Use Object Pooling where applicable</li>
<li>Don't do heavy tasks in Update</li>
</ul>
<p>My game now runs at 60FPS on Samsung S21 Ultra and 30-40 FPS on Galaxy Note 8.</p>
<p>I can finally continue on adding more buildings to the game :)</p>
<p>This is my game running on Nokia 6.1: <a target="_blank" href="https://twitter.com/DmytroGladkyi/status/1481540410098991104">https://twitter.com/DmytroGladkyi/status/1481540410098991104</a></p>
<h1 id="heading-more-details-on-my-remake-in-unity">More Details on My Remake in Unity</h1>
<p>The first version of my game has features from a 'real' full-sized game you used to have in a city building genre:</p>
<ul>
<li><p>Manufacturing lines</p>
</li>
<li><p>Create compose materials out of simpler materials</p>
</li>
<li><p>Research influences production</p>
</li>
<li><p>Building and Upgrading buildings (each type of building has 3 level with all unique voxel art!)</p>
</li>
<li><p>Local Map with random events</p>
</li>
<li><p>Global huge map</p>
</li>
<li><p>Conquering new areas</p>
</li>
<li><p>Weather events</p>
</li>
</ul>
<p>You can learn more and download my old Sloboda game here: <a target="_blank" href="https://locadeserta.com/citybuilding/index_en">Loca Deserta: Sloboda</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642189551113/zykxItcb6.png" alt="sich_screen.png" /></p>
]]></content:encoded></item><item><title><![CDATA[Dead Simple Example of Using Keys in Flutter Widgets]]></title><description><![CDATA[Lots of folks struggle to understand when to use keys in Flutter. In this very short article I will show you nice example where they must be used.
There is an all-in-one video  When to Use Keys - Flutter Widgets 101 Ep. 4 that tells you everything yo...]]></description><link>https://gladimdim.org/dead-simple-example-of-using-keys-in-flutter-widgets</link><guid isPermaLink="true">https://gladimdim.org/dead-simple-example-of-using-keys-in-flutter-widgets</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Sat, 02 Oct 2021 19:06:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633201488581/6cVOL96ma.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lots of folks struggle to understand when to use keys in Flutter. In this very short article I will show you nice example where they must be used.</p>
<p>There is an all-in-one video  <a target="_blank" href="https://www.youtube.com/watch?v=kn0EOS-ZiIc">When to Use Keys - Flutter Widgets 101 Ep. 4</a> that tells you everything you need. </p>
<p>The examples are great but I would like to show you another application of Flutter keys when you need animations!</p>
<p>Jump directly to <a target="_blank" href="https://dartpad.dev/?id=116f0f3b5f3937698bb3316367a3edf6&amp;null_safety=true">DartPad example</a> </p>
<h1 id="introduction">Introduction</h1>
<p>In my game  <a target="_blank" href="http://locadeserta.com/locadesertachumaki/index_en.html">Chumaki</a> I wanted to animate the NPC level up by showing a growing widget with a text "New Level", like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633201317894/vft_HwDaC.gif" alt="level_up.gif" /></p>
<h2 id="widget-implementation">Widget implementation</h2>
<p>I implemented the following widget, it runs initial animation when the widget is created and scales the widget from 0 to 1 (full size).</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ScaleAnimated</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">final</span> Widget child;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">Duration</span> duration;

  <span class="hljs-keyword">const</span> ScaleAnimated({Key? key, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.child, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.duration})
      : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  _ScaleAnimatedState createState() =&gt; _ScaleAnimatedState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ScaleAnimatedState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ScaleAnimated</span>&gt;
    <span class="hljs-title">with</span> <span class="hljs-title">SingleTickerProviderStateMixin</span> </span>{
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> AnimationController _controller;
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> Animation&lt;<span class="hljs-built_in">double</span>&gt; _animation;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    _controller = AnimationController(
        vsync: <span class="hljs-keyword">this</span>,
        duration: widget.duration,
        upperBound: <span class="hljs-number">1.0</span>,
        lowerBound: <span class="hljs-number">0.0</span>);
    _controller.forward();
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> ScaleTransition(
      scale: _animation,
      child: widget.child,
    );
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> dispose() {
    _controller.dispose();
    <span class="hljs-keyword">super</span>.dispose();
  }
}
</code></pre><p>The widget is called in this way:</p>
<pre><code> <span class="hljs-selector-tag">ScaleAnimated</span>(
          <span class="hljs-attribute">duration</span>: const Duration(<span class="hljs-attribute">seconds</span>: <span class="hljs-number">2</span>),
          <span class="hljs-attribute">child</span>: Text(<span class="hljs-string">"New Level: $level"</span>),
        ),
</code></pre><h1 id="problem">Problem</h1>
<p>But the animation was playing only once...I level up one time, second time, third...The widget updated the level value but the animation was not playing! I checked debugger - the build method was called each time I level upped but initState was never called again.</p>
<h1 id="why">Why?</h1>
<p>Please watch the video <a target="_blank" href="https://www.youtube.com/watch?v=kn0EOS-ZiIc">When to Use Keys - Flutter Widgets 101 Ep. 4</a>.</p>
<p>TLDR: Flutter builds new tree on player level up event. The new widget is of the same type as previous, their keys are null, so Flutter thinks it is the same widget and just updates it with new state (text input parameter). The <strong>initState</strong> is not called cause old widget was reused.</p>
<h1 id="solution">Solution</h1>
<p>We have to help Flutter to distinguish identical widgets like in our case. The good thing: we dont have to change the Widget source code. We need to provide a Key parameter for its constructor like this:</p>
<pre><code> <span class="hljs-selector-tag">ScaleAnimated</span>(
          <span class="hljs-attribute">key</span>: ValueKey(counter), <span class="hljs-comment">// &lt;--- here we provide key!</span>
          <span class="hljs-attribute">duration</span>: const Duration(<span class="hljs-attribute">seconds</span>: <span class="hljs-number">2</span>),
          <span class="hljs-attribute">child</span>: Text(
              <span class="hljs-string">"New Level: $counter"</span>),
        ),
</code></pre><p>We have a nice way of knowing when we really have to animate "New Level" widget - when the new level is received. This unique value can be used as a ValueKey.</p>
<h1 id="real-example-on-dartpad">Real example on DartPad</h1>
<p>Please open this link for a very short example of using ScaleAnimation with and without keys:</p>
<p> <a target="_blank" href="https://dartpad.dev/?id=116f0f3b5f3937698bb3316367a3edf6&amp;null_safety=true">DartPard Example for ValueKey</a> </p>
]]></content:encoded></item><item><title><![CDATA[A Retro of My Last 3 Years]]></title><description><![CDATA[The second post on my site:
https://gladimdim.org/do-not-quit-or-how-i-resurrected-my-7-years-old-idea was 2 years ago.
It was a time when I finally decided to stop wasting time (having three kids just forces you to do that, each 'free' minute is not...]]></description><link>https://gladimdim.org/a-retro-of-my-last-3-years</link><guid isPermaLink="true">https://gladimdim.org/a-retro-of-my-last-3-years</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Sun, 12 Sep 2021 19:48:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1635531714188/EObtaw9NW.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The second post on my site:</p>
<p>https://gladimdim.org/do-not-quit-or-how-i-resurrected-my-7-years-old-idea<strong> was 2 years ago.</strong></p>
<p>It was a time when I finally decided to stop wasting time (having three kids just forces you to do that, each 'free' minute is not spent on tiktok/yt shorts or nextflix, but is truly dedicated to something meaningful to me) and resurrected my dream: create a game about Ukraine in XVII century (cossacks time).</p>
<p>You can read about my first attempt back in 2011, when iPhones were rare in Ukraine but I already created a multiplayer turn-based chess-like game with Game Center. But something stopped me. I remember I took all the art from Cossacks game by GSC (just for debugging) and then realized how much effort it would take to make my own. And eventually abandoned a working game...It is still somewhere on Github but I am not sure it will run hehe.</p>
<p>3 years ago we were returning from another Living History event and I told my friends about that game. They asked where was it? And I understood that 6 years have passed. It was enough to graduate from another Uni, to change the profession. Even 2 years was enough for me to learn 3D modelling and continue on the game. </p>
<h1 id="first-tries">First Tries</h1>
<p>As I was dirty with React/Reason I decided to use them for my interactive fiction game. Then I saw the news that Flutter is in beta and they even had an event and announced Web support (experimental).<strong> I remember I liked A LOT Dart back in 2015</strong> when I wanted to incorporate DartAngular in the startup I worked at.</p>
<p>So I took Flutter, rebuild everything in 3 days and...it all worked!</p>
<h1 id="then-i-started-from-small-steps">Then I started from small steps</h1>
<ul>
<li>used existing pics from our Living History club to make Interactive Fiction Stories: http://locadeserta.com/interactive/index_en</li>
<li>created a turn-based economic game</li>
<li>converted turn-based game into real time :)</li>
<li>started drawing Voxel art on my Galaxy Note</li>
<li>all the art went into released game Sloboda</li>
<li>based on this game created another one - Puzzle Hex: http://locadeserta.com/locadesertahex/index_en</li>
<li>based on voxel art (more than 200(!) already: https://github.com/gladimdim/free_ukrainian_art) started top-notch trading game called Chumaki: http://locadeserta.com/locadesertachumaki/index_en</li>
<li>created Telegram Bot that can play interactive stories: https://t.me/locadesertabot</li>
<li>backend to save and play stories is also in dart: http://dikepole.locadeserta.com/catalog</li>
</ul>
<h1 id="all-of-that-was-possible-due-to-the-best-crossplatform-ui-toolkit-flutter">All of that was possible due to the best Crossplatform UI toolkit: Flutter.</h1>
<p><strong>Flutter just clicks. Dart just clicks.</strong> I was never stuck due to some weird dart language tricks (like in JS). I think for first year I even never looked at the syntax. Everything I typed worked.
I could target all mobile platforms. As I am a frequent platform switcher it was a crucial thing for me: run the same game on all mobile devices nevertheless of my current 'favourite' platform.</p>
<p>After that followed lots of different apps done by me. One of them is still used internally by my family: Family Planner. As we have three kids it was difficult to track who goes to which place, who will bring kid back, whats the plans on weekends, etc. Now we use it on a daily basis (btw, its web flutter version!)</p>
<h1 id="summary">Summary</h1>
<p>So, during these 3 years I have Flutter 100% of my hobby time after the work. I even created an MVP with Flutter in our company but it was later replaced by the standard web app ;)</p>
<p><strong>All the code everywhere is running dart</strong>: my telegram bot, all my 4 games, backend for interactive fictions, backend for game saves and syncs. It is all dart.</p>
<p>I still have a dream to make a bigger game. Flutter is not enough anymore for me. So plans for next 3 years: get dirty with Unreal Engine and release a game!</p>
<p>Let's see what's next for me in 5 years! (Unreal Engine and C++ :D )</p>
]]></content:encoded></item><item><title><![CDATA[Flutter: how to animate movement of InteractiveViewer from one point to another on a Map]]></title><description><![CDATA[In my game called  "Loca Deserta: Chumaki"  (still WIP) I have an InteractiveWidget widget used as a scroll/pan/zoom surface of the game map.
Different cities are shown on it like this:

To help navigate the map I decided to add following feature: pl...]]></description><link>https://gladimdim.org/flutter-how-to-animate-movement-of-interactiveviewer-from-one-point-to-another-on-a-map</link><guid isPermaLink="true">https://gladimdim.org/flutter-how-to-animate-movement-of-interactiveviewer-from-one-point-to-another-on-a-map</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter Widgets]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Games]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Sun, 06 Jun 2021 14:39:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1635531903254/zSwsJQ49t.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my game called  <a target="_blank" href="http://locadeserta.com/index_en.html">"Loca Deserta: Chumaki"</a>  (still WIP) I have an InteractiveWidget widget used as a scroll/pan/zoom surface of the game map.</p>
<p>Different cities are shown on it like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622967457323/E3Ztd4ki5.jpeg" alt="Screenshot_20210606-111616.jpg" /></p>
<p>To help navigate the map I decided to add following feature: player should be able to navigate to another city with a button press.</p>
<p>Say like here, to unlock the city you must buy the route to it in another city:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622989483835/DpXdKXA4L.png" alt="Untitled.png" /></p>
<p>And it should nicely animate to the other part of the map, with target City in center:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622987732013/qqCfyVq_n.gif" alt="ezgif-2-9d2b382b1f0b.gif" /></p>
<p> <a target="_blank" href="https://gladimdim.org/animating-interactiveviewer-in-flutter-or-how-to-animate-map-in-your-game">In my previous post I've shown how to use AnimationController</a> to move from one hardcoded point to another point on a map when the game starts. But in this case we have to react to user action and nicely move the current viewport to the other city.</p>
<p>The technical solution for such feature is split into different tasks:</p>
<ul>
<li>Animate InteractiveViewer to zoom + pan to the other City.</li>
<li>Figure out how to tell InteractiveViewer widget to start moving viewport when player presses button buried deep in the widget tree.</li>
</ul>
<h1 id="animate-interactiveviewer-to-zoom-pan-to-the-other-city">Animate InteractiveViewer to zoom + pan to the other City</h1>
<p>You can read my  <a target="_blank" href="https://gladimdim.org/animating-interactiveviewer-in-flutter-or-how-to-animate-map-in-your-game">previous post</a> which has detailed description on how to use AnimatedBuilder with Matrix4Tween. In this post I will focus on how to get current Matrix4 value of the InteractiveViewer and use it as a starting point in animation. <strong>(All the variables, widget code, etc is taken from that post).</strong></p>
<p>Why do we need it? Player does not necessarily position city in the center of the screen and for sure player can zoom in/out the Widget which changes the scale. We should animate movement from any of these combinations to the final destination (another City on a map).</p>
<h2 id="setup-animations">Setup Animations</h2>
<p>In initState we create AnimationController and ask the StateWidget to animate to city called Sich. Each city object has a property 'point' and we know how it is positioned on a map. </p>
<pre><code>  <span class="hljs-meta">@override</span>
  <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">initState</span><span class="hljs-params">()</span> </span>{
    _animationController =
        AnimationController(duration: animationDuration, vsync: <span class="hljs-keyword">this</span>);
    navigateFromToCity(to: Sich(), withDuration: animationDuration);
    <span class="hljs-keyword">super</span>.initState();
  }
</code></pre><h2 id="setup-matrix4-object">Setup Matrix4 object</h2>
<p>Inside <strong>navigateFromToCity</strong> method we have to set starting and final values for our animation.</p>
<p>Read current Matrix4 values from InteractiveViewer:</p>
<pre><code>Matrix4 matrixStart = Matrix4.inverted(_transformationController.<span class="hljs-keyword">value</span>);
</code></pre><p>Pay attention to use Matrix4.inverted instead of reading the value directly! If you use just pure _controller.value then you will get weird positions. Remember that InteractiveViewer has a 'viewport' (what you see on screen) that is static . When you pan the map, you are actually moving the 'canvas' in the opposite direction from your finger movement. The viewport does not move, but the canvas does! </p>
<p>You can also use some custom logic to provide any other starting point for the animation, like I have when the game loads for the first time (I animate from the very bottom right to the Sich town):</p>
<pre><code> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">from</span> == <span class="hljs-keyword">null</span>) {
      final startPoint = <span class="hljs-type">Point</span>(CANVAS_WIDTH, CANVAS_HEIGHT);
      matrixStart = Matrix4.<span class="hljs-keyword">identity</span>()..translate(startPoint.x, startPoint.y);
}
</code></pre><p>Then calculate the end Point:</p>
<pre><code>final endPoint = calculateCenterPointForCity(<span class="hljs-keyword">to</span>);
var end = Matrix4.<span class="hljs-keyword">identity</span>()..translate(endPoint.x, endPoint.y);
</code></pre><h2 id="setup-animations-and-controllers">Setup Animations and controllers.</h2>
<pre><code>_mapAnimation = Matrix4Tween(<span class="hljs-keyword">begin</span>: matrixStart, <span class="hljs-keyword">end</span>: <span class="hljs-keyword">end</span>)
        .animate(_animationController);
_animationController.duration = withDuration;
_mapAnimation.addListener(mapAnimationListener);
_mapAnimation.addStatusListener((status) {
<span class="hljs-keyword">if</span> (status == AnimationStatus.completed) {
  _mapAnimation.removeListener(mapAnimationListener);
}
</code></pre><p>Notice that we remove listener to the animation when it is done. Once the animation is finished we no longer need it. That is why we can clean up and do not leak unneeded listeners that are created on each tap.</p>
<p>Reset animation to start from the beginning and play it!</p>
<pre><code><span class="hljs-selector-tag">_animationController</span><span class="hljs-selector-class">.reset</span>();
<span class="hljs-selector-tag">_animationController</span><span class="hljs-selector-class">.forward</span>();
</code></pre><p>Each time somebody calls this method the animation sets up the start/end values and starts. We are reusing the same controller for each animation + we remove listener once this one-time-use animation is finished.</p>
<p>Animation part is done.</p>
<h1 id="ask-interactiveviewer-to-move-from-current-view-to-another-city">Ask InteractiveViewer to move from current view to another City</h1>
<p>The button that is used to navigate to another city is buried deep in the widget tree:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622989852947/CMkU910VjE.png" alt="Untitled.png" /></p>
<p>How can we tell InteractiveViewer widget to start moving? We don't have access to it...</p>
<p>Here comes to the rescue the GlobalKey feature that is available in all Widgets. You can read about it at official site:  <a target="_blank" href="https://api.flutter.dev/flutter/foundation/Key-class.html">https://api.flutter.dev/flutter/foundation/Key-class.html</a> </p>
<h2 id="create-globalkey">Create GlobalKey</h2>
<p>Create a global key instance that can be accessed from any part of your code:</p>
<pre><code><span class="hljs-attribute">final</span> globalViewerKey =
    GlobalKey&lt;GameCanvasViewState&gt;(<span class="hljs-literal">debug</span>Label: <span class="hljs-string">"interactiveViewer"</span>);
</code></pre><p>It is a single variable used across the whole application. We do not have to store it inside State of some widget as there can be only one GameCanvas with the map running on screen.</p>
<h2 id="use-it">Use it</h2>
<p>Use that key instance in super call when creating GameCanvasView widget</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GameCanvasView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">final</span> Company company;
  <span class="hljs-keyword">final</span> Size screenSize;

  GameCanvasView({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.company, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.screenSize})
      : <span class="hljs-keyword">super</span>(key: globalViewerKey);
</code></pre><p>Now anywhere in our code we can get a link to this widget:</p>
<pre><code><span class="hljs-keyword">final</span> viewer = globalViewerKey.currentState;
</code></pre><h2 id="send-a-message-to-widget-to-start-animation">Send a message to widget to start animation</h2>
<p>The feature is almost complete. Just implement onTap event handler to read GameCanvas's state object reference and call a method to navigate from one city to another:</p>
<pre><code>  _navigateViewerToCity(City cityThatUnlocks, BuildContext context) {
    <span class="hljs-keyword">final</span> viewer = globalViewerKey.currentState;
    <span class="hljs-keyword">if</span> (viewer == <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span>;
    }
    viewer.navigateFromToCity(from: city, to: cityThatUnlocks);
  }
</code></pre><p>And here how it looks in a real game:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622989238619/R714fEJpX.gif" alt="ezgif-2-c90e3ea90d61-2.gif" /></p>
<h1 id="summary">Summary</h1>
<p>With the help of TransformationController we can control how the InteractiveViewer widget should show us our canvas. Animations can smoothly run the map through start and end Matrix4 values, global key can be used to trigger the animation event from any part of our code.</p>
]]></content:encoded></item><item><title><![CDATA[Animating InteractiveViewer in Flutter. Or how to animate Map in your Game.]]></title><description><![CDATA[In my 4th game in the Loca Deserta Game Universe I am using InteractiveViewer widget in order to implement a map. It allows me to pan, zoom in/out, basically everything you expect from the map in a game:

When the game starts I want to animate fly-ov...]]></description><link>https://gladimdim.org/animating-interactiveviewer-in-flutter-or-how-to-animate-map-in-your-game</link><guid isPermaLink="true">https://gladimdim.org/animating-interactiveviewer-in-flutter-or-how-to-animate-map-in-your-game</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Sat, 01 May 2021 09:44:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1635531823646/ZsHT9TlBS.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my 4th game in the <a target="_blank" href="http://locadeserta.com/index_en.html">Loca Deserta Game Universe</a> I am using <a target="_blank" href="https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html">InteractiveViewer</a> widget in order to implement a map. It allows me to pan, zoom in/out, basically everything you expect from the map in a game:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619781516791/ZquRnU2RN.gif" alt="map_interaction.gif" /></p>
<p>When the game starts I want to animate fly-over the map from the end to the start and focus it on the very first city on the map. Also I want to fly to the given city on the map when user select its. All this with an animation.</p>
<p>So, let's do it.</p>
<h1 id="getting-started-with-interactiveviewer">Getting started with InteractiveViewer</h1>
<p>It is pretty easy to use InteractiveViewer when you just need to allow user to pan and zoom your widget. But how can you control it programmatically?</p>
<p>Reading the official InteractiveViewer docs you can see it has a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/InteractiveViewer/transformationController.html">transformationController</a> property.</p>
<p>This doc file has awesome info about using TransformationController in order to animate some changes (but on 30th of April 2021 the example there does not work :) ). In order to modify the viewport of the interactive viewer we need to set <strong>value</strong> property. Via the ValueNotifier the controller will update the InteractiveViewer and the viewport is adjusted.</p>
<p>Add controller property to our StatefulWidget:</p>
<pre><code>  <span class="hljs-attribute">TransformationController</span> _transformationController =
      TransformationController();
</code></pre><p>By setting the property value to some Matrix4 object we can control the viewport of the widget:</p>
<pre><code>_transformationController.<span class="hljs-keyword">value</span> = Matrix4.<span class="hljs-keyword">identity</span>()..translate(x, y);
</code></pre><p>If you set it and call the setState, it will move the viewport to the given coordinates X, Y. But the zooming will be not done. You just move existing viewport frame from one coordinates to the others.</p>
<h1 id="adding-animations">Adding Animations</h1>
<p>Now we animate this <strong>value</strong> via the AnimationController.</p>
<p>Add AnimationController and Animation to the ViewState properties:</p>
<pre><code>  <span class="hljs-keyword">late</span> AnimationController _animationController;
  <span class="hljs-keyword">late</span> Animation&lt;Matrix4&gt; _mapAnimation;
</code></pre><p>And declare animations with listeners in <strong>initState</strong> method:</p>
<pre><code>  @override
  <span class="hljs-type">void</span> initState() {
    var start = Matrix4.<span class="hljs-keyword">identity</span>()..translate(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
    var end = Matrix4.<span class="hljs-keyword">identity</span>()..translate(<span class="hljs-number">500</span>, <span class="hljs-number">500</span>);
    _animationController =
        AnimationController(duration: Duration(seconds: <span class="hljs-number">5</span>), vsync: this);
    _mapAnimation =
        Matrix4Tween(<span class="hljs-keyword">begin</span>: <span class="hljs-keyword">start</span>, <span class="hljs-keyword">end</span>: <span class="hljs-keyword">end</span>).animate(_animationController);
    _mapAnimation.addListener(() {
      setState(() {
        _transformationController.<span class="hljs-keyword">value</span> = Matrix4.inverted(_mapAnimation.<span class="hljs-keyword">value</span>);
      });
    });
    _animationController.forward();
    super.initState();
  }
</code></pre><p>In this method we animate using  <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Matrix4Tween-class.html">Matrix4Tween</a> by providing <strong>begin</strong> and <strong>end</strong>  Matrix4 values. You can construct manually that Matrix4 by providing 16(4x4 matrix) values to the constructor but it is better to use Matrix4.identity constructor and then call translate method. It will automatically calculate correct matrix values for the given coordinates.</p>
<p>Pay attention that you have to use Matrix4.inverted when assigning the value to the controller. Why? Say you pan the InteractiveViewer, the finger moves from left to right, but the map under the finger actually moves in the opposite direction. If you do not use Matrix4.inverted and assign directly then do not forget to provide negative values to the begin and end matrixes.</p>
<p>At the end of method we start the animation.</p>
<p>That's it, now you know how to set transformation to move InteractiveViewer to the given point.</p>
<h1 id="bonus-move-to-the-specific-point-and-center-it-on-the-screen">Bonus: Move to the specific point and center it on the screen.</h1>
<p>The values you pass to the <strong>Matrix.identity..translate()</strong> describe the top left position of the viewport. But this is not very useful as a user expects  the target point to be in the center of the screen.</p>
<p>In my game all spots on the maps are cities with the coordinates. I also know the size of the city avatar in pixels.</p>
<p>So, in order to correctly animate InteractiveViewer to the given city I have to calculate shifted coordinates by the following rule:</p>
<ol>
<li>Find shift needed to position viewport in the middle of the real devices. For this we use MediaQuery.of call.</li>
<li>Read city coordinates.</li>
<li>Substract center coordinates from the city point.</li>
<li>Now we need to adjust the City Avatar widget sizes in order to nicely position it in its very center. So we have to read city size and divide it by 2 in order to get the center point.</li>
</ol>
<p>This is how the InteractiveViewer will show the City on the map without steps 3 &amp; 4:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619807953707/XdekLTMuR.png" alt="Screenshot 2021-04-30 at 21.30.32.png" /></p>
<p>This is how the InteractiveViewer positions viewport if step #4 is omitted (we do not adjust shift values to widget dimensions):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619807939658/GE56anZEZ.png" alt="Screenshot 2021-04-30 at 21.35.17.png" /></p>
<p>And this is a drawn schema of 4 step logic:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619808243739/0WTApPL9e.png" alt="chumaki_map_schema_viewer.png" /></p>
<p><em>Pay attention, again, that if you want to 'move' viewer bottom and right you would want to increase x, y coordinates but you have to remember, that we invert these values (the canvas moves in opossite direction). So instead of adding shift you have to subtract it. If you want the viewer to move up and left, then you have to add shift values.</em></p>
<pre><code>  <span class="hljs-function">Point&lt;<span class="hljs-keyword">double</span>&gt; <span class="hljs-title">calculateCenterPoint</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">final</span> width = widget.screenSize.width;
    <span class="hljs-keyword">final</span> height = widget.screenSize.height;
    <span class="hljs-keyword">final</span> sichPoint = Sich().point;
    <span class="hljs-comment">// shift values to center Widget's top left point in the center of smartphone</span>
    <span class="hljs-keyword">final</span> middleX = width / <span class="hljs-number">2</span>;
    <span class="hljs-keyword">final</span> middleY = height / <span class="hljs-number">2</span>;
    <span class="hljs-keyword">return</span> Point&lt;<span class="hljs-keyword">double</span>&gt;(
      sichPoint.x - middleX + Sich().size * CITY_SIZE / <span class="hljs-number">2</span>, <span class="hljs-comment">// adjusting center point again but relative to City Avatar</span>
      sichPoint.y - middleY + Sich().size * CITY_SIZE / <span class="hljs-number">2</span>,  <span class="hljs-comment">// adjusting center point again but relative to City Avatar</span>
    );
  }
</code></pre><p>Final result. The City Avatar is positioned just like the player expects: center of the avatar is in the center of the view:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619807913551/0_rFWh4_5.png" alt="Screenshot 2021-04-30 at 21.36.45.png" /></p>
<p>And here it is in action in my game map:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619861852168/v2QFakNxd.gif" alt="SmartSelect_20210501-123542.gif" /></p>
]]></content:encoded></item><item><title><![CDATA[Дике Поле: Слобода. Що нового у версії 3.0.5?]]></title><description><![CDATA[Стисло
Повністю перероблена мапа околиць. Тепер це воксельна мапа намальована в ручну.
Збалансовано епічні події.
Всі сповіщеня в середині гри показують результат зміни інвентарю.
Відео версія: https://www.youtube.com/watch?v=XpoF1Ybfrug
Де взяти гру...]]></description><link>https://gladimdim.org/dike-pole-sloboda-sho-novogo-u-versiyi-305</link><guid isPermaLink="true">https://gladimdim.org/dike-pole-sloboda-sho-novogo-u-versiyi-305</guid><dc:creator><![CDATA[Dmytro Gladkyi]]></dc:creator><pubDate>Wed, 27 Jan 2021 19:59:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1611777525911/rcrtI6jPs.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="stislo">Стисло</h1>
<p>Повністю перероблена мапа околиць. Тепер це воксельна мапа намальована в ручну.
Збалансовано епічні події.
Всі сповіщеня в середині гри показують результат зміни інвентарю.</p>
<p>Відео версія: <a target="_blank" href="https://www.youtube.com/watch?v=XpoF1Ybfrug">https://www.youtube.com/watch?v=XpoF1Ybfrug</a></p>
<h1 id="de-vzyati-gru">Де взяти гру?</h1>
<p>Гра доступна онлайн:  <a target="_blank" href="https://locadeserta.com/sloboda/">https://locadeserta.com/sloboda/</a>, але будьте уважні - тут викладаються розробницькі збірки.</p>
<p>Релізні випуски гри доступні на Google Play Store:  <a target="_blank" href="https://play.google.com/store/apps/details?id=com.gladimdim.sloboda">Дике Поле: Слобода</a> </p>
<p>Та на App Store: <a target="_blank" href="https://apps.apple.com/ua/app/sloboda/id1543669328?l=uk">Дике Поле: Слобода</a></p>
<p>Ви також можете скачати APK та Windows збірки напряму з github:</p>
<p> <a target="_blank" href="https://github.com/gladimdim/locadeserta/releases/tag/3.0.5">Реліз 3.0.5 на Github</a> </p>
<p> <a target="_blank" href="https://github.com/gladimdim/locadeserta/releases/download/3.0.5/sloboda_305.apk">Збірка для Android APK</a> </p>
<p> <a target="_blank" href="https://github.com/gladimdim/locadeserta/releases/download/3.0.5/sloboda_windows_305.zip">Windows 10 збірка</a> </p>
<h1 id="nova-mapa-okolic">Нова мапа околиць</h1>
<p>Ця мапа більше не генерується випадково з різних квадратиків, а була намальована у воксельній графіці. Ресурси та події і далі з'являються на ній, як і раніше. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611777040433/I0D-0Q5y2.png" alt="Screenshot 2021-01-27 214903.png" /></p>
<p>Ресурси з'являються у відповідних місцях. Тобто, дерево - в лісі, гінці від Січі та розвідники - на хфігурах, або біля табору уходників на сході біля лісу та озера.</p>
<p>Також на мапі наявні інші об'єкти: озеро, річка, поля, садочок, табір, скійська могила та каменярня.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611777093044/3FAWv-jTP.png" alt="Screenshot 2021-01-27 214939.png" /></p>
<h1 id="zmenshenij-vpliv-katastrofichnih-podij">Зменшений вплив катастрофічних подій</h1>
<p>Деякі епічні події дуже сильно руйнували слободу. Тепер вони випадають не так часто і вірогідність їх впливу зменшилась.</p>
]]></content:encoded></item></channel></rss>