Chapter 3 : Scripting in Unity

3.1 Introduction to C# for Unity

C# (C-sharp) is a powerful, versatile programming language that's widely used in Unity to create dynamic, interactive, and engaging games. Developed by Microsoft, C# is known for its ease of use, robustness, and object-oriented nature, making it an ideal choice for both beginners and experienced developers in the gaming industry.

Why C# in Unity?

Unity, one of the leading game development platforms, primarily uses C# due to its high performance, flexibility, and strong integration capabilities. C# allows developers to write scripts that control the behavior of game objects, manage game logic, and handle user input. Unlike JavaScript (which Unity previously supported), C# offers strong type safety, which reduces the likelihood of runtime errors, and supports features like lambda expressions, LINQ queries, and async programming.

Setting Up the Environment

Before diving into scripting, it’s essential to set up the development environment. Unity comes bundled with Visual Studio, a powerful integrated development environment (IDE) that offers advanced features like IntelliSense, debugging tools, and integration with Unity’s API.

  1. Installing Visual Studio: During Unity installation, ensure that the Visual Studio component is selected. If not, you can download it separately from Microsoft’s official website.
  2. Configuring Unity with Visual Studio: Once installed, open Unity and go to Edit > Preferences > External Tools, and set Visual Studio as the external script editor.

Hello World in Unity

A "Hello World" program is traditionally the first step in learning any new programming language. In Unity, a simple script to display a message in the console will serve as our "Hello World."

  1. Creating a New Script: In Unity, right-click in the Project window, go to Create > C# Script, and name it HelloWorld.
  2. Writing the Script:
   using UnityEngine;

   public class HelloWorld : MonoBehaviour
   {
       // Start is called before the first frame update
       void Start()
       {
           Debug.Log("Hello, World!");
       }
   }

This script uses Unity’s MonoBehaviour class. The Start method is automatically called when the game begins. The Debug.Log function prints "Hello, World!" to Unity’s console.

  1. Attaching the Script to a GameObject: Drag the HelloWorld script onto any GameObject in the scene (such as the main camera). Run the game, and the message will appear in the console.

This simple example demonstrates how scripts are structured in Unity, how they are attached to game objects, and how they interact with the game engine.


3.2 Basic Scripting Concepts

With a basic script in place, it’s important to understand the foundational concepts of C# scripting in Unity. These concepts are critical as they form the building blocks of more complex game behaviors and mechanics.

Variables and Data Types

Variables are used to store data in your scripts, and C# supports various data types:

  • int: Integer numbers (e.g., int score = 100;).
  • float: Floating-point numbers, typically used for decimals (e.g., float speed = 5.5f;).
  • string: Text (e.g., string playerName = "Hero";).
  • bool: Boolean values (true or false) (e.g., bool isGameOver = false;).

Understanding data types is crucial for writing efficient and error-free scripts.

Operators and Expressions

Operators allow you to perform operations on variables and values:

  • Arithmetic Operators: +, -, *, /, %
    • Example: int total = score + bonus;
  • Relational Operators: ==, !=, >, <, >=, <=
    • Example: if (score > highScore)
  • Logical Operators: &&, ||, !
    • Example: if (isGameOver && playerLives > 0)

Control Flow Statements

Control flow statements dictate the flow of the program:

  • If-Else Statements: Execute code based on conditions.
  if (score >= 100)
  {
      Debug.Log("Level Up!");
  }
  else
  {
      Debug.Log("Keep Trying!");
  }
  • Loops: Repeat code multiple times.
    • For Loop: Typically used when the number of iterations is known.
    for (int i = 0; i < 10; i++)
    {
        Debug.Log("Iteration: " + i);
    }
  • While Loop: Repeats as long as a condition is true.
    while (playerHealth > 0)
    {
        // Continue game
    }

Functions and Methods

Functions (or methods) are blocks of code that perform specific tasks. They help to organize code and make it reusable:

  • Defining a Function:
  void DisplayScore(int currentScore)
  {
      Debug.Log("Score: " + currentScore);
  }
  • Calling a Function:
  DisplayScore(score);

Classes and Objects

C# is an object-oriented language, meaning it revolves around objects and classes:

  • Class: A blueprint for creating objects.
  public class Player
  {
      public string name;
      public int health;

      public void TakeDamage(int damage)
      {
          health -= damage;
      }
  }
  • Object: An instance of a class.
  Player player1 = new Player();
  player1.name = "Knight";
  player1.health = 100;
  player1.TakeDamage(10);

