Functions and Methods in C

Functions and methods are the building blocks of organized, reusable code. They let you break down complex problems into smaller, manageable pieces, eliminate code duplication, and create clean, maintainable game logic. In Unity game development, methods power everything from player movement to enemy AI to UI interactions.

In this chapter, you'll learn how to create functions, pass data to them, get results back, and organize your code effectively. By the end, you'll be able to write clean, modular code that's easy to understand, test, and maintain.

What You'll Learn

  • Method syntax - How to declare and call methods
  • Parameters - Passing data to methods
  • Return values - Getting results from methods
  • Method overloading - Creating multiple versions of the same method
  • Scope and access modifiers - Controlling method visibility
  • Practical Unity examples - Real-world game development applications

Prerequisites


Understanding Methods

A method is a block of code that performs a specific task. Instead of writing the same code multiple times, you write it once in a method and call it whenever you need it.

Why Methods Matter in Games:

  • Code Reusability - Write once, use many times
  • Organization - Break complex logic into manageable pieces
  • Maintainability - Fix bugs in one place, not everywhere
  • Readability - Method names describe what code does
  • Testing - Test individual pieces of functionality

Basic Method Syntax

// Method declaration
void MethodName()
{
    // Code to execute
}

// Method call
MethodName();

Key Components:

  • Return type - What the method gives back (void means nothing)
  • Method name - Describes what the method does
  • Parentheses - Where parameters go (empty for now)
  • Body - The code that runs when called

Creating Your First Method

Let's start with a simple example: a method that prints a message to the console.

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    void Start()
    {
        GreetPlayer();
    }

    void GreetPlayer()
    {
        Debug.Log("Welcome to the game!");
    }
}

What's Happening:

  1. GreetPlayer() is declared as a method
  2. Start() calls GreetPlayer() when the game starts
  3. The message appears in Unity's console

Pro Tip: Method names should be verbs that describe actions. Use GreetPlayer() not PlayerGreeting().


Methods with Parameters

Parameters let you pass data into methods, making them flexible and reusable.

Single Parameter

void DealDamage(int damage)
{
    playerHealth -= damage;
    Debug.Log($"Player took {damage} damage!");
}

// Usage
DealDamage(25);  // Player takes 25 damage
DealDamage(10);  // Player takes 10 damage

Multiple Parameters

void HealPlayer(int amount, bool showMessage)
{
    playerHealth += amount;

    if (showMessage)
    {
        Debug.Log($"Player healed for {amount} HP!");
    }
}

// Usage
HealPlayer(50, true);   // Heal 50 HP and show message
HealPlayer(25, false);  // Heal 25 HP silently

Parameter Types:

  • Value parameters - Pass a copy of the value (default)
  • Reference parameters - Pass a reference to the original variable
  • Output parameters - Return multiple values

Reference Parameters

void SwapValues(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

// Usage
int x = 10;
int y = 20;
SwapValues(ref x, ref y);
// Now x = 20, y = 10

When to Use Reference Parameters:

  • When you need to modify the original variable
  • For performance with large data structures
  • When returning multiple values

Methods with Return Values

Return values let methods give results back to the caller.

Basic Return

int GetPlayerHealth()
{
    return playerHealth;
}

// Usage
int currentHealth = GetPlayerHealth();
Debug.Log($"Current health: {currentHealth}");

Return Types

// Return an integer
int CalculateDamage(int baseDamage, float multiplier)
{
    return Mathf.RoundToInt(baseDamage * multiplier);
}

// Return a boolean
bool IsPlayerAlive()
{
    return playerHealth > 0;
}

// Return a string
string GetPlayerStatus()
{
    if (IsPlayerAlive())
    {
        return "Alive";
    }
    return "Dead";
}

// Usage
int damage = CalculateDamage(50, 1.5f);  // Returns 75
bool alive = IsPlayerAlive();            // Returns true/false
string status = GetPlayerStatus();       // Returns "Alive" or "Dead"

Pro Tip: Use descriptive return types. If a method checks something, return bool. If it calculates something, return a number.


Method Overloading

Method overloading lets you create multiple versions of the same method with different parameters.

// Version 1: No parameters
void Attack()
{
    DealDamage(10);
}

// Version 2: One parameter
void Attack(int damage)
{
    DealDamage(damage);
}

// Version 3: Two parameters
void Attack(int damage, float range)
{
    if (IsInRange(range))
    {
        DealDamage(damage);
    }
}

// Usage
Attack();              // Uses version 1
Attack(25);            // Uses version 2
Attack(50, 5.0f);      // Uses version 3

Overloading Rules:

  • Methods must have different parameter lists
  • Return type alone doesn't count (can't overload by return type only)
  • Parameter names don't matter, types and order do

When to Use Overloading:

  • Provide default values for optional parameters
  • Support different data types
  • Create convenience methods

