Notes on Intermediate Gameplay Scripting

Here is the reading notes on Intermediate Gameplay Scripting

Intermediate Gameplay Scripting from Unity Toturials

Porperties

For public variables shared between scripts, a better way is to use properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Example{
public int Money{
get{
return Money;
}
set{
// Directly use the keyword 'value'
Money = value;
}
}
}

public class UseExample{
void Start(){
Example ex = new Example();

ex.Money = 1;
Debug.Log(ex.Money);
}
}

There are two advantages for this.

  1. You can omit either get or set to make the variable readonly or writeonly.
  2. You can regard the accessors as a funciton and do what you want in it.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Example{
public int Money{
get{
return Money;
}
set{
// Directly use the keyword 'value'
Money = value;
}
}

public int Money100{
get{
return Money * 100;
}
}
}

public class UseExample{
void Start(){
Example ex = new Example();

ex.Money = 1;
Debug.Log(ex.Money);
Debug.Log(ex.Money100);
}
}

Ternary Operator

Can replace some simple IF statement.

1
2
int i = 1;
String str = i > 0 ? "i larger than 0" : "i smaller than or equals to 0";

Generics

Generics is a feature where by classes and mothods among other things can be passed the type as a parameter. This in fact allows you to program generically without knowing the actual type.

Method Generics

1
2
3
4
5
public class Example(){
public T GenericsMethod(T param){
return T;
}
}

However, if you call a method of T which doesn’t belong to that type, an error will be given.
Therefore, we need to add some constraints to that type T.
Constraints can be:

  • class: T is a class type
  • struct: T is a value type
  • new(): T has a construct funciton without parameters
  • ClassName: T is that class or derived from that class
  • InterfaceName: T has implements that interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Example(){
public T GenericsMethod(T param) where T : IEnumerable
return param;
}

public class UseExample(){
void Start(){
Example ex = new Example();
List<int> list = new List<int>();

foreach(int i in ex.GenericsMethod<List>(list))
Debug.Log(i);
}
}

Interface

Interface allows you to realize common functionalities among multiple different classes.

An interface con contains signatures of the following members:

  • Properties
  • Methods
  • Indexers
  • Events
    Which means you are not allowed to declare a variable (field) inside the interface.

All signatures inside interface should be realized in the class which implements that interface.

Interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//This is a basic interface with a single required
//method.
public interface IKillable
{
void Kill();
}

//This is a generic interface where T is a placeholder
//for a data type that will be provided by the
//implementing class.
public interface IDamageable<T>
{
void Damage(T damageTaken);
}

Realize Interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Avatar : MonoBehaviour, IKillable, IDamageable<float>
{
//The required method of the IKillable interface
public void Kill()
{
//Do something fun
}

//The required method of the IDamageable interface
public void Damage(float damageTaken)
{
//Do something fun
}
}

Why we use interface instead of polymorhism is that many times you would have two class which are not much related but have some common behaviours. For example, both a wall and a car should be damagable and destroyable. But they should not inherit from the same class. That’s why we use interface to realize the common behaviours.

Another reason for that is in C#, a class can only inherits from one class, but can implement multiple interfaces.

Extension Method

Extension method is used when you want to extent the functionalities of classes which is encapsulated in Unity and code-untouchable. You need to create a static class for that and use a “this” keyword to define the parameter.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static class ExtensionMethods
{
//Even though they are used like normal methods, extension
//methods must be declared static. Notice that the first
//parameter has the 'this' keyword followed by a Transform
//variable. This variable denotes which class the extension
//method becomes a part of.
public static void ResetTransformation(this Transform trans)
{
trans.position = Vector3.zero;
trans.localRotation = Quaternion.identity;
trans.localScale = new Vector3(1, 1, 1);
}
}

public class SomeClass : MonoBehaviour
{
void Start () {
//Notice how you pass no parameter into this
//extension method even though you had one in the
//method declaration. The transform object that
//this method is called from automatically gets
//passed in as the first parameter.
transform.ResetTransformation();
}
}