Classes and objects are essential for managing complex game behaviors and interactions.


3.3 Working with MonoBehaviour

The MonoBehaviour class is the foundation of all Unity scripts. It provides a framework for integrating scripts with Unity’s event-driven architecture.

Understanding MonoBehaviour

MonoBehaviour is the base class from which every script in Unity derives. It allows scripts to be attached to game objects and provides essential lifecycle methods like Start, Update, and OnDestroy.

Commonly Used MonoBehaviour Methods

  • Start(): Called before the first frame update, typically used for initialization.
  void Start()
  {
      Debug.Log("Game Started");
  }
  • Update(): Called once per frame, used for frame-dependent logic like player input.
  void Update()
  {
      if (Input.GetKeyDown(KeyCode.Space))
      {
          Jump();
      }
  }
  • FixedUpdate(): Called at a fixed interval, ideal for physics calculations.
  void FixedUpdate()
  {
      rb.AddForce(Vector3.up * force);
  }
  • LateUpdate(): Called after all Update methods, useful for following objects (like cameras).
  void LateUpdate()
  {
      cameraTransform.position = playerTransform.position + offset;
  }

Coroutines

Coroutines are a special type of function in Unity that can pause execution and resume at a later time, making them ideal for implementing delays or timed events.

  • Defining a Coroutine:
  IEnumerator WaitAndPrint()
  {
      yield return new WaitForSeconds(2);
      Debug.Log("2 seconds later...");
  }
  • Starting a Coroutine:
  StartCoroutine(WaitAndPrint());

Managing Game Objects

Using MonoBehaviour, you can create, modify, and destroy game objects during gameplay:

  • Instantiate: Create new instances of objects.
  GameObject clone = Instantiate(original);
  • Destroy: Remove objects from the scene.
  Destroy(clone);

Handling User Input

User input is critical in games. MonoBehaviour provides several ways to handle input:

  • Keyboard Input:
  if (Input.GetKeyDown(KeyCode.W))
  {
      // Move forward
  }
  • Mouse Input:
  if (Input.GetMouseButtonDown(0))
  {
      // Fire weapon
  }
  • Controller Input:
  float move = Input.GetAxis("Horizontal");

3.4 Managing Game Logic

Game logic is the core of your game’s behavior and flow. This section covers how to structure and manage complex game mechanics using scripts in Unity.

Game State Management

Managing different states of the game (e.g., Main Menu, In-Game, Pause) is crucial for creating a fluid experience:

  • State Machine Approach:
  enum GameState { Menu, Playing, Paused, GameOver }
  GameState currentState;



  void Update()
  {
      switch (currentState)
      {
          case GameState.Menu:
              // Show menu
              break;
          case GameState.Playing:
              // Game logic
              break;
          case GameState.Paused:
              // Pause logic
              break;
          case GameState.GameOver:
              // Game over logic
              break;
      }
  }

Event Systems

Unity’s event system is a powerful way to handle interactions between different game components:

  • Creating an Event:
  public delegate void OnPlayerDeath();
  public static event OnPlayerDeath playerDeathEvent;

  void Die()
  {
      if (playerDeathEvent != null)
          playerDeathEvent();
  }
  • Subscribing to an Event:
  void OnEnable()
  {
      playerDeathEvent += ShowGameOverScreen;
  }

  void OnDisable()
  {
      playerDeathEvent -= ShowGameOverScreen;
  }

  void ShowGameOverScreen()
  {
      // Display game over screen
  }

Timers and Counters

Timers and counters are often used for timed events, cooldowns, or scoring:

  • Simple Timer Example:
  float countdown = 10.0f;

  void Update()
  {
      if (countdown > 0)
      {
          countdown -= Time.deltaTime;
      }
      else
      {
          Debug.Log("Time's up!");
      }
  }

Physics and Collisions

Unity’s physics engine allows for realistic interactions between objects. Scripts can be used to manage these interactions:

  • Detecting Collisions:
  void OnCollisionEnter(Collision collision)
  {
      if (collision.gameObject.tag == "Enemy")
      {
          TakeDamage(10);
      }
  }
  • Applying Forces:
  rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);

AI and NPC Behavior