Access Modifiers

Access modifiers control who can call your methods.

Public Methods

public void TakeDamage(int damage)
{
    playerHealth -= damage;
}

Public methods can be called from anywhere, including other scripts.

Private Methods

private void UpdateHealthBar()
{
    // Update UI health bar
}

Private methods can only be called from within the same class.

Protected Methods

protected void Initialize()
{
    // Setup code
}

Protected methods can be called from the same class or derived classes.

Access Modifier Summary:

  • public - Accessible from anywhere
  • private - Only accessible within the same class
  • protected - Accessible within class and derived classes
  • internal - Accessible within the same assembly

Pro Tip: Make methods private by default. Only make them public if other scripts need to call them.


Static Methods

Static methods belong to the class itself, not to instances of the class.

public class GameManager : MonoBehaviour
{
    public static int GetMaxHealth()
    {
        return 100;
    }

    public static float CalculateDistance(Vector3 a, Vector3 b)
    {
        return Vector3.Distance(a, b);
    }
}

// Usage (no instance needed)
int maxHealth = GameManager.GetMaxHealth();
float distance = GameManager.CalculateDistance(player1.position, player2.position);

When to Use Static Methods:

  • Utility functions that don't need instance data
  • Helper methods used across multiple classes
  • Math calculations and conversions

Common Unity Static Methods:

  • Mathf.Max(), Mathf.Min(), Mathf.Clamp()
  • Vector3.Distance(), Vector3.Lerp()
  • Input.GetKey(), Input.GetMouseButton()

Practical Unity Examples

Let's see how methods work in real Unity game development scenarios.

Example 1: Player Health System

using UnityEngine;

public class PlayerHealth : MonoBehaviour
{
    private int maxHealth = 100;
    private int currentHealth;

    void Start()
    {
        currentHealth = maxHealth;
    }

    public void TakeDamage(int damage)
    {
        currentHealth -= damage;
        currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);

        if (currentHealth <= 0)
        {
            Die();
        }

        UpdateHealthUI();
    }

    public void Heal(int amount)
    {
        currentHealth += amount;
        currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
        UpdateHealthUI();
    }

    public bool IsAlive()
    {
        return currentHealth > 0;
    }

    public int GetHealth()
    {
        return currentHealth;
    }

    public float GetHealthPercentage()
    {
        return (float)currentHealth / maxHealth;
    }

    private void Die()
    {
        Debug.Log("Player died!");
        // Handle death logic
    }

    private void UpdateHealthUI()
    {
        // Update health bar UI
        Debug.Log($"Health: {currentHealth}/{maxHealth}");
    }
}

What This Example Shows:

  • Public methods for external access (TakeDamage, Heal)
  • Private helper methods (Die, UpdateHealthUI)
  • Methods that return values (IsAlive, GetHealth)
  • Methods with parameters (TakeDamage, Heal)

Example 2: Enemy AI Movement

using UnityEngine;

public class EnemyAI : MonoBehaviour
{
    public float moveSpeed = 5f;
    public float detectionRange = 10f;

    private Transform player;

    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player").transform;
    }

    void Update()
    {
        if (IsPlayerInRange())
        {
            MoveTowardsPlayer();
        }
        else
        {
            Patrol();
        }
    }

    bool IsPlayerInRange()
    {
        if (player == null) return false;

        float distance = Vector3.Distance(transform.position, player.position);
        return distance <= detectionRange;
    }

    void MoveTowardsPlayer()
    {
        Vector3 direction = (player.position - transform.position).normalized;
        transform.position += direction * moveSpeed * Time.deltaTime;
    }

    void Patrol()
    {
        // Simple patrol logic
        transform.position += transform.forward * moveSpeed * Time.deltaTime;
    }
}

What This Example Shows:

  • Methods that check conditions (IsPlayerInRange)
  • Methods that perform actions (MoveTowardsPlayer, Patrol)
  • Separation of concerns (each method does one thing)

Example 3: Score System

using UnityEngine;

public class ScoreManager : MonoBehaviour
{
    private int currentScore = 0;
    private int highScore = 0;

    public void AddScore(int points)
    {
        currentScore += points;
        CheckHighScore();
        UpdateScoreUI();
    }

    public void ResetScore()
    {
        currentScore = 0;
        UpdateScoreUI();
    }

    public int GetScore()
    {
        return currentScore;
    }

    public int GetHighScore()
    {
        return highScore;
    }

    private void CheckHighScore()
    {
        if (currentScore > highScore)
        {
            highScore = currentScore;
            SaveHighScore();
        }
    }

    private void SaveHighScore()
    {
        PlayerPrefs.SetInt("HighScore", highScore);
    }

    private void UpdateScoreUI()
    {
        // Update score display
        Debug.Log($"Score: {currentScore}");
    }
}

Common Mistakes to Avoid

