Naming Conventions

I couldn't find anything online about agreed standards for naming objects/assets/etc in Unity projects.

Here are my arbitrary guidelines.

"Scene<name>"
- Each scene name has "Scene" prefix.
- Use the same name that you use for this scene (menu/level/room/etc) in your documentation/notes.

"SceneController"
- An empty GameObject at the root level of a scene hierarchy.
- Contains the controller script for that scene ("Controller<name>").
- Maximum 1 per scene.

"Controller<name>"
- A script that contains all the global controller code for a single scene.
- Each controller script has "Controller" prefix.
- Use the same name as the scene.
- All scene controller scripts are in the custom "Assets/Controllers" folder.

Assets:
- There shouldn't be any files floating at the root level of the "Assets" folder. (Except for technical things like "link.xml")

Assets/Resources:
- Camel case names, such as "configPlayers.txt".
- Start name with the category/purpose of the file, followed by the specifics.
- Assets/Resources/Prefabs: prefabs that can be found and loaded from Scripts

Assets/Scripts:
- Custom folder to contain all scripts.
- Assets/Scripts/Controllers: for all controllers
- Assets/Scripts/Global: for Singletons and ScriptableObjects - anything global that does not need to be attached to GameObjects
- Assets/Scripts/Utilities: for small, reusable behaviors

Assets/Sprites:
- Custom folder to contain all sprites.


System Variables

Screen


int width = Screen.width; //width of application window
int height = Screen.height; //height of application window
Resolution resolution = Screen.currentResolution
bool isFullScreen = Screen.fullScreen; //is application in full screen mode?

Data Path


string path = Application.persistentDataPath;

On Windows the path is:
C:\Users\<username>\AppData\LocalLow\<company name>\<app name>

On OSX the path is:
Users/<username>/Library/Application Support/<company name>/<app name>

On Linux the path is (from forums, not Microsoft):
[LinuxHome]/unity3d/<Company>/<ProjectName>

Project Settings

General

Edit menu > Project Settings > Player tab
- set Company Name
- set Product Name
- set Version Number
- set Default Icon
- set Default Cursor
- under Other Settings > Configuration > Scripting Runtime Version: this sets what .Net framework is required to run the game.

Capture Single Screen: if true, application will not darken secondary monitors when running in full screen mode.

Display Resolution Dialog: if enabled, there will be a resolution selection screen before the application starts.

Use Player Log: enable logging of debugging information. Must be disabled to publish to the MAC store.

2D Games

1. Edit menu > Project Settings > Editor tab > Default Behavior Mode > select "2D"
* Selecting "2D Template" when creating new project will do this, also
* By default, textures will be imported as sprites
* By default, the scene view will be in 2D mode

Debugging

To enable breakpoints in Visual Studio when running Unity code:
Edit menu > Preferences > External Tools tab > check Editor Attaching
Adding References

Core .Net

To include assemblies in your Unity project:

1. Create a text file named "mcs.rsp" in the project's "Assets" directory.
2. Add lines to the text file like "-r:System.Net.Http.dll". This example includes the assembly "System.Net.Http".
3. Restart the Unity editor after any change to this file.
4. You'll still need the "using System.Net.Http;" line in the C# file.

NuGet

To include a NuGet package:

1. Browse NuGet and download the package you want.
2. Open the download location and rename the *.nupkg file to *.zip.
3. Within the zip file, find the *.dll (such as lib/netstandard2.0/Newtonsoft.Json.dll) and copy it.
4. Add folder "Plugins" to the project's "Assets" folder. This is a special folder name in Unity.
5. Paste the *.dll file into the "Plugins" folder.
6. Create text file "link.xml" in the project's "Assets" folder. This will fix reflection errors.
- link.xml contents:

    <linker>
      <assembly fullname="System.Core">
        <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
      </assembly>
    </linker>
7. Give focus to Unity program and let it think through those changes.
8. Add the "using Newtonsoft.Json;" line to your C# file.

Any dependencies the package have must be added manually in the same way.

Resources

General

Resources are things like text files that you want installed with the game.

Create a "Resources" folder under the project's "Assets" folder. This is a special folder name recognized by Unity.

Everything in a "Resources" folder will be included in the build, whether or not it is used in the game.

To load the resource:

//loading xml example
TextAsset xml = Resources.Load<TextAsset>(filename);
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml.text);

"Resources.Load" searches the "Resources" folder.

You may have more than one folder named "Resources" somewhere under the "Assets" folder, such as "Assets/Resources" and "Assets/Level4/Resources".
If you use multiple folders, every file name across all those folders must be unique because they are all searched automatically. You cannot specify which Resources folder to search.

You can create folder structures under the "Resources" folder. Files can then be loaded just be filename or by the path within the "Resources" folder.

Extensions are omitted when searching for resource files.

Paths cannot use backslash characters.

TextAsset


TextAsset asset = Resources.Load<TextAsset>(filename);

Valid extensions are .txt, .html, .htm, .xml, .bytes, .json, .csv, .yaml, .fnt

Resources.Load will not recognize .ini files at all.

Texture


