TGViewer
Channel Public Channel
SpellSinger - разработчик игр на Unity

SpellSinger - разработчик игр на Unity

@spellsingergames

Привет! Я Денис и я делаю игры на Unity. В этом блоге я делюсь своим опытом в геймдеве и не только.

Навигация в закрепе: https://t.me/spellsingergames/3

Написать в личку: @sinedsem
Subscribers
548
Photos
90
Videos
22
Links
105

Showing posts older than #112 · Back to latest

Older Posts 20 shown
Post #111 758
TextMesh Pro и Addressables. Иконки в тексте (Sprite Asset)

Нам понадобилось добавить иконку ресурса в текст. Сложность в том, что у нас несколько локаций, в каждом будет свой ресурс. То есть <sprite name="gems_icons_3"> в пустыне – это рубин, а в другой локации это может быть мандарин или крышка от кока-колы.

Я реализовал это довольно просто с помощью прокси-ассета.
1) Создал пустой SpriteAsset, назвал его Proxy
2) В TMP Settings выставил Proxy как defaultSpriteAsset
3) Добавил все нужные мне атласы с иконками в свойства локации, которую я подгружаю.
4) После загрузки мира, добавляю иконки как fallbackSpriteAssets в Proxy:
var proxy = Resources.Load<TMP_SpriteAsset>("Sprite Assets/Proxy");
proxy.fallbackSpriteAssets.Add(currentWorld.Value.FontSprites.GemsIcons);


Таким образом, у меня в конкретной локации будут только иконки из этой локации, подгруженные асинхронно через Addressables. Единственный возможный минус, что не будет работать вставка символов через индекс, типа <sprite=3> , ну и ладно.

#ui #textmeshpro #addressables
  • 👍 6
Post #110 694
TextMesh Pro и Addressables. Шрифты

Есть проблема: TMP не особо дружит с Addressables. TMP предполагает, что шрифты хранятся в ресурсах и сделать их асинхронную загрузку не тривиально. Есть подробный пост в обсуждениях Unity, где расписаны все проблемы.

В этом же посте предлагается ряд решений, в моем случае почти все не сработали, но я расскажу что сработало:

1) В билд добавляем только одну пустую сцену, все остальные сцены загружаем через Addressables. В этой пустой сцене не должно быть ссылок на шрифты.
2) Шрифты не складываем в Resources, кладём куда-нибудь в другое место, и выносим в отдельный Asset Group, чтобы они не дублировались.
3) Разбираемся с TMP Settings. В нем есть ссылка на шрифт по умолчанию. Её нужно удалять на время билда, чтобы шрифт не попал в основной билд. Можно делать это вручную, но я сделал это автоматическим скриптом:

    public class TmpSettingsCleaner : IPreprocessBuildWithReport
{
private static TMP_Settings tmpSettings;
private static FieldInfo defaultFontField;
private static object defaultFont;

public int callbackOrder => 0;

public void OnPreprocessBuild(BuildReport report)
{
tmpSettings = Resources.Load<TMP_Settings>("TMP Settings");
defaultFontField = tmpSettings.GetType()
.GetField("m_defaultFontAsset", BindingFlags.Instance | BindingFlags.NonPublic);
defaultFont = defaultFontField!.GetValue(tmpSettings);
Debug.Log($"Default TMP font was {defaultFont}, now it is null");
defaultFontField.SetValue(tmpSettings, null);
}

[PostProcessBuild]
public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
{
defaultFontField.SetValue(tmpSettings, defaultFont);
Debug.Log($"Restored default font {defaultFont}");
}
}

Готово.

Бонус для тех, кто дочитал до сюда:
В TMP есть два варианта создания атласа: статический и динамический. Я считаю, что для веб игр однозначно выигрывает динамический вариант, потому что он сильно уменьшает размер билда. Плюс, если в игре есть лидерборды, то для статического варианта нужно будет сразу добавлять в атлас все возможные символы, так как в именах игроков могут встретиться любые символы. И атлас получается огромный, особенно если шрифт поддерживает несколько языков.

