Нужен совет C # OOP: Какая для этого правильная структура данных? - PullRequest
3 голосов
/ 24 июня 2011

Хорошо, мне нужен твердый ООП совет. Вот настройка: у меня есть объект игрока с кодом, который обрабатывает такие вещи, как движение, логика поиска пути и т. Д. Теперь мне нужно назначить каждому игроку большую таблицу переменных и настроек. А именно, у каждого игрока есть около 40 различных действий с оружием, которые он может выполнить, и с каждым оружием связано 6-8 переменных. Мой вопрос в том, какая структура данных является наиболее подходящей. Обратите внимание, я не хочу создавать класс для каждого оружия, потому что оружие никогда не создается в моей игре.

Вот особенности: у каждого игрока есть 40 различных видов оружия, которые нужно вытащить по имени. Например:

Weapon #1: Pistol
Weapon #2: Shotgun
Weapon #3: Rifle
Weapon #4: Bow
Weapon #5: RocketLauncher

Каждому оружию присваивается одинаковый набор переменных. Например:

Pistol.HasBeenUnlocked (true/false)
Pistol.Priority (1-40)
Pistol.AmmoQuantityMax (0-10)
Pistol.AmmoQuantityCurrent (0-10)
Pistol.UIButtonState ("Hidden", "Selected", "Deselected", "CoolOff")
Pistol.CoolOffTimerMax (0.0-5.0)
Pistol.CoolOffOn (true/false)

Теперь мне нужна какая-то структура данных, которая организует все эти переменные и облегчает доступ к ним. Например, я хочу иметь возможность циклически перебирать все 40 видов оружия и генерировать для них кнопки, основываясь на том, были ли они разблокированы конкретным игроком. Вот что я хочу сделать:

for (int i = 0; i < Player.Weapons.NumberOfWeaponsTotal(); i++) {
     if (Player.Weapons.Weapon[i].UIButtonState == "Hidden"){
          // don't create a UI button to fire weapon
     }
     else {
          // create a UI button to fire weapon
     }
}

Имена переменных для разных видов оружия будут одинаковыми. Таким образом, Weapons.Pistol.UIButtonState будет существовать, как и Weapons.Rifle.UIButtonState.

Так, каков наилучший способ организовать это? 2D-массив не очень хорош. Структура не кажется достаточно надежной. Класс видит странное. Я никогда не использовал классы для такого рода вещей. Я хочу убедиться, что это каким-то образом связано с игроком, поэтому, когда он уничтожен, то же самое относится и к этому «объекту».

Ответы [ 3 ]

4 голосов
/ 24 июня 2011

Я думаю, что классы были бы идеальными для этого сценария. Создайте интерфейс IWeapon, который имеет общие свойства и методы, такие как Name, GetAmmoCount(), IsEnabled и т. Д. Затем создайте классы для каждого типа оружия со всеми конкретными свойствами. (Но если у вас есть 8 разных пистолетов с одинаковыми методами и свойствами, вы можете создать общий класс Pistol и просто установить соответствующие переменные).

У объекта Player есть List<IWeapon> для хранения всего оружия, которое есть у игрока.

Когда Player уничтожен, просто вызовите метод .Empty() в Списке, чтобы удалить их всех.

Удерживаемое оружие станет дополнительным членом класса Игрока: IWeapon ActiveWeapon.

Вы можете делать такие вещи, как:

if(ActiveWeapon.GetAmmoCount() > 0) ActiveWeapon.Fire());

Или:

this.ActiveWeapon = new Pistol("LaserPistol");
this.Weapons.Add(this.ActiveWeapon);

this.ActiveWeapon = new AssaultRifle();

Преимущество здесь в том, что ваш Player не должен точно знать, какие типы оружия они могут иметь до времени выполнения, и вы можете легко поменять его на другое.

Редактировать: Я нашел страницу с примером этого, используя Шаблон дизайна стратегии : http://hnaser.blogspot.com/2009/07/abc-of-design-patterns.html

1 голос
/ 24 июня 2011

Отказ от ответственности