Texture2D asset = Resources.Load<Texture2D>(filename);

Sprite


Sprite asset = Resources.Load<Sprite>(filename);

AudioClip


AudioClip asset = Resources.Load<AudioClip>(filename);

Project

The project pane shows a directory listing of all the project files, with detailed contents of the selected folder on the side.

Adding files to the project in Windows Explorer automatically adds them to the project in Unity.

The slider on the bottom-right of the pane changes the detail view icon size. Increase the size to make longer file names visible.
Scene

The scene pane shows the current scene you are working on.

A scene is general a game level or a menu.
Scenes are saved under "Assets/Scenes".

You can drag assets around the scene.

If you select the "hand" icon in the upper-left of the screen, you can drag the scene around to look at different parts of it.

Zoom Out: Alt + Right-Click and drag mouse upward
Zoom In: Alt + Right-Click and drag mouse downward

Above the scene are the "Play/Stop", "Pause", and something else buttons. Use these to test the scene.

First

File menu > Build Settings

The first scene in the list of included scenes will be where the game starts.

New

To add a new scene:
1. Go to "Assets/Scenes" in the Project Pane
2. Right-click and add a new Scene
3. Name the scene

4. Double-click the scene to open it in the Hierarchy Pane
5. File menu > Build Settings > click "Add Open Scenes" to include this scene in the build

6. In the Hierarchy Pane, add your first UI object
- this will automatically create the "Canvas" object and the "EventSystem" object

Hierarchy

The hierarchy pane looks like it shows all the assets in the project.

Double-clicking on an asset will center it in your view of the Scene.
Inspector

The inspector pane will show and edit details of the selected asset.

At the very top is the name of the object or asset.

Position, depth, and dimensions are at the top.
Also at the top is an image with a red cross-hair. Click that to set how the asset is anchored to the view. The position and dimensions are relative to the anchor point.

At the bottom is the "Add Component" button.
Add Component > Layout > some selection - can be used to set a lot of default behavior.
Console

Display the Console pane with Window menu > General > Console.

You can display debug messages to the Console pane from your scripts:

Debug.Log(string);
Scripts

Scripts contain C# code.

If you rename a script in Unity:
- Links to script in Unity are updated.
- The name of the class in the C# code is not updated. This must be manually updated to match the name of the script or you'll get errors in Unity.

Methods

Load a new scene:

using UnityEngine.SceneManagement;
//...
SceneManager.LoadSceneAsync("SceneName", LoadSceneMode.Single);

Exit application:

Application.Quit();
This is ignored running within Unity, but works on "Build and Run".

Links

You can link GameObjects and Assets to any public non-static field in a script.
The script must inherit from MonoBehavior or ScriptableObject.

Once the script is written, look at it in the Inspector pane. Your public non-static fields will show up.
You can link their values to GameObjects or Assets.

If make a link of (ex) type "Image", and you drag-n-drop any element that has an Image component on it to the link, it will be the Image component of that element that you are linking to.
So if there is only one component of a GUI element that you need access to, use that as the data type.
Events

UnityEvent

Publisher

using UnityEngine;
using UnityEngine.Events;

public MyPublisher : MonoBehavior
{
    public UnityEvent MyCustomEvent;
    
    public void Start()
    {
        if(MyCustomEvent == null)
            MyCustomEvent = new UnityEvent();    
    }
    
    private void Something()
    {
        MyCustomEvent.Invoke();
    }
}

Subscriber

using UnityEngine;
using UnityEngine.Events;

public MySubscriber : MonoBehavior
{
    public void Something()
    {
        myPublisher.MyCustomEvent.AddListener(OnCustomEvent);
    }
    
    public void OnCustomEvent()
    {
        Debug.Log("Subscriber: Custom Event");
    }
}

UnityEvent can have 0 to 4 arguments. Ex: UnityEvent<int, string> has two arguments.

public class MyCustomEvent : UnityEvent<int>
{
}

public class MyPublisher : MonoBehavior
{
    public MyCustomEvent EventA;
    
    public void Start()
    {
        if(EventA == null)
            EventA = new MyCustomEvent();    
    }
    
    private void Something()
    {
        EventA.Invoke(0);
    }
}

UnityAction

UnityAction is a pointer to a function with 0-4 parameters.
You can pass functions as parameters with UnityAction.

UnityAction
UnityAction<T>
UnityAction<T,U>
UnityAction<T,U,V>
UnityAction<T,U,V,W>

UnityAction can be extended (derived from).

Mouse Clicks

OnMouseUpAsButton: mouse released over same GUIElement or Collider as it was pressed down over.

OnMouseDown: mouse pressed down over this element.
OnMouseUp: mouse released over this element, regardless of where the mouse was pressed down.

OnMouseEnter
OnMouseExit

OnMouseDrag

Button elements support OnClick, through the Inspector pane.

To add custom events to GUI elements:
1)

//Mouse Click Example
using UnityEngine;
using UnityEngine.EventSystems;

