5.2 - Classes and Objects
In the previous section, we introduced Object-Oriented Programming (OOP) and its core principles. Now, let's dive deeper into the fundamental building blocks of OOP: classes and objects.
Classes: The Blueprints
A class in C# is a blueprint or template that defines the structure and behavior of objects. It's like an architectural plan for a house—it specifies what the house will contain and how it will function, but it's not the house itself.
Defining a Class
Here's the basic syntax for defining a class in C#:
[access_modifier] class ClassName
{
// Fields (data)
// Properties
// Methods (behavior)
// Constructors
// Events
// etc.
}
Let's create a simple class for a game character:
public class GameCharacter
{
// Fields - store the data
public string name;
public int health;
public int level;
// Method - define behavior
public void TakeDamage(int amount)
{
health -= amount;
if (health < 0)
health = 0;
}
public void Heal(int amount)
{
health += amount;
}
public void LevelUp()
{
level++;
health += 10; // Gain some health when leveling up
}
}
This GameCharacter
class has:
- Three fields:
name
,health
, andlevel
- Three methods:
TakeDamage()
,Heal()
, andLevelUp()
Fields (Member Variables)
Fields are variables declared within a class. They store the data that belongs to objects of the class.
public class Weapon
{
// Fields
public string name;
public int damage;
public float weight;
public bool isEquipped;
private int durability; // Private field - only accessible within the class
}
Fields can have different access modifiers:
public
: Accessible from anywhereprivate
: Accessible only within the classprotected
: Accessible within the class and derived classesinternal
: Accessible within the same assembly
Best Practice: In most cases, it's better to make fields private
and provide controlled access through properties (which we'll cover in a later section).
Methods (Member Functions)
Methods define the behaviors or actions that objects of the class can perform.
public class Enemy
{
public int health = 100;
public int damage = 10;
// Method with no parameters and no return value
public void Patrol()
{
Console.WriteLine("Enemy is patrolling...");
// Patrol logic would go here
}
// Method with parameters
public void TakeDamage(int amount)
{
health -= amount;
if (health <= 0)
Die();
}
// Method with a return value
public bool IsAlive()
{
return health > 0;
}
// Private method - internal implementation
private void Die()
{
Console.WriteLine("Enemy has been defeated!");
// Death logic would go here
}
}
Methods, like fields, can have different access modifiers to control their visibility.
Objects: The Instances
An object is an instance of a class—a concrete entity created from the class blueprint. If a class is like a cookie cutter, an object is like a cookie made from that cutter.
Creating Objects
To create an object in C#, you use the new
keyword followed by the class name and parentheses:
// Create a GameCharacter object
GameCharacter player = new GameCharacter();
// Create a Weapon object
Weapon sword = new Weapon();
// Create an Enemy object
Enemy goblin = new Enemy();
In C# 9 (supported by Unity 6.x), you can also use target-typed new
expressions:
GameCharacter player = new(); // C# 9 feature
Accessing Members
Once you have an object, you can access its fields and methods using the dot (.
) operator:
// Set field values
player.name = "Hero";
player.health = 100;
player.level = 1;
sword.name = "Steel Sword";
sword.damage = 15;
sword.weight = 5.5f;
sword.isEquipped = true;
// Call methods
player.TakeDamage(20);
Console.WriteLine($"{player.name}'s health: {player.health}"); // Output: Hero's health: 80
player.LevelUp();
Console.WriteLine($"{player.name}'s level: {player.level}"); // Output: Hero's level: 2
// Check if enemy is alive
if (goblin.IsAlive())
{
goblin.Patrol();
}
Multiple Objects from the Same Class
You can create multiple objects from the same class, each with its own set of data:
GameCharacter player1 = new GameCharacter();
player1.name = "Warrior";
player1.health = 120;
player1.level = 1;
GameCharacter player2 = new GameCharacter();
player2.name = "Mage";
player2.health = 80;
player2.level = 1;
Console.WriteLine($"{player1.name}: {player1.health} HP");
Console.WriteLine($"{player2.name}: {player2.health} HP");
// Each object behaves independently
player1.TakeDamage(30);
player2.Heal(20);
Console.WriteLine($"{player1.name}: {player1.health} HP"); // Output: Warrior: 90 HP
Console.WriteLine($"{player2.name}: {player2.health} HP"); // Output: Mage: 100 HP
The this
Keyword
The this
keyword refers to the current instance of the class. It's useful when:
- There's a name conflict between a parameter and a field
- You want to make it explicit that you're referring to a member of the current object
- You need to pass the current object as a parameter to another method
public class Player
{
// Fields
private string name;
private int score;
// Method with parameter that has the same name as a field
public void SetName(string name)
{
// Use 'this' to distinguish between the field and the parameter
this.name = name;
}
public void AddScore(int points)
{
// Using 'this' explicitly (optional in this case)
this.score += points;
// Check if high score was achieved
if (this.score > 1000)
{
this.UnlockAchievement("High Scorer");
}
}
private void UnlockAchievement(string achievementName)
{
Console.WriteLine($"{this.name} unlocked: {achievementName}");
// Achievement logic would go here
}
// Method that passes the current object to another method
public void RegisterPlayer(LeaderboardManager leaderboard)
{
leaderboard.AddPlayer(this); // Pass the current Player object
}
}
Practical Game Development Example
Let's create a more complete example that demonstrates classes and objects in a game development context:
using System;
using System.Collections.Generic;
// A simple inventory system
public class InventoryItem
{
public string name;
public string description;
public int value;
public bool isStackable;
public int quantity = 1;
public void Use()
{
Console.WriteLine($"Using {name}: {description}");
quantity--;
}
public string GetTooltip()
{
return $"{name} - {description}\nValue: {value} gold";
}
}
public class PlayerInventory
{
private List<InventoryItem> items = new List<InventoryItem>();
private int maxCapacity = 20;
public bool AddItem(InventoryItem newItem)
{
// Check if inventory is full
if (items.Count >= maxCapacity)
{
Console.WriteLine("Inventory is full!");
return false;
}
// Check if item is stackable and already exists
if (newItem.isStackable)
{
foreach (InventoryItem existingItem in items)
{
if (existingItem.name == newItem.name)
{
existingItem.quantity += newItem.quantity;
Console.WriteLine($"Added {newItem.quantity} {newItem.name}(s) to stack.");
return true;
}
}
}
// Add as new item
items.Add(newItem);
Console.WriteLine($"Added {newItem.name} to inventory.");
return true;
}
public void RemoveItem(string itemName)
{
for (int i = 0; i < items.Count; i++)
{
if (items[i].name == itemName)
{
items[i].quantity--;
if (items[i].quantity <= 0)
{
Console.WriteLine($"Removed {items[i].name} from inventory.");
items.RemoveAt(i);
}
else
{
Console.WriteLine($"Used 1 {items[i].name}, {items[i].quantity} remaining.");
}
return;
}
}
Console.WriteLine($"Item '{itemName}' not found in inventory.");
}
public void ListItems()
{
if (items.Count == 0)
{
Console.WriteLine("Inventory is empty.");
return;
}
Console.WriteLine("Inventory Contents:");
Console.WriteLine("------------------");
foreach (InventoryItem item in items)
{
if (item.quantity > 1)
{
Console.WriteLine($"{item.name} (x{item.quantity}) - {item.value} gold each");
}
else
{
Console.WriteLine($"{item.name} - {item.value} gold");
}
}
}
}
// Usage example
public class Program
{
public static void Main()
{
// Create inventory items
InventoryItem healthPotion = new InventoryItem
{
name = "Health Potion",
description = "Restores 25 health points",
value = 30,
isStackable = true,
quantity = 3
};
InventoryItem sword = new InventoryItem
{
name = "Iron Sword",
description = "A basic sword made of iron",
value = 150,
isStackable = false
};
InventoryItem shield = new InventoryItem
{
name = "Wooden Shield",
description = "A simple wooden shield",
value = 100,
isStackable = false
};
// Create player inventory
PlayerInventory inventory = new PlayerInventory();
// Add items to inventory
inventory.AddItem(healthPotion);
inventory.AddItem(sword);
inventory.AddItem(shield);
// List inventory contents
inventory.ListItems();
// Use a health potion
healthPotion.Use();
inventory.RemoveItem("Health Potion");
// List inventory again
inventory.ListItems();
// Add more health potions
InventoryItem moreHealthPotions = new InventoryItem
{
name = "Health Potion",
description = "Restores 25 health points",
value = 30,
isStackable = true,
quantity = 2
};
inventory.AddItem(moreHealthPotions);
// List inventory one more time
inventory.ListItems();
}
}
This example demonstrates:
- Creating multiple classes (
InventoryItem
andPlayerInventory
) - Defining fields and methods in each class
- Creating objects from these classes
- Interacting with objects through their methods
- Managing relationships between objects (a
PlayerInventory
containsInventoryItem
objects)
Classes and Objects in Unity
In Unity, you'll work with classes and objects constantly:
using UnityEngine;
// This is a class that inherits from MonoBehaviour
public class PlayerController : MonoBehaviour
{
// Fields
public float moveSpeed = 5f;
public int health = 100;
public GameObject projectilePrefab;
private Rigidbody rb;
private bool isJumping = false;
// Called when the script instance is being loaded
void Awake()
{
// Get a reference to the Rigidbody component on the same GameObject
rb = GetComponent<Rigidbody>();
}
// Called once per frame
void Update()
{
// Handle player input
float horizontalInput = Input.GetAxis("Horizontal");
float verticalInput = Input.GetAxis("Vertical");
// Create a movement vector
Vector3 movement = new Vector3(horizontalInput, 0f, verticalInput);
// Move the player
transform.Translate(movement * moveSpeed * Time.deltaTime);
// Handle shooting
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}
// Custom method for shooting
void Shoot()
{
// Instantiate a projectile
GameObject projectile = Instantiate(
projectilePrefab,
transform.position + transform.forward,
transform.rotation
);
// Get the projectile's Rigidbody and apply force
Rigidbody projectileRb = projectile.GetComponent<Rigidbody>();
if (projectileRb != null)
{
projectileRb.AddForce(transform.forward * 20f, ForceMode.Impulse);
}
}
// Method to handle taking damage
public void TakeDamage(int amount)
{
health -= amount;
if (health <= 0)
{
Die();
}
}
// Private method for handling player death
private void Die()
{
Debug.Log("Player has died!");
// Disable the player GameObject
gameObject.SetActive(false);
}
}
In Unity:
- Your scripts are classes that typically inherit from
MonoBehaviour
- Each script attached to a GameObject creates an object (instance) of that class
- You can have multiple GameObjects with the same script, each with its own state
- Unity provides special methods like
Awake()
,Start()
, andUpdate()
that get called automatically - You can create your own methods for custom functionality
Conclusion
Classes and objects are the foundation of Object-Oriented Programming in C#. Classes define the structure and behavior of objects, while objects are the actual instances that exist in memory during program execution.
By organizing your code into well-designed classes, you can create more maintainable, reusable, and understandable code—essential skills for any Unity developer.
In the next section, we'll dive deeper into value types versus reference types, which is crucial for understanding how objects behave in memory.
Practice Exercise
Exercise: Design a simple weapon system for a game with the following requirements:
- Create a
Weapon
class with appropriate fields (name, damage, range, etc.) and methods (Attack, Reload, etc.) - Create at least three different weapon objects with different characteristics
- Create a
Player
class that can equip and use weapons - Demonstrate how the player can switch between weapons and use them
Try to implement this in a console application and think about how you would extend it in a Unity context.