5.15 - OOP Exercise: RPG Character System
In this mini-project, you'll apply the object-oriented programming concepts you've learned to build a simple RPG character system. This project will help you practice using classes, inheritance, polymorphism, interfaces, and other OOP concepts in a practical context.
Project Overview
You'll create a character system for an RPG game that includes:
- A base
Character
class with common properties and methods - Different character classes (e.g.,
Warrior
,Mage
,Rogue
) that inherit from the base class - Interfaces for different abilities (e.g.,
IAttacker
,IHealer
,IMagicUser
) - Structs for representing character stats and equipment
- A simple combat system that demonstrates polymorphism
By the end of this project, you'll have a solid foundation for a character system that you could expand into a full RPG game.
Project Requirements
1. Character Base Class
Create an abstract Character
class with:
- Properties for name, level, health, mana, and basic stats (strength, dexterity, intelligence)
- Methods for leveling up, taking damage, and healing
- An abstract method for a special ability
2. Character Classes
Create at least three character classes that inherit from the base Character
class:
Warrior
: Focuses on strength and melee combatMage
: Focuses on intelligence and spell castingRogue
: Focuses on dexterity and stealth
Each class should:
- Override the special ability method with a class-specific implementation
- Have unique properties and methods relevant to their role
3. Interfaces
Create at least three interfaces:
IAttacker
: For characters that can perform attacksIHealer
: For characters that can heal themselves or othersIMagicUser
: For characters that can cast spells
Implement these interfaces in the appropriate character classes.
4. Structs
Create at least two structs:
CharacterStats
: For representing a character's statisticsEquipment
: For representing a piece of equipment
5. Combat System
Create a simple combat system that:
- Allows characters to attack each other
- Calculates damage based on character stats and equipment
- Demonstrates polymorphism by treating different character types uniformly
6. Unity Integration
Create a simple Unity scene that:
- Instantiates different character types
- Allows the player to control a character
- Demonstrates the combat system
Project Structure
Here's a suggested structure for your project:
Assets/
Scripts/
Characters/
Character.cs (abstract base class)
Warrior.cs
Mage.cs
Rogue.cs
Interfaces/
IAttacker.cs
IHealer.cs
IMagicUser.cs
Structs/
CharacterStats.cs
Equipment.cs
Systems/
CombatSystem.cs
CharacterManager.cs
UI/
CharacterUI.cs
CombatUI.cs
Step-by-Step Guide
Step 1: Create the Character Base Class
Start by creating an abstract Character
class that will serve as the foundation for all character types:
using UnityEngine;
public abstract class Character : MonoBehaviour
{
[SerializeField] protected string characterName;
[SerializeField] protected int level = 1;
[SerializeField] protected int maxHealth = 100;
[SerializeField] protected int maxMana = 50;
[SerializeField] protected int strength = 10;
[SerializeField] protected int dexterity = 10;
[SerializeField] protected int intelligence = 10;
protected int currentHealth;
protected int currentMana;
protected virtual void Awake()
{
currentHealth = maxHealth;
currentMana = maxMana;
}
public virtual void LevelUp()
{
level++;
maxHealth += 10;
maxMana += 5;
strength += 1;
dexterity += 1;
intelligence += 1;
currentHealth = maxHealth;
currentMana = maxMana;
Debug.Log($"{characterName} leveled up to level {level}!");
}
public virtual void TakeDamage(int damage)
{
currentHealth = Mathf.Max(0, currentHealth - damage);
Debug.Log($"{characterName} takes {damage} damage. Health: {currentHealth}/{maxHealth}");
if (currentHealth <= 0)
{
Die();
}
}
public virtual void Heal(int amount)
{
currentHealth = Mathf.Min(maxHealth, currentHealth + amount);
Debug.Log($"{characterName} heals for {amount} health. Health: {currentHealth}/{maxHealth}");
}
public virtual void UseMana(int amount)
{
if (currentMana >= amount)
{
currentMana -= amount;
Debug.Log($"{characterName} uses {amount} mana. Mana: {currentMana}/{maxMana}");
}
else
{
Debug.Log($"{characterName} doesn't have enough mana!");
}
}
public virtual void RestoreMana(int amount)
{
currentMana = Mathf.Min(maxMana, currentMana + amount);
Debug.Log($"{characterName} restores {amount} mana. Mana: {currentMana}/{maxMana}");
}
protected virtual void Die()
{
Debug.Log($"{characterName} has died!");
// Additional death logic here
}
// Abstract method that all derived classes must implement
public abstract void UseSpecialAbility();
// Getters for properties
public string Name => characterName;
public int Level => level;
public int CurrentHealth => currentHealth;
public int MaxHealth => maxHealth;
public int CurrentMana => currentMana;
public int MaxMana => maxMana;
public int Strength => strength;
public int Dexterity => dexterity;
public int Intelligence => intelligence;
}
Step 2: Create the Interfaces
Next, create the interfaces that define different character capabilities:
// Interface for characters that can attack
public interface IAttacker
{
void Attack(Character target);
int CalculateDamage();
}
// Interface for characters that can heal
public interface IHealer
{
void HealTarget(Character target, int amount);
int CalculateHealAmount();
}
// Interface for characters that can use magic
public interface IMagicUser
{
void CastSpell(string spellName, Character target);
bool HasEnoughMana(int manaCost);
}
Step 3: Create the Structs
Create structs to represent character stats and equipment:
// Struct to represent character stats
public struct CharacterStats
{
public int strength;
public int dexterity;
public int intelligence;
public int vitality;
public int wisdom;
public CharacterStats(int strength, int dexterity, int intelligence, int vitality, int wisdom)
{
this.strength = strength;
this.dexterity = dexterity;
this.intelligence = intelligence;
this.vitality = vitality;
this.wisdom = wisdom;
}
public int CalculatePhysicalDamage()
{
return strength * 2 + dexterity;
}
public int CalculateMagicalDamage()
{
return intelligence * 2 + wisdom;
}
public int CalculateMaxHealth()
{
return vitality * 10 + strength * 2;
}
public int CalculateMaxMana()
{
return wisdom * 10 + intelligence * 2;
}
}
// Enum for equipment types
public enum EquipmentType
{
Weapon,
Armor,
Accessory
}
// Struct to represent equipment
public struct Equipment
{
public string name;
public EquipmentType type;
public int physicalBonus;
public int magicalBonus;
public int healthBonus;
public int manaBonus;
public Equipment(string name, EquipmentType type, int physicalBonus, int magicalBonus, int healthBonus, int manaBonus)
{
this.name = name;
this.type = type;
this.physicalBonus = physicalBonus;
this.magicalBonus = magicalBonus;
this.healthBonus = healthBonus;
this.manaBonus = manaBonus;
}
public override string ToString()
{
return $"{name} ({type}): +{physicalBonus} Physical, +{magicalBonus} Magical, +{healthBonus} Health, +{manaBonus} Mana";
}
}
Step 4: Create the Character Classes
Now, create the specific character classes that inherit from the base Character
class and implement the appropriate interfaces:
// Warrior class
public class Warrior : Character, IAttacker
{
[SerializeField] private Equipment weapon;
[SerializeField] private Equipment armor;
[SerializeField] private float rageMeter = 0f;
[SerializeField] private float maxRage = 100f;
protected override void Awake()
{
base.Awake();
// Set warrior-specific starting stats
strength += 5;
maxHealth += 20;
currentHealth = maxHealth;
// Initialize with default equipment if none is set
if (weapon.name == null)
{
weapon = new Equipment("Iron Sword", EquipmentType.Weapon, 5, 0, 0, 0);
}
if (armor.name == null)
{
armor = new Equipment("Leather Armor", EquipmentType.Armor, 0, 0, 10, 0);
}
}
public override void LevelUp()
{
base.LevelUp();
// Warriors gain more strength and health per level
strength += 2;
maxHealth += 15;
currentHealth = maxHealth;
}
public override void TakeDamage(int damage)
{
base.TakeDamage(damage);
// Warriors gain rage when taking damage
rageMeter = Mathf.Min(maxRage, rageMeter + damage * 0.5f);
}
public override void UseSpecialAbility()
{
if (rageMeter >= 25)
{
Debug.Log($"{characterName} uses Whirlwind Attack!");
rageMeter -= 25;
// In a real game, this would damage all nearby enemies
Debug.Log("All nearby enemies take damage!");
}
else
{
Debug.Log($"{characterName} doesn't have enough rage to use Whirlwind Attack!");
}
}
// IAttacker implementation
public void Attack(Character target)
{
int damage = CalculateDamage();
Debug.Log($"{characterName} attacks {target.Name} for {damage} damage!");
target.TakeDamage(damage);
// Warriors gain rage when attacking
rageMeter = Mathf.Min(maxRage, rageMeter + 10);
}
public int CalculateDamage()
{
return strength * 2 + weapon.physicalBonus;
}
// Additional warrior-specific methods
public void Block()
{
Debug.Log($"{characterName} raises their shield, reducing incoming damage!");
// In a real game, this would set a "blocking" state that reduces damage
}
public float GetRagePercentage()
{
return rageMeter / maxRage;
}
}
// Mage class
public class Mage : Character, IAttacker, IMagicUser, IHealer
{
[SerializeField] private Equipment staff;
[SerializeField] private Equipment robe;
[SerializeField] private List<string> knownSpells = new List<string>();
protected override void Awake()
{
base.Awake();
// Set mage-specific starting stats
intelligence += 5;
maxMana += 20;
currentMana = maxMana;
// Initialize with default equipment if none is set
if (staff.name == null)
{
staff = new Equipment("Apprentice Staff", EquipmentType.Weapon, 1, 5, 0, 10);
}
if (robe.name == null)
{
robe = new Equipment("Apprentice Robe", EquipmentType.Armor, 0, 3, 5, 15);
}
// Initialize known spells
if (knownSpells.Count == 0)
{
knownSpells.Add("Fireball");
knownSpells.Add("Frostbolt");
knownSpells.Add("Arcane Missile");
knownSpells.Add("Heal");
}
}
public override void LevelUp()
{
base.LevelUp();
// Mages gain more intelligence and mana per level
intelligence += 2;
maxMana += 15;
currentMana = maxMana;
// Learn a new spell at certain levels
if (level % 2 == 0)
{
LearnNewSpell();
}
}
public override void UseSpecialAbility()
{
if (currentMana >= 30)
{
Debug.Log($"{characterName} uses Arcane Explosion!");
UseMana(30);
// In a real game, this would damage all nearby enemies
Debug.Log("All nearby enemies take arcane damage!");
}
else
{
Debug.Log($"{characterName} doesn't have enough mana to use Arcane Explosion!");
}
}
// IAttacker implementation
public void Attack(Character target)
{
int damage = CalculateDamage();
Debug.Log($"{characterName} attacks {target.Name} with their staff for {damage} damage!");
target.TakeDamage(damage);
}
public int CalculateDamage()
{
return intelligence + staff.magicalBonus;
}
// IMagicUser implementation
public void CastSpell(string spellName, Character target)
{
if (!knownSpells.Contains(spellName))
{
Debug.Log($"{characterName} doesn't know the spell {spellName}!");
return;
}
int manaCost = GetSpellManaCost(spellName);
if (HasEnoughMana(manaCost))
{
UseMana(manaCost);
switch (spellName)
{
case "Fireball":
int fireballDamage = intelligence * 2 + staff.magicalBonus;
Debug.Log($"{characterName} casts Fireball on {target.Name} for {fireballDamage} damage!");
target.TakeDamage(fireballDamage);
break;
case "Frostbolt":
int frostboltDamage = intelligence * 1.5f + staff.magicalBonus;
Debug.Log($"{characterName} casts Frostbolt on {target.Name} for {frostboltDamage} damage!");
target.TakeDamage(frostboltDamage);
// In a real game, this would also slow the target
break;
case "Arcane Missile":
int arcaneDamage = intelligence + staff.magicalBonus;
Debug.Log($"{characterName} casts Arcane Missile on {target.Name} for {arcaneDamage} damage!");
target.TakeDamage(arcaneDamage);
break;
case "Heal":
int healAmount = CalculateHealAmount();
Debug.Log($"{characterName} casts Heal on {target.Name} for {healAmount} health!");
target.Heal(healAmount);
break;
default:
Debug.Log($"{characterName} casts {spellName}!");
break;
}
}
else
{
Debug.Log($"{characterName} doesn't have enough mana to cast {spellName}!");
}
}
public bool HasEnoughMana(int manaCost)
{
return currentMana >= manaCost;
}
// IHealer implementation
public void HealTarget(Character target, int amount)
{
Debug.Log($"{characterName} heals {target.Name} for {amount} health!");
target.Heal(amount);
}
public int CalculateHealAmount()
{
return intelligence * 1.5f + staff.magicalBonus;
}
// Additional mage-specific methods
private void LearnNewSpell()
{
string[] possibleSpells = new string[]
{
"Polymorph",
"Blink",
"Frost Nova",
"Greater Heal",
"Arcane Intellect",
"Fire Blast"
};
foreach (string spell in possibleSpells)
{
if (!knownSpells.Contains(spell))
{
knownSpells.Add(spell);
Debug.Log($"{characterName} learned a new spell: {spell}!");
return;
}
}
Debug.Log($"{characterName} already knows all available spells!");
}
private int GetSpellManaCost(string spellName)
{
switch (spellName)
{
case "Fireball":
return 15;
case "Frostbolt":
return 10;
case "Arcane Missile":
return 5;
case "Heal":
return 20;
case "Polymorph":
return 25;
case "Blink":
return 15;
case "Frost Nova":
return 20;
case "Greater Heal":
return 30;
case "Arcane Intellect":
return 15;
case "Fire Blast":
return 25;
default:
return 10;
}
}
public List<string> GetKnownSpells()
{
return new List<string>(knownSpells);
}
}
// Rogue class
public class Rogue : Character, IAttacker
{
[SerializeField] private Equipment weapon;
[SerializeField] private Equipment armor;
[SerializeField] private float energyMeter = 100f;
[SerializeField] private float maxEnergy = 100f;
[SerializeField] private bool isStealthed = false;
protected override void Awake()
{
base.Awake();
// Set rogue-specific starting stats
dexterity += 5;
// Initialize with default equipment if none is set
if (weapon.name == null)
{
weapon = new Equipment("Dagger", EquipmentType.Weapon, 4, 0, 0, 0);
}
if (armor.name == null)
{
armor = new Equipment("Leather Armor", EquipmentType.Armor, 0, 0, 5, 0);
}
}
public override void LevelUp()
{
base.LevelUp();
// Rogues gain more dexterity per level
dexterity += 2;
}
public override void UseSpecialAbility()
{
if (energyMeter >= 25)
{
Debug.Log($"{characterName} uses Backstab!");
energyMeter -= 25;
// In a real game, this would deal extra damage to a target
Debug.Log("Target takes critical damage!");
}
else
{
Debug.Log($"{characterName} doesn't have enough energy to use Backstab!");
}
}
// IAttacker implementation
public void Attack(Character target)
{
int damage = CalculateDamage();
// Rogues deal extra damage from stealth
if (isStealthed)
{
damage = (int)(damage * 2.5f);
isStealthed = false;
Debug.Log($"{characterName} attacks {target.Name} from stealth for {damage} damage!");
}
else
{
Debug.Log($"{characterName} attacks {target.Name} for {damage} damage!");
}
target.TakeDamage(damage);
// Rogues use energy when attacking
energyMeter = Mathf.Max(0, energyMeter - 10);
}
public int CalculateDamage()
{
return dexterity * 1.5f + weapon.physicalBonus;
}
// Additional rogue-specific methods
public void Stealth()
{
if (energyMeter >= 30)
{
isStealthed = true;
energyMeter -= 30;
Debug.Log($"{characterName} enters stealth!");
}
else
{
Debug.Log($"{characterName} doesn't have enough energy to enter stealth!");
}
}
public void RegenerateEnergy()
{
energyMeter = Mathf.Min(maxEnergy, energyMeter + 5);
}
public bool IsStealthed()
{
return isStealthed;
}
public float GetEnergyPercentage()
{
return energyMeter / maxEnergy;
}
}
Step 5: Create the Combat System
Create a combat system that demonstrates polymorphism by treating different character types uniformly:
public class CombatSystem : MonoBehaviour
{
[SerializeField] private List<Character> combatants = new List<Character>();
[SerializeField] private float turnDelay = 1.0f;
private int currentTurnIndex = 0;
private bool isCombatActive = false;
public void StartCombat()
{
if (combatants.Count < 2)
{
Debug.LogWarning("Not enough combatants to start combat!");
return;
}
isCombatActive = true;
currentTurnIndex = 0;
Debug.Log("Combat started!");
// Start the turn sequence
StartCoroutine(TurnSequence());
}
public void StopCombat()
{
isCombatActive = false;
Debug.Log("Combat ended!");
}
public void AddCombatant(Character character)
{
if (!combatants.Contains(character))
{
combatants.Add(character);
Debug.Log($"{character.Name} joined the combat!");
}
}
public void RemoveCombatant(Character character)
{
if (combatants.Contains(character))
{
combatants.Remove(character);
Debug.Log($"{character.Name} left the combat!");
if (combatants.Count < 2)
{
StopCombat();
}
}
}
private IEnumerator TurnSequence()
{
while (isCombatActive)
{
if (combatants.Count < 2)
{
StopCombat();
yield break;
}
// Get the current character
Character currentCharacter = combatants[currentTurnIndex];
Debug.Log($"{currentCharacter.Name}'s turn!");
// Perform an action based on the character type
PerformAction(currentCharacter);
// Wait for the turn delay
yield return new WaitForSeconds(turnDelay);
// Move to the next character
currentTurnIndex = (currentTurnIndex + 1) % combatants.Count;
}
}
private void PerformAction(Character character)
{
// Find a target (simple implementation: just pick the next character in the list)
Character target = GetTarget(character);
if (target == null)
{
Debug.LogWarning("No valid target found!");
return;
}
// Perform an action based on the character's capabilities
if (character is IAttacker attacker)
{
// 80% chance to perform a basic attack
if (Random.value < 0.8f)
{
attacker.Attack(target);
}
else
{
// 20% chance to use special ability
character.UseSpecialAbility();
}
}
else if (character is IMagicUser magicUser)
{
// If the character is a magic user, cast a spell
List<string> spells = new List<string>();
if (character is Mage mage)
{
spells = mage.GetKnownSpells();
}
if (spells.Count > 0)
{
string randomSpell = spells[Random.Range(0, spells.Count)];
magicUser.CastSpell(randomSpell, target);
}
else
{
Debug.LogWarning($"{character.Name} doesn't know any spells!");
}
}
else
{
// If the character doesn't have any special capabilities, just use their special ability
character.UseSpecialAbility();
}
// Check if any combatants have died
for (int i = combatants.Count - 1; i >= 0; i--)
{
if (combatants[i].CurrentHealth <= 0)
{
Debug.Log($"{combatants[i].Name} has been defeated!");
combatants.RemoveAt(i);
}
}
}
private Character GetTarget(Character currentCharacter)
{
// Simple target selection: just pick the next character in the list
for (int i = 0; i < combatants.Count; i++)
{
if (combatants[i] != currentCharacter)
{
return combatants[i];
}
}
return null;
}
}
Step 6: Create a Character Manager
Create a character manager to handle character creation and management:
public class CharacterManager : MonoBehaviour
{
[SerializeField] private Transform characterContainer;
[SerializeField] private GameObject warriorPrefab;
[SerializeField] private GameObject magePrefab;
[SerializeField] private GameObject roguePrefab;
private List<Character> characters = new List<Character>();
public Character CreateCharacter(string characterType, string name)
{
GameObject prefab = null;
switch (characterType.ToLower())
{
case "warrior":
prefab = warriorPrefab;
break;
case "mage":
prefab = magePrefab;
break;
case "rogue":
prefab = roguePrefab;
break;
default:
Debug.LogWarning($"Unknown character type: {characterType}");
return null;
}
if (prefab == null)
{
Debug.LogWarning($"Prefab for {characterType} is not set!");
return null;
}
GameObject characterObject = Instantiate(prefab, characterContainer);
Character character = characterObject.GetComponent<Character>();
if (character != null)
{
// Set the character's name
characterObject.name = name;
// Add the character to the list
characters.Add(character);
Debug.Log($"Created {characterType} named {name}");
return character;
}
Debug.LogWarning($"Failed to create {characterType}!");
return null;
}
public void RemoveCharacter(Character character)
{
if (characters.Contains(character))
{
characters.Remove(character);
Destroy(character.gameObject);
Debug.Log($"Removed character: {character.Name}");
}
}
public List<Character> GetAllCharacters()
{
return new List<Character>(characters);
}
public List<Character> GetCharactersByType<T>() where T : Character
{
return characters.OfType<T>().ToList();
}
}
Step 7: Create a Simple UI
Create a simple UI to interact with the character system:
public class CharacterUI : MonoBehaviour
{
[SerializeField] private CharacterManager characterManager;
[SerializeField] private CombatSystem combatSystem;
[SerializeField] private TMP_InputField characterNameInput;
[SerializeField] private TMP_Dropdown characterTypeDropdown;
[SerializeField] private Button createCharacterButton;
[SerializeField] private Button startCombatButton;
[SerializeField] private Button stopCombatButton;
[SerializeField] private Transform characterListContainer;
[SerializeField] private GameObject characterListItemPrefab;
private void Start()
{
// Set up button listeners
createCharacterButton.onClick.AddListener(CreateCharacter);
startCombatButton.onClick.AddListener(StartCombat);
stopCombatButton.onClick.AddListener(StopCombat);
// Populate the character type dropdown
characterTypeDropdown.ClearOptions();
characterTypeDropdown.AddOptions(new List<string> { "Warrior", "Mage", "Rogue" });
// Update the UI
UpdateCharacterList();
}
private void CreateCharacter()
{
string name = characterNameInput.text;
if (string.IsNullOrEmpty(name))
{
name = "Unnamed";
}
string type = characterTypeDropdown.options[characterTypeDropdown.value].text;
Character character = characterManager.CreateCharacter(type, name);
if (character != null)
{
UpdateCharacterList();
}
}
private void StartCombat()
{
List<Character> characters = characterManager.GetAllCharacters();
foreach (Character character in characters)
{
combatSystem.AddCombatant(character);
}
combatSystem.StartCombat();
}
private void StopCombat()
{
combatSystem.StopCombat();
}
private void UpdateCharacterList()
{
// Clear the current list
foreach (Transform child in characterListContainer)
{
Destroy(child.gameObject);
}
// Get all characters
List<Character> characters = characterManager.GetAllCharacters();
// Create a list item for each character
foreach (Character character in characters)
{
GameObject listItem = Instantiate(characterListItemPrefab, characterListContainer);
CharacterListItem item = listItem.GetComponent<CharacterListItem>();
if (item != null)
{
item.Initialize(character, this);
}
}
}
public void RemoveCharacter(Character character)
{
characterManager.RemoveCharacter(character);
UpdateCharacterList();
}
}
// Component for character list items
public class CharacterListItem : MonoBehaviour
{
[SerializeField] private TMP_Text characterNameText;
[SerializeField] private TMP_Text characterTypeText;
[SerializeField] private TMP_Text characterStatsText;
[SerializeField] private Button removeButton;
private Character character;
private CharacterUI characterUI;
public void Initialize(Character character, CharacterUI characterUI)
{
this.character = character;
this.characterUI = characterUI;
// Set up the UI elements
characterNameText.text = character.Name;
characterTypeText.text = character.GetType().Name;
characterStatsText.text = $"Level: {character.Level}, HP: {character.CurrentHealth}/{character.MaxHealth}, Mana: {character.CurrentMana}/{character.MaxMana}";
// Set up the remove button
removeButton.onClick.AddListener(RemoveCharacter);
}
private void RemoveCharacter()
{
if (character != null && characterUI != null)
{
characterUI.RemoveCharacter(character);
}
}
private void Update()
{
// Update the stats text
if (character != null)
{
characterStatsText.text = $"Level: {character.Level}, HP: {character.CurrentHealth}/{character.MaxHealth}, Mana: {character.CurrentMana}/{character.MaxMana}";
}
}
}
Step 8: Set Up the Unity Scene
- Create a new Unity scene
- Add an empty GameObject named "Managers"
- Add the
CharacterManager
andCombatSystem
components to the Managers GameObject - Create a UI canvas with:
- Input field for character name
- Dropdown for character type
- Buttons for creating characters and starting/stopping combat
- A panel for displaying the character list
- Add the
CharacterUI
component to the canvas and set up the references
Testing Your Project
Once you've implemented all the components, test your project by:
- Creating characters of different types
- Starting combat to see how the different character types interact
- Checking that polymorphism works correctly (e.g., all attackers can attack regardless of their specific type)
- Verifying that each character type's unique abilities work as expected
Extensions
If you want to extend the project, here are some ideas:
- Add more character classes (e.g.,
Cleric
,Ranger
,Paladin
) - Implement an inventory system for equipment
- Add a leveling system with experience points
- Create a more sophisticated combat system with turns and initiative
- Add status effects (e.g., poison, stun, buff)
- Implement a quest system
Conclusion
In this mini-project, you've applied many of the object-oriented programming concepts you've learned:
- Abstract classes and inheritance
- Interfaces and polymorphism
- Structs for data representation
- Encapsulation of behavior and data
You've created a foundation for an RPG character system that demonstrates good OOP design principles. This project can serve as a starting point for more complex game systems as you continue to develop your Unity skills.
Remember that good OOP design is about creating clear, maintainable, and extensible code. As you expand this project or create new ones, keep these principles in mind to build robust and flexible systems.