[Unity - Introduction for the business developer Series - Part 2] Scripting



Jim Mc̮̑̑̑͒G
  ·  
7 March 2020  
  ·  
 13 min read

This is part two of a beginner-level series about Unity, aimed at existing .NET business developers, who are interested in dipping a toe into something different.

In Part 1 - Getting started, we explore how a business developer could make a start using Unity, look at the Editor and overview some of the common words that you’ll encounter.

In Part 2 - Scripting, we learn about some coding concepts that are applicable to Unity.

In Part 3 - A prototype mobile app, we work through the steps needed to make a super-simple application and deploy it to a mobile device.

In Part 4 -Logging and debugging with Android , we look at how we can attach the debugger and use logging - basic tools for any developer.

In Part 5 - Unity UI 101 , we take a first look at Unity UI and interact with a basic button control.

In Part 6 - Unity UI Scrolling List - Creating the Scene , we take on a slightly more complex project, by creating a scrolling list of items using Unity UI.

In Part 7 - Unity UI Scrolling List - Adding the Code , we finish the project started in Part 6, by writing a script to call a REST service and populate items in our UI.



Overview

In this article, we’ll be looking at using C# scripting in Unity.

As a business developer working with C# and .NET, the scripting in Unity will be a familiar experience. However, it’s still useful to appreciate that things aren’t exactly the same as we may be accustomed to.This article will hopefully be a useful guide in this respect.

As a reminder, this series is not intended to be an in-depth beginner guide to Unity. Instead, this is meant to be a birds-eye view, orientated towards existing business developers. You’ll probably be the sort of person who already has a good feel for how software is created, but just wants an overview of the main features and semantics of how a Unity project hangs-together.



Associate a code editor

Before we start writing code, we should associate our prefered code-editor or IDE from within the Unity Editor.

  • We can change the default association (e.g. we want to use VSCode rather than Visual Studio), by changing :
    • Edit → Preferences → External Tools (on Mac, preferences are located under “Unity”)
    • Changing the “External Script Editor”.

screenshot of Unity default code editor setting



General hints and tips

  • Be conscious of changing shared files

    • Both Unity and your IDE (e.g. Visual Studio) may be interacting with the same project/solution - so save our code changes frequently to avoid file conflicts. Be prepared to see the “your project has been modified…” warning fairly often.
  • Unity uses Mono Framework

    • Unity supports .NET Standard 2, meaning there is a good compatibility with other .NET APIs.
    • Unity is not API compatible with any libraries that target only .NET Framework or .NET Core.
    • Read more at Unity Manual - .NET profile support
  • Unity C# version

    • According to the Unity Blog, Unity currently supports C# 7.3
    • C# 8 is not currently supported, but according to Unity Forum - C# 8 support is planned for Unity 2020 with no availability announcements confirmed.



Adding a new script in Unity.

As a reminder, a Script is a type of Component - so it is something that must be associated with a GameObject in order to run.

  • Method #1

    • In the Unity Editor, first select the GameObject that we intend to add a script to, using the Hierarchy View
    • From the Inspector View click the “Add Component” button and select “Script” from the options. Creating a script this way creates the script-file itself in the root of the “Assets” folder.
  • Method #2 (recommended)

    • It’s more common to organise our scripts into a folder called “Scripts” (and depending on our project, using further subfolders to group together similar scripts).
    • Using the Project View, we can right-click and create new scripts in the desired folder.
    • Having now created a script in the folder that we prefer, we can subsequently associate it with a GameObject by simply dragging+dropping it either directly onto the GameObject (in the Hierarchy View) or into the Inspector View.

There is nothing to stop us creating a script outside of the Unity Editor - but make sure that your class inherits from the monobehaviour baseclass and that we add the necessary using unityengine references etc.



Editing a script

  • In the Unity Editor, if we double-click the script in the Project View, this will start our associated code-editor and open up the script for editing.
  • A problem with this, however, is that Unity usually opens up a single file - not the solution. This doesn’t give a very workable experience - we really want to be working with a solution.

  • I recommend that you just start your IDE separately, opening the solution file manually.

