>
>
>
Why should Unity game developers use st…

Artem Rovenskii
Articles: 24

Why should Unity game developers use static analysis?

The cost of making a video game has increased over the years. Game development is becoming increasingly complex, the code base is getting larger as well. It's getting harder for developers to track bugs. And a game full of bugs entails financial losses and reputational damage. How can static analysis help?

Introduction

We often check projects and write articles describing errors we find. Today's article describes code errors as well, however, the main topic is the pressing challenges faced in the game development industry. We will concentrate mostly on the development with the Unity, however many points in this article are also relevant for programmers from other fields. So, the main topics to discuss today are the increasing development cost of video games, the reputational risks of game developers, and static analysis.

Game development cost

The cost of making games is rising every year. Consequently, the prices of video games are going up. Let's figure out why this is so.

In 2020, the price of AAA games increased by $10. The standard cost has been $60 and stayed constant since 2000. Of course, game studios understand that in many countries, the purchasing power of people is not increasing, and in some countries it's even decreasing. And if game studios bump up prices even more, people simply won't buy games. So, there are two possible scenarios:

  • Prices go up. It affects the number of copies sold;
  • Prices remain the same or slightly go up. In this case, the quality of a game would suffer, because companies won't operate at a loss.

So why are game budgets growing from year to year? To answer this question, you need to ask yourself, "Would I buy a game that doesn't differ from last year's one?" I think many people would answer "no". Developers are forced to make games more entertaining to surprise players.

You can ask a logical question: "How much has the cost of creating games increased?" To answer it, let's turn to statistics. I managed to find the chart of game budgets up to 2017. This data set excludes marketing spends and displays only the development cost.

The graph above shows that the development cost goes up 10 times approximately every 10 years. These figures are quite impressive even for the present time. By the way, here is the recent case for you.

The famous Cyberpunk 2077 had an approximate budget of $313 million. This figure includes the marketing costs for promoting the game. Usually, marketing costs are less than or equal to development costs. But even if we split the total budget in half, we still get a huge amount of money.

It is worth noting that the game came out quite raw and was full of bugs. I think the game budget has already grown quite a lot — developers have been constantly fine-tuning on the game for 2 years. At the same time, they're developing the game's expansions. Such activity is called post-launch support. Every major project needs to release post-launch content, because what's the point of a game if most of the players stop playing it after the launch. This stage includes the development of new content and fixing bugs that could not be fixed at the main stage of development.

Speaking of bugs...

Errors and bugs

Can you remember at least one game with bugs? Well, all games have bugs.

Of course, this phrase applies not only to games, but also to any other projects. It's impossible to create a large and complex product that does not contain errors. A game, or any serious program, can have hundreds of people working on it. And all these people are not perfect gods — they tend to make mistakes.

Modern games are huge in size. And because of that, it may be hard and expensive to search bugs. Developers snowed under with tasks — they have to work overtime and sacrifice some aspects of the game to finish it on time.

Also, the late bug fixing affects the studio reputation. Just Imagine that company where you work has released a game full of bugs. People tend to remember the negative more than the positive — this is how people work. Why risk your hard-earned reputation on mistakes that could have been easily avoided?

Static analysis

Modern game studios have a huge set of tools to simplify development. One of these tools is static analysis. The methodology of finding potential bugs in the source code can help studios and their developers find various problems and security defects in their programs. Why wait for the player to stumble upon the bugs if these bugs can be fixed at the code writing stage?

Someone may ask, "How does this correlate with the budget, pricing, and so on?" The search for bugs takes place during working hours, which must be paid for. When using static analysis, developers search for defects much faster. This means that they have more time to create new in-game content, game mechanics and other things.

It is worth noting that most mistakes made by game developers do not differ from those made by developers from other fields. This idea is also proven by the results of our checks of open-source games. The analyzer always detects suspicious code fragments in such projects. That's why static analysis may be helpful to programmers from other fields.

PVS-Studio is one of the tools that performs static analysis. As a person who is a part of team that works on this tool, I will tell you how useful it can be for Unity projects.

PVS-Studio had several special improvements to the analysis of projects made with this game engine. For example, we added information about the behavior of Unity methods from the frequently used classes. This helps find more errors in code. By the way, my colleague wrote an article on this topic: "How the PVS-Studio analyzer began to find even more errors in Unity projects."

PVS-Studio also pays attention to other features of this engine. For example, the analyzer knows that Unity provides an unusual implementation of the '==' operator for types that are inherited from the UnityEngine.Object class.

