UNITY - Intro to C# Scripting in Unity
This tutorial covers intro topics for scripting in Unity, including a simple 2D game.

RESOURCES: 3 PNGs( tree, paddle, boom spritesheet) and 3 MP3 SoundFX
 
PART 1: INTRO SCRIPTING
[1A]: Project Prep
[1B]: Local Variables and Debug.Log()
[1C]: Class Variables and If-Conditions
[1D]: Unity Variables and CoRoutines
[1E]: Loops: For and While
[1F]: Randomness
[1G]: Arrays for repeated tasks
[1H]: C# Script Overview

PART 2: SAMPLE GAME: CATCH-A-TREE
[2A]: Project Prep
[2B]: Tree Game Object
[2C]: Paddle Game Object
[2D]: Add Scoring
[2E]: VFX / SFX Feedback
[2F]: Random Tree Spawner
[2G]: Add a Time LImit
[2H]: Improve Performance
[2I]: Add an EndScene with a static variable
PART 3: How to Plan a New Feature   |   Common Compiler Errors   |   Helpful Notes
 
WHAT IS THIS TUTORIAL ABOUT?
This tutorial is both for accomplished coders looking to understand how to code for a Unity project, AND for those who are new to coding and Unity. The scripts here will work with any version of the Unity Editor and Unity Hub from 2019 +.

The main skills needed to code in C# for Unity are:

(1) A basic understanding of the structure and syntax of C# files: namespaces, class, variables, methods, and how scripts can find or identify objects and talk to other scripts (all of which are covered at least lightly in this tutorial).

(2) Become familiar with Important Unity Classes to avoid scripting existing functions from scratch. We will use the GameObject, Transform, and Random classes here.

(3) The skill of knowing how to search online for examples of what you are trying to do. Someone has posted something close to what you want to do on the Unity forums, on StackOverflow, on YouTube, or other sites, and you can copy and adapt their code to your project. The skill is to learn enough about Unity and how elements are named to run a useful search, and then to be willing to patiently read, watch, and try enough examples to determine which is the best fit for your project.

If you do not yet have Unity on your computer, download the free Unity Hub and the Unity Engine. Learn how to get them here.
You need a 3-button mouse that works with your computer!

 
GET TO KNOW THE UNITY INTERFACE: 6 KEY PANELS
START HERE! To get started in Unity, it is helpful to learn the names and locations of the 6 key interface panels:
      (1) Project panel
            (2) Console panel
                  (3) Inspector panel
                        (4) Hierarchy panel
                              (5) Scene view
                                    (6) Game view

