Небольшое вступление

В данной статье хотелось бы рассказать и показать как мы реализовали идею установки улучшений (апгрейдов) для грузовиков в нашем проекте.
Стояла интересная задача — позволить игроку устанавливать апгрейды на свой автомобиль, а также снимать их. При всём при этом нужно учитывать влияние эффектов, которые будут изменять характеристики грузовика, в зависимости от того установлено сейчас конкретное улучшение или нет. С виду всё кажется довольно просто, давайте же попробуем реализовать задуманное.

Ремарка:
Наш проект делается на Unity с использованием языка программирования C#. Если вы с ним не знакомы, то советуем для начала ознакомиться с основами. Например здесь есть неплохой базис http://www.tutorialspoint.com/csharp/ . Без осознания основных принципов есть риск не понять содержание статьи.

 

Приступаем к реализации

Для начала создадим класс Upgrade, который будет служить основой для наших будущих улучшений:

public abstract class Upgrade
{
    public virtual string Name { get; set; } // Название апгрейда
    public virtual float Price { get; set; } // Его цена
 
    public virtual void ApplyUpgradeEffect(Car car)
    {
        // метод, который будет отвечать за применение
        // эффектов к определенной машине
    }
 
    public virtual void RemoveUpgradeEffect(Car car)
    {
        // метод, который будет отвечать за снятие
        // эффектов c определенной машины
    }
 
    public override string ToString()
    {
        // код для вывода отформатированного описания апгрейда в консоль
        return String.Format("Name = {0}, Price = {1}", Name, Price);
    }
}

 

Как вы, наверняка, заметили, в методы ApplyUpgradeEffect и RemoveUpgradeEffect мы передаем экземпляр класса Car. Здесь я не буду расписывать класс идентичный тому, который используется в нашем проекте, так как слишком многое будет лишним и не будет касаться темы статьи, поэтому ограничимся лишь необходимым. К тому же, отмечу, что наш класс статический, что говорит о том, что мы не можем создавать его экземпляры. Вы спросите: «Почему, ведь нам нужны реальные улучшения?». Пока скажу лишь то, что в будущем нам это пригодится, но не будем забегать вперед. Итак, класс автомобиля выглядит следующим образом:

public class Car
{
    // параметры, которые мы планируем улучшать
    public float MaxHull { get; set; } // максимальная прочность автомобиля
    public float MaxFuel { get; set; } // максимальная вместимость топливного бака
    public float EnginePower { get; set; } // мощность двигателя
 
    public List<Upgrade> InstalledUpgrades;
 
    public Car()
    {
        InstalledUpgrades = new List<Upgrade>();
    }
 
    public void AddUpgrade(Upgrade upgrade)
    {
        // добавляем эффект 
        upgrade.ApplyUpgradeEffect(this);
        InstalledUpgrades.Add(upgrade);
    }
 
    public void RemoveUpgrade(Upgrade upgrade)
    {
        // удаляем эффект
        upgrade.RemoveUpgradeEffect(this);
        InstalledUpgrades.Remove(upgrade);
    }
 
    public override string ToString()
    {
        // код для вывода отформатированного описания машины в консоль
        return String.Format("MaxHull = {0}, MaxFuel = {1}, EnginePower = {2}", MaxHull, MaxFuel, EnginePower);
    }
}

 

Итак, сейчас у нас есть возможность создать машину с определенными характеристиками и добавить к ней апгрейд или снять его. Но у нас есть одна проблема —  сейчас наши методы, отвечающие за применение эффектов от улучшений, пусты, а ведь они должны менять какие-то характеристики автомобиля, иначе что же это за апгрейды такие. Но какая логика должна быть заложена в эти методы, чтобы получить то, что нам нужно?
Давайте исходить от того, что есть: мы имем машину, у которой есть 3 характеристики. Мы хотим их менять. Если делать просто, то можно создать 3 дополнительных класса, каждый из которых будет наследоваться от класса Upgrade (напр. UpgradeHull, UpgradeEngine и т.д.), в каждом из которых методы, отвечающие за применение эффектов, будут перегружены для изменения конкретной характеристики автомобиля. К примеру, класс UpgradeEngine может выглядеть так:

class UpgradeEngine : Upgrade
{
    public override void ApplyUpgradeEffect(Car car)
    {
        // увеличим мощность двигателя на 20
        car.EnginePower += 20;
    }
 