public class ControllerElementOption : MonoBehaviour
{
    public void OnPointerClick(BaseEventData eventData)
    {
        Debug.Log("OnPointerClick");

        if(eventData is PointerEventData)
        {
            PointerEventData pointerEventData = (eventData as PointerEventData);

            if(pointerEventData.button == PointerEventData.InputButton.Left)
                Debug.Log("Left click");
            else if(pointerEventData.button == PointerEventData.InputButton.Middle)
                Debug.Log("Middle click");
            else if(pointerEventData.button == PointerEventData.InputButton.Right)
                Debug.Log("Right click");
        }
    }
}
2) Add this controller to the scene.
3) For the element you want to click, open Inspector pane and Add Component > Event > Event Trigger
4) On the component, select the Pointer Click event type
5) On the component, select the element with the controller on it, then the OnPointerClick function

Global Messaging System

EventManager.cs

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

public class EventManager : Singleton<EventManager>
{
    private Dictionary<string, UnityEvent> eventDictionary = new Dictionary<string, UnityEvent>();

    protected EventManager() { } //protect constructor

    public static void StartListening(string eventName, UnityAction listener)
    {
        UnityEvent thisEvent = null;
        if(Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
        {
            thisEvent.AddListener(listener);
        }
        else
        {
            thisEvent = new UnityEvent();
            thisEvent.AddListener(listener);
            Instance.eventDictionary.Add(eventName, thisEvent);
        }
    }

    public static void StopListening(string eventName, UnityAction listener)
    {
        UnityEvent thisEvent = null;
        if(Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
        {
            thisEvent.RemoveListener(listener);
        }
    }

    public static void TriggerEvent(string eventName)
    {
        UnityEvent thisEvent = null;
        if(Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
        {
            thisEvent.Invoke();
        }
    }
}

Subscribe listener

using UnityEngine;
using UnityEngine.Events;

public class MyClass : MonoBehaviour
{
    private UnityAction listenerCustomEvent;
    
    public void Awake()
    {
        listenerCustomEvent = new UnityAction(OnCustomEvent);
    }

    public void OnEnable()
    {
        EventManager.StartListening("customEventName", listenerCustomEvent);
    }

    public void OnDisable()
    {
        EventManager.StopListening("customEventName", listenerCustomEvent);
    }
    
    public void OnCustomEvent()
    {
        Debug.Log("Listener: Custom Event");
    }
}

Trigger event

//...
EventManager.TriggerEvent("customEventName");
Windowed

Unity starts the application by asking the user to select what size window they want to use.
- can I turn that off?


Resolution x = Screen.CurrentResolution; //get resolution of window
Resolution[] y = Screen.resolutions; //list of resolutions supported by the monitor, smallest to largest (may not include all options)

Is application full screen?

bool isFullScreen = Screen.fullScreen;
You can also set this value to change it.

Set window size manually:

Screen.SetResolution(width, height, boolFullScreen);
Screen.SetResolution(width, height, FullScreenMode);
//preferredRefreshRate is an optional parameter
FullScreenMode:
- ExclusiveFullScreen: Unity will change the monitor's resolution and claim control of it.
- FullScreenWindow: A normal full screen window, at the monitor's resolution. OS UI can display over the window.
- MaximizedWindow: A normal window that is windowed but sized to fill the screen.
- Windowed: A normal window that is windowed and sized smaller than the screen.

Sprites

Sprites are an Asset type.
Sprites are 2D textures.

Add sprites to your project by simply copying them into the Asset folder (or a subdirectory of it).

Or right-clicking in the Project Detail pane > Import New Asset.
If the project is in 2D mode, all images are automatically imported as sprites.

Edit menu > Project Settings > Graphics > Transparency Sort Mode
This will determine which sprites are rendered on top of each other. For 2D games, this is commonly based on the y-coordinate.

Graphic Sources

[https://pixabay.com]
The Pixabay license is "Free for commercial use, no attribution required".

Sprite Atlas

An atlas is one image that contains multiple sprite images, separated by transparent space. The atlas will be unpacked into its individual pieces.

Unity has a utility to automatically pack these images together as efficiently as possible.

Asset menu > Create > Sprite Atlas
This will create a *.spriteatlas file.

This seems to be intended for (example) a character made up of many different sprites, all with unusual shapes.

9-Slicing

How to set a background on a button/menu/etc that will resize smartly, without strange distortion.

1. Make your basic button sprite
2. Import the sprite
3. Select sprite > Inspector pane
- set "Mesh Type" to "Full Rectangle"
4. Click "Sprite Editor" > Resize the border lines that start at the edges of the image
- drag-n-drop the green border lines, or specify the L/R/T/B values (measurements are inward from each edge)
- this will divide the image into 9 regions
- when the image is resized, the corners will not be changed, only the other 5 sections will be stretched
- you'll only see this in effect on UI objects
5. Click "Apply" to save changes
6. Set the sprite as the Image on a button
- set "Image Type" to "Sliced" OR set "Draw Mode" to "Sliced"

You can also set "Draw Mode" to "Tiled", which will cause the 5 center segments of the sprite to be tiled instead of stretched.

File Formats

Supported formats: bmp, exr, gif, hdr, iff, jpg, pict, png, psd, tga, tiff

As of version 2018.2, Unity supports *.svg

Window menu > Package Manager > All button > select Vector Graphics > Install button

Now you can drag-n-drop *.svg files into the Project pane, or right-click > Import New Asset.

Update: I can't find anyone saying Vector Graphics is not available to Unity Personal, but I also can't find it to install in Package Manager.
Layouts

Layout components can be added to GameObjects.

Grid Layout

Cell size must be specified exactly. The only way I've seen to auto-resize the cells to fit their container is through scripts.

Consider using nested Vertical Layout and Horizontal Layouts instead, which do support auto-resizing.

Script to auto-resize grid:

using UnityEngine;
using UnityEngine.UI;

public AutoSizeGrid : MonoBehavior
{
    public int RowCount;
    public int ColCount;

    public void Start()
    {
        RectTransform container = gameObject.GetComponent<RectTransform>();
        GridLayoutGroup gridLayout = gameObject.GetComponent<GridLayoutGroup>();
        gridLayout.cellSize = new Vector2(container.rect.width / ColCount, container.rect.height / RowCount);
    }
}
Add this script to the GameObject that has a Grid Layout component.
Basic UI

Event Propagation

Unlike Game Maker Studio, only the top active UI object will take a user interaction. Meaning that if you click on a stack of UI objects, only the top object will react.

Methods

You cannot easily size and position ui elements relative to each other in code.

GameObject

Looks like all UI objects derive from GameObject.

Attributes:

string name = myGameObject.name; //the name of the GameObject as seen in the Hierarchy pane
string tag = myGameObject.tag; //the tag can be set at the top of the Inspector pane

To show/hide UI objects:

myObject.gameObject.SetActive(true);
myObject.gameObject.SetActive(false);

//for GameObject type variables
myGameObject.SetActive(bool);

To loop through all the children of a UI element in script, it's a bit weird but this works:

foreach(Transform child in myGameObject.transform)
{
    Debug.Log("child: " + child.gameObject);
}
"transform" also has attribute "childCount" available.

Persist GameObject across scenes:

public class MyScript : MonoBehaviour
{
    void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}
Add this script to a GameObject to make it persistent.
If you add this to an object on the Canvas, it won't persist unless you also add this to the Canvas itself.
So it's best used for global behaviors.

Panel

There is not an actual "Panel" type object in Unity.
When you add a Panel to the scene, you are adding a GameObject with RectTransform and Image components already added.

So to reference a Panel from a script, use the GameObject data type.

Button

Change button text in script:

myButton.GetComponentInChildren<Text>().text = myText;

InputField

Focus on field and select its text:

myInputField.Select();

Dropdown

GameObject menu > UI > Dropdown

The new dropdown asset will be listed in the Hierarchy pane under "Canvas".

Static list of options:
Set the options in the Inspector pane, under Dropdown > Options.

Dynamic list of options:
Add a script to set the dropdown options.
1. Right-click in Project Pane in Assets > Create > C# Script.
2. The script will appear in the Assets folder.
3. You can rename the script.
4. Double-click the script to open it in Visual Studio.
* Downloading and installing Developer Pack 4.7.1 since it is the default target for this.
* https://dotnet.microsoft.com/download/thank-you/net471-developer-pack
5. Example of loading options:

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

public class OptionsAccounts : MonoBehaviour
{
    private List<string> options = new List<string>(); //options maintained on backend

    public Dropdown dropdown; //will point to the dropdown ui element in the scene

    // Start is called before the first frame update
    public void Start()
    {
        PopulateOptions();
    }

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

    /// <param name="index">0-based index of selected option</param>
    public void OnSelectionChanged(int index)
    {
        Debug.Log(options[index]);
    }

    private void PopulateOptions()
    {
        //todo: load account names from configuration, if possible
        options.Add("Player 1");
        options.Add("Player 2");
        options.Add("Player 3");

        dropdown.ClearOptions();
        dropdown.AddOptions(options);
    }
    
}
6. Back in the Hierarchy pane, create an Empty GameObject and put it in the highest level (even with Canvas). Rename it "SceneController".
7. Drag-n-Drop new script from Project pane into the Inspector pane for the SceneController.
8. Drag-n-Drop new dropdown object from Hierarchy pane to the empty "Dropdown" input in the script in the "SceneController" Inspector pane.
9. In new dropdown's Inspector > Dropdown section > click "+" on the "On Value Changed" input. Select "SceneController". Select Function "new script name" > OnSelectionChanged.
10. Run the scene to see the options are loaded.

If you are using your own code, and you want to do something, say, when the selection changes, it is recommended that you maintain the list of options within the script object rather than relying on looking up in the index in "dropdown.options". Basically the recommendation is to feed display information to the UI but not rely on the UI to store your information.

ScrollView

To use a Layout component (Vertical Layout, Grid Layout, etc) in a ScrollView:
1. Add the X Layout component to the ScrollView.ViewPort.Content object
2. Add a Content Size Fitter component as well, with Vertical set to Preferred Size

When populating the ScrollView, add new objects to the ScrollView.ViewPort.Content object.

Image

To change the coloring of an Image:

Color color = myImage.color;
color.a = 1.0f;
myImage.color = color;

Remain Square

How to keep a panel square, while automatically resizing it to be as large as possible in the window?

1. Create the panel
2. Add component "Layout" > "Aspect Ratio Fitter"
3. Set "Aspect Mode" to "Fit In Parent"
4. Set "Aspect Ratio" to 1 (indicating a square)

Searching

Search through active GameObjects:

GameObject x = GameObject.FindGameObjectWithTag("tag");
GameObject y = GameObject.Find("name");

Removing Children

Detach all children from parent:

myObject.transform.DetachChildren();

Detach children from parent with loop:

//NOT SURE THIS WORKS - was an infinite loop in testing
while(myObject.transform.childCount > 0)
{
    Transform child = myObject.transform.GetChild(0);
    child.parent = null;
} 

Destroy child:

Destroy(child);
Popup

EditorWindow

Note that EditorWindows cannot be modal - the user can still interact with the rest of the window.

There also is not a built in way to return values from EditorWindows.

I think these aren't supposed to be used for anything except editing, like, settings.

Example of custom popup:

using UnityEngine;
using UnityEditor;

public class EditTextPopup : EditorWindow
{
    private static readonly int popupWidth = 200;
    private static readonly int popupHeight = 150;

    protected string defaultText = "";

    public static void Init(string defaultText = "")
    {
        EditTextPopup window = ScriptableObject.CreateInstance<EditTextPopup>();
        window.defaultText = defaultText;
        window.position = new Rect((Screen.width + popupWidth) / 2, (Screen.height + popupHeight) / 2, popupWidth, popupHeight);
        window.ShowPopup();
    }

    public void OnGUI()
    {
        EditorGUILayout.LabelField("Edit slot name:", EditorStyles.wordWrappedLabel);
        EditorGUILayout.TextField(defaultText);

        GUILayout.Space(30);

        GUILayout.BeginHorizontal();
        if(GUILayout.Button("Save"))
            this.Close();
        if(GUILayout.Button("Cancel"))
            this.Close();
        GUILayout.EndHorizontal();
    }
}

Calling the popup from an event:

EditTextPopup.Init("default text");
Nothing is else is required here. The popup auto-closes when the button is clicked.

Modal Dialog

[Video: Making a Generic Modal Window with Adam Buckner, Part 1 of 3]

1. Add Panel UI object to scene. It should auto-fill the scene.
- let's call it "PanelModal"
- give it a grey color and about 50% alpha
- this panel will block interaction with the rest of the window
- once design is complete, we'll default this to "Inactive" so it is not visible when the game starts

2. Add, nested in "PanelModal", another panel that will hold text and buttons.
- let's call it "PanelDialog"
- make it smaller and centered in the "PanelModal"
- give it a background image

3. Add, nested in "PanelDialog", another panel that will hold text.
- let's call it "PanelText"
- make it fill the top half of "PanelDialog" and anchor it to the top-center of "PanelDialog"
- make sure it has no background image and give it 0% alpha
- "Add Component" > "Layout" > "Horizontal Layout Group"
- give the layout some space between elements and align them to upper-left and turn off force-expand-children

4. Add, nested in "PanelText", an Image UI object (this will hold an icon)
- make sure it has 100% alpha
- "Add Component" > "Layout" > "Layout Element"
- set min-width and preferred-width to 64, same for height settings

5. Add, nested in "PanelText", a Text UI object (this will hold the question)
- "Add Component" > "Layout" > "Layout Element"
- set min-width and min-height to 0
- "Add Component" > search for "Shadow" > "Shadow"
- position shadow at (1, -1) to improve legibility
- color shadow white with 50% alpha

6. Add, nested in "PanelDialog", another panel that will hold buttons
- let's call it "PanelButtons"
- make it fill the bottom half of "PanelDialog" and anchor it to the bottom-center of "PanelDialog"
- make sure it has no background image and give it 0% alpha
- "Add Component" > "Layout" > "Horizontal Layout Group"
- give the layout some space between elements and align them to middle and leave on force-expand-children

7. Add, nested in "PanelButtons", a Button UI object
- let's call it "ButtonYes" and set button text to "Yes"
- (once you get the button looking as you like, you can drag-n-drop it from Hierarchy Pane to a "Prefabs" folder in the Project Pane, so it can be reused as a template)

8. Add, nested in "PanelButtons", a duplicate of "ButtonYes"
- let's call it "ButtonNo" and set button text to "No"

9. Add, nested in "PanelButtons", a duplicate of "ButtonYes"
- let's call it "ButtonCancel" and set button text to "Cancel"

10. Create a C# script
- let's call it "BringToFront"

using UnityEngine;
using System.Collections;

public class BringToFront : MonoBehaviour
{
    void OnEnable()
    {
        transform.SetAsLastSibling();
    }
}
- this will make the current object the last child in the Hierarchy, meaning it will be on top of everything else

11. Drag-n-drop "BringToFront" from the Project Pane onto "PanelModal" in the Hierarchy Pane
- now, every time "PanelModal" is activated/enabled, it will appear on top of everything else

12. Create a C# script
- let's call it "ControllerModal"

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System.Collections;

//This script will be updated in Part 2 of this series.
public class ControllerModal : MonoBehaviour
{
    public Text question;
    public Image iconImage;
    public Button yesButton;
    public Button noButton;
    public Button cancelButton;
    public GameObject modalPanelObject;

    private static ControllerModal modalPanel;

    //Singleton pattern
    public static ControllerModal Instance()
    {
        if (!modalPanel)
        {
            modalPanel = FindObjectOfType(typeof(ControllerModal)) as ControllerModal;
            if (!modalPanel)
                Debug.LogError("There needs to be one active ControllerModal script on a GameObject in your scene.");
        }

        return modalPanel;
    }

    //configure dialog
    public void Choice(string question, UnityAction yesEvent, UnityAction noEvent, UnityAction cancelEvent)
    {
        modalPanelObject.SetActive(true);

        yesButton.onClick.RemoveAllListeners();
        yesButton.onClick.AddListener(yesEvent);
        yesButton.onClick.AddListener(ClosePanel);

        noButton.onClick.RemoveAllListeners();
        noButton.onClick.AddListener(noEvent);
        noButton.onClick.AddListener(ClosePanel);

        cancelButton.onClick.RemoveAllListeners();
        cancelButton.onClick.AddListener(cancelEvent);
        cancelButton.onClick.AddListener(ClosePanel);

        this.question.text = question;

        //because we set up horizontal layouts, making some images/buttons inactive will cause the layout to auto-adjust
        this.iconImage.gameObject.SetActive(false); //haven't enabled setting an icon yet, so hide it
        yesButton.gameObject.SetActive(true);
        noButton.gameObject.SetActive(true);
        cancelButton.gameObject.SetActive(true);
    }

    void ClosePanel()
    {
        modalPanelObject.SetActive(false);
    }
}
- example of calling this modal

ControllerModal modalPanel = ControllerModal.Instance();
UnityAction yesAction = new UnityAction(OnYes);
//and so on...
modalPanel.Choice("My question", yesAction, noAction, cancelAction);
//...
void OnYes()
{
    Debug.Log("Yes was clicked");
}

13. Connect the scripts to the scene
- add "ControllerModal" to the scene's controller GameObject (I'm calling it "SceneController")
- set all the public UI objects that "ControllerModal" expects to the matching objects within "PanelModal"

14. Inactivate "PanelModal"
- when you're done with design, select "PanelModal" and uncheck the box to the left of the object name in the Inspector Pane
- this will default the "PanelModal" to inactive so it is not visible when the game starts

[Video: Making a Generic Modal Window with Adam Buckner, Part 2 of 3]

1. Making panel invisible
- so far, we've just turned the panel's image alpha to 0%
- you could also remove the "Canvas Renderer" and "Image" components from the panel
- you could also use an empty GameObject, provided it has the "RectTransform" component and a layout component

2. UnityAction argument
- you can just send a function name instead of making an explicit UnityAction variable first

modalPanel.Choice("My question", OnYes, OnNo, OnCancel);

3. Create C# script called "DeactivateMe"

using UnityEngine;
using System.Collections;

public class DeactivateMe : MonoBehaviour 
{
    void Awake()
    {
        gameObject.SetActive(false);
    }
}
- add this script to "PanelModal" to ensure it starts off inactive

Todo: continue from minute 28, he's talking about lambdas and transforms



Prefabs

Prefabs are GameObjects (with attached Components and Scripts) that are saved as templates under the Assets folder.

You can instantiate objects from Prefabs at runtime.

To turn an object on the Canvas into a Prefab, simply drag it to anywhere under "Assets" in the Project pane.
Objects on the Canvas that are from Prefabs will display in blue in the Hierarchy pane.

You can click "Open Prefab" in the Inspector pane to view and edit the details of the Prefab.

Find prefab from script:

//searching a "Resources/Prefabs" folder
GameObject prefab = (GameObject)Resources.Load("Prefabs/myPrefab", typeof(GameObject));
(This is slower than using a prefab that was linked to the script through the Inspector.)

Instantiate prefab from script:

using UnityEngine;

public class MyScript : MonoBehaviour
{
    public Transform myPrefab; //attach the prefab to this field in the Inspector pane

    public void MyMethod()
    {
        //instantiate object at root of Scene (outside the Canvas)
        Transform myObject = Instantiate(myPrefab);
        
        //OR instantiate object on the Canvas
        Transform modalObject = Instantiate(prefabModal, Vector3.zero, Quaternion.identity, GameObject.Find("Canvas").transform);
        
        //call method from script attached to object
        MyScriptB myScriptB = myObject.gameObject.GetComponent<MyScriptB>()
        myScriptB.CustomMethod();
    }
}    

Singleton

Unity does not have built-in support for Singleton Pattern.

Usage example:

using UnityEngine;

public MyClass : Singleton<MyClass>
{
    // (Optional) Prevent non-singleton constructor use.
    protected MyClass() { }
}

[Code copied from Unity wiki]

Singleton.cs

using UnityEngine;

/// <summary>
/// Inherit from this base class to create a singleton.
/// e.g. public class MyClassName : Singleton<MyClassName> {}
/// </summary>
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    // Check to see if we're about to be destroyed.
    private static bool m_ShuttingDown = false;
    private static object m_Lock = new object();
    private static T m_Instance;

    /// <summary>
    /// Access singleton instance through this propriety.
    /// </summary>
    public static T Instance
    {
        get
        {
            if (m_ShuttingDown)
            {
                Debug.LogWarning("[Singleton] Instance '" + typeof(T) + "' already destroyed. Returning null.");
                return null;
            }

            lock (m_Lock)
            {
                if (m_Instance == null)
                {
                    // Search for existing instance.
                    m_Instance = (T)FindObjectOfType(typeof(T));

                    // Create new instance if one doesn't already exist.
                    if (m_Instance == null)
                    {
                        // Need to create a new GameObject to attach the singleton to.
                        var singletonObject = new GameObject();
                        m_Instance = singletonObject.AddComponent<T>();
                        singletonObject.name = typeof(T).ToString() + " (Singleton)";

                        // Make instance persistent.
                        DontDestroyOnLoad(singletonObject);
                    }
                }

                return m_Instance;
            }
        }
    }


    private void OnApplicationQuit()
    {
        m_ShuttingDown = true;
    }


    private void OnDestroy()
    {
        m_ShuttingDown = true;
    }
}

GetOrAddComponent.cs

using UnityEngine;

static public class UnityEngineExtensions
{
    /// <summary>
    /// Returns the component of Type type. If one doesn't already exist on the GameObject it will be added.
    /// </summary>
    /// <typeparam name="T">The type of Component to return.</typeparam>
    /// <param name="gameObject">The GameObject this Component is attached to.</param>
    /// <returns>Component</returns>
    static public T GetOrAddComponent<T>(this GameObject gameObject) where T : Component
    {
        return gameObject.GetComponent<T>() ?? gameObject.AddComponent<T>();
    }
}
ScriptableObjects

Scriptable objects do not need to be added to GameObjects in a scene, they are simply Assets.

Scriptable objects are serializable objects.
They let you store large quantities of shared data independent of script instances.
Frequently used just as data containers.

Define global variable:

[CreateAssetMenu]
public class Stats : ScriptableObject
{
    public int Strength;
    public int Dexterity;
    public int Intelligence;
}
You can add a "Stats" type field anywhere in the code, and they will all be editing this one shared object.
For instance, one MonoBehavior could update Stats.Strength and another could read it to display in the GUI.

The intended usage of ScriptableObjects is:
1) create the ScriptableObject script
2) create instances of the ScriptableObject with specific data values set
Ex: one "InventoryItem" ScriptableObject, and then instances for "Armor", "Weapon", etc.