#ui #textmeshpro #addressables
  • 👍 6
  • 🔥 2
Post #109 714
Гм.. Модерация Яндекса отклонила новые иконку и обложку.
Просим переделать иконку/обложку. Согласно п. 5.6 требований, запрещено использовать скриншоты из игры. [Обложка]

Интересно, на каком основании модератор решил, что это скриншот из игры? Мы добавили на обложку некоторые элементы интерфейса, чтобы продемонстрировать механики игры, но это не скриншот - в игре интерфейса больше и он скомпанован по другому.
Post #108 732
Запустили А/Б тест иконок на Яндексе

Что думаете, какой вариант победит?
Post #107 937
Каждый день сталкиваюсь с новыми задачами. Сегодняшняя задача – научиться не сгорать с отзывов.
  • 😁 11
  • 👀 1
Post #106 834
VK OAuth - плохо, очень плохо

Мне нужно было сделать авторизацию для свой админ панели, чтобы только мы с Нефритом могли модерировать ники. Решил подключить авторизацию через VK и потерпел фиаско.

Во-первых, они как-то по своему переизобрели протокол, добавив параметр device_id, в следствие чего стандартные oauth клиенты не поддерживают VK (например, в Postman есть удобный способ получить токен). Во-вторых, они заставляют использовать PKCE подход в авторизации с бэкендом (передавать code_verifier и code_challange). В третьих, из-за кривой документации у меня на протяжении двух часов не получалось получить токен.

В поддержку им написал. На то, что они переделают протокол, у меня надежды нет, но может хотя бы документацию исправят, чтобы другие разработчики не тратили время. Кстати, проблема в документации именно в разделе Web, в разделе про Anrdoid всё верно написано.

#репорчу_баги
  • 👍 8
  • 😁 1
Post #105 745
Пилим фильтр мата для наших лидербордов

Меньше месяца продержались наши лидерборды без плохих слов в никах. В RuStore нет авторизации, поэтому игрок может ввести любой ник прямо в игре.

Теперь не сможет. Вернее сможет, но будет видеть его только сам, другим игрокам с сервера придёт пустой ник.

Постарался учесть большинство нюансов, и написание слов через точку/пробелы, и замена букв на похоже выглядящие, и ложные срабатывания например в словах "Не психуй".

В базу по умолчанию загрузим самые популярные маты и оскорбления, и оставили возможность через админку добавлять новые правила на лету.

#бурить_копать #лидерборды
  • 🤣 7
  • 👍 4
  • 🤬 1
Post #104 598
Мы убрали баннер из игры в RuStore

Игрокам сильно не понравился баннер и нам наставили кучу единиц в отзывах. Плюс, как вы правильно писали в комментариях, на в мобильных играх баннер очень мало приносит, CPM в 10 раз меньше, а показов не сильно больше получается.

Значит, на мобиле надо двигаться в сторону донатов, а пока что хотя бы не будем копить отрицательные отзывы.

Глянуть все отзывы можно тут

#бурить_копать #rustore #монетизация
  • 👍 8
Post #103 1.05K
Мое решение проблемы с высотой баннера

Если плагин Яндекса не предоставляет информацию о высоте баннера, это не значит, что мы не можем её получить. Для этого нужно использовать Android Java Native Interface и Java Reflection

Заглянем в код Yandex Mobile Ads Unity Plugin. Баннер представлен Java классом com.yandex.mobile.ads.unity.wrapper.banner.BannerAdWrapper

Это класс из библиотеки yandex-ads-unity-plugin.aar. Идём в Assets\Plugins\Android и находим там эту библиотеку. Распаковываем этот aar архив, внутри лежит файл classes.jar, распаковываем и его тоже. Это скомпилированные java классы. Закидываем их в декомпилятор (в моём случае просто открываю папку в Intellij IDEA). Видим вот такую картину:

public class BannerAdWrapper {
...
private final a<BannerAdView> d;
...
}

public class a<T> {
...
public T a() {
if (this.b == null) {
this.b = this.a.a();
}
return this.b;
}
...
}