With the help of PVS-Studio, we managed to find some suspicious code fragments in the Unity engine itself. We have three articles describing the check results: from 2016, from 2018, and the most recent one — from 2022.

How do I use PVS-Studio in Unity?

To start, you should already have the analyzer installed and the license entered. If you haven't done it yet, this page may help you.

Open your project in Unity Hub. Click: Assets -> Open C# Project. Unity will open the IDE chosen in the editor settings. You can choose the IDE in the Preferences window:

I'm using Visual Studio 2022. To analyze the project in this version of the IDE, you can use the menu item: Extensions -> PVS-Studio -> Check -> Solution.

You may find the detailed information on how to use PVS-Studio here.

Examples of errors

It would be wrong to write an article on how static analysis helps the game project development without giving examples of errors found by the static analyzer. Unfortunately, the source code of many large and popular video games is not available to the public. Thus, we won't be able to check the code quality of games such as Ori and the Blind Forest, Superhot, Beat Saber, Rust, Firewatch and others. But it's possible to find a solution. Let's analyze the open-source project. For analysis, I took one simple game made with Unity — Card-Game-Simulator. This project will be more than enough to demonstrate the work of the analyzer.

Example 1

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 Expression 'Inputs.IsPageVertical && !Inputs.IsPageVertical' is always false. Probably the '||' operator should be used here. LobbyMenu.cs 159

The analyzer reports that the Inputs.IsPageVertical && !Inputs.IsPageVertical expression is always false. IsPageVertical is a property that returns the value of thebool type.

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

Obviously, there is no such value at which the given condition is true. At the same time, we can see an expression with similar properties slightly below. After looking at these properties, it becomes clear that there is a typo in the code fragment indicated by the analyzer.

Most likely, the condition should look as follows:

Inputs.IsPageVertical && !Inputs.WasPageVertical

Example 2

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 It's odd that this method always returns one and the same value of 'false'. Inflater.cs 288

PVS-Studio indicates that this method always returns false. If we look at code, we will see that there are only two return keywords and they both return false. It is worth noting that there is a comment before the method declaration:

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

According to the comment, the DecodeDict method returns false only if there is not enough input data. In fact, the method always returns false :). It may be that the method really should never return true, but it looks strange, and this comment is misleading.

Example 3

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 Possible infinite recursion inside 'PixelWidth' property. XBitmapSource.cs 97

The analyzer detected a potential infinite recursion. The get accessor contains several directives of the #if preprocessor. And in the last directive, the property accesses itself. If the PORTABLE symbol is defined during the build, and other symbols from directives are not, an infinite recursion will occur.

Example 4

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

V3019 Possibly an incorrect variable is compared to null after type conversion using 'as' keyword. Check variables 'obj', 'selector'. PdfFormXObjectTable.cs 217

The analyzer has detected a potential error that may lead to memory access by a null reference. An object of the base class is casted to a derived class by using the as operator. If the conversion fails, null will be assigned to the value of selector. In this code fragment, the base type obj variable is checked for null. Thus, an exception will not be thrown if a reference to an object of the Selector type or a null reference is passed to the method. In other cases, an exception will be thrown. Usually, it is expected that method would return false in this case. Most likely, it is the selector variable that should be checked for null.

Example 5

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 The return value of function 'ImportPageNumberOutOfRange' is required to be utilized. PdfFormXObject.cs 99

Here, the return value of the ImportPageNumberOutOfRange method is not used. Let's take a closer look:

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}.", ....);
}

We can see that the method returns an error message that is not used in any way. Because of such an oversight, it may be difficult to find the reason why the program doesn't work correctly. Most likely, if the values are pdfForm.PageNumber < 1 or pdfForm.PageNumber > importPages.Count, the code should not have been executed further.

Conclusion

To sum up, game development is becoming more complex and expensive. Therefore, developers should use various tools that help improve the code quality and reduce the cost of finding bugs. PVS-Studio static analyzer can become your faithful companion in creating a new game.

I'd be interested to know your opinion on this topic. Well, maybe you are a game developer. Do you use static analysis? Well, let's discuss this in the comments.

We created a page on GitHub where you can offer a project for the PVS-Studio analysis. Check it out if you have any of the Unity project you would like us to check.

You can try PVS-Studio on your project as well. It may help detect defects that have escaped your attention during the code review and testing.