Fields

As with normal Scripts, all public non-static fields will be visible in the Inspector pane.

You can also make private non-static fields visible by marking them "serializable".

[SerializeField]
private int goldAmount;

Events

Awake() is called when a ScriptableObject instance is created in a script.

Start() is not used on ScriptableObjects.

OnEnable() is called when the the ScriptableObject is loaded.
This does not seem to work when calling Static methods on the object without creating an instance of it.

Asset Menu

You can configure any ScriptableObject to be listed in the normal Assets > Create menu.


[CreateAssetMenu]
public class MyObject : ScriptableObject
{
}
Now you can easily create instances of this object in your project.


[CreateAssetMenu(fileName = "DefaultFileName", menuName = "AssetMenuLabel", order = 51)]
public class MyObject : ScriptableObject
{
}
fileName: default file name of new instances of object
menuName: how this object is labeled in the Asset > Create menu
order: where in the Asset > Create menu this object will show up
- the normal options are in reserved indexes 0 - 50
GuiSkins
Layers

You can lock layers in place with the "Layers" menu in the upper-right of the screen.
Save Game

Data Class

Create a serializable data class that contains the structure of the data you want to save.

Example:

using System;

[Serializable]
public class CharacterData
{
    public string name;
    public float progress;
    public int gold;
}