Красивые названия классов и методов от нас спрятали, но ничего страшного. Вполне можно понять, что непосредственно сам BannerAdView можно получить, вызвав у поля d класса BannerAdWrapper метод a().

Окей, супер. Из Unity это делается следующим образом:

        public static AndroidJavaObject GetBannerView(AndroidJavaObject bannerViewWrapper)
{
using var javaClass = new AndroidJavaClass("java.lang.Class");
using var clazz = javaClass.CallStatic<AndroidJavaObject>("forName", "com.yandex.mobile.ads.unity.wrapper.banner.BannerAdWrapper");
using var field = clazz.Call<AndroidJavaObject>("getDeclaredField", "d");
field.Call("setAccessible", true);
return field.Call<AndroidJavaObject>("get", bannerViewWrapper).Call<AndroidJavaObject>("a");
}


Осталось только узнать, как получить высоту у этого BannerAdView. Это класс из библиотеки com.yandex.android:mobileads. Лезем в mvnrepository, скачиваем aar архив и также декомпилируем классы. Эти классы написаны уже на Котлине, и мы не можем посмотреть код методов, но это нам и не нужно. Зато мы видим, что
class BannerAdView : com.yandex.mobile.ads.impl.si0

и
import android.widget.RelativeLayout;
public abstract class si0 extends RelativeLayout

соответственно наш BannerAdView наследуется от android.widget.RelativeLayout. Идём в документацию и видим, что у этого класса, а вернее у его предка android.view.View есть метод int getHeight().

Готово. Значит мы можем при срабатывании события banner.OnAdLoaded определять высоту загруженного баннера (технически высоту View, в котором он отрисовывается), с помощью кода
GetBannerView(bannerView).Call<int>("getHeight")


Яндекс, добавьте, пожалуйста, информацию о высоте баннера в SDK, это позволит делать более красивые интерфейсы, и не городить такие жуткие костыли. Либо также через геттер, либо в аргументы события banner.OnAdLoaded.

#самый_крутяк #тайные_знания #рся
  • ⚡ 13
  • 🤯 8
  • 👍 7
Post #102 668
Проблема с высотой баннера от РСЯ в мобильном приложении

Хочу подробнее рассказать о специфичной для Бурить-Копать! проблеме с баннером. У нас часть интерфейса, а именно глубина и цель, привязаны к нижнему краю экрана.

Проблема в том, что баннеры Яндекса могут быть разной высоты. Они ограничены 15% от высоты экрана, но могут быть и меньше. Yandex Mobile Ads Unity Plugin не предоставляет информацию о высоте текущего баннера, поэтому наш интерфейс иногда висит в воздухе (см. скриншоты).

Как бы вы решили эту проблему?

#бурить_копать #рся
  • 🤔 4
Post #94 714
Успехи в RuStore

Сейчас мы показываемся в новинках RuStore на главной странице, а также в рубрике "Новинки недели" в разделе игры. Не уверен на счет главной, но в "Новинки недели" попадают игры, вручную отобранные редакцией RuStore.

Для нас это было неожиданно, но в RuStore мы получили довольно много органики. DAU уже 300+, и мы получаем примерно по 200 новых пользователей в день. Очень любопытно, что будет, когда мы перестанем показываться в новинках.

#бурить_копать #rustore
  • 🔥 8
  • 👍 3
Post #93 810
Что делать дальше?

Не совсем понимаю, в каком направлении сейчас выгоднее двигаться. У нас запланировано большое обновление для игры, в котором будут лутбоксы, уникальные скины для бура и донаты, в том числе отключение рекламы за донат.

Также у нас в планах выйти на остальных платформах. В целом, мы уже можем выйти в Google Play, CrazyGames, GameDistribution, Одноклассниках, Вконтакте.

Как выставить приоритеты? Сначала сделать обнову, и сразу выходить с более качественной и прибыльной игрой? Или выйти с тем, что есть сейчас, начать получать хоть какой-то доход, а потом везде обновить игру?

