﻿# Зачем разработчикам игр на Unity использовать статический анализ?

С годами стоимость создания игр стала больше, вырос их масштаб, а следовательно, и их кодовая база\. Разработчикам становится всё сложнее уследить за ошибками\. А забагованная игра влечёт финансовые и репутационные убытки\. Как же с этим может помочь статический анализ?

![0945_Why_should_Unity_game_developers_use_static_analysis_ru/image1.png](https://import.viva64.com/docx/blog/0945_Why_should_Unity_game_developers_use_static_analysis_ru/image1.png)

## Введение

Мы часто пишем статьи про проверку проектов с разбором ошибок из них\. Сегодняшняя статья также будет включать обзор ошибок в коде, однако главная тема — обсуждение насущных проблем в сфере разработки игр\. В основном мы будем говорить про разработку под Unity, но многие моменты актуальны и для программистов из других сфер\. Главными темами будут возрастающая стоимость игр, репутация компаний\-игроделов и статический анализ\.

## Стоимость создания игр

Стоимость разработки игр с каждым годом растёт\. Как следствие – дороже становятся и сами игры\. Давайте разбираться, почему же так происходит\.

В 2020 году произошло [увеличение цены](https://www.businessinsider.com/video-game-price-increase-60-70-nba-2k-xbox-ps5-2020-7) за AAA\-тайтл на $10\. Ранее цена была на уровне $60 и была сформирована ещё в 2000\-x\. Естественно, игровые студии понимают, что во многих странах покупательская способность людей не растёт, а в некоторых даже падает\. Если ещё поднять стоимость игр, то их просто не будут покупать\. В итоге возможны два варианта развития событий: 

* цену на игры всё же поднимают\. Это скажется на количестве проданных копий; 
* цену оставляют на том же уровне или немного увеличивают\. В таком случае будет страдать качество, ведь компании не будут работать себе в убыток\.

Так почему же из года в год бюджеты игр растут? Чтобы ответить на этот вопрос, нужно спросить себя: "А стал бы я покупать игру, которая ничем не отличается от прошлогодней?"\. Думаю, многие ответили бы "нет"\. Разработчики вынуждены делать игры всё масштабнее, чтобы удивлять игроков\.

Вы можете задать логичный вопрос: "А как сильно возросла стоимость создания игр?"\. Для ответа на него предлагаю обратиться к статистике\. Мне удалось найти график бюджетов игр до 2017 года\. В [этих данных](https://www.raphkoster.com/2018/01/17/the-cost-of-games/) исключены расходы на маркетинг, то есть отображена только стоимость разработки\.

![0945_Why_should_Unity_game_developers_use_static_analysis_ru/image2.png](https://import.viva64.com/docx/blog/0945_Why_should_Unity_game_developers_use_static_analysis_ru/image2.png)

Из графика выше видно, что примерно каждые 10 лет стоимость разработки растёт в 10 раз\. Причём в настоящее время цифры весьма внушительные\. Кстати, есть интересный свежий пример\.

Возьмём знаменитый Cyberpunk 2077, который имел примерный [бюджет в $313 млн](https://en.wikipedia.org/wiki/Cyberpunk_2077)\. Данная цифра содержит в себе расходы на продвижение игры\. Обычно маркетинговые расходы меньше или равны расходам на разработку\. Но даже если мы разделим общий бюджет пополам, то всё равно получим огромную сумму\.

Стоит отметить, что игра вышла довольно сырой и имела много багов\. Думаю, что её бюджет вырос уже довольно сильно, учитывая то, что игру продолжают доделывать уже 2 года и готовят к ней дополнения\. Подобная деятельность называется пост\-релизной поддержкой\. Она необходима почти для любого крупного проекта, ведь какой смысл в игре, если после старта из неё уйдёт большинство игроков\. Данный этап включает в себя создание нового контента и исправление багов, которые не получилось исправить на основном этапе разработки\. 

Кстати, о багах\.\.\.

## Ошибки и баги

Вы можете вспомнить хотя бы одну игру с багами? Да во всех играх есть ошибки\.

![0945_Why_should_Unity_game_developers_use_static_analysis_ru/image3.png](https://import.viva64.com/docx/blog/0945_Why_should_Unity_game_developers_use_static_analysis_ru/image3.png)

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

Современные игры огромны, из\-за этого искать ошибки в них в очень тяжело и дорого\. Разработчикам становится всё тяжелее работать — приходится перерабатывать, а также жертвовать различными аспектами игры, чтобы закончить работу в срок\.

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

## Статический анализ

У современных игровых студий есть целый арсенал технологий для упрощения разработки\. Одной из них является статический анализ\. Данная методология поиска потенциальных ошибок в исходном коде способна помочь студиям и их разработчикам найти различные проблемы и дефекты безопасности в их программах\. Зачем ждать, пока баг всплывёт у игрока, если его можно исправить ещё при написании кода?

Кто\-то может спросить: "А как это коррелирует с бюджетом, ценами и прочим?" Естественно, что поиск ошибок происходит в рабочее время, которое нужно оплачивать\. При использовании статического анализа поиск дефектов происходит гораздо быстрее\. Следовательно, освободившееся время можно использовать для наполнения мира, создания новых механик и прочего\.

Стоит заметить, что большинство ошибок, совершаемых разработчиками игр, не отличаются от ошибок разработчиков из других сфер деятельности\. Это можно понять, например, по нашим [проверкам](https://pvs-studio.ru/ru/blog/posts/?tag=GameDev) Open Source игр\. В этих проектах постоянно находятся подозрительные места\. Поэтому статический анализ будет полезен всем программистам\.

Одним из инструментов, выполняющих статический анализ, является PVS\-Studio\. Я, как человек, причастный к разработке данного продукта, расскажу, в чём его польза для проектов на Unity\.

В PVS\-Studio было несколько особых доработок анализа проектов, использующих этот игровой движок\. К примеру, мы занимались добавлением информации о поведении методов популярных классов из Unity\. Это помогает находить больше ошибок в коде\. Кстати, этой теме посвящена отдельная статья: "[Как анализатор PVS\-Studio стал находить ещё больше ошибок в проектах на Unity](https://pvs-studio.ru/ru/blog/posts/csharp/0744/)"\.

Также PVS\-Studio учитывает и другие особенности этого движка\. Например, что Unity предоставляет [необычную реализацию](https://docs.unity3d.com/ScriptReference/Object-operator_eq.html) оператора '\=\=' для типов, которые наследуются от класса _UnityEngine\.Object_\.

С помощью PVS\-Studio удалось найти ряд странностей и в коде самого Unity\. Данной теме посвящены 3 публикации: [статья](https://pvs-studio.ru/ru/blog/posts/csharp/0423/) 2016 года, [статья](https://pvs-studio.ru/ru/blog/posts/csharp/0568/) 2018 года и совсем свежая [статья](https://pvs-studio.ru/ru/blog/posts/csharp/0932/) 2022 года\. 

## А как воспользоваться PVS\-Studio в Unity?

Для начала у вас уже должен быть установлен анализатор и введена лицензия\. Если же вы ещё этого не сделали, то в этом вам поможет данная [страница](https://pvs-studio.ru/ru/pvs-studio/try-free/)\.

Откройте ваш проект в Unity Hub\. Далее нажмите: Assets \-\> Open C\# Project\. У вас откроется IDE, заданная в настройках редактора Unity\. Она указывается здесь:

![0945_Why_should_Unity_game_developers_use_static_analysis_ru/image4.png](https://import.viva64.com/docx/blog/0945_Why_should_Unity_game_developers_use_static_analysis_ru/image4.png)

Я буду использовать Visual Studio 2022\. Чтобы проанализировать проект в данной версии IDE, можно использовать пункт меню Extensions \-\> PVS\-Studio \-\> Check \-\> Solution\.

![0945_Why_should_Unity_game_developers_use_static_analysis_ru/image5.png](https://import.viva64.com/docx/blog/0945_Why_should_Unity_game_developers_use_static_analysis_ru/image5.png)

Более подробно почитать про использование анализатора вы можете [здесь](https://pvs-studio.ru/ru/docs/manual/0007/)\.

## Примеры ошибок

Было бы неправильно написать статью про то, что при разработке игр нужно использовать статический анализ, и не привести примеров найденных анализатором ошибок\. К сожалению, многие большие и популярные игры имеют закрытый код\. Таким образом, мы не сможем проверить качество кода таких игр, как Ori and the Blind Forest, Superhot, Beat Saber, Rust, Firewatch и других\. Выход можно найти на просторах Open Source\. Для анализа я взял одну простую игру на Unity — [Card\-Game\-Simulator](https://github.com/finol-digital/Card-Game-Simulator)\. Этого проекта будет более чем достаточно для демонстрации работы анализатора\.

**Пример 1**

```cpp
private void Update()
{
  ....
  if (Inputs.IsSubmit && joinButton.interactable)
    Join();
  else if (Inputs.IsNew)
    Host();
  else if (Inputs.IsFocusBack)
    roomIdIpInputField.ActivateInputField();
  else if (Inputs.IsFocusNext)
    passwordInputField.ActivateInputField();
  else if (Inputs.IsPageVertical && !Inputs.IsPageVertical) // <=
    ScrollPage(Inputs.IsPageDown);
  else if (Inputs.IsPageHorizontal && !Inputs.WasPageHorizontal)
    ToggleConnectionSource();
  else if (Inputs.IsCancel)
    Close();
}
```

[V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'Inputs\.IsPageVertical && \!Inputs\.IsPageVertical' is always false\. Probably the '\|\|' operator should be used here\. LobbyMenu\.cs 159

Анализатор сообщает о том, что выражение _Inputs\.IsPageVertical && \!Inputs\.IsPageVertical_ всегда ложно\. _IsPageVertical_ представляет из себя свойство, которое возвращает значение типа _bool_\.

```cpp
public static bool IsPageVertical =>
  Math.Abs(Input.GetAxis(PageVertical)) > Tolerance;
```

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

Скорее всего, условие должно выглядеть следующим образом:

```cpp
Inputs.IsPageVertical && !Inputs.WasPageVertical
```

**Пример 2**

```cpp
private bool DecodeDict()
{
  while (neededBits > 0)
  {
    int dictByte = input.PeekBits(8);
    if (dictByte < 0)
    {
      return false;
    }
    input.DropBits(8);
    readAdler = (readAdler << 8) | dictByte;
    neededBits -= 8;
  }
  return false;
}
```

[V3009](https://pvs-studio.ru/ru/docs/warnings/v3009/) It's odd that this method always returns one and the same value of 'false'\. Inflater\.cs 288

PVS\-Studio указывает на то, что данный метод всегда возвращает _false_\. Если мы взглянем на код, то увидим, что имеется только два _return_ и оба возвращают _false_\. Стоит отметить, что перед объявлением метода имеется комментарий:

```cpp
/// <summary>
/// Decodes the dictionary checksum after the deflate header.
/// </summary>
/// <returns>
/// False if more input is needed.
/// </returns>
private bool DecodeDict()
```

Комментарий даёт понять, что метод _DecodeDict_ возвращает _false_ только в случае, если недостаточно входных данных\. Фактически же метод всегда возвращает _false_ :\)\. Может быть и так, что метод действительно никогда не должен возвращать _true_, но выглядит это странно, а данный комментарий вводит в заблуждение\.

**Пример 3**

```cpp
public override int PixelWidth
{
  get
  {
#if (CORE_WITH_GDI || GDI) && !WPF
    try
    {
      Lock.EnterGdiPlus();
      return _gdiImage.Width;
    }
    finally { Lock.ExitGdiPlus(); }
#endif
#if GDI && WPF
    int gdiWidth = _gdiImage.Width;
    int wpfWidth = _wpfImage.PixelWidth;
    Debug.Assert(gdiWidth == wpfWidth);
    return wpfWidth;
#endif
#if WPF && !GDI
    return _wpfImage.PixelWidth;
#endif
#if NETFX_CORE || UWP
    return _wrtImage.PixelWidth;
#endif
#if __IOS__
    return (int)_iosImage.CGImage.Width;
#endif
#if __ANDROID__
    return _androidImage.Width;
#endif
#if PORTABLE
    return PixelWidth;                    // <=
#endif
  }
}
```

[V3110](https://pvs-studio.ru/ru/docs/warnings/v3110/) Possible infinite recursion inside 'PixelWidth' property\. XBitmapSource\.cs 97

Анализатор обнаружил потенциальную бесконечную рекурсию\. Аксессор _get _содержит несколько директив препроцессора _\#if_, и в последней из них свойство обращается само к себе\. Если при сборке символ _PORTABLE_ будет определён, а другие символы из директив — нет, возникнет бесконечная рекурсия\.

**Пример 4**

```cpp
public override bool Equals(object obj)
{
  Selector selector = obj as Selector;
  if (obj == null)
    return false;
  return _path == selector._path; ;
}
```

[V3019](https://pvs-studio.ru/ru/docs/warnings/v3019/) Possibly an incorrect variable is compared to null after type conversion using 'as' keyword\. Check variables 'obj', 'selector'\. PdfFormXObjectTable\.cs 217

Анализатор обнаружил потенциальную ошибку, которая может привести к обращению по нулевой ссылке\. Объект базового класса привели к производному с помощью _as_\. В случае неудачного преобразования значению _selector_ будет присвоен _null_\. В данном случае на равенство _null_ проверяется переменная базового типа — _obj_\. Таким образом, при передаче в метод ссылки на объект типа _Selector_ или нулевой ссылки, исключение выброшено не будет\. В остальных случаях будет выбрасываться исключение\. Обычно же ожидается, что метод в таком случае будет возвращать _false_\. Скорее всего, стоит проверять на равенство _null_ именно переменную _selector_\.

**Пример 5**

```cpp
internal PdfFormXObject(....): base(thisDocument)
{
  ....
  XPdfForm pdfForm = form;
  // Get import page
  PdfPages importPages = importedObjectTable.ExternalDocument.Pages;
  if (   pdfForm.PageNumber < 1
      || pdfForm.PageNumber > importPages.Count)
  {
    PSSR.ImportPageNumberOutOfRange(pdfForm.PageNumber, 
                                    importPages.Count,
                                    form._path);
  }
  PdfPage importPage = importPages[pdfForm.PageNumber - 1];
  ....
}
```

[V3010](https://pvs-studio.ru/ru/docs/warnings/v3010/) The return value of function 'ImportPageNumberOutOfRange' is required to be utilized\. PdfFormXObject\.cs 99

Здесь возвращаемое значение метода_ ImportPageNumberOutOfRange_ не используется\. Давайте взглянем на него:

```cpp
public static string ImportPageNumberOutOfRange(....)
{
  return String.Format("The page cannot be imported from document '{2}'," +
    " because the page number is out of range. " +
    "The specified page number is {0}," +
    " but it must be in the range from 1 to {1}.", ....);
}
```

Видно, что метод возвращает сообщение об ошибке, которое никак не используется\. Подобная оплошность может стать причиной затруднённого поиска причины неправильной работы программы\. Скорее всего, при значениях _pdfForm\.PageNumber < 1_ или_ pdfForm\.PageNumber \> importPages\.Count_ выполнение кода не должно было идти дальше\.

## Заключение

Резюмируя, можно сказать, что разрабатывать игры становится всё труднее и дороже\. Поэтому разработчикам стоит использовать различные инструменты, которые помогут повысить качество кода и удешевить процесс поиска ошибок\. Статический анализатор PVS\-Studio может стать вашим верным товарищем при создании новой игры\.

Мне было бы интересно узнать ваше мнение по данной теме\. Быть может, вы являетесь разработчиком игры и уже используете статический анализ? В общем, приглашаю в комментарии для обсуждений\.

Кстати, если у вас есть проекты на Unity, которые вы хотите, чтобы мы проверили, то для подобного у нас имеется [страница на GitHub](https://github.com/viva64/pvs-studio-check-list)\.

Вы также можете [попробовать PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/try-free/) на своём проекте\. Возможно, это поможет обнаружить проблемы, которые укрылись от вашего взора во время ревью и тестирования\.