PlayerPrefs

PlayerPrefs is a built-in system for saving basic data in key/value pairs.

Probably best used for application level settings.

PlayerPrefs data is saved in the registry and is relatively easy to edit.

Confirmed: you can test persistence of PlayerPrefs when testing game in Unity across multiple "Plays" of game.

Example:

using System;
using UnityEngine;

public class CharacterData
{
    public string name;
    public float progress;
    public int gold;

    public static void Save(CharacterData data, int slot)
    {
        PlayerPrefs.SetString("slot" + slot + "_name", data.name);
        PlayerPrefs.SetFloat("slot" + slot + "_progress", data.progress);
        PlayerPrefs.SetInt("slot" + slot + "_gold", data.gold);
        PlayerPrefs.Save();
    }
    
    public static CharacterData Load(int slot)
    {
        CharacterData data = new CharacterData();
        data.name = PlayerPrefs.GetString("slot" + slot + "_name", defaultName);
        data.progress = PlayerPrefs.GetFloat("slot" + slot + "_progress", defaultProgress);
        data.gold = PlayerPrefs.GetInt("slot" + slot + "_gold", defaultGold);
        return data;
    }
}

JSON

JSON files are easily read and edited by people.

JSON is not suggested for very long strings as it can cause memory management errors.

If you need to save more than basic data types, like if you need to save objects that reference other objects, you should probably use Binary format instead of JSON.