Mistake 1: Methods That Do Too Much

// Bad: Method does too many things
void UpdatePlayer()
{
    // Movement
    // Health
    // Inventory
    // UI
    // Audio
    // Everything!
}

// Good: Separate methods for each concern
void UpdateMovement() { }
void UpdateHealth() { }
void UpdateInventory() { }
void UpdateUI() { }

Mistake 2: Not Using Return Values

// Bad: Method doesn't return useful information
void CheckHealth()
{
    if (playerHealth <= 0)
    {
        Debug.Log("Player is dead");
    }
}

// Good: Method returns a boolean
bool IsPlayerDead()
{
    return playerHealth <= 0;
}

Mistake 3: Unclear Method Names

// Bad: Unclear what the method does
void DoStuff() { }
void Process() { }
void Handle() { }

// Good: Clear, descriptive names
void DealDamageToPlayer(int damage) { }
void ProcessPlayerInput() { }
void HandleEnemyCollision() { }

Mistake 4: Ignoring Method Scope

// Bad: Everything is public
public void InternalCalculation() { }
public void UpdatePrivateData() { }

// Good: Use appropriate access modifiers
private void InternalCalculation() { }
private void UpdatePrivateData() { }

Best Practices

1. Single Responsibility Principle

Each method should do one thing well.

// Good: Each method has a single responsibility
void MovePlayer() { }
void CheckCollisions() { }
void UpdateUI() { }

2. Descriptive Names

Method names should clearly describe what they do.

// Good: Names are descriptive
bool IsPlayerInRange(float range) { }
void DealDamageToEnemy(int damage) { }
Vector3 GetPlayerPosition() { }

3. Keep Methods Small

Small methods are easier to understand, test, and maintain.

// Good: Small, focused methods
void Attack()
{
    if (CanAttack())
    {
        PerformAttack();
        PlayAttackSound();
    }
}

bool CanAttack()
{
    return Time.time >= nextAttackTime;
}

void PerformAttack()
{
    // Attack logic
}

4. Use Parameters Wisely

Don't use too many parameters. If you need many, consider using a class or struct.

// Bad: Too many parameters
void CreateEnemy(string name, int health, int damage, float speed, Color color, Vector3 position) { }

// Good: Use a class or struct
void CreateEnemy(EnemyData data) { }

5. Document Complex Methods

Add comments for complex logic.

/// <summary>
/// Calculates damage based on base damage, player level, and weapon multiplier
/// </summary>
int CalculateDamage(int baseDamage, int level, float weaponMultiplier)
{
    // Formula: (baseDamage + level) * weaponMultiplier
    return Mathf.RoundToInt((baseDamage + level) * weaponMultiplier);
}

Exercises

Exercise 1: Create a Damage Calculator

Create a method that calculates damage based on:

  • Base damage
  • Attacker level
  • Weapon multiplier
  • Critical hit chance
// Your code here
int CalculateDamage(int baseDamage, int level, float weaponMultiplier, float critChance)
{
    // Calculate base damage with level
    // Apply weapon multiplier
    // Check for critical hit
    // Return final damage
}

Exercise 2: Create a Health System

Create methods for:

  • Taking damage
  • Healing
  • Checking if alive
  • Getting health percentage
// Your code here
public class HealthSystem
{
    private int maxHealth = 100;
    private int currentHealth;

    // Your methods here
}

Exercise 3: Create Utility Methods

Create static utility methods for:

  • Calculating distance between two points
  • Clamping a value between min and max
  • Checking if a value is within a range
// Your code here
public static class GameUtils
{
    // Your utility methods here
}

Troubleshooting

Problem: Method Not Found

Error: The name 'MethodName' does not exist in the current context

Solution: Check that:

  • Method name is spelled correctly
  • Method is accessible (not private from another class)
  • Method is declared before it's called (or use proper class structure)

Problem: Wrong Number of Arguments

Error: No overload for 'MethodName' takes X arguments

Solution: Check that:

  • Number of parameters matches the method signature
  • Parameter types match
  • Parameter order is correct

Problem: Cannot Convert Return Type

Error: Cannot implicitly convert type 'X' to 'Y'

Solution: Check that:

  • Return type matches what you're trying to assign
  • Use explicit casting if needed: int result = (int)GetFloatValue();

Summary

Methods are essential for organizing code in game development. They let you:

  • Break down complex problems into smaller pieces
  • Reuse code instead of duplicating it
  • Organize logic into logical, named blocks
  • Test individual pieces of functionality
  • Maintain code more easily

Key Takeaways:

  • Methods have a return type, name, parameters, and body
  • Parameters pass data into methods
  • Return values give results back
  • Method overloading creates multiple versions
  • Access modifiers control visibility
  • Static methods belong to the class, not instances

Next Steps:


Related Guides


Last updated: February 20, 2026