Implementing AI for non-player characters (NPCs) involves scripting behaviors such as movement, decision-making, and interactions:

  • Basic AI Patrol:
  public Transform[] points;
  private int destPoint = 0;
  private UnityEngine.AI.NavMeshAgent agent;

  void Start()
  {
      agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
      agent.autoBraking = false;

      GotoNextPoint();
  }

  void GotoNextPoint()
  {
      if (points.Length == 0)
          return;

      agent.destination = points[destPoint].position;
      destPoint = (destPoint + 1) % points.Length;
  }

  void Update()
  {
      if (!agent.pathPending && agent.remainingDistance < 0.5f)
          GotoNextPoint();
  }

3.5 Debugging and Testing Scripts

Debugging and testing are critical steps in ensuring that your scripts work as intended and that your game runs smoothly.

Debugging Tools in Unity

Unity offers several tools to assist in debugging:

  • Console Window: Displays messages, errors, and warnings generated by your scripts.
    • Debug.Log: Print messages to the console.
    Debug.Log("Player health: " + health);
  • Debug.Break: Pauses the game during runtime.
    Debug.Break();
  • Breakpoints: Set breakpoints in Visual Studio to pause execution and inspect variables.
  • Step-Through Debugging: Allows you to step through code line by line to identify issues.

Common Debugging Techniques

Some techniques can help resolve issues quickly:

  • Null Reference Checks: Always check if objects are null before using them.
  if (player != null)
  {
      player.TakeDamage(10);
  }
  • Boundary Testing: Test scripts with extreme or unexpected inputs to ensure robustness.
  • Logging: Use extensive logging to track variable values and flow of control.

Writing Unit Tests

Unity supports unit testing through its Test Framework:

  • Creating a Test Script:
  using UnityEngine;
  using UnityEngine.TestTools;
  using NUnit.Framework;
  using System.Collections;

  public class PlayerTests
  {
      [Test]
      public void PlayerTakeDamageTest()
      {
          Player player = new Player();
          player.TakeDamage(10);
          Assert.AreEqual(90, player.health);
      }
  }
  • Running Tests: Tests can be run from the Test Runner window in Unity.

Performance Profiling

Unity’s Profiler is a tool for analyzing the performance of your game:

  • Profiling a Script:
  Profiler.BeginSample("SampleName");
  // Code to profile
  Profiler.EndSample();

Testing on Different Platforms

Games often need to run on multiple platforms (PC, console, mobile). Test scripts on each platform to ensure compatibility and performance:

  • Platform-Specific Code:
  #if UNITY_IOS
      // iOS-specific code
  #elif UNITY_ANDROID
      // Android-specific code
  #endif

3.6 Common Scripting Pitfalls and Best Practices

Scripting in Unity, like any coding task, comes with its challenges. Understanding common pitfalls and following best practices will help you write cleaner, more efficient code.

Avoiding Common Mistakes

  • Null References: Null references are one of the most common errors in Unity scripts. Always check for null before accessing objects.
  • Infinite Loops: Ensure loops have a valid exit condition to avoid freezing the game.
  • Memory Leaks: Properly destroy or clean up objects that are no longer needed.

Code Optimization Tips

  • Avoid Expensive Operations in Update: Move resource-intensive operations out of the Update method whenever possible.
  • Use Object Pooling: Reuse objects instead of instantiating and destroying them frequently, which can be costly in terms of performance.
  • Optimize Physics Calculations: Reduce unnecessary physics calculations by adjusting the collision detection settings and using appropriate colliders.

Maintaining Clean Code

  • Consistent Naming Conventions: Use clear, consistent naming conventions for variables, methods, and classes.
  • Commenting: Regularly comment on your code to explain complex logic.
  • Modular Code: Break down large scripts into smaller, manageable functions or classes.

Version Control

Using version control systems like Git helps track changes, collaborate with others, and roll back to previous versions if necessary.

  • Basic Git Commands:
    • git init: Initializes a new repository.
    • git add: Adds files to the staging area.
    • git commit: Commits changes to the repository.
    • git push: Pushes commits to a remote repository.

Continuous Learning

The field of game development is always evolving. Stay up-to-date with the latest Unity features and scripting techniques through:

  • Official Documentation: Regularly consult the Unity documentation for updates and best practices.
  • Community Forums: Engage with the Unity community to learn from others and share knowledge.
  • Online Courses and Tutorials: Platforms like Coursera, Udemy, and YouTube offer valuable resources for improving your Unity scripting skills.