Вы можете использовать такую ​​модель для архивирования того, что вам нужно.Пожалуйста, имейте в виду, что, как и любой дизайн, он может содержать недостатки.Он предназначен для того, чтобы помочь вам решить вашу проблему, а не как решение.

Оружие

Этот класс представляет оружие в вашем приложении.Обратите внимание на переопределяющие методы для правильной реализации сравнения / поиска / индексации.

public class Weapon
{
    public Weapon(WeaponName name)
    {
        Name = name;
    }

    public WeaponName Name { get; set; }
    public int Priority { get; set; }
    public int MaxAmmoQuantity { get; set; }
    public int CurrentAmmoQuantity { get; set; }

    public override string ToString()
    {
        return Enum.GetName(typeof(WeaponName), Name);
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;

        var other = obj as Weapon;
        if (other == null) return false;

        return Name == other.Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

WeaponName

Исправлены опции для названий оружия (при условии, как вы сказали,у них будет ограниченное количество вариантов)

public enum WeaponName
{
    Pistol,
    Shotgun,
    Rifle,
    Bow,
    RocketLauncher
}

Player

Обратите внимание:

  • Внутри вы можете хранить оружие вструктура, которая вам нравится, но для внешнего мира публикуется только экземпляр IEnumerable
  • Он представляет собой тот факт, что игрок может взять оружие или поменять активное (здесь вы можете добавить любые необходимые действия).Через события игрок сообщает внешнему миру о своих действиях.
  • Вы передаете методы объекта семантике.Вместо player.Weapons.Add вы явно указываете поведение игрока.

    открытый класс Player {private readonly IList _ownedWeapons = new List ();защищенное оружие ActiveWeapon {get;приватный набор;}

    public IEnumerable<WeaponName> OwnedWeapons { get; set; }
    
    public event EventHandler<WeaponEventArgs> WeaponGrabbed;
    public event EventHandler<WeaponEventArgs> ActiveWeaponChanged;
    
    public void InvokeActiveWeaponChanged()
    {
        var handler = ActiveWeaponChanged;
        if (handler != null) handler(this, new WeaponEventArgs(ActiveWeapon));
    }
    public void InvokeWeaponGrabbed(Weapon weapon)
    {
        var handler = WeaponGrabbed;
        if (handler != null) handler(this, new WeaponEventArgs(weapon));
    }
    
    public void SwitchWeapon(WeaponName weaponName)
    {
        ActiveWeapon = _ownedWeapons.Where(weapon => weapon.Name == weaponName).First();
        InvokeActiveWeaponChanged();
    }
    public void Grab(Weapon weapon)
    {
        _ownedWeapons.Add(weapon);
        InvokeWeaponGrabbed(weapon);
    }
    

    }

    открытый класс WeaponEventArgs: EventArgs {public WeaponEventArgs (оружие оружие) {Weapon = оружие;}

        public Weapon Weapon { get; set; }
    }
    

WeaponsRepository

  • Централизует все знания об оружии, которые игрок может использовать в вашем приложении, регистрируя доступные названия оружия иих создание в конструкторе.
  • Кроме того, он предоставляет методы для создания и запроса доступного оружия.
  • Вы можете определить, какого оружия у игрока еще нет, используя метод WeaponsNotOwnedBy.

    открытый класс WeaponRepository {только для чтения словарь> _availableWeapons = new Dictionary> ();

    public WeaponRepository()
    {
        _availableWeapons.Add(WeaponName.Pistol, () => new Weapon(WeaponName.Pistol) );
        _availableWeapons.Add(WeaponName.Shotgun, () => new Weapon(WeaponName.Shotgun) );
    }
    
    public Weapon Create(WeaponName name)
    {
        return _availableWeapons[name]();
    }
    
    public IEnumerable<WeaponName> AvailableWeapons()
    {
        return Enum.GetValues(typeof (WeaponName)).Cast<WeaponName>();
    }
    
    public IEnumerable<WeaponName> WeaponsNotOwnedBy(Player player)
    {
        return AvailableWeapons().Where(weaponName => !player.OwnedWeapons.Contains(weaponName));
    }
    

    }

Использование

  • Вы можете поручить классу создать и обновить панель инструментов игрока.
  • Создать одну кнопку для каждого оружия, используя метод AvailableWeapons в WeaponRepository в методе BuildToolbar.
  • В этом классе подписаться на событие игрока.Таким образом, вы можете включать / отключать / выделять активное оружие по мере необходимости.

Примечание: я для краткости опускаю WeaponButton

public class WeaponToolbarBuilder
{
    private readonly Player _player;
    private readonly WeaponRepository _weaponRepository;
    private List<WeaponButton> _buttons = new List<WeaponButton>();

    public WeaponToolbarBuilder(Player player, WeaponRepository weaponRepository)
    {
        _player = player;
        _weaponRepository = weaponRepository;

        _player.ActiveWeaponChanged += _player_ActiveWeaponChanged;
        _player.WeaponGrabbed += _player_WeaponGrabbed;
    }

    void _player_WeaponGrabbed(object sender, WeaponEventArgs e)
    {
        var weaponButton = _buttons.Where(button => button.WeaponName == e.Weapon.Name).First();
        weaponButton.Enable();
    }

    void _player_ActiveWeaponChanged(object sender, WeaponEventArgs e)
    {
        var currentActiveButton = _buttons.Where(button => button.Highlighted).First();
        currentActiveButton.Highlight(false);

        var newActiveButton = _buttons.Where(button => button.WeaponName == e.Weapon.Name);
        newActiveButton.Highlight(true);
    }

    public void BuildToolbar() { ... }
}
1 голос
/ 24 июня 2011

Определите структуру или тип класса, который содержит все свойства, которые имеют все различные виды оружия.Создайте массив [40] этого типа оружия и сохраните его как свойство типа игрока.Определите перечисление целых чисел для использования в качестве именованных индексов в массиве оружия.Пистолет = 0, Дробовик = 1 и т. Д. Используйте эти имена перечислений для индексации массива оружия в вашем коде.

В редких случаях, когда вам нужно найти оружие по строковому имени, вы можете просто сканировать массив оружия, пока не найдете оружие, имя которого соответствует тому, что вы ищете.Захватите индекс этого оружия и выбросьте имя строки, чтобы все дальнейшие операции выполнялись с помощью индексированного массива.Стоимость линейного сканирования по 40 объектам не будет заметна.

Если вам действительно действительно нужно избегать сканирования списка на предмет совпадения имени, вы можете создать словарь, чтобы обеспечить быстрый поиск по имени.Вы захотите, чтобы записи словаря возвращали оружие конкретного игрока, поэтому вам понадобится отдельный экземпляр словаря для каждого игрока, и он будет храниться в поле объекта игрока.Если простого сканирования достаточно (почти наверняка), не беспокойтесь о словарном подходе.

Независимо от того, определяете ли вы тип оружия как структуру или класс, это структурированный тип, и высоздаем экземпляры этого типа каждый раз, когда вы его используете.40 экземпляров на игрока.Основное различие между классом и структурой состоит в том, где / как распределена память и копирует ли присвоение значения или нет.Если вы осторожны, чтобы никогда не назначать оружие из массива в локальную переменную оружия, и вы не используете словарь, описанный выше, или делаете что-либо еще, чтобы делиться ссылками на те же данные об оружии, тогда структура должна быть в порядке.Если сомневаетесь, используйте класс.

Вам не нужно беспокоиться о том, какое оружие будет выброшено, когда игрок уходит - пока массив оружия является единственной вещью, которая сохраняет долгосрочную ссылку наоружие, тогда они все будут уничтожены, когда игрок будет уничтожен.Правильная инкапсуляция и минимизация перекрестных ссылок между различными объектами помогут здесь.

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

Если вы начинаете попадать в особые случаи, когда это действие ведет себя по-разному для этого оружия, или у этого оружия есть дополнительные данные, которые ни один издругое оружие нужно, тогда вы должны определить базовый класс оружия и его потомков для специализации по мере необходимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...