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
- Completed Control Flow: If Statements and Loops
- Understanding of variables and data types
- Basic familiarity with Unity (optional, but helpful)
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:
GreetPlayer()is declared as a methodStart()callsGreetPlayer()when the game starts- 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:
- Practice creating methods for common game tasks
- Experiment with different parameter types
- Try method overloading for flexibility
- Move on to Classes and Objects: Object-Oriented Programming
Related Guides
- C# Basics: Variables, Data Types, and Operators - Foundation concepts
- Control Flow: If Statements and Loops - Decision making and repetition
- Classes and Objects: Object-Oriented Programming - Next chapter in this guide
Last updated: February 20, 2026