    public override void RemoveUpgradeEffect(Car car)
    {
        // вернем старое значение мощности
        car.EnginePower -= 20;
    }
}

 

Вроде бы всё хорошо, можно на этом успокоиться. Но давайте подумаем. А что если нам захочется сделать апгрейды, которые будут менять несколько характеристик сразу? Например, какое-нибудь улучшение, дающее нам +10 к прочности и +15 к размеру бака. Получается для него нужно создавать отдельный класс а-ля UpgradeHullAndFuel. А потом мы захотим ещё один, но уже меняющий все 3 харакетристики, и для него тоже нужен будет свой класс. Ещё прибавьте к этому классы для различных вариаций этих параметров. А уж если вдруг захочется добавить ещё одну характеристику, а то и больше… Представляете как много комбинаций образуется и как много классов потребуется создавать? В случае с большим числом характеристик это число устрашает, да и запутаться можно очень легко. Но не стоит отчаиваться, ведь этого можно избежать. Сейчас я покажу вам как можно реализовать подобные сценарии, при этом не создавая больше одного дополнительного класса на одну характеристику машины. Заинтересовал? Давайте разбираться вместе.

 

Пробуем новый подход

Для воплощения идеи в жизнь мы возьмем за основу один из шаблонов (паттернов) проектирования, а именно — шаблон “декоратор” (моя реализация может быть не совсем точна, так что советую ещё самим почитать в интернете на эту тему, если интересно). Если коротко, то он позволит нам создавать многоплановые улучшения, состоящие из узкоспециализированных. Давайте приступим.

Создадим класс UpgradeDecoratorBase, который станет основой для декораторов, предназначенных для изменения определенных характеристик:

public class UpgradeDecoratorBase : Upgrade
{
    public Upgrade Upgrade;
 
    public override string Name
    {
        get { return Upgrade.Name; }
        set { Upgrade.Name = value; }
    }
 
    public override float Price
    {
        get { return Upgrade.Price; }
        set { Upgrade.Price = value; }
    }
 
    public UpgradeDecoratorBase(Upgrade upgrade)
    {
        Upgrade = upgrade;
    }
 
    public override void ApplyUpgradeEffect(Car car)
    {
        Upgrade.ApplyUpgradeEffect(car);
    }
 
    public override void RemoveUpgradeEffect(Car car)
    {
        Upgrade.RemoveUpgradeEffect(car);
    }
}

 

Теперь создадим 3 класса-декоратора для 3 характеристик автомобиля. Вот класс для апгрейда двигателя, по его лекалу можно создать оставшиеся два, соответственно именуя необходимые методы и меняя нужные свойства:

public class UpgradeDecoratorEngine : UpgradeDecoratorBase
{
    public float MaxEnginePowerAddValue;
 
    public UpgradeDecoratorEngine(Upgrade upgrade, float value)
        : base(upgrade)
    {
        MaxEnginePowerAddValue = value;
    }
 
    public override void ApplyUpgradeEffect(Car car)
    {
        UpMaxEnginePower(car, MaxEnginePowerAddValue);
        Upgrade.ApplyUpgradeEffect(car);
    }
 
    public override void RemoveUpgradeEffect(Car car)
    {
        DownMaxEnginePower(car, MaxEnginePowerAddValue);
        Upgrade.RemoveUpgradeEffect(car);
    }
 
    private void UpMaxEnginePower(Car car, float value)
    {
        // приватный метод для увеличения значения мощности двигателя
        // вызывается при установке апгрейда
        car.EnginePower += value;
    }
 
    private void DownMaxEnginePower(Car car, float value)
    {
        // приватный метод для уменьшения значения мощности двигателя
        // вызывается при снятии апгрейда
        car.EnginePower -= value;
    }
}

 

После этого нам нужно не забыть добавить ещё один класс, мы назовем его UpgradeConcrete. Он позволит нам создавать изначальные апгрейды, на которые мы уже будем навешивать декораторы, чтобы в итоге получить итоговое улучшение с необходимыми характеристиками. Его код выглядит так:

public class UpgradeConcrete : Upgrade
{
    public override string Name { get; set; }
    public override float Price { get; set; }
 
    public override void ApplyUpgradeEffect(Car car)
    {
        Console.WriteLine("Апгрейд установлен!");
    }
 