CLICK HERE
for a visual tour of these six main panels in Unity.


 
What Scripting Tool Do You Have?
An "IDE" is your scripting software. We type our code in a program outside of Unity, so we can work and make changes even while Unity is in Play mode.
There are many free options:
For PC, I prefer Notepad++ ([Ctrl]+[q] to comment-out code, see more C# support here)
or for Mac, Atom ([Shift]+[Ctrl]+[m] to comment-out code).
Unity recommends using Microsoft Visual Studio or VS Code: for Mac or PC.

Connect your IDE to Unity:
Once the IDE is installed, we set it to open automatically when double-clicking a script in Unity:

1. In Unity go to the top menu to choose Edit > Preferences. (on Mac you can find Preferences under the Unity icon > Preferences).

2. Select External Tools, and next to External Script Editor click the rolldown to choose your IDE (if it is not there, choose Browse to find and select the program EXE, likely in your Programs folder).

See an image like this?
Click it for a larger image illustrating the step.

NOTE: In a pinch, Wordpad works for entering code, though it lacks tools to support debugging (like color coding and syntax tips).

All objects in the Unity Assets folder, like scripts, images, and audio, are just files on your computer in the Project folder!

Want to change an asset directly?
RightClick a file in the Unity Project panel > Assets folder to find them outside of Unity using Windows Explorer (or the Mac equivalent).
Then you can edit them directly using any tools you choose.

Images can be updated by dropping a new file with the same name into the Assets folder outside of Unity. The new file replaces the old, and any Unity parameters on the original are automatically applied to the new version! For this reason, you should not have multiple copies of an image or sound file in your Assets folder: only the newest version that replaced the previous one.




 PART 1: INTRO SCRIPTING:
[1A] PREP:

Unity 2D Projects are different from 3D projects in 5 ways:

  • The "2D" button at the top of all Scenes is auto-enabled to lock-out the Z-axis.

  • 2D packages like the Sprite Editor and Tilemapping are auto-imported.

  • Uses only 2D Physics: Rigidbody2D, BoxCollider2D, etc.

  • All PNGs are auto-imported as Sprites (objects instead of textures).

  • Hierarchy Art needs "Order in Layer" set to decide what is in front:
    Higher numbers = closer to camera (player =100, pickups = 90, etc)
    Lower numbers = further away (set background layers to 0, 5, 10, etc).



  • NOTE: The end of Part 1 shows
    an Overview of a C# script, which
    can offer context.


    1. Create a new 2D Unity project from the Unity HUB:
    (if you do not yet have the free Unity HUB and engine installed, learn how to get them here).


    Now that you have a 2D Unity project created, it is time to add content. In Unity, scripts are relatively short, and assigned to the specific object they are meant to affect. So, a door object will typically get a script that affects when the door is open or closed, whether it is locked or available to be opened, wheter it displays a message when a player is nearby, etc.

    Let's start with something simple:


    2. Create a new C# script:
    In the Project panel Assets folder, RightClick to Create > C# script.

    Name it "FirstScript.cs" and hit [Enter / Return] to commit.

    By naming the script before hitting [Enter], the Class name inside the script auto-matches the file name. It is important that they match, but both the file name and class name can easily be changed later if needed.


    3. In the top menu create GameObject > Create Empty to add an "Empty" Game Object to the Hierarchy.

    a) Select this object. In the Inspector top name it "TestObject" and [Return / Enter] to commit. Note it has only one Component: Transforms for Move, Rotate, and Scale on X, Y, and Z axis.

    b) At top-right of Transforms click the 3-dot stack to Reset transforms (so position = 0,0,0).

    c) With TestObject selected, drag FirstScript.cs from the Assets folder onto this Empty Game Object (directly in Hierarchy, or fast-drag into Inspector just above [Add Component]).


    4. Double-click the script in the project panel to open it in your IDE (scripting program).
    Notice that by default there are two empty functions:
          Start() is for any commands intended to run as soon as the game is played.
          Update() is for commands intended to run every frame that the game plays.

    FirstScript.cs script:
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    FirstScript : MonoBehaviour {

          // Start is called before the first frame update
          void Start(){

          }

          // Update is called once per frame
          void Update(){

          }

    }

    A function in C# includes:
          1. The function PRIVACY: whether functions in other scripts can access this function
             (both of these are missing privacy, so are private by default)
          2. What the function RETURNS (both of these return nothing-- "void"-- so the action
              happens only inside the function, usually by changing ther value of variables)
          3. A function NAME() followed by PARENTHESIS (the parenthesis make it a function,
              and optionally can include variables to pass to the commands within the brackets)
          4. START and END BRACKETS that will contain the commands: { }.


    A NOTE ABOUT NAMING:
    Game Objects and Scripts (components) can be named whatever you want, but should only include letters, numbers, and underscores: avoid spaces or any other special characters, and always start with a letter (not a number). HumpCap notation is common (make two words into one word, with the first letters capitalized, like these examples: TestObject, FirstScript, etc).

    A NOTE ABOUT COMMENTS:
    Do you see the grayed-out lines that start with two back-slashes?: //
    These are "comments": messages about the code that the engine does not read.
    Good code includes comments that explain the use of functions in-game.

     
    [1B] LOCAL VARIABLES: SIMPLE MATH and the DEBUG.LOG():
    Local Variables are variables declared inside a function, and so designed to be used only by that function (not directly by any other function).

    1. Replace the script with this OR, to learn better, type the content yourself into the Start() function:
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    FirstScript : MonoBehaviour {

          void Start(){
                int a = 5;
                int b = 2;
                Debug.Log("a plus b = " + (a+b));
          }

          void Update(){
          }
    }

    Hit Save ([Ctrl/Cmd]+[s])

    Click back on the Unity Editor so the changes compile.

    Hit [Play] ([Ctrl/Cmd]+[p])
    Look in the Console tab to see the result! The content between the quotation marks is a "string"; just displayed as text.
    Turn off Play.


    2) Try other arithmetic operations: +, -, *, /.
    What happens when we divide the variables?
    Back in the script, change the Debug.Log() line to divide /.
               Debug.Log("a divided by b = " + (a / b));
    Save the script, return to Unity, and hit Play again.
    Does the result seem right to you?
    Because we used the variable type "int" (integer) we lose the decimal.


    3) To do math with decimals, change the ints to floats, and add the letter f to the end of all numbers.
                float a = 5f;
                float b = 2f;
                Debug.Log("a divided by b = " + (a / b));
    Now try dividing the variables again to see the full decimal:
    Save the script, return to Unity, hit Play again.


     
    [1C] CLASS VARIABLES, EXPOSED, and IF-CONDITIONS:

    Class Variables are variables that are declared toward the top of the class definition, OUTSIDE of any specifc function, so they can be used by all of the functions.

    We can make these variables more interactive in Unity by setting their privacy to public, exposing the variables in the Inspector, so they can be changed before or during [Play].

    1. In the script, use cut ([Ctrl/Cmd]+[c]) and paste ([Ctrl/Cmd]+[v]) to move the first 2 variables out of the Start() function up to the top of the class, to make them CLASS VARIABLES (so they can be used by any function in the Class, instead of just by the Start() function). We "expose" these variables by adding the word "public" to the start of each, changing their privacy "access" from private (the default) to public (so we can see and change them in the Inspector).
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    FirstScript : MonoBehaviour {
          public float a = 5f;
          public float b = 2f;

          void Start(){
               Debug.Log("a plus b = " + (a+b));
          }

          void Update(){
          }
    }
    Save and return to Unity.
    In the Hierarchy select TestObject, in the Inspector note available slots for changing "a" and "b".
    Make a change and hit [Play].

    NOTE: If you change a value DURING Play mode the console result will not change.
    This is because the Debug.Log() command is still in the Start() function, so it is only called when the script begins-- when we first hit play in this Scene.


    2. To make this script interactive in Play mode, move the Debug.Log() command from the Start() function down to the Update() function.
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    FirstScript : MonoBehaviour {
          public float a = 5f;
          public float b = 3f;

          void Start(){
          }

          void Update(){
               Debug.Log("a plus b = " + (a+b));
          }
    }

    Save and in Unity hit [Play]. Try changing the exposed variables in the Inpector to see how the result changes in the Console panel.

    IMPORTANT: Any changes we make in Play mode are changed back when we turn off [Play]. This is by design: if the Player character gets destroyed in Play mode, we want the Player GameObject to be back to normal when we replay the game!




    IF-ELSE-CONDITION:
    An If-Condition allows us to define paramters to produce varied results.
    For example, for an enemy boss character that has both a distance-attack and a close-up melee attack, we can decide which will be used based on the distance from the player.



    3. Add an if-else-condition: if the value is above, below, or equal to a value, do a thing!
          In this case, let's simply put a value if-condition around the Debug.Log().
          void Update(){
               if ((a + b) > 20){
                      Debug.Log("a plus b = " + (a+b));
              } else {
                      Debug.Log("Your numbers are too low.");
              }
          }
          Save ([Ctrrl/Cmd]+[s]).
          In Unity hit [Play].
          Try inceasing the Inspector values until the console displays a new result.


    4. Add an exposed variable of type boolean to run the if-condition.
          A bool is either true or false.
          We use a single "=" to declare a value, and a pair "==" to check the condition status.
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    FirstScript : MonoBehaviour {
          public float a = 5f;
          public float b = 3f;
          public bool canDisplay = false;

          void Update(){
               if (canDisplay == true){
                      Debug.Log("a plus b = " + (a+b));
              } else {
                      Debug.Log("I will show you nothing!");
              }
          }
    }
          Save ([Ctrrl/Cmd]+[s]).
          In Unity hit [Play].
          In the Inspector try turning the bool toggle on and off to display a change in the console.


    5. Add an exposed variable of type string at the top of the class:
                public string myName;
    In the Update() function's first Debug.Log(), add this variable outside of the quotation marks, with "+" to link the parts together (concatenate).
                      Debug.Log("Hey, " + myName + ", a plus b = " + (a+b));
          Save, return to Unity, and hit [Play].
          In the Inspector type into the myName field to change the Debug.Log() text.


    Here is the entire FirstScript.cs script to copy and paste:

    FirstScript.cs script:
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    FirstScript : MonoBehaviour {
          public float a = 5f;
          public float b = 3f;
          public bool canDisplay = false;
          public string myName;

          void Start(){
          }

          void Update(){
               if (canDisplay == true){
                      Debug.Log("Hey, " + myName + ", a plus b = " + (a+b));
              } else {
                      Debug.Log("I will show you nothing!");
              }
          }
    }

     
    [1D] SPECIAL UNITY VARIABLES, LISTENERS, and COROUTINES:
    Unity-specific variable types include GameObject, Transform, Text, and any public scripted Class that you create. Each of these can be the type of a variable!
    1) Create an Empty Game Object:
          In the top menu GameObject > Create Empty.
          In Inspector reset Transforms (so all Position and Rotation values = 0, all Scale = 1).
          Name it "Tree", hit [Enter] to commit the name.

    2) Download this PNG image: tree
          Click open, RightClick, choose "Save Image As", choose Desktop as location.
          In Unity import this image into your Project: RightClick the Project panel Assets folder, choose "Import New Asset", and browse your Desktop to select the image.

    3) With the tree .png selected in the Project > Assets folder, set parameters in the Inspector.
          Since this is a piece of pixel art, change to the following:
                Pixels Per Unit = 16
                Filter Mode = Point (no filter)
                Compression = None
          hit [Apply] to complete these changes.

    4) Drag the tree PNG into the Hierarchy panel. Note the image appears in the Scene panel.
    Please avoid selecting any object in Scene: only select objects by name in the Hierarchy.
          In the Inspector name the selected image "tree_art" (hit [Enter] to commit).
          Reset Transforms (so it is the same Position as the Tree object: 0, 0, 0).
          In the Hierarchy Drag tree_art onto the Tree object to make it a child of Tree.

    5) Create a new C# script (RightClick Project > Assets panel, choose Create > C# script).
          Name it "DisplayChange.cs", hit [Enter] to commit.
          DoubleClick your script to open it in your editor.
          As seen below, add one Class variable at the top: public GameObject treeArt;
          add one command line in the Start() function: treeArt.SetActive(
    false);
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    DisplayChange : MonoBehaviour {
          public GameObject treeArt;

          void Start(){
                treeArt.SetActive(
    false); //this command disables the referenced object
          }
     
         Save the script ([Ctrl/Cmd]+[s]).
          In Unity drag the script to the Tree object to apply it.
          Then fast-drag the tree_art object in the Hierarchy into the "Tree Art" script slot.
          Hit [Play], and your art disappears. Note tree_art in Hierarchy turns gray.


    6) Now, let's make make it interactive by adding an if-condition that LISTENS for input in the Update() function:
          void Update(){
                if (Input.GetKeyDown(KeyCode.Space)){
                      treeArt.SetActive(true);

                }
          }
    }
          Save the script ([Ctrl/Cmd]+[s]).
          Hit [Play]. Your art disappears. Which keyboard key can you hit to bring it back?

    Note C# syntax in Unity offers two ways to call a keyboard button in an if-condition:
                if (Input.GetKeyDown(KeyCode.Space)){
                if (Input.GetKeyDown("space")){

    7) Now, make it a toggle (so it can be hit repeatedly to turn it on and off):
          Create a new Class variable to go at the top: public bool isVisible =
    true;
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    DisplayChange : MonoBehaviour {
          public GameObject treeArt;
          public bool isVisible = true;

          Replace the entire Update() function with this one:
          void Update(){
                if (isVisible == true){
                      treeArt.SetActive(true);
                } else {
                      treeArt.SetActive(false);
                }

                if (Input.GetKeyDown(KeyCode.Space)){
                      isVisible = !isVisible;
                }
          }
          Save the script. Hit [Play], and hit the [Spacebar] to toggle the tree visibility!

     
          Here is the entire DisplayChange.cs script to copy and paste:

    DisplayChange.cs script:
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    DisplayChange : MonoBehaviour {

          public GameObject treeArt;
          public bool isVisible = true;

          void Start(){
               treeArt.SetActive(
    false);
          }

          void Update(){
                if (isVisible == true){
                      treeArt.SetActive(true);
                } else {
                      treeArt.SetActive(false);
                }

                if (Input.GetKeyDown(KeyCode.Space)){
                      isVisible = !isVisible;
                }
          }
    }
    NOTE: The Start() function no longer seems to turn off the object visibility!
    Can you see why?



    COROUTINES / IENUMERATOR:
    A Coroutine is an IEnumerator function that manages a time delay.
    (Programmers: It is a function specific to Unity to make up for the lack of multi-threading).

    All IEnumerators return a "yield" value (not just "void" like in Start() and Update()).
    A common yield command to create a delay is:
               yield return new WaitForSeconds(1f);
          1f = one second of delay. 0.5f = half a second. 2f = two seconds, etc.

    NOTE: Include multiple yield commands for multiple delays between other commands, for flashing colors, a sequence of screenshakes, etc.

    The bold-faced content below adds a Coroutine to the DisplayChange.cs script, to make the tree disappear a couple of seconds after we make it re-appear. This needs three parts:
          An IEnumerator function (bottom)
          An Update() command to activate it: StartCoroutine();.
          To prevent a new command overlapping another, add a StopCoroutine(); command before StartCoroutine(); in the Update() function.

    DisplayChange.cs script: (new content in bold)
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    DisplayChange : MonoBehaviour {

          public GameObject treeArt;
          public bool isVisible = true;

          void Start(){
               isVisible = false; //new command to disable the object through the bool
          }

          void Update(){
                if (isVisible == true){
                      treeArt.SetActive(true);
                } else {
                      treeArt.SetActive(false);
                }

                if (Input.GetKeyDown(KeyCode.Space)){
                      isVisible = !isVisible;
                      StopCoroutine(DelayTreeAway());
                      StartCoroutine(DelayTreeAway());
                }
          }

          IEnumerator DelayTreeAway(){
               yield return new WaitForSeconds(2f);
               isVisible = false;
          }
    }
    Hit Play, hit the [spacebar] to make the tree visible, then wait two seconds to see the tree disappear again on its own!


    HOW-TO NOTES:

    TURN OFF AN OBJECT IN THE EDITOR:

    An Object can be "turned off" (disabled) by selecting it in the Hierarchy, then in the Inspector click-off the little check-box in the top-left corner. Scripts on a disabled Object will not run until the Object is enabled again.

    TURN OFF A SCRIPT ON AN OBJECT:

    Individual scripts on an object can be disabled by turning off their own check-box, but some functions may still run if the object is still enabled. Better to remove an unwanted script Component.

    REMOVE A SCRIPT FROM AN OBJECT:
    To remove a script or any other Component on a given object, RightClick the name of the script and choose "Remove Component".

    ERROR NOTE:
    When you try to run do you get an "Unassigned Reference Exception"?
    This happens when the code tries to do something to an object that has not been assigned.

    You likely missed the step in Unity when you are supposed to fast-drag tree_art into the "treeArt" script slot on Tree.
    Please follow these steps:
    1. In the Hierarchy, select Tree.
    2. In the Inspector, see Tree's properties, including the DisplayChange.cs Component and empty Script Slot called "treeArt".
    3. Select and fast-drag tree_art into the artThing slot, to assign it to that variable.

    NOTE: "Select and fast-drag" is a mouse technique that takes some practice.
    If you just select, the Inspector changes to show the new object you have selected, which prevents you from dragging that object into the other object's script slot.
    If this happens, just re-select Tree and try fast-dragging tree_art again.
    Once the art is assigned to the slot, your script should no longer show this error.

     
    [1E] LOOPS: For and While

    A FOR-LOOP is a conditional statement that operates over a specfic time.
    It includes three commands: Variable, Limit, and Change:
            a. A Variable declation for the value to be incremented.
            b. The intended Limit for that variable, stating the condition of staying in the loop.
            c. How the value Changes once in each loop.

    For example, in this for-loop:

          for (int i = 0; i < 100; i++){

          }

    here is the meaning of each part:
          int i = 0;        "Create an integer named i, set it equal to zero"
          i < 100;         "As long as i is less than 100, stay in this loop"
          i++                 "Increment i by 1 every loop"
          This loop will run 100 times and then stop.
          Any commands added inside the curly-brackets will occur every loop.
          A Debug.Log() in the loop with variable "i" will display loop progress in the Unity Console.

    1). Create a new C# script. (RightClick Project > Assets panel, choose Create > C# script).
          Name it "LoopTest" and hit [Enter] to commit.
          Add the following content, Save ([Ctrl/Cmd]+[s]), and return to Unity to compile:

    LoopTest.cs script:
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    LoopTest : MonoBehaviour {

          void Start(){
                MyForLoop();
          }

          void MyForLoop(){
                for (int i = 0; i < 100; i++){
                     Debug.Log("The for-loop is now at " + i);
               }
          }
    }
    2). Create a new Empty Game Object: GameObject > Create Empty.
          In the Inspector name it "LoopTester", hit [Enter] to commit.
          Drag the LoopTest.cs script from the Assest folder onto this Object.

    3). Hit [Play] and open the Console tab to view ther loops. A loop in Unity C# is one cycle ("tic") of a computer, which is very fast, so these 100 lines in the console should appear almost instantly.





    A WHILE-LOOP is a similar idea, with all three parts (Value, Limit, Change), but structured differently to allow flexibility in how the variable is used (for example, the variable for a while-loop could be declared as a Class variable at the top, so it can then be used in multiple functions).

    4). Edit the LoopTest.cs script to add with the following (new content is bold-faced):

    LoopTest.cs script:
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    LoopTest : MonoBehaviour {

          void Start(){
                MyForLoop();
                MyWhileLoop();
          }

          void MyForLoop(){
                for (int i = 0; i < 100; i++){
                     Debug.Log("The for-loop is now at " + i);
               }
          }

          void MyWhileLoop(){
                int w = 0;
                while (w < 100){
                     Debug.Log("The while-loop is now at " + w);
                     w++;
               }
          }

    }
    Save and hit [Play] to test.


     
    [1F] RANDOMNESS:
    Random number generators are useful in games to immitate the chaotic systems of reality.
    We define a range of two possible values and then use the Random.Range() function to choose a value between them.

    1) Create a new Empty Game Object: GameObject > Create Empty.
          In the Inspector name it "RandomTester", hit [Enter] to commit. Reset Transforms.

    2) RightClick in the Assest folder to create a new C# script named "RandomTest".
          Add the content below, Save, and return to Unity to compile.
          In Unity drag the script onto RandomTester.

    RandomTest.cs script (Display Integers):
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    RandomTest : MonoBehaviour {

          public int rangeStart = 1;
          public int rangeEnd =100;

           void Update(){
                  //the listener for player input goes in the Update() function
                  if (Input.GetKeyDown("r")){
                         RandomMaker();
                  }
           }

           public void RandomMaker(){
                //The randomizer code:
                int randomNum = Random.Range(rangeStart, rangeEnd);

                //What we do with the randomized value:
                Debug.Log("Your new number is " + randomNum);
           }
    }

    3) Hit Play, and tap the letter [r] on the keyboard to generate new numbers in the Console.

    NOTE 1: Does nothing appear in the Console when you tap [r]?
    Click in the Game view before hitting [r] to actual input into the live game!

    NOTE 2: If you tap and no new number appears to display, this may be because the random number generator spit out the same number twice. Try again!


    4) RANDOM TEXT SELECTION:
    The same script can be modified to choose between phrases to display instead of numbers.
    The range variables in the script below limit the range to just five numbers: 0, 1, 2, 3, 4.
    There are five String variables populated with text.
    The RandomMaker() function associates each number with one of these strings (text), so that if the number is selected the string will display.

    Try replacing the contents of RandomTest.cs with the following.
    Save, return to Unity to compile. Hit [Play] and hit [r] to playtest:

    RandomTest.cs script (Display Strings):
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    RandomTest : MonoBehaviour {

          public int rangeStart = 0;
          public int rangeEnd = 4;
          public string myPhrase1 = "Sit upright.";
          public string myPhrase2 = "Take an eye break: look out a window.";
          public string myPhrase3 = "Ask for help.";
          public string myPhrase4 = "You should drink more water.";
          public string myPhrase5 = "Get up and stretch.";

           void Update(){
                  //the listener for player input goes in the Update() function
                  if (Input.GetKeyDown("r")){
                         RandomMaker();
                  }
           }

           public void RandomMaker(){
                //The randomizer code:
                int randomNum = Random.Range(rangeStart, rangeEnd);

                //An if-condition to choose what to display based on the random value:
                if (randomNum == 0){ Debug.Log("" + myPhrase1);}
                else if (randomNum == 1){ Debug.Log("" + myPhrase2);}
                else if (randomNum == 2){ Debug.Log("" + myPhrase3);}
                else if (randomNum == 3){ Debug.Log("" + myPhrase4);}
                else if (randomNum == 4){ Debug.Log("" + myPhrase5);}
           }
    }

    NOTE ON PUBLIC VARIABLES:
    For public variables Unity prioritizes values in the Inspector over values set in the script.
    So, after changing yoru script to the content above, you will likely still need to select the RandomTester object in the Hierarchy and set the Inspector values directly:
    rangeStart to 0 and rangeEnd to 4.
     
    [1G] ARRAYS:
    Arrays allow us to elegantly repeat a task with varied amounts of data.

    For example we can change the second RandomTest.cs script above to allow the designer to input the various phrases on the fly in the Unity Inspector, and even to decide how many phrases would be available, so that the same script could be used flexibly in multiple scenes.
    An Array is an ideal scripting tool for this task.

    An Array variable is declared by the type of variable, followed by square brackets: [ and ].
    This declares an Array of integers:
          public int[] myInts;
    This declares an Array of strings (text phrases):
          public string[] myStrings;
    This declares an Array of floats:
          public float[] myFloats;
    This declares an Array of GameObjects:
          public GameObject[] myGameObjects;

    The length of an array is needed in most uses (in this case, to limit the possible random numbers to the number of phrases). We get the length with the command ArrayName.Length;

    The Index of an array is the specific number associated with a particular entry.
    Arrays start at index [0], which represents the first item in the array.
    The second item is index [1], the third is index [2], etc.
    So, the final item in an array can be found at index [ArrayName.Length - 1].


    1) Replace the content of your RandomTest.cs script with the following content.
    Save the script and return to Unity to compile it.

    2) In the Hierarchy select the RandomTester object to view the script in the Inspector.

    3) In the Inspector under the RandomTest component find the myPhrases array.
          Hit the [+] button at least 3 times to add phrases.
          Then hit [Play] and hit [r] multiple times to see the phrases appear!:

    RandomTest.cs script: (new content in bold)
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;

    public class
    RandomTest : MonoBehaviour {

          public int rangeStart = 0;
          public int rangeEnd;
          public string[] myPhrases;

           void Start(){
                  //assign the length of the array to the end of the random range!
                 rangeEnd = myPhrases.Length;
           }

           void Update(){
                  //the listener for player input goes in the Update() function
                  if (Input.GetKeyDown("r")){
                         RandomMaker();
                  }
           }

           public void RandomMaker(){
                //The randomizer code:
                int randomNum = Random.Range(rangeStart, rangeEnd);

                //Display the content of the array at the index of the random number:
                Debug.Log("" + myPhrases[randomNum]);
           }
    }

    INT vs FLOAT: Random.Range includes the maximum value for floats, but not for integers.
    This is why the full array length is used in this script, not rangeEnd = myPhrases.Length -1;
    If there are four items in the myPhrases array, the length of the array is 4, and the items will be indexed 0,1,2,3. Because Random.Range is not inclusive of the end value for ints, this function needs rangeEnd = 4 in order to successfully output 0, 1, 2, 3.


    This concludes our tutorial on key introductory coding concepts in C# for Unity.
    What follows is an overview of the parts of a script, and then Part 2, where these concepts are put together to make a simple 2D game.

       
    [1H] Unity C# SCRIPT OVERVIEW:
    Here is an overview of the structure of a C# Unity script:
    NameSpaces, Class, Variables and functions:
     

    1. NAMESPACES: libraries of code that our code references.

    2. CLASS DECLARATION:
    [access = public]
    [type = class]
    [name (must be same as file)]
    [ : = derived from]
    [MonoBehavior]

    3. CLASS VARIABLES:
    access, type, name, and (optionally) value.
    int = integer (whole number).
    float = decimal, ending in f.
    string = text, in quotes.
    GameObject any Unity object!
    Transform is the position-rotation-scale of a GameObject.

    4. START() FUNCTION:

    Any commands here happen just once, when the script is first enabled (hitting "Play," or when the object is first instantiated)

    5. UPDATE():
    Any commands here happen every frame the game runs, depending on computer speed. Good for code that "listens" for player input (key-press), or conditional actions.
    FIXEDUPDATE() is the same, but is locked at 60fps (better for physics calulations and timers).

    6. COLLISIONS:
    OnCollisionEnter() detects collisions between two objects with colliders.
    OnTriggerEnter() is used if at least one of the objects'
    colliders is set to isTrigger.
    Use an if-condition to check for a Tag, Layer, or Name to decide the actions on impact.

    7. CUSTOM FUNCTIONS:
    You can make a custom function to do anything. Call this function in another function, like Start() or Update():
         CustomFunctionName();

    8. COROUTINES:
    Need to add a delay before an action? Call a Coroutine!:
    StartCoroutine(CoroutineName())



    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class
    FirstScript : MonoBehaviour {

          //Lines starting with 2 slashes are "Comments".
          //Commented lines are not compiled with the code.
          //Use this to explain what your code is doing!

          public int playerHealth = 100;
          public float speed = 0.2f ;
          public string axisName = "Horizontal";
          public GameObject explosionPrefab;
          public Transform playerPos;





          void Start () {
               
         }



          void Update () {
               
               
         }







          void OnCollisionEnter2D(Collision2D other) {
                if (other.gameObject.tag == "tagName"){
                   
              }
         }






          public void CustomFunctionName () {
               
         }




          IEnumerator CoroutineName() {
               yield return new WaitForSeconds(0.5f);
         }

    }





      PART 2: CATCH-A-TREE GAME:
    Follow all steps below to create your first 2D Unity game
    For your first game in unity, you will move a paddle side to side in order to catch as many falling trees as you can before the time runs out!

     
    [2A]: PREP
    0. Open Game view (next to Scene view).
    Change Display from "Free Aspect" to "16:9"
    (do this for all computer games).


    1. Create a File > new Scene in your 2D game.
    Choose a 2D Scene, NOT "Empty".
    File > Save As to name it “TreeGame1"


    2. Download or create two PNGs, with transparent backgrounds:
               a tree (file pixel size 64 x 64)
               a paddle (file pixel size 256 x 256,
                          paddle in middle, about 32 pixels high)


    3. Import these two PNG assets into Unity:
    In Project panel > Assets folder, RightClick in the empty work space (the file directory) to choose "Import New Asset". Navigate to the location of these PNG assets and click [Import].


    4. In the Inspector, with both Sprite images selected (since we can change settings for multiple files at once), change these settings:
               Pixels Per Unit = (from 100 to) 16
               Filter Mode = (from Bilinear to) Point (no filter)
               Compression = (from Normal Quality to) None

               Finally, click [Apply] (towards the bottom).


    UNITY BEST PRACTICE NOTE #1:

    FOR FUNCTIONAL OBJECTS, ALWAYS MAKE ART AND FUNCTION DISTINCT:

    For characters and other interactive objects, make art a child of an Empty Game Object.
    Put all physics on the Empty parent: Rigidbody, Colliders, Scripts, AudioSource, etc.
    Do not add scripts to the art (except the animator component, if you have animation)!

    MAKE SURE NOT TO MOVE ART AWAY FROM ITS PARENT:

    Do not try to select the object in the Scene view. That only selects the art, and when you move it, the art is beign moved away from its colliders, etc.
    Select the parent in the Hierarchy
    , and use Move to move it in the Scene as needed.

     
    [2B] TREE GAME OBJECT:
    1. Create an Empty Game Object:
          GameObject > Create Empty


    2. In the Inspector name it "Tree", hit Reset Transforms, and [Add Components]:
          Physics2D > BoxCollider2D
                Set Offset Y = -1 and Size X =2,
                or, after adding the art, click [Edit Collider] button to change the
                collider size by hand, to surround art base
          Physics2D > RigidBody2D.

    3. Drag the tree art into Hierarchy.
    In the Inspector name it "tree_art".
    Set Order In Layer =100
    At top, Reset Transforms, then set Scale X and Y = 0.3.

    OPTIONALLY, set position Y = 1.3.
    This moves art up, so the pivot of the parent Tree object is near the child art bottom (which helps the "you caught it" effect to be placed correctly).
    To see the actual pivot location, change the Pivot View from "Center" to "Pivot"

    4. Make tree_art a child of Tree: In the Hierarchy drag tree_art onto Tree.

    5. Select the Tree parent object in the Hierarchy.
    In the Scene view hit [w] to see the Move tool (red side-to-side arrow and green up-down arrow).
    Click the green "up" arrow and drag Tree up to the top of the Camera view (be careful to move the parent object, not just the child art).
    Hit [Play] to watch it drop. Turn off [Play] to continue editing.

    6. Select the Tree parent object.
    In Inspector hit tag > Untagged (top). Choose "Create a New Tag".
    In the Tags and Layers panel that opens hit [+] to add a tag.
    Name it "tree".
    In the Hierarchy re-select the Tree parent and in the Inspector add the newly available tag "tree".

     
    [2C] PLAYER PADDLE OBJECT AND COLLIDERS:
    1. Create a new GameObject > Empty GameObject.

    2. In the Inspector name it "Paddle", hit Reset Transforms and hit [Add Components] to add these two Physics Components:
          Physics2D > BoxCollider2D (set Size X =7 and Y = 0.5,
                    or click Edit Collider button to change size by hand, to surround horizontal shape)
          Physics2D > RigidBody2D (set Body Type from Dynamic to Kinematic,
                    so gravity and collisions do not move the paddle object).

    3. Drag the paddle art into Hierarchy.
    In the Inspector name it "paddle_art" and set Order In Layer =90, Scale X and Y = 0.5.

    4. Make paddle_art a child of Paddle: In the Hierarchy drag paddle_art onto Paddle.

    5. Drag Paddle to near the bottom of the camera view.

    NOTE: Always select the parent by its name in the Hierarchy to avoid separating the art from its parent/Collider (DO NOT click on the art in the Scene view).


    6. Create a new C# script:
  • RightClick in Project panel > Assets folder to Create a C# script. Name it "PaddleMove.cs".
  • Open the script in your script editor, copy the entire script block below and paste it to replace all content. Hit File > Save.
  • Click back into Unity. When the script finishes compiling, drag the script onto the Paddle object.

    PaddleMove.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PaddleMove : MonoBehaviour {

          public Rigidbody2D rb;
          public float moveSpeed = 5f;
          public Vector2 movement;

          // Auto-load the RigidBody component into the variable:
          void Start(){
                rb = GetComponent<Rigidbody2D> ();
          }

          // Listen for player input to move the object:
          void FixedUpdate(){
                movement.x = Input.GetAxisRaw ("Horizontal");
                rb.MovePosition(rb.position + movement * moveSpeed * Time.fixedDeltaTime);
          }

          // Makes objects with the tag "tree" disappear on contact:
          void OnCollisionEnter2D(Collision2D other){
                if (other.gameObject.tag == "tree"){
                      Destroy(other.gameObject);
                }
          }
    }


    Hit [Play] to test the paddle motion with [a] / [d] or left-right arrow keys.
    Also test the Tree disappearing when the paddle touches it.
    Turn off [Play] to continue editing.

  •  
    [2D] ADD SCORING:




    Why is my Canvas enormous?:

    In the Scene View, the Canvas rectangle is displayed MUCH bigger than the Camera view rectangle.
    This is intentional, to more easily manage the Canvas elements separately.

    In the Game View, where it matters, the Canvas UI elements fit neatly over the Camera game view.

    To navigate the Scene view, and easily zoom between the Camera and Canvas:
      Select either the Camera object or the Canvas object in the Hierarchy

    Hover your mouse over the Scene view

    Hit [ f ] on your keyboard to "Focus-zoom" on that space.


    SUMMARY:
    Canvas:

      User Interface (UI) elements are typically added as a "Heads Up Display" (HUD) to a Unity Canvas object: a 2D layer that displays over the Game View.

    UI elements can include Text, Images, Buttons, Sliders, Menus, and more.

    Game Handler:
      All Player and Game State information (stats like scoring or player health) should be managed by a single script called "GameHandler.cs".

    This script should exist on an Empty Game Object that we also call the GameHandler.

    Create a new tag called "GameHandler" and add it to this object for easy reference by other GameObjects.

    The GameHandler prefab should exist in every Scene (level) of your game, so that "Static" variables can preserve their changes between Scenes.


    1. In Game view panel, upper-left corner, change the display from "Free Aspect" to "16 : 9".
    Use this setting for all games intended to be viewed on a computer.

    2. In Scene view, create a UI > Canvas
    (The Canvas appears enormous in the Scene View, but will overlay correctly in the Game view).
    In the Inspector under Canvas Scaler:
    Change UI Scale Mode from "Constant Pixel Size" to "Scale With Screen Size".
    Set Reference Resolution to 1280 x 720 (this is recommended for all computer games).

    3. Create a GameObject > UI > Image.
    In the Inspector name it "scoreBG".
    Set width = 250, height = 60.
    Click the color block, choose a darker color, set opacity = 150.
    Add the Background sprite for curved corners.
    In the Scene view, click w for move and place in the upper-right corner of the Canvas.

    4. Create a GameObject > UI > Legacy > Text (Note: in earlier versions of Unity, UI > Text).
    Name it TextScore.
    Set width = 240, height = 50.
    Set Font Size = 30, Bold, and align center vertical. Set color bright.
    Enter into middle text field: "Score:??".
    Move scoreBG up into the upper-left corner.

    5. Create an Empty Game Object called "GameHandler".
    Create a new tag "GameHandler" and apply it to this object.

    6. Create a new C# Script named "GameHandler" and apply it to the GameHandler object.
    Open the C# script and add the following (note the new namespace to handle the UI content!):

    In Unity fast-drag the Text object into the GameHandler’s scoreText script slot.

    GameHandler.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class GameHandler : MonoBehaviour {

          public GameObject scoreText;
          private int playerScore = 0;

          void Start(){
                UpdateScore();
          }

          public void AddScore(int points){
                playerScore += points;
                UpdateScore();
          }

          void UpdateScore(){
                Text scoreTextB = scoreText.GetComponent<Text>();
                scoreTextB.text = "SCORE: " + playerScore;
          }
    }


    7. Finally, update the PaddleMove.cs script to speak with this GameHandler:


    add to the PaddleMove.cs script (added parts are bold-faced):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PaddleMove : MonoBehaviour {

         public Rigidbody2D rb;
         public float moveSpeed = 5f;
         public Vector2 movement;
         public GameHandler gameHandlerObj;

         void Start(){
              rb = GetComponent<Rigidbody2D> ();
              if (GameObject.FindWithTag("GameHandler") != null){
                   gameHandlerObj = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
              }
         }

         void FixedUpdate(){
              movement.x = Input.GetAxisRaw ("Horizontal");
              rb.MovePosition(rb.position + movement * moveSpeed * Time.fixedDeltaTime);
         }

         void OnCollisionEnter2D(Collision2D other){
              if (other.gameObject.tag == "tree"){
                   Destroy(other.gameObject);
                   gameHandlerObj.AddScore(1);
              }
         }
    }




    8. Game Test #1:
    Hit Play, watch the Score change when you catch the tree. Turn off Play.


    9. Drag the tree down to the Project panel to make it a Prefab.
         The original in the Hierarchy turns blue, to indicate it is now connected to this Prefab.
         Now, any change to the Prefab in the Project panel will also change the Scene version, and any duplicates in any Scene.
         On the other hand, changes added directly to the Scene version, like changing an Inspector paramater or adding a new script, will only effect that single instance in the Scene.


    10. Game Test #2:
    Make around 6 Tree duplicates in the Hierarchy: select Tree and hit [Ctrl/Cmd] + [d].
    Move them up above the play area, staggered left and right (but stay within the play area when they drop). Hit Play and see how many you can catch with the paddle


    11. File > Save to save the Scene.
    Also File > Save Project.


    UNITY BEST PRACTICE NOTE #2:

    USE A CENTRALIZED STATE:

    A GameHandler that is made into a Prefab and appears in every Scene to manage all Stats is called a "Centralized State": a single GameObject through which all critical information flows.
    Do not add health or any other stat to a play that can be destroyed and respawned: put all stats on the GameHandler for erasy reference!

    COMBINE GAMEHANDLER AND CANVAS UNDER A SINGLE GAMEOBJECT:

    A great way to make work easier on a larger project is to make an Empty GameObject, name it "GameHandlerCanvas", Reset Transforms, drag both the GameHandler and Canvas onto it to make them children, drag it into the Project to make it a Prefab and add it to all Scenes. Now, any scoring or PauseMenu connections can be set once in the Prefab, and will work in all Scenes!

     
    [2E] VFX / SFX FEEDBACK:
    Make an explosion and a sound effect when a tree is caught:
    "Game Feel" makes a game seem responsive and more enjoyable to play.
    It includes visual and audio feedback for our actions.
    Making a bouncing ball squash and stretch with tweening, making impact particles or color changes or sound effects.
    Add feedback to our simple tree-catching game: an animated spritesheet effect and a sound.

    (a) ANIMATION:
    1. Create a VFX spritesheet image and export as a PNG: 64x64, each quadrant 32x32.
    (or download one of these:
    explosion1 | explosion2 | stars | splash)
       
             

    2. Import your spritesheet PNG into Unity:
    Select the Project panel Assets folder, RightClick, hit "Import New Asset", and choose your PNG.

    3. Select the PNG in the Project panel.
    In the Inspector set the following and hit [Apply]:
               Sprite Mode = Multiple
               Pixels Per Unit = 16
               Filter Mode = Point (no filter)
               Compression = None
    (These settings assume your image is pixel art)

    4. Press the [Sprite Editor] button in the Inspector (middle, right side) to open the Sprite Editor window.
    Find the Slice menu at top-left and click it open.
    Set Type= Grid by Cell Count, and choose C=2 and R=2.
    Hit the [Slice] button to commit those changes
       (Note you can always change settings and re-slice).
    In the Sprite Editor upper-right hit [Apply].
    Close the Sprite Editor.

    NOTE: Is your [Sprite Editor] button not working?
    You likely created a 3D project at the start instead of a 2D project.
    The fix is to open the Window menu > Package Manager, change "Packages" from "in project" to "all", find 2D Sprites and hit [install]. You also want to hit the [2D] button at the top of Scene.


    5. In the Project panel Assets folder, your spritesheet PNG now has a triangle on the right side.
    Click this triangle to open the PNG and see the newly sliced sprites.
    Drag the first new image into the Hierarchy.
    In the Inspector name it "Boom" and set Order in Layer = 105.

    6. Create a new folder under Project panel > Assets > Media called "Animation".

    7. In the top menu, open Window > Animation > Animation.
    With Boom selected find the the [Create] button at the center
    (if you don't see the [Create] button, make the Animation window wider / taller, and make sure your boom object is selected).
    Hit the [Create] button. It wants to save your first animation clip.
    Choose the new Animation folder, and name the new clip “boom_anim”.
    Hit [OK] to close the Create/Save window.

    8. Drag all 4 sliced sprites from the Project panel into the top of the Animation timeline.
    Spread the keyframes out between frames 1-30.
    (Try selecting all 4 frames and click-drag the right-hand blue line to the right, to stretch the distance between the frames).
    Drag the last sprite into the Tinmeline again and place it at the end, for a total of 5 images set to around frames 1, 10, 20, 30, 40.
    Hit the Animation Play triangle to see the Scene image animate.
    Adjust timing as you desire.
    Close the Animation panel.

    9. Make it into a prefab: Drag the Boom object from the Hierarchy into the Project panel.
    Delete the original from the Hierarchy.


    (b) AUDIO:
    1. Import your audio .WAV or MP3 file (or download this squish sound).

    2. Drag the audio clip onto the Paddle object to add an AudioSource Component.
    In the Inspector turn OFF the option "Play on Awake."
    (If it is left on, the sound plays automatically when the scene is played.)


    (c) SCRIPT:
    Add code for both animation and audio activation to the PaddleMove.cs script:
    A new variable to the top to hold the Boom prefab, and lines to the OnCollisionEnter() to spawn ("Instantiate") the VFX and play the audio file.

    here is the revised PaddleMove.cs script (added parts are bold-faced):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PaddleMove : MonoBehaviour {

         public Rigidbody2D rb;
         public float moveSpeed = 5f;
         public Vector2 movement;
         public GameHandler gameHandlerObj;
         public GameObject hitVFX;

         void Start(){
              rb = GetComponent<Rigidbody2D> ();
              if (GameObject.FindWithTag("GameHandler") != null){
                   gameHandlerObj = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
              }
         }

         void FixedUpdate(){
              movement.x = Input.GetAxisRaw ("Horizontal");
              rb.MovePosition(rb.position + movement * moveSpeed * Time.fixedDeltaTime);
         }

         void OnCollisionEnter2D(Collision2D other){
              if (other.gameObject.tag == "tree"){
                   gameObject.GetComponent<AudioSource>().Play();
                   GameObject boomFX = Instantiate(hitVFX, other.gameObject.transform.position, Quaternion.identity);
                   StartCoroutine(DestroyVFX(boomFX));

                   Destroy(other.gameObject);
                   gameHandlerObj.AddScore(1);
              }
         }

         IEnumerator DestroyVFX(GameObject theEffect){
              
    yield return new WaitForSeconds(0.5f);
              Destroy(theEffect);
              gameObject.GetComponent<AudioSource>().Stop();
         }


    }

    Note this new code includes a "CoRoutine" -- an IEnumerator function that is handy for creating time delays. The IEnumerator needs at least one command for what it returns:
              yield return new WaitForSeconds(0.5f);
    You can use multiple copies of this command to create pauses between actions.

    Calling an IEnumerator is different than calling a regular function.
    A regular function public void UpdatePlayerStats(){} can be called simply by typing its name:
              UpdatePlayerStats();
    A Coroutine IEnumeraor UpdatePlayerStats(){} must be called inside of a Coroutine call:
              StartCoroutine(UpdatePlayerStats());


    DESIGN QUESTION:
    What other types of visual feedback could you create: for when a tree is caught, or when a tree is NOT caught (when one slips past the Paddle)?

     
    [2F] RANDOM TREE SPAWNER:
    1. Create an EmptyGameObject called "TREES", reset Transforms. This will act as a folder.

    2. Create an EmptyGO called "treeSpawnPoint", drag onto TREES to make it a child.

    3. Duplicate treeSpawn to make 5 copies ([Ctrl/Cmd]+[d]), and distribute ABOVE the play area, between the sides of the camera view.

    4. Create a new C# script "TreeSpawner.cs" and apply to TREES.
          Add the content below.
          Drag the 5 treeSpawnPoints into the spawnPoint script slots.
          Drag the Tree Prefab (step [2D]:9) from the Project panel into the treePrefab slot.


    TreeSpawner.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class TreeSpawner : MonoBehaviour {

          //Object variables
          public GameObject treePrefab;
          public Transform spawnPoint1;
          public Transform spawnPoint2;
          public Transform spawnPoint3;
          public Transform spawnPoint4;
          public Transform spawnPoint5;
          private Transform spawnPoint;

          //Timing variables
          public float spawnRangeStart = 0.5f;
          public float spawnRangeEnd = 1.2f;
          private float timeToSpawn;
          private float spawnTimer = 0f;

          void FixedUpdate(){
                timeToSpawn = Random.Range(spawnRangeStart, spawnRangeEnd);
                spawnTimer += 0.01f;
                if (spawnTimer >= timeToSpawn){
                      spawnTree();
                      spawnTimer =0f;
                }
          }

          void spawnTree(){
                int SPnum = Random.Range(1, 6);
                if (SPnum == 1){ spawnPoint = spawnPoint1;}
                else if (SPnum == 2){ spawnPoint = spawnPoint2;}
                else if (SPnum == 3){ spawnPoint = spawnPoint3;}
                else if (SPnum == 4){ spawnPoint = spawnPoint4;}
                else if (SPnum == 5){ spawnPoint = spawnPoint5;}
                Instantiate(treePrefab, spawnPoint.position, Quaternion.identity);
          }
    }
    Test your script: hit Play, and see how many trees you can catch!
    NOTE: Random.Range interacts with int and float types differently: the top of the range for an int is ignored (so in the above we need to make it 6 if we want it to include 5).


    5. The revised script below uses an array for Editor flexibility on the number of spawn points.
          Replace the content of TreeSpawner.cs with this revised script.
          In the Inspector open SpawnPoints array and change the Size value from 0 to the desired number of spawn point objects. Hit [Enter] to see the script slots created (starts at 0).
          Drag the actual objects from the Hierarchy into these script slots.

    TreeSpawner.cs script (with an Array):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class TreeSpawner : MonoBehaviour {

          //Object variables
          public GameObject treePrefab;
          public Transform[] spawnPoints;
         
    private int rangeEnd;
          private Transform spawnPoint;

          //Timing variables
          public float spawnRangeStart = 0.5f;
          public float spawnRangeEnd = 1.2f;
          private float timeToSpawn;
          private float spawnTimer = 0f;

          void Start(){
                  //assign the length of the array to the end of the random range
                 rangeEnd = spawnPoints.Length - 1 ;
           }

          void FixedUpdate(){
                timeToSpawn = Random.Range(spawnRangeStart, spawnRangeEnd);
                spawnTimer += 0.01f;
                if (spawnTimer >= timeToSpawn){
                      spawnTree();
                      spawnTimer =0f;
                }
          }

          void spawnTree(){
                int SPnum = Random.Range(0, rangeEnd);
                spawnPoint = spawnPoints[SPnum];
                Instantiate(treePrefab, spawnPoint.position, Quaternion.identity);
          }
    }


    UNITY BEST PRACTICE NOTE #3: Sort your Hierarchy so things are easier to find!

    a). Practice Useful Object NAMING:
          Name each Game Object for the category and then the specific item.
          So, a speed boost pickup could be named pickup_Speed.

    b). Create "FOLDERS" to organize objects by category:
          (1) create an Empty Game Object
          (2) Reset Transforms. This is critical, as child objects inherit any transforms.
          (3) Name the object in all-caps.
          (4) Drag all relevant objects onto this new object to make it act like a "folder".
          So, for example, all enemy NPCs go into ENEMIES, all pickups go into PICKUPS, etc.

     
    [2G] ADD A TIME LIMIT:
    Add a game time limit to the random tree spawner:
    1. Create two GameObject > UI > Legacy >Text objects and one UI > Image object.

    2. Put the Image in the top middle, name it "ImageTimeBG". Choose a color, set transparency (alpha level) to 150, and add the Background sprite.

    3. Name one Text object "TextTimer" and locate over ImageTimeBG. Set size of both to match the height of the score Image and Text. Type into the text field “???”.

    4. Name the other text "TextGameOver". Set the text box size to 400 x 300. Type into text field “GameOver,” center vertically and horizontally, set size to 60, bold, and choose color.

    5. Replace the TreeSpawner.cs script with the following.
    Add the text objects to the two new “timeText” and “gameOverText” script slots:


    add to the TreeSpawner.cs script (added parts are bold-faced):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class TreeSpawner : MonoBehaviour {

          //Object variables
          public GameObject treePrefab;
          public Transform[] spawnPoints;
         
    private int rangeEnd;
          private Transform spawnPoint;
          public GameObject timeText;
          public GameObject gameOverText;


          public float spawnRangeStart = 0.5f;
          public float spawnRangeEnd = 1.2f;
          private float timeToSpawn;
          private float spawnTimer = 0f;

          public int gameTime = 20;
          private float gameTimer = 0f;


          void Start(){
               //assign the length of the array to the end of the random range
               rangeEnd = spawnPoints.Length - 1 ;
               gameOverText.SetActive(
    false);
               UpdateTime();
          }


          void FixedUpdate(){
                timeToSpawn = Random.Range(spawnRangeStart, spawnRangeEnd);
                spawnTimer += 0.01f;
                if (spawnTimer >= timeToSpawn){
                      spawnTree();
                      spawnTimer =0f;
                }
                gameTimer += 0.01f;
                if (gameTimer >= 1f){
                            gameTime -= 1;
                            gameTimer = 0;
                            UpdateTime();
                }
                if (gameTime <= 0){
                            gameTime = 0;
                            gameOverText.SetActive(true);
                }

          }

          void spawnTree(){
                int SPnum = Random.Range(0, rangeEnd);
                spawnPoint = spawnPoints[SPnum];

                if (gameTime > 0){

                      Instantiate(treePrefab, spawnPoint.position, Quaternion.identity);
                }
          }

          public void UpdateTime(){
                Text timeTextB = timeText.GetComponent<Text>();
                timeTextB.text =
    "" + gameTime;
          }

    }

    Test your script: hit PLAY, and see how many trees you can catch in the allotted time!


    NOTE about changing public variables:
    If a script has been applied to a GameObject, then any public variables that are changed in-script will NOT automatically update in the Scene Inspector. Change those public variables directly in the Inspector, since Unity assumes the Scene value is meant to overwrite the script value.

    So, for the gameTime value above, to get a longer gameTime, open the game Scene, select the TREES object in the Hierarchy, and in the Inspector change the value (not in the script).

    Alternatively, if the variable does not need to be exposed or accessible by other scripts, set the variable to private in the scipt, and then change the value as needed in the script.

     
    [2H] IMPROVE PERFORMANCE BY DESTROYING OLD OBJECTS:
    Make the game more performant by adding a script to the Tree Prefab: destroy each tree if it gets past the player paddle. In this way multiple trees are not falling infinitely.
    Get the Transform of the paddle-player, and check to see if the Tree is a certain distance below it on the Y axis. If so, apply Destroy(gameObject);

    1. Create a new C# script: RightClick Project > Assets and choose Create > C# Script.
          Name it "TreeReclaim" and hit [Enter] to commit.
          DoubleClick the script to open it. Add the content below. Save ([Ctrl/Cmd]+[s]).
          Return to Unity to compile the script.

    2. In the Project panel DoubleClick the Tree Prefab to view it (appears instead of Scene view).
          Drag the TreeReclaim.cs script onto the Tree Object to assign it.
          Return to normal Scene view: In the Hierarchy UpperLeft click the little triangle (points left).

    3. In the normal Scene Hierarchy select your Paddle object, and apply the default tag "Player".

    TreeReclaim.cs script:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class TreeReclaim : MonoBehaviour {

          public Transform paddle;
          public float distanceToDestroy = 5f;
          public float distCheck;

          void Start(){
                //find the Paddle object by tag, then load its transform into our variable
                paddle = GameObject.FindWithTag("Player").GetComponent<Transform>();
          }

          void Update () {
                if (transform.position.y < (paddle.position.y - distanceToDestroy)){
                      Destroy(gameObject);
                }
         }
    }


    CHALLENGE QUESTIONS:
    How might you use the timer to change the rate of the spawned trees, to make the game harder as it continues?
    What else can you do to vary and increase the challenge in this little game?

     
    [2i] ADD A FINAL SCORE SCENE:
    Create new scene to display the score, use a STATIC VARIABLE to transmit value between Scenes, add both Scenes to Build Settings:

    NOTE: If you have not yet made a Prefab of your GameHandler, please drag the GameHandler object from your TreGame1 Scene Hierarchy into the Assest > Prefabs folder. Also, move all Scenes into Assets > Scenes folder.

    1. Make a File > New Scene.
        File > Save As to name it "EndScene".
        (save into Assets > Scenes folder).

    2. Add a GameObject > UI > Canvas.
    In the Inspector, under Canvas Scaler:
        Set UI Scale Mode = "Scale With Screen Size".
        Set "Reference Resolution" to 1280 x 720.
    NOTE: Please use these settings on EVERY Canvas you make in this course!

    3. Make the background:
    RightClick the Canvas to add a UI > Image.
    In the Inspector:
        Name the image "ImageBG".
        Set width and height to 1280x720.
        Under Image click the color rectangle to set the color as desired.

    4. Make the Title Text:
    RightClick the Canvas to add a UI > Legacy > Text.
    In Scene view hit [w] for the Move tool. Click and drag the green arrow to move it up near the top.
    In the Inspector:
        Name it TextTitle.
        Set Rect width / height = 800 x 150.
        Set Font Size = 80, Font Style = bold.
        Set Alignment = center (mid choice in both groups of 3).
        Set text field to say "GAME OVER".

    5. Make the Score Display Text:
    RightClick the Canvas to add another UI > Legacy > Text.
    In Scene view hit [w], drag below TextTitle.
    In the Inspector:
        Name it TextScoreDisplay.
        Set Rect width / height = 800 x 150.
        Set Font Size = 40, Font Style = bold.
        Set Alignment = center (mid choice in both groups of 3).
        Set text field: "FINAL SCORE: ??" (all of this will be replaced by the script)

    6. Drag the GameHandler Prefab from the Project panel Assets > Prefab folder into your Scene Hierarchy.

    7. Revise the GameHandler.cs script (see below).
    Note the player score int has been made "static" so it preserves value across Scenes.
        In Hierarchy select the GameHandler object and in Inspector find the scoreText script slot.
        Drag TextScoreDisplay into this scoreText script slot.

    revise the GameHandler.cs script (added parts are bold-faced):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.SceneManagement;

    public class GameHandler : MonoBehaviour {

          public GameObject scoreText;
          public static int playerScore = 0;

          void Start(){
                UpdateScore();
          }

          public void AddScore(int points){
                playerScore += points;
                UpdateScore();
          }

          void UpdateScore(){
                Text scoreTextB = scoreText.GetComponent<Text>();
                if (SceneManager.GetActiveScene().name == "EndScene") {
                      scoreTextB.text = "FINAL SCORE: " + playerScore;
                }
               
    else {
                      scoreTextB.text = "" + playerScore;
                }

          }

            public void RestartGame(){
                    playerScore = 0;
                    SceneManager.LoadScene("TreeGame1");
            }

            public void QuitGame(){
                    #if UNITY_EDITOR
                    UnityEditor.EditorApplication.isPlaying = false;
                    #else
                    Application.Quit();
                    #endif
            }


    }

    8. RightClick the Canvas to add UI > Legacy > Button.
    In the Inspector set Button Settings:
        Name it "Button_Restart".
        Set Width and Height = 300 x 80
        Under "Button" set the first three color options as desired: Normal, Highlighted, and Pressed.

    Inspector Text Settings:
    In the Hierarchy click the triangle to the left of the Buttons to select the child Text object.
        Set Font Size = 40, Font Style = bold.
        Change Text field to "RESTART".

    9. Duplicate the Button:
    Select the button, hit [Ctrl/Cmd] + [d].
    Make two changes in Inspector:
        Change Button name to "Button_Quit".
        Change Text field to "QUIT".

    10. Add Onclick events to each button:
    Select all buttons (hold [Shift] to add to selection).
    Inspector:
        Towards Inspector bottom find "OnClick" and hit [+] to add an event.
        Drag the GameHandler object from the Hierarchy into the "None" slot (Please do NOT use the GameHandler Prefab from the Project panel-- only the one from the Hierarchy!).
        Select just one button at a time, set the Functons to GameHandler > RestartGame() and QuitGame().

    11. Revise the TreeSpawner.cs script (see below).
    There are only three changes, meant to change what happens when the timer treaches zero:

    revise the TreeSpawner.cs script (added parts are bold-faced):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.SceneManagement; //change #1: added namespace

    public class TreeSpawner : MonoBehaviour {

          //Object variables
          public GameObject treePrefab;
          public Transform[] spawnPoints;
         
    private int rangeEnd;
          private Transform spawnPoint;
          public GameObject timeText;
          public
    GameObject gameOverText;

          public float spawnRangeStart = 0.5f;
          public float spawnRangeEnd = 1.2f;
          private float timeToSpawn;
          private float spawnTimer = 0f;

          public int gameTime = 20;
          private float
    gameTimer = 0f;

          void Start(){
               //assign the length of the array to the end of the random range
               rangeEnd = spawnPoints.Length - 1 ;
              
    gameOverText.SetActive(false);
               UpdateTime();
          }

          void FixedUpdate(){
                timeToSpawn = Random.Range(spawnRangeStart, spawnRangeEnd);
                spawnTimer += 0.01f;
                if (spawnTimer >= timeToSpawn){
                      spawnTree();
                      spawnTimer =0f;
                }
                gameTimer += 0.01f;
                if (gameTimer >= 1f){
                            gameTime -= 1;
                            gameTimer = 0;
                            UpdateTime();
                }
                if (gameTime <= 0){
                            gameTime = 0;
                            SceneManager.LoadScene ("EndScene"); //change #2: switch scene
                            //gameOverText.SetActive(true); //change #3: comment gameOverText
                }
          }

          void spawnTree(){
                int SPnum = Random.Range(0, rangeEnd);
                spawnPoint = spawnPoints[SPnum];

                if
    (gameTime > 0){
                      Instantiate(treePrefab, spawnPoint.position, Quaternion.identity);
                }
          }

          public void UpdateTime(){
                Text timeTextB = timeText.GetComponent<Text>();
                timeTextB.text = "" + gameTime;
          }
    }
     

    12. CRITICAL: Add TreeGame1 and EndScene Scenes to Build Settings:
    In order for Unity to be able to switch between Scenes, all the Scenes in the game need to be added to the Build Settings. The order does not matter, except in games meant to be Built the first item must be the MainMenu (the first thing you want users to see when they hit [Play] in a built app).

    a). Open File > Build Settings.
    This opens a separate window (you may want to make your Unity screen less wide, so you can fit the new window side-by-side with Unity).

    b). Find the top area "Scenes In Build".
    It should have just one item, SampleScene.
    Select and RightClick SampleScene to choose [Remove Selection]

    c). Back in the Unity Project panel, open Scenes folder.
    Drag TreeGame1 and EndScene up into the Scenes In Build area of the Build Settngs panel.

    d). Close the Build Settings panel, and hit [Play].


    Now, when the time runs out during gameplay, the Scene switches to the EndScene, where the player can see their final score (transmited by the static variable) and choose to hit RESTART.



     PART 3: Helpful Notes
     
    HOW TO PLAN A NEW FEATURE:
    Want to add something that is not included in this tutorial? Consider the following steps:

    STEP 1. Concisely describe your Desired Functionality:
    Before you start coding, tell the User Story of this feature: What will the player experience?
    Describe what will cause (trigger) the interaction, and what will happen as a result.
    Be as specific and concise as you can!
    Do not take the shortcut of referencing another game in your description, even if you have one in mind. Make your description full and clear, as if you have never seen it before.


    STEP 2. Choose the Trigger Object, add code to that object for behavior/interaction:
    What Game Object will cause the functionality to happen?
    Is it a mushroom the player touches, an enemy that "sees" the player when they get close?
    Put the triggering code on this other Object that will cause the interaction.
    Unity prefers interactions be coded onto these separate, relevant objects.

    The triggering code typically activates in one of two ways:
    (a) A collider interaction between the Player and the other object:
                OnCollisionEnter2D() or OnTriggerEnter2D().
    (b) The Player moves within a threshold distance of the other object, using
                Vector3.Distance(transform.position, target.position);
    Other ways to trigger an interaction include RayCasting, or a flat timer ("Ready or not...").

    The triggering code can have an effect on the Object, like activating an animation or instantiating particles, and it usually references the GameHandler, to directly impact player state (see Step 3).


    STEP 3. Choose the Variable and Function to change in the GameHandler:
    A meaningful interaction typically changes the player status.
    Have they lost or gained health? Found treasure? Lost energy? Found part of their spaceship?
    We make player progress tangible by updating a variable in the GameHandler.

    The GameHandler is an Empty Game Object with a script holding all game state variables: player health, player progress -- anything that the game is meant to track goes there, and the GameHandler needs to exist as a prefab in every Scene.
    This "centralized state" makes development much more efficient ("all roads lead to the GameHandler") and it is critical for multiplayer games.

    STATIC VARIABLES: While this simple example game only has one in-game Unity Scene (one game level), most games have many levels / Scenes.
    In order for a variable value to persist between scenes, especially a variable for a player stat like health, we set that variable to "static":
                public static int playerHealth = 100;

    The trigger object usually updates this variable on the Game Handler object when interacting with the player character object.


    4. Define Player Feedback:
    How do we make that updated variable visible to the player?
    Do we update the UI?
    Do we show the change on the player through animation, color change, knockback, other?
    Games are most exciting when they are feedback-rich!


    5. Make sure UI checks for updates even without the trigger:
    Add code to the Start() function, so the HUD has the correct starting display.



     
    TYPICAL COMPILER ERRORS:

    Misspelling (capitalization matters!):
        The class name must match the script name.
        Uses of variables must match the original variable name declaration.
        Function calls must match the function name.

    Missing semicolon (at the end of each command) or unmatched / extra curly brackets
    are the most common mistakes.

    Missing NameSpace:
        Any interface work (like using a UI Text object) needs the UI Namespace:
            using UnityEngine.UI;
        Changing the scene or IDing a scene name needs the Scene Management namespace:
            using UnityEngine.SceneManagement;

    GameObject (uppercase G) vs gameObject (lowercase g):
      Use GameObject for type declaration or a search: GameObject.FindWithTag("tagName");
      Use gameObject for a specific object, like the object holding the script, or in a collision.

    Is a collision not working as you expect? Check the following:
    1. OnCollisionEnter(Collision other) only works if both object Colliders have isTrigger = off.
    2. OnTriggerEnter(Collider other) is used if at least one of the colliders has isTrigger = on.
    3. To test collisions: try identifying the collisons that are working by showing their name:
            Display.Log("colliding with " + other.gameObject.name);
    4. Confirm all of your Physics (Rigidbody and Collisions) are either 2D (Rigidbody2D, BoxCollider2D, etc) OR 3D (just Rigidbody, BoxCollider, etc). Do not mix 2D and 3D Physics in the same Unity Scene: they are separate physics engines, and are not meant to intermix.
    5. Make sure the Collider outline (visible when the parent empty game object is selected) is actually centered around the art. These can get misaligned if you acidentally grab the art in the Scene view and move just the art, leaving the parent and Collider behind. Avoid this problem by only selecting objects by their name in the Hierarchy, not by clicking on the art in the Scene.

    Unassigned Reference means that a variable is being used but never populated, like an exposed GameObject script slot that has not been assigned a GameObject.
    All script slots need to be populated, either by a direct drag into the Inspector or populated by the code at runtime.



     
    MORE HELPFUL NOTES:
    Remember to study this list of Important Unity Classes (methods to facilitate Unity coding work)


    To remove a component (like a script) from a GameObject, RightClick on the script Component header and choose "Remove Component".

    To disable a component with code, like a 2D Collider:
       gameObject.GetComponent<Collider2D>().enabled = false;
    For a Rigidbody, instead of disabling, we set the type to kinematic (to turn off gravity):
       gameObject.GetComponent<Rigidbody2D>().isKinematic = true;

    Mathf is a set of very useful methods, like making an object bob up-and-down with Mathf.sin, or getting the absolute value of an operation, like Mathf.Abs(5 - 10) = 5.

    Time.deltaTime is a measure of time in Unity. Multiplying an action by Time.deltaTime will cause it to repeat forward in time.


    TIME SAVERS:
    An "Instance" in Unity is called a PREFAB. To create a Prefab, drag an object from the Hierarchy into the Project > Assets folder. The copy in the Assets folder is the prime Prefab, and any changes made to it will also appear in all Scene copies (Scene copies are blue).
    So, for example, a UI button can be made into a Prefab, and then duplicated for all Scene buttons. Then, to change the colors or art or font of all buttons in the game, you only need to change the Prefab in the Assets folder, and those changes will be populated to all the duplicates.
    Prefabs are used for any game object that appears multiple times or in multiple scenes: characters, hazards, instantiated (spawned) objects like bullets or explosions, etc.

    To copy a Component from one object to another: Rightclick on the top bar of the first, choose "Copy Component" and then Rightclick aagain to choose "Paste Component as New".


    OBJECT IDENTIFICATION:
    Tags are useful to find a specific object (like the Player) or decide what to do with a Collision:          GameObject.FindWithTag("Player");

    Layers help identify a category of objects, like enemies or all parts of the ground:
        LayerMask.NameToLayer("Enemies");

    Object Name: While not prefered (because names change so easily), in a pinch we can also find an object by its name in the Hierarchy: GameObject.Find("Player");

    Objects in a Hierarchy can be identified relative to each other, especially for accessing scripts:
        Find Component in Self = gameObject.GetComponent<ScriptName>().VariableName;
        Find in Child = gameObject.GetComponentInChildren<ChildScriptName>().VariableName;
        Find In Parent = gameObject.transform.parent.GetComponent<ParentScriptName >(). VariableName;

    DISTANCE:
    To get the distance between two objects, like a player and an enemy, create variables to capture their Transforms (here called player and enemy):
                float playerDistance = Vector3.Distance(player.position, enemy.position);

    LISTS:
    Are like Arrays, but the number of items can be changed dynamically during Play.
    Like Arrays, Lists are useful to hold multiple items for a change-able dropdown menu (https://youtu.be/URS9A4V_yLc) or as re-set-able waypoints for an NPC to navigate between.

    To activate multiple audio files on the same object try this system.




    MULTI-PURPOSE SCRIPTS:
    Want to use the same script to function differently under different conditions?
    Add public variables to manage the options.
    For example, to use the same Pickup script for both Health and Speed boost objects, create a boolean for each and put the results of getting the object in if-conditions for those booleans:
                public bool isHealth = true;
                public bool isSpeed = false;

    inside the OnTriggerEnter2D():
                if (isHealth == true) { // increase health }
                if (isSpeed == true) { // increase speed }
    Then, when level designing, add this same script to both types of pickups, and simply turn on the option you want in the inspector.


    This also works for making a re-usable door at the end of each level to move the Player to the next level. The door script can include a public string variable for the name of the next level, which can be manually entered in the Inspector:

          public string NextLevel;

          public void OnTriggerEnter2D(Collider2D other){
                if (other.gameObject.tag == "Player"){
                      SceneManager.LoadScene (NextLevel);
                }
          }




    Want functionality to be specific to the level you are currently playing?:
    Step 1: Add the Scene Management namespace to the top of the script:
                using UnityEngine.SceneManagement;

    Step 2: Add a string variable to hold the current scene name:
                private string thisScene;

    Step 3: Get the current scene name in the Start() function:
                thisScene = SceneManager.GetActiveScene();

    Step 4: Use this name as a condition in other functions:
                if (thisScene == "MainMenu"){ //do the scene-specific thing for MainMenu }

         



    Tutorial by Jason Wiser, Madwomb.com.
    The instructional content of this page is © 2015-2024 Jason Wiser.
    See more Unity tutorials at http://www.Madwomb.com/Unity