PS: It is common to create a class to contain all of your extension methods. This class must be static.

Namespace

Namespace is used to avoid ambiguity and conflict between scripts.

Chances are all the scriptsa you are writing so far are using namespaces. When you are using the “using” statement, it means you are using that namespace, and all classes in that namespace can be used on the script.

For example, Random is a class both in System and UnityEngine. To avoid ambiguity, you can use namespace as following format:

1
2
System.Random rd1 = new System.Random();
UnityEngine.Random rd2 = new System.Random();

To create your own namespace, you only need to use “namespace” statement with a brace, and write your class inside.

1
2
3
4
5
6
7
8
9
10
namespace SampleNamespace
{
public class SomeClass : MonoBehaviour
{
void Start ()
{

}
}
}

Quaternion

Quaternion is used to represent rotations in Unity. It’s described by four properties x, y, z and w. However 99% of the time you are not suggested to directly change those values instead of using some useful functions provided by Quaternion.

public void Rotate(Vector3 eulerAngles, Space relativeTo = Space.Self);

1
2
3
4
5
6
7
8
9
public class MotionScript : MonoBehaviour 
{
public float speed = 3f;

void Update ()
{
transform.Translate(-Input.GetAxis("Horizontal") * speed * Time.deltaTime, 0, 0);
}
}

public static Quaternion LookRotation(Vector3 forward, Vector3 upwards = Vector3.up);

1
2
3
4
5
6
7
8
9
10
public class LookAtScript : MonoBehaviour 
{
public Transform target;

void Update ()
{
Vector3 relativePos = target.position - transform.position;
transform.rotation = Quaternion.LookRotation(relativePos);
}
}

public static Quaternion Slerp(Quaternion a, Quaternion b, float t);

Spherically interpolates between a and b by t. The parameter t is clamped to the range [0, 1].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class GravityScript : MonoBehaviour 
{
public Transform target;

void Update ()
{
Vector3 relativePos = (target.position + new Vector3(0, 1.5f, 0)) - transform.position;
Quaternion rotation = Quaternion.LookRotation(relativePos);

Quaternion current = transform.localRotation;

transform.localRotation = Quaternion.Slerp(current, rotation, Time.deltaTime);
transform.Translate(0, 0, 3 * Time.deltaTime);
}
}

Event

Event is special delegate used when you want to alert other classes when something has happened.

Other reference: http://www.unitygeek.com/delegates-events-unity/

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class EventManager : MonoBehaviour 
{
public delegate void ClickAction();
public static event ClickAction OnClicked;

void OnGUI()
{
// Manually create a button
if(GUI.Button(new Rect(Screen.width / 2 - 50, 5, 100, 30), "Click"))
{
// Null reference should be checked here
if(OnClicked != null)
OnClicked();
}
}
}

public class TeleportScript : MonoBehaviour
{
void OnEnable()
{
EventManager.OnClicked += Teleport;
}


void OnDisable()
{
EventManager.OnClicked -= Teleport;
}


void Teleport()
{
Vector3 pos = transform.position;
pos.y = Random.Range(1.0f, 3.0f);
transform.position = pos;
}
}

Event should be defined public and static. It works like public delegate and there is something different.

Events adds a layer of abstraction and protection on delegate, this protection prevents client of the delegate from resetting the delegate and invocation list. Events only allow to add and remove method from invocation list.

Therefore, if you try to invoke or override an event defined in other class, an error will be given.

1
2
3
4
5
6
7
public class InvokeExample : MonoBehaviour
{
public void OnButtonClick()
{
EventManager.OnClicked();
}
}
1
Assets/TeleportScript.cs(10,16): error CS0070: The event `EventManager.OnClicked' can only appear on the left hand side of += or -= when used outside of the type `EventManager'

Conclusion:

  • Delegates and Events help us to write modular and reusable code.
  • Always use Events together with Delegates for safety.
  • Do not forget to unsubscribe otherwise, it will lead to memory leak.