Example:

using System.IO;
using UnityEngine;

public class JsonCharacterSaver
{
    private string dataPath;

    void Start ()
    {
        dataPath = Path.Combine(Application.persistentDataPath, "CharacterData.txt");
    }

    static void Save(CharacterData data)
    {
        string jsonString = JsonUtility.ToJson(data);

        using(StreamWriter streamWriter = File.CreateText(dataPath))
        {
            streamWriter.Write (jsonString);
        }
    }

    static CharacterData Load()
    {
        using (StreamReader streamReader = File.OpenText(dataPath))
        {
            string jsonString = streamReader.ReadToEnd();
            return JsonUtility.FromJson<CharacterData>(jsonString);
        }
    }
}

Binary

It is safe to assume that no user can read or edit a binary file in-place.

Example:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public class BinaryCharacterSaver
{
    private string dataPath;

    void Start ()
    {
        dataPath = Path.Combine(Application.persistentDataPath, "CharacterData.dat");
    }

    static void Save(CharacterData data)
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        using(FileStream fileStream = File.Open(dataPath, FileMode.OpenOrCreate))
        {
            binaryFormatter.Serialize(fileStream, data);
        }
    }

    static CharacterData Load()
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        using(FileStream fileStream = File.Open(dataPath, FileMode.Open))
        {
            return (CharacterData)binaryFormatter.Deserialize(fileStream);
        }
    }
}
Debugging

Break Points

To debug Unity scripts with Visual Studio (Windows):
1. In Visual Studio > Tools menu > Extensions and Updates > install the Visual Studios Tools For Unity plug-in.
2. In Unity, Edit menu > Preferences > External Tools tab > check Editor Attaching.
3. Set a breakpoint in the code open in Visual Studio.
4. In Visual Studio, click "Attach to Unity" along the top.
5. In Unity, click Play.

If you have Visual Studio 2017, this should work without error.

If you have Visual Studio 2015:
1. When you click "Attach to Unity", you'll get a popup about errors in the build, do you want to use the latest build?
2. Click "Yes"
3. You see this error in the errors console:
* Invalid option 'latest' for /langversion; must be ISO-1, ISO-2, Default or an integer in range 1 to 6.
4. Ignore the error and click "Play" in Unity. Debugging will work.

Windowed Vs Full Screen

There does not seem to be any way to test windowed vs full screen functionality when running a normal "Play" test inside the Unity IDE.

Seems you must build the project fully and run it as its own application.
In Unity: File menu > Build and Run > select folder to save build to > Ok

Note the build will be starting with a fresh set of PlayerPrefs.

Log Files

Everything sent to Debug.Log is automatically saved to log file "Editor.log".

macOS: ~/Library/Logs/Unity/Editor.log
Windows: C:\Users\<username>\AppData\Local\Unity\Editor\Editor.log

Steam

How to publish Unity games to Steam.

Enable Steamworks.Net

[How-to video]

[Steamworks.Net]
Download Steamworks.Net, which is a C# wrapper for Valve Steamworks API. Free to use.

Follow the installation instructions, or this:
1. Open your project in Unity
2. Double-click on the downloaded *.unitypackage from the latest release on Github
- you should see a "Steamworks.Net" popup in Unity now
3. Click "Import" on the popup
- adds Assets/Editor/Steamworks.NET
- adds Assets/Plugins/Steamworks.NET, steam_api.bundle, x86, x86_64
- adds Assets/Scripts/Steamworks.NET

4. Look at file <project root>/steam_appid.txt
- it will come with default value "480"
5. Replace the default steam_appid.txt value with the id for your game

6. Create an empty GameObject in your default Scene, call it "SteamManager"
7. Drag-n-drop the script "Assets/Scripts/Steamworks.NET/SteamManager" into the Inspector for "SteamManager"
8. Open the "SteamManager" script in Visual Studio
9. Search for default value "480" to find this line:

if (SteamAPI.RestartAppIfNecessary(AppId_t.Invalid)) {
10. Replace "AppId_t.Invalid" with your Steam app id, the same value as in "steam_appid.txt"

if (SteamAPI.RestartAppIfNecessary((AppId_t)myActualAppId)) {
11. Save changes

(There's another edit in the tutorial. I'll add it if it turns out to be necessary.)

Achievements

[How-to video]

1. Setup your achievements in the Steamworks website
- each achievement will have a unique id set by you AND a unique id set by Steam

2. Open your project in Unity
3. Create an empty GameObject in your default Scene, call it "SteamAchievements"
4. Add your custom achievements script to this GameObject

todo: finish tutorial

Build

[How-to video]

todo: watch tutorial