Введение
Всем привет! Меня зовут Александр и я Unity Developer более 7 лет. В этой статье мы попробуем решить проблему шрифтов раз и навсегда (в мобильных играх так точно). Способ для Unity не самый очевидный, про него не так много написано и все ответы приходилось собирать по кусочкам, собственно поэтому и решил написать статью. Сразу перейдем к техническому заданию.
Нужно сделать локализацию для мобильной игры, с такими требованиями:
Шрифты не должны занимать много места в билде, желательно до 10 мб максимум
Шрифты должны быть сгенерированы без лишних заморочек для Text Mesh Pro
Шрифты должны поддерживать такие языки: English, Russian, Ukrainian, German, French, Spanish, German, Italian, Portuguese, Arabic, Japanese, Chinese, Korean, Hindi
Проблема
Итак, давайте разбираться в самой задаче по порядку. Первый пункт про размер шрифта: если подбирать шрифт для европейских языков, то такой шрифт будет весить обычно не больше 1 мб. Но чем ближе мы будем двигаться в сторону Азии, тем больше будет размер шрифта (интересное сходство🤔). В итоге получается примерно 24.6 мегабайт, что не мало для мобильной игры. Стоит учесть, что некоторые шрифты взяты в единственном стиле, иначе размер увеличивается практически вдвое.
Language | Font | Size |
English, Russian, Ukrainian, German, French, Spanish, German, Italian, Portuguese | Noto Sans | 0.6 MB |
Arabic | Noto Sans Arabic | 0.7 MB |
Hindi | Noto Sans Devanagari | 0.9 MB |
Japanese | Noto Sans Japanese Black | 5.7 MB |
Korean | Noto Sans Korean Black | 6.2 MB |
Chinese | Noto Sans Chinese Simplified | 10.5 MB |
Итого: | 24.6 MB |
Второй пункт так же не простой из‑за не знания всех языков, при генерации TMP шрифта можно упустить какие‑то символы. Например для китайского языка есть 3500 часто используемых иероглифов из 20 000 только официального словаря. С третьим чуть полегче, нам помогут дизайнеры найти нужные шрифты, и наше дело их добавить в игру.
Решение проблемы
После долгого поиска шрифтов и осознания, что они будут занимать 1/3 размера игры, появилась идея: «В операционных системах же есть уже встроенные шрифты для разных языков, почему бы их не использовать». Потратив некоторое время на поиски, я наткнулся на форум, где уже этот вопрос обсуждался с разработчиком TMP. В треде добились чтобы разработчик сделал такой функционал. Хоть функционал есть, но шрифты, о которых пишет Apple и Google, находятся не все или названы совсем не так. Тут можно найти списки шрифтов для мобильных платформ
Допустим, мы нашли нужные нам шрифты, теперь давайте их подключим. Нам понадобиться какой‑то основной шрифт, скорее всего это будет шрифт Английского язык, его мы заранее создадим в нашем проекте (туториал). Дальше загрузим нужные нам шрифты из ОС:
private static IEnumerable<(NativeFontData data, string path)> GetNativeFonts(IEnumerable<NativeFontData> data)
{
var nativeFonts = Font.GetPathsToOSFonts();
foreach (var fontData in data)
{
var currentFontName = fontData.Name;
var nativeFont = nativeFonts.FirstOrDefault(nf => IsTargetFont(currentFontName, nf));
if (nativeFont is not null)
{
yield return (fontData, nativeFont);
}
else
{
Debug.Log($"Can't find any font with name: {fontData.Name}");
}
}
yield break;
bool IsTargetFont(string target, string current)
{
return target == Path.GetFileNameWithoutExtension(current);
}
}
Теперь нам нужно эти шрифты конвертировать в TMP и настроить. При настройке шрифта нужен индивидуальный подход для каждого, так как Японский шрифт с теми же настройками как у Итальянского может хуже читаться или занимать больше места в памяти.
private static IEnumerable<TMP_FontAsset> ConvertNativeFontsToTmp(IEnumerable<(NativeFontData data, string fontPath)> fontPaths)
{
foreach (var (data, fontPath) in fontPaths)
{
var font = new Font(fontPath);
var tmpFontAsset = TMP_FontAsset.CreateFontAsset(font, data.PointSize, data.Padding,
GlyphRenderMode.SDFAA_HINTED,
data.AtlasSize.x, data.AtlasSize.y);
yield return tmpFontAsset;
}
}
Чуть подробнее опишу некоторые конфигурации у шрифта:
Point Size — качество рендеринга символа, выше лучше (хороший показатель 40–50)
Padding — расстояние между символами в атласе
Atlas Size — размер каждого создаваемого атлас
Dynamic Font — каждый символ, перед отображением, будет пытаться найти такой же в шрифте и после добавит в атлас
Multi Atlas Textures — если заканчивается место для символов создастся новый атлас, иначе не будет добавлен символ
Полный код можно найти здесь. Как видно из кода, в функции есть аргумент текущего языка платформы, в моем случае мы используем только 1 язык без возможности изменения его во время игры. В случае, когда понадобятся все поддерживаемые языки (например для чатов), необходимо немного усовершенствовать функцию. Здесь стоит быть осторожным: память на устройстве не резиновая, и стоит контролировать количество и размеры атласов шрифтов (опять же отсылка к Китайскому языку). Так же я добавил пример настроек для разных платформ, которыми сам пользуюсь.
var fontData = Application.platform switch
{
RuntimePlatform.Android => new[]
{
//Android
new NativeFontData("NotoNaskhArabic-Regular"), //Arabic
new NativeFontData("NotoSansDevanagari-Regular"), //Hindi
new NativeFontData("NotoSansCJK-Regular", 40, 3, new Vector2Int(2048, 2048)), //Chinese, Japanese, Korean
new NativeFontData("Roboto-Black"), //Unicode
new NativeFontData("Arial"), new NativeFontData("LiberationSans")
},
RuntimePlatform.IPhonePlayer => new[]
{
//iOS
new NativeFontData("HiraginoMincho"), //Japanese
new NativeFontData("NotoNastaliq"), //Arabic
new NativeFontData("DevanagariSangamMN"), //Hindi
new NativeFontData("PingFang", 40, 3, new Vector2Int(2048, 2048)), //Chinese
new NativeFontData("AppleSDGothicNeo"), //Korean
new NativeFontData("SFUI"), //Currency symbols
new NativeFontData("Arial"), new NativeFontData("LiberationSans")
},
_ => new[] {new NativeFontData("Arial"), new NativeFontData("LiberationSans")}
};
Заключение
В финальном результате получилось оптимизировать билд, процессы поиска и добавления шрифтов. Вот небольшой список достижений:
В проекте только один шрифт
Уменьшился размер всех шрифтов до 500 кб
Теперь нет необходимости конвертации шрифта в TMP
Поддержка практически любых языков
P. S. Еще раз продублирую ссылку на репозиторий. Если понадобится сделать из этого пакет — напишите об этом в комментах.