I have found the integration of Unity and the IDE - Visual Studio in my case - to be a bit inconsistent. The pattern I have seen is this:

  • If we have not yet attempted to open up a script file using Unity, neither the solution or project files will have been generated.

  • The first time we double-click a script file in the Unity Editor, it creates a new solution (.sln) and project (.csproj) file. My experience was that:

    • the first time the IDE is auto-started for you, it opens up the newly created solution - meaning you can see both your script file and a solution-explorer view.

    • however, if we subsequently double-click a script file from Unity, it seems to have forgotten that a solution exists … so, instead opens up the script as a single file.

  • Out of curiosity, I have experimented by deleting the project and solution files. Unity will duly recreate new solution files if they are missing. Strangely though, my experience was that Visual Studio continued to only open up a single file in isolation.

  • Unity does not appear to offer an option to open the VS solution specifically. Reading up on the subject, this appears to be a long time-unresolved issue.



Code performance needs more consideration.

There aren’t many arguments to be made against the importance of code performance in our work.

However, the reality is that In the world of business application development, many organisations place their commercial priorities upon “deliver more features, more quickly”. This inevitably cultivates a degree of apathy, or just a lack of awareness, towards issues of code performance.

For many businesses, even when poor-performance becomes a concern, decision-makers will commonly just address the issue with additional server-resources, rather than investing developer-time into investigation and performance tuning.

There will, of course, be exceptions to this generalisation - for example, developers working on financial trading systems or simulations etc.

With Unity, code performance needs to be much more of a consideration. We need to produce an application that performs well within the performance limitations of a single device. In general, that will be a device owned by our customers - and commonly that device will be a mobile one, which places even greater relevance on this topic.

Much of the code that we write in Unity will be running as part of the “Update” loop. Simply put, that means that it will be running each and every frame. With the displays of many contemporary devices operating at 60Hz, that’s potentially a great deal of processing work.

If our code is not performant, we are going to find ourselves acutely aware of this, very quickly.

Even without researching into the subject a great deal, or going significantly out of our way to improve performance, we can try and be more conscious of some performance-related fundamentals. These are applicable to any program (not just Unity apps) - for example:

  • heavy iteration (e.g. running a big loop, inside of our Update() method)
  • needless repetition (e.g. cache calculated results, don’t recalculate things unless needed)
  • careless data access or transfer (e.g. querying or transmitting more data than we need)
  • unrecommended use of Exception handling (e.g. using Exceptions for regular code-flow)
  • consider use of C# struct (e.g. its relevance to the memory heap and garbage collection)

If we are interested in learning more, a terrific starting point can be found here Unity Learn : Fixing Performance Problems



Unity uses Coroutines

Coroutines are a way to write asynchronous code

As a business developer with a background using .NET Framework or .NET Core, we probably haven’t come across “Coroutines” before. However, we probably have heard of C#’s Async/Await language features though.

Unity Coroutines and C# Async/Await are not the same things - but, they do solve similar problems; They’re both a way to create asynchronous code that addresses the issue of “thread blocking” operations.

As a business developer, there’s a good chance that you already understand the problem of thread-blocking. For those who aren’t familiar, let’s quickly summarise it:

  • Sometimes, our code requires us to invoke a slow-running external process. For example, loading a file from the disk or making a web request.

  • It’s completely expected that our code will have to stop and wait for the slow operation to finish - before it can resume and finish whatever it was it was working on.

  • The problem with synchronous code is that this “waiting around” blocks anything else from running. In any system, this is really undesirable - we want the processor to be able to get on with other work.

  • Asynchronous programming provides a way to yield control back to the thread pool, freeing it up for other processes. This allows resources to be used more efficiently.

As a further distinction, it’s also worth mentioning that it’s a common misconception that asynchronous programming and multithreading are directly related. Check out Stackoverflow : What is the difference between asynchronous programming and multithreading? .


Quite reasonably, we might ask: “if Coroutines are being used for async programming - why doesn’t Unity just use the C# language feature?”