#бурить_копать
  • 🤔 2
  • 🤯 2
  • 👍 1
Post #92 774
Appodeal выключил нам рекламу

Мы подключили рекламу в мобильном приложении через медиатор Appodeal. Как оказалось, они требуют, чтобы приложение было опубликовано в официальном магазине. Так как в Google Play мы ещё не готовы выкладывать игру, нам отключили рекламу.

Пришлось срочно переписывать показ рекламы напрямую с помощью Yandex Mobile Ads, всё равно мы подключили только эту площадку. Это оказалось довольно нетривиально, т.к. в отличие от Appodeal в Яндексе ты должен вручную управлять загрузкой рекламы и обрабатывать ошибки загрузки.

Ещё в Яндексе есть проблемы с показом баннера. Почему-то при блокировке экрана он пропадает и больше не показывается. Возможно, это проявляется не на всех смартфонах, но на моём проявляется. И ещё есть проблема с высотой баннера, но она специфична для нашего приложения. В итоге пришлось отключить баннерную рекламу.

Временное решение готово, билд уже залит в RuStore, но в будущем либо вернемся к Appodeal, либо будем искать другой медиатор, без багов.

По поводу бага с пропаданием баннера попробую написать в саппорт, как будет время

#бурить_копать #rustore
  • 👍 5
  • 🤬 2
  • 🤯 1
Post #91 685
Post #90 762
Про UnityWebRequest и UniTask

Если захотите обернуть UnityWebRequest в UniTask, будьте внимательны, у них отличается способ обработки ошибок. UnityWebRequest всегда возвращает Result, который может быть Success или нет.

Но UniTask’и меняют этот подход и кидают Exception в случае неуспешного запроса. Это особенно неочевидно, потому что ты всё равно продолжаешь работать с классом UnityWebRequest, у которого есть поле Result. Вот только он никогда не будет ConnectionError или ProtocolError, вместо этого вылетит Exception.

Забавно почитать, что думает об этом сам разработчик UniTask. На вполне адекватный на мой взгляд вопрос, заданный кем-то, он отвечает

Treating errors as exceptions is a general idea in C#, not just async/await.


А после попытки подискутировать на эту тему, на мой вгзляд довольно непрофессионально отвечает

Your technical skills are not at the level to discuss this.


#unitask
GitHub Why does 'UnityWebRequest / SendWebRequest' throw an exception on failure? · Issue #304 · Cysharp/UniTask Original UnityWebRequest version is designed to check request.result against UnityWebRequest.Result.Success and allows to decide how to treat an error (as an expected error or an exceptional situat...
  • 🗿 6
  • 👍 1
Post #89 720
Яндекс рекламирует нас на внешних ресурсах

Неделю назад у нас выросло количество игроков, при этом показов внутри яндекса больше не стало.

В Яндекс Метрике видно, что это рекламный трафик. Получается, это Яндекс рекламирует нашу игру на внешних сайтах.

Приятно, однако. Но непонятно, что на это повлияло и как долго это счастье продлится. Вероятно, мы просто попали в ротацию.

Также, благодаря этому подрос CPMV. Раньше он колебался в районе 20 рублей, а на последней неделе доходил и до 30 рублей. Очевидно, новые для площадки игроки ценятся больше.

#яндекс_игры #бурить_копать
  • 🔥 10
  • ❤ 1
  • 👍 1
Post #88 676
Мы сделали свой сервер для лидербордов

И с его помощью реализовали ежедневные лидерборды в Яндекс Играх. Довольно быстро (за сутки) и без вопросов получили одобрение от Яндекса на доступ к нашему хосту, а модерацию обновление игры прошло вообще за 3 часа, включая 2-часовую задержку.

Интересно будет глянуть, повлияют ли ежедневные рекорды на удержание игроков. Есть опасение, что придётся делить рекорды на лиги, в зависимости от бура, а то новичкам может быть не интересно соревноваться с топами.

Интересны ли подробности про сервер? Если да, напишите в комментах, сделаю пост.