    public override void RemoveUpgradeEffect(Car car)
    {
        Console.WriteLine("Апгрейд удален!");
    }
}

 

Ну что же, в целом функционал мы реализовали, осталось лишь его проверить. Давайте создадим несколько апгрейдов и добавим их в список:

// ---- создаем разные апгрейды
//+1 хар-ка
Upgrade baseUpgrade = new UpgradeConcrete
{
    Name = "Увеличивает бак",
    Price = 500
};
var advancedFuel = new UpgradeDecoratorFuel(baseUpgrade, 5); // увеличим бак на 5
 
//+3 хар-ки
baseUpgrade = new UpgradeConcrete
{
    Name = "Улучшает все 3 хар-ки",
    Price = 3000
};
var fisrtGrade = new UpgradeDecoratorFuel(baseUpgrade, 10); // увеличим бак на 10
var secondGrade = new UpgradeDecoratorHull(fisrtGrade, 20); // укрепим корпус на 20
var superBoost = new UpgradeDecoratorEngine(secondGrade, 40); // усилим двигатель на 40
 
List<Upgrade> upgrades = new List<Upgrade>
{
    advancedFuel, 
    superBoost
};

 

А теперь, немного модифицируем класс Car. А точнее, добавим возможность машине знать, какие апгрейды на ней установлены. Добавляем следующий код после объявления свойств:

public List<Upgrade> InstalledUpgrades; // список апгрейдов, установленных на машине
 
public Car()
{
    // инициализируем список при создании новой машины
    InstalledUpgrades = new List<Upgrade>(); 
}

 

А обновленные методы работы с улучшениями будут выглядеть так:

public void AddUpgrade(Upgrade upgrade)
{
    // добавляем эффект 
    upgrade.ApplyUpgradeEffect(this);
    InstalledUpgrades.Add(upgrade);}
 
public void RemoveUpgrade(Upgrade upgrade)
{
    // удаляем эффект
    upgrade.RemoveUpgradeEffect(this);
    InstalledUpgrades.Remove(upgrade);}

 

А теперь пробуем добавить/удалить апгрейды и смотрим на характеристики машины:

//не забываем про то, что машину перед этим нужно создать 🙂
var car = new Car
{
    MaxFuel = 100,
    MaxHull = 100,
    EnginePower = 100,
};
 
//устанавливаем апгрейды
foreach (var upgrade in upgrades)
{
    Console.WriteLine(upgrade);
    car.AddUpgrade(upgrade);
    Console.WriteLine("Новые хар-ки машины с апгрейдом:");
    Console.WriteLine(car);
}
 
Console.WriteLine();
 
//удаляем апгрейды
foreach (var upgrade in upgrades)
{
    Console.WriteLine(upgrade);
    car.RemoveUpgrade(upgrade);
    Console.WriteLine("Новые хар-ки машины без апгрейда:");
    Console.WriteLine(car);
}

 

Консольный вывод:

Name = Увеличивает бак, Price = 500
Апгрейд установлен!
Новые хар-ки машины с апгрейдом:
MaxHull = 100, MaxFuel = 105, EnginePower = 100
Name = Улучшает все 3 хар-ки, Price = 3000
Апгрейд установлен!
Новые хар-ки машины с апгрейдом:
MaxHull = 120, MaxFuel = 115, EnginePower = 140
 
Name = Увеличивает бак, Price = 500
Апгрейд удален!
Новые хар-ки машины без апгрейда:
MaxHull = 120, MaxFuel = 110, EnginePower = 140
Name = Улучшает все 3 хар-ки, Price = 3000
Апгрейд удален!
Новые хар-ки машины без апгрейда:
MaxHull = 100, MaxFuel = 100, EnginePower = 100

 

Заключение

Запускаем — всё работает. Ну, как минимум, должно.

На этом всё. Надеюсь вы почерпнули что-то интересное из этой статьи и это вам где-нибудь пригодится, либо подвигнет на изучение чего-то нового. Спасибо за прочтение (:

P.S.
А здесь вы можете скачать готовый проект Visual Studio с полной реализацией описанного в статье.

 

Filed under:

JUMP TO TOP
SHARE THIS POST
Apologies, for this post the comments are closed.
512 Studio
TAKING TOO LONG?
CLICK/TAP HERE TO CLOSE LOADING SCREEN.