Historical context is helpful here:

  • Unity was originally released in 2005.
  • async/await was introduced with C#5 in 2012.
  • Support for C# Async/Await, was only added in Unity v2017

So although Unity does now support Await/Async, this has only been added as a feature relatively recently. The use of Coroutines as a way to write asynchronous code is relatively entrenched.

Later, in Part 7 - Unity UI Scrolling List - Adding the Code , we will demonstrate how to use UnityWebRequest to call a web service asynchronously.


Coroutines are a way to defer work

Coroutines are more than just a mechanism to facilitate asynchronous code.

Coroutines provide a way to write code that can be conditionally paused and restarted. Code in a coroutine that has been paused can resume from exactly where it left off.

In Unity, code that runs in the Update() loop is running each and every frame. At a common frame-rate of 60Hz, this means that all the code needs to have concluded within 0.016sec.

Conventional C# methods are started - and then run until concluded. If the work that a method performs is time-consuming, it could negatively interfere with other work that needs to take place. In the context of a running game, this impact could manifest as a noticeable drop in frame-rate.

Some tasks:

  • Only needs to run periodically (e.g. “do something every 5 seconds”)
  • Represent complex long-running work, that would be okay to run in parallel over the course of many frames (e.g. “recalculate a complex matrix of data, but don’t interfere with the rendering of the game whilst that is happening”)

Coroutines are a way to spread the processing requirements of “time-consuming and lower-priority” tasks, across multiple frames. This frees up the limited amount of computing time, available within each frame, for other more important processes.

There are a number of Coroutine behaviours that we can use. These are selected by the use of an appropriate yield operator:

  • yield return null - “The yield return null line is the point at which execution will pause and be resumed the following frame”.
  • yield return new WaitForEndOfFrame(); - “Waits until the end of the frame after Unity has rendered every Camera and GUI, just before displaying the frame on screen.”
  • yield return new WaitForFixedUpdate(); - “Waits until the next fixed frame rate update function.”
  • yield return new WaitForSeconds(5); - “Suspends the coroutine execution for the given amount of seconds using scaled time.”
  • yield return new WaitForSecondsRealtime(5); - “Suspends the coroutine execution for the given amount of seconds using unscaled time.”
  • yield return new WaitUntil(true); - “Suspends the coroutine execution until the supplied delegate evaluates to true.”
  • yield return new WaitWhile(true);- “Suspends the coroutine execution until the supplied delegate evaluates to false.”


A couple of points worth mentioning about CoRoutines:



Unity and Exceptions

When developing business systems, we generally tend to find out about runtime faults straight away.

Usually, what happens is an exception gets thrown, code execution grinds to a halt and a console output (of some description) displays an error in red text. Some frameworks are better at this than others, but in general, .NET is pretty good at taking us straight to the problem with intelligible error messages.

In contrast, Unity doesn’t stop when a runtime exception is encountered (by default).

This isn’t to suggest that Unity swallows exceptions without a trace - instead, they are simply listed to a console log and Unity carries on. There’s nothing wrong with this, but it’s worth highlighting, because we may not be expecting it.

  • If we are testing our project in the Unity Editor, we will see the exception displaying fairly prominently in the console window, so this isn’t much of an issue.
  • However, if we are testing a version deployed to a device, we will need to take additional steps to connect and view an output log. Without this, it may not be apparent that something has even gone wrong!

I want to emphasize that we’re talking about design/development-time here. By the time our system has been deployed live to a production environment, we should expect our code to have been refined to be more robust.

According to Microsoft Documentation : Troubleshooting and known issues (Visual Studio Tools for Unity) (quote):

“Unity introduced a new way for managing user exceptions and as a result, all exceptions are seen as “user-handled” even if they are outside a try/catch block.”

Unity projects are based around dozens or potentially hundreds of separate modules all running together at the same time. If something breaks in one module at runtime, the overall execution of the app doesn’t grind to a halt, unless it was catastrophic.



In the next part of this series, we look at how to create and deploy a basic mobile app using Unity.

NEXT: Read Part 3 - A prototype mobile app




Archives

2020 (21)
2019 (27)