А нас тем временем не осталось никаких препятствий к выходу в RuStore. Ждите новостей)

#бурить_копать #веб_игры #яндекс_игры #лидерборды
  • 🔥 14
Post #87 1.73K
PlayerPrefs, которые не сбрасываются в вебе

Когда заливаешь обновление на Яндекс Игры или itch.io, игра попадает на другой Simple Storage Service (S3) сервер. Например, сегодня Бурить-Копать! на Яндексе грузится, отсюда: https://app-258901.games.s3.yandex.net/258901/rsb34pc2hznuitjmsciekjqzs3ovokmh/index.html, а после обновления этот URL будет уже другой.

PlayerPrefs в Unity WebGL привязан к URL, на котором находится игра, поэтому после каждого обновления настройки сбрасываются (а вернее старые настройки не могут загрузиться).

Чтобы решить это проблему, я сделал BetterPlayerPrefs. Это декоратор для стандартных PlayerPrefs, которые работают на всех платформах как обычно, но в вебе используют файловую систему idbfs, причём складывает данные независимо от URL, на котором находится игра. То есть данные сохраняются в браузере, но не потеряются после обновления.

Я считаю, что настройки правильнее хранить именно таким образом. Сохранять, например, громкость звуков/музыки в облачное сохранение плохая идея, ведь игрок может хотеть иметь разные настройки на телефоне и на ПК.

Как обычно, делюсь пакетом с вами. Можно добавить его к себе через Package Manager по git ссылке:
https://github.com/SpellSingerGames/better-player-prefs.git

Синтаксис идентичен обычным PlayerPrefs. Просто дописываете префикс Better (PlayerPrefs -> BetterPlayerPrefs), и всё работает. Если не работает, пишите в личку или комменты, разберёмся, исправим.

#selfmade #самый_крутяк #веб_игры #яндекс_игры
  • 👍 13
Post #86 633
Сделали адаптивные закладки для UI

Мы сделали закладки, которые умеют по-разному выглядеть в зависимости от ширины экрана.

В основе лежит Vertical Layout Group, который отвечает за ряды закладок, каждый ряд это Horizontal Layout Group.

Группы описываются массивом интов - каждое число описывает количество закладок в ряду. В примере на видео для широких экранов используется конфиг [4], а для узких [2, 2].

Строчка, в которой находится активная закладка, переносится вниз, чтобы образовать единый фон с содержимым. Также для единого фона у каждой закладки есть Canvas, которому выставляется overrideSorting=true, если она активная.

Я не уверен, что смогу оформить эти закладки надлежащим образом, чтобы их можно было переиспользовать, т. к. для этого нужно их хорошо задокументировать и подготовить примеры. Но могу поделиться кодом компонентов, который вы сможете доработать под свои нужды.

#ui #selfmade
  • 👍 10
  • 🔥 3
  • 😱 2
Post #85 765
Делюсь новостями

По личным причинам этим летом не получается много времени уделять проекту, но тем не менее потихоньку работаем в двух направлениях:

1) Художник освоил Spine, и переделал многие спрайтовые анимации. Анимации на костях с мешами и выглядят круче, и весят меньше. Например, динамит теперь распирает перед взрывом.

2) Мы готовимся к публикации в RuStore, но не хотим там выходить без лидербордов. Сам RuStore не предоставляет таких возможностей, нужно либо использовать какое-то стороннее решение. Мы будем писать свой сервер для лидербордов, т. к. у меня достаточно компетенций в разработке бэкенда на Java. Это позволит нам гарантированно получить всё фичи, которые бы мы хотели от лидербордов, а именно
- Ежедневные/еженедельные рейтинги
- Работа без авторизации игроков в какой-либо системе
- Возможность удалять записи читеров из базы (в Яндексе для этого нужно писать письма в поддержку)
- Потенциально, можно прикрутить защиту от хакеров (когда просто на сервер посылают огромный рекорд)

Вчера на стриме уже успели немного поработать над сервером, заходите в гости)

#бурить_копать #лидерборды
  • 🔥 13
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →