C# Basics

Unity Logo

Are we recording?

Meow Cam

Review

What is a prefab?

  • A reusable asset that contains a GameObject with all its children, components and properties
  • Can be referenced from a script as a GameObject

Review

What is the Event Loop?

  • A set of functions executed over a scripts lifetime in a predetermined order by the engine
  • The most important functions from it are Start and Update

Review

How do we expose member variables to the Editor?

  • Making them public
  • With the SerializeField attribute

Reference and Value types

  • A variable of a value type(struct) contains an instance of the type
  • A variable of a reference type(class) contains a reference to an instance of the type

Built-in types

  • Value types
    • bool
    • char (16 bit UTF-16)
    • byte/sbyte
    • short/ushort
    • int/uint
    • long/ulong
    • decimal/double/float
  • Reference types
    • object
    • string (immutable)

ref and out

  • By default C# uses pass by value semantics
    • transform.position.x = 5;
  • ref and out provide pass by reference semantics
    • ref requires the passed variable to be initialized
    • out does NOT require the passed variable to be initialized
    • out requires the passed variable to be assigned in the methods scope

Using ref and out

						
	private void Foo(ref int a, out float b) {
		b = a;
	}
	//...
	void Start() {
		int a = 10;

		// This
		float b;
		Foo(ref a, out b);
		// Is the same as
		Foo(ref a, out float c);
	}
						
					

Assembly

  • The compiled output of your code, typically a .dll, but an .exe is also an assembly
  • Typically contains MSIL(Microsoft Intermediate Language) code
  • Will be JIT(Just-In-Time) compiled to native code the first time it is executed
  • In Unity compiled scripts by default go to "Project Name"\Temp\bin\Debug\Assembly-CSharp.dll

Inheritance

Inherit or override funtionality from a specified base class.

In C# you can:

  • Inherit from multiple interfaces
  • Inherit from only one class

Interfaces

Provide a contract. Any implementing class or struct must adhere to the contract.

						
	interface ISomeInterface {
		void ImplementMe();
	}
	//...
	class Implementer : ISomeInterface {
		void ImplementMe() 
			// Method implementation
		}
		static void Main() {
			ISomeInterface foo = new Implementer();
			foo.ImplementMe();
		}
	}
						
					

Access modifiers

  • public
    • default for interface and enum members
  • private
    • default for methods and class members
  • protected
  • internal
    • default for classes
    • can be accessed by any code in the same assembly, but not from another assembly

Properties

Provide convenient encapsulation

						
	class MyClass {
		private string myField;        // field

		public string myProperty {     // property 
			get { return myField;  }
			set { myField = value; }
		}

		public string myAutoProperty { // property
			get; 
			private set; 
		}
	}
						
					

Namespaces

A namespace provides a way to keep one set of names separate from another. The class names declared in one namespace do not conflict with the same class names declared in another.

using

						
	// Include namespaces
	using System;

	// Use static methods without a type name
	using static UnityEngine.Mathf;

	// Type aliasing
	using MyType = Some.Other.Type;	
						
					

Generics

"Code templates" that allows developers to define type-safe classes and methods without committing to an actual data type.

						
	class MyClass<T> {
		private T mymember;
		//...
		public void Foo<U>(T classGenericType, 
		                   U methodGenericType) {
			//...
		}
	}
						
					

where

Add constraints on generic type parameters

						
	class MyClass<T> where T : class, 
	                           IComparable<T>, 
	                           new() {
		// Not possible without the new() constraint
		T item = new T();
	}
						
					

Extension methods

Add methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Useful when you don't have the source code for the type.

						
public static class Extender {
	public static void Reset(this Transform transform) {
		transform.position = Vector3.zero;
	}
}
						
					

String interpolation

						
	int a = 1, b = 2;
	// Instead of this
	print("a = " + a + "; b = " + b + ";");
	// You can do this
	print($"a = {a}; b = {b};");
						
					

Data Structures

C# C++ Java
Dynamic Array List<> vector<> ArrayList<>
Linked List LinkedList<> list<> LinkedList<>
Hash Set HashSet<> unordered_set<> HashSet<>
Hash Map Dictionary<> unordered_map<> HashMap<>
Tree SortedSet<> set<> TreeSet<>

var

Lets the compiler determine the type for you

						
	// Explicit typing
	Dictionary<int, int> dict = new Dictionary<int,int>();
	// Implicit typing
	var dict = new Dictionary<int, int>();
						
					

Object Initializers

Instantiate an object and perform member assignments in a single statement

						
		MyClass obj = new MyClass(arg, ...) { 
			memberVar = data, 
			... 
		};
		// Anonymous type
		var obj = new { 
			memberVar = data, 
			... 
		};
						
					

Collection Initializers

Like object initializers but for collections

						
	//Has to implement IEnumerable and an Add function
	List<int> list = new List<int> { 0, 1, 2 };
						
					

foreach

						
	int[] arr = new int[] { 3, 14, 15, 92, 6 };

	// This
	for (int i = 0; i < arr.Length; ++i) {
		print(arr[i]);
	}
	// Is the same as
	foreach (var number in arr) {
		print(number);
	}
						
					

Reflection

Reflection provides type info objects for everything in the language. It can, for example, be used to invoke private methods.

						
	using System.Reflection;
	//...
	MethodInfo updateMethod = typeof(MyClass)
		.GetMethod("Update", 
		 BindingFlags.NonPublic | BindingFlags.Instance);

	if (updateMethod != null) {
		updateMethod.Invoke(this, null);
	}
						
					

Attributes

Provide metadata for code

						
[SerializeField]
private float speed = 5;
//...
FieldInfo speedField = typeof(MyClass)
	.GetField("speed", 
	 BindingFlags.NonPublic | BindingFlags.Instance);
if (speedField.GetCustomAttribute<SerializeField>() != null) {
	speedField.SetValue(this, 6);
	print($"The new values for speed is {speed}");
}
						
					

Most Useful Attributes

  • [System.Serializable]
    • Displays the whole objects in the inspector
  • [SerializeField]
    • Exposes a field to the inspector
  • [RequireComponent(typeof(SomeComponent))]
    • Requires the gameObject to have that component
  • [Range(float min, float max)]
    • Clamps a float between 2 values in the inspector

Delegates

  • Similar to a function pointer in C/C++
  • Reference type variable that can hold a reference to several methods
  • Primary used for communication between scripts
  • Syntax:
    • delegate <returnType> <name> (<parameters>);

Built-in delegates

						
	using System;
	//...
	Func<in argument, ..., out result> myFunction;

	// For void methods
	Action<in argument, ...> my Action;
						
					

Script Communication Problem

  • Say you have the following situation:
    • You have a player script with health
    • A UI script that controlls the healthbar
    • An AI script that will agro when the player is on low health

Naive Solution

						
		// In the Player script
		void ReceiveDamage(int damage) {
			health = Max(health - damage, 0);
			uiScript.ScaleHealthbar(health);
			aiScript.TestAgro(health)
		}
						
					

Multicast Delegate Solution

						
	// In the Player script
	public static Action<int> OnTakeDamage;
	if (OnTakeDamage != null) {
		OnTakeDamage();
	}

	// In the UI script
	private void ScaleHealthbar(int health) {...}
	Player.OnTakeDamage += ScaleHealthbar;

	// In the AI script
	private void TestAgro(int playerHealth) {...}
	Player.OnTakeDamage += TestAgro;
						
					

Important!

						
	// Always unsubscribe from the delegate when disabled
	// This will prevent memory leaks
	private void OnEnable() {
		Player.OnTakeDamage += ScaleHealthbar;
	}

	private void OnDisable() {
		Player.OnTakeDamage -= ScaleHealthbar;
	}
						
					

Events

Multicast delegates that restrict outside classes to only be able to subscribe and unsubscribe from the delegate

						
	// Syntax
	public static event Action<int> OnTakeDamage;
						
					

yield

Execute your functions in parts by wraping them in an iterator

						
	IEnumerable<int> GetCollection() {
		List<int> collection = new List<int>();
		for (int i = 0; i < 10; ++i) {
			collection.Add(i);
		}
		return collection;
	}
	// Omits the collection declaration
	IEnumerable<int> GetFancyCollection() {
		for (int i = 0; i < 10; ++i) {
			yield return i;
		}
	}
						
					

Coroutines

Regular functions execute within a single frame. Coroutines are special functions that can execute over several frames.

						
	void Start() {
		StartCoroutine(MyCoroutine());
	}

	IEnumerator MyCoroutine() {
		while (true) {
			//...
			yield return new WaitForSeconds(3);
		}
	}
						
					

Main Coroutine Functions/Keywords

						
Coroutine c = StartCoroutine(...); // Starts the coroutine
StopCoroutine(c); // Stops the coroutine
yield return null; // Pauses until next frame
yield return new WaitForSeconds(x) // Pauses for x seconds
yield break; // Exits the coroutine
						
					

Questions?

Question Cat