Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Flash > ActionScript 3.0

Версия для печати  Отправить по электронной почте    « Предыдущая тема | Следующая тема »  
Опции темы Опции просмотра
 
Создать новую тему Ответ
Старый 20.06.2018, 16:35
Appleman вне форума Посмотреть профиль Отправить личное сообщение для Appleman Найти все сообщения от Appleman
  № 1  
Ответить с цитированием
Appleman
 
Аватар для Appleman

Регистрация: Dec 2014
Адрес: Санкт-Петербург
Сообщений: 479
По умолчанию Проблема "раздутого" класса

Друзья, поделитесь опытом.

У меня по мере разработки игры слишком разросся класс Character. Причём если смотреть на него с т.з. ООП, то вроде всё на месте. Есть штук 25 постоянных и неизменных на протяжении всей игры свойств, ещё штук 20 изменяемых параметров, описывающих текущее состояние, полтора десятка умений, что-то ещё по мелочам. Вся ботва с сеттерами/геттерами - это уже около 700 строк кода. Дальше идут методы. Их много, т.к. много чего должно рассчитываться для персонажа, в том числе по-своему в зависимости от наследника Character. Так что особо не уберёшь никуда.

Таким образом, даже реализовав менеджер экипировки и менеджер статус-эффектов как отдельные классы, всё равно кол-во строк зашкалило за 1500. Работать с такой громадиной становится затруднительно. Как решаются подобные ситуации?

Единственное, что пока приходит в голову, это сделать отдельный класс CharacterData для неизменных "справочных" значений и передавать в конструктор каждого экземпляра Character. Но всё равно будет геморрой с созданием наследников CharacterData, плюс в самом Character не обойтись без записей записей типа:

Код AS3:
public function get intelligence() : Number {return _data.intelligence}
Поэтому эффективность такого решения остаётся под вопросом.
__________________
Не сломано - не чини!

Старый 22.06.2018, 19:02
amesqit вне форума Посмотреть профиль Отправить личное сообщение для amesqit Найти все сообщения от amesqit
  № 2  
Ответить с цитированием
amesqit
 
Аватар для amesqit

Регистрация: Oct 2007
Сообщений: 255
Записей в блоге: 1
Если я верно понимаю проблему, то она решается цепочкой наследования, не обязательно чтобы у каких-то сущностей было несколько потомков, сущность может выступать в роли родителя просто ради того чтобы упростить структуру детей. Так напрмер когда-то я делал сущность определяющую взаимоотношения Видео и Рекламы. В зависимости от типа рекламы и момента воспроизведения видео таких взаимоотношений получалось доасточно много, я решил эту проблему сделав цепочку наследования типа Base->WithPreroll->WithMidroll->WithPostroll... При поддержке подобного кода и обнаружении проблем в прероллах или мидроллах в первую очередь смотрел что не так в специфичных сущностях. Думаю у вас тоже можно выделить какие-то более базовые логичные сущности определяющие конкретные аспекты вашего класса.
__________________
ответ прост: be || !be == true

Старый 22.06.2018, 21:18
Wolsh вне форума Посмотреть профиль Отправить личное сообщение для Wolsh Найти все сообщения от Wolsh
  № 3  
Ответить с цитированием
Wolsh
Нуб нубам
 
Аватар для Wolsh

модератор форума
Регистрация: Jan 2006
Адрес: Бердск, НСО
Сообщений: 6,445
Навряд ли тут вообще есть какие-то линейные решения. Если пойти линейно, то всякое живое существо должно наследоваться от сундука, так ведь? Ибо у любого существа, которое может быть убито, есть какой-то лут: мясо для готовки, ингридиенты для алхимии, компоненты для крафтинга (шкуры, рога, клыки, перья, чешуя и тп), а у человекоподобных вообще любые предметы (в Скайриме добавили "правдоподобности", и в хищниках можно найти мелкие неперевариваемые предметы — монетки, кольца, драгоценные камни))) Даже в самой "минимально навороченной" игре должна быть возможность что-то взять с трупа хотя-бы босса, оружие или квестовый предмет. Но при этом сам набор лута генерится только после смерти рандомно (в пределах возможного для данного вида предметов). То есть лут есть у Трупа, а у живого есть Поведение, которого нет у трупа. Должны ли это быть разные классы?
Нужна ли неписям система слотов для экипировки? Нужно ли нам разбираться (на уровне Модели, не Вью), что вот это надето на ноги, а это — на голову? Или достаточно списка вещей? Если неписи могут иметь дополнительное, то есть НЕ экипированное оружие, доспехи и амулеты, то нам нужно будет знать, какие предметы были экипированы и значит создавали активные эффекты, баффы и резисты. Кроме того, экипированные предметы не могут генериться после смерти непися. А дополнительные могут. Достаточно ли иметь список экипированного — для отображения персонажа и расчета эффектов, а после смерти генерить дополнительный список скрытого лута, и не иметь при этом менеджер слотов как у Героя, которого мы постоянно переодеваем и меняем оружие "вручную"? Ну, в Скае неписи иногда меняют оружие во время боя, лучники могут вытащить топор, а маги кинжал, если мана кончилась) А еще оружие можно выбить из рук.
Могут ли животные и монстры иметь эффекты и лечиться во время боя? А если это какой-нибудь волк-компаньон мага, или боевой конь — может маг на него накладывать резисты или баффы и лечить во время боя?
От таких нюансов может зависеть система наследования и архитектуры классов вообще.
Всегда можно попробовать вынести что-то из Персонажа в Досье о персонаже, то есть не хранить данные О персонаже непосредственно в нем самом, а брать из справочника по айди. Ежу понятно, что вся игра крутится вокруг Героя и все так или иначе замыкается на него. Но должно ли это "всё" именно храниться непосредственно в свойствах экземпляра Героя, или можно хранить это в "досье"? Название локации, координаты героя на локации, список квестов и их активные стадии, прогресс в умениях и прочие статистики — все ли нужно пихать в один объект? Ты и сам уже заметил, что вслед за данными подтягивается и их обработка: не успеешь глазом моргнуть, как экземпляр персонажа начнет у тебя проверять расстояние до стен в коридоре и траекторию полета стрелы, шанс попадания и время суток. Как только появляется нужда в обработке, следом приходит нужда в дополнительных данных из внешнего мира, и ты начинаешь ломать голову, как Герою запросить погоду или количество детей вот этого торговца... Данные должны (в идеале конечно) храниться там, где они обрабатываются. Если программа из совершенно разных мест постоянно лезет в один и тот же объект за данными, которые нужны ей только в ЭТОМ месте, значит что-то пошло не так. Пример: Менеджер квестов не должен дергать экземпляр Героя, чтобы узнать активную фазу текущего квеста. Он должен знать это САМ. Герой — один. Менеджер движения/локации не должен спрашивать у Героя его координаты — он должен хранить их САМ, они больше никому не нужны. Герой не должен хранить в себе специфические данные "для кого-то одного". Можно хранить данные, которые нужны многим и не должны быть рассинхронизированы так, что один менеджер считает что здоровье 100, а другой насчитал 15. Тогда есть смысл.
__________________
Reality.getBounds(this);


Последний раз редактировалось Wolsh; 22.06.2018 в 21:35.
Старый 25.06.2018, 14:14
Appleman вне форума Посмотреть профиль Отправить личное сообщение для Appleman Найти все сообщения от Appleman
  № 4  
Ответить с цитированием
Appleman
 
Аватар для Appleman

Регистрация: Dec 2014
Адрес: Санкт-Петербург
Сообщений: 479
Цитата:
Сообщение от amesqit Посмотреть сообщение
Если я верно понимаю проблему, то она решается цепочкой наследования, не обязательно чтобы у каких-то сущностей было несколько потомков, сущность может выступать в роли родителя просто ради того чтобы упростить структуру детей.
amesqit, спасибо, но боюсь, не мой случай. У меня класс персонажа уже наследуется в самых традиционных ООП-эшных целях переопределения методов наследниками. Т.е. суперкласс Character, а от него уже специфические CharacterNPC, CharacterPlayer и т.п. Причём в наследниках всё более-менее лаконично, а вот в супере - "мясо".

Добавлено через 52 минуты
Цитата:
Сообщение от Wolsh Посмотреть сообщение
Герой не должен хранить в себе специфические данные "для кого-то одного". Можно хранить данные, которые нужны многим и не должны быть рассинхронизированы так, что один менеджер считает что здоровье 100, а другой насчитал 15. Тогда есть смысл.
Wolsh, спасибо. Какой-никакой а уже подход Хотя да, я и сам заметил, что не зная деталей проекта и его специфики, трудно что-либо советовать по части архитектуры (тем более я благодарен всей аудитории, которая откликнулась на мои наивные вопросы, когда я только начинал).

Я пока выбрал для себя такой путь. Выделять в отдельные сущности какие-то тематически целостные вещи. Например, ты мне в своё время очень удачно посоветовал реализовать менеджер экипировки как отдельный класс, иначе класс Character ещё сильнее бы увеличился. Сейчас попробовал сделать SkillManager и загнать туда все скиллы, а также методы, связанные с моделью их увеличения.
__________________
Не сломано - не чини!


Последний раз редактировалось Appleman; 25.06.2018 в 15:07.
Старый 26.06.2018, 13:49
RedHead90 вне форума Посмотреть профиль Отправить личное сообщение для RedHead90 Найти все сообщения от RedHead90
  № 5  
Ответить с цитированием
RedHead90

Регистрация: Apr 2018
Сообщений: 42
Цитата:
Единственное, что пока приходит в голову, это сделать отдельный класс CharacterData для неизменных "справочных" значений и передавать в конструктор каждого экземпляра Character. Но всё равно будет геморрой с созданием наследников CharacterData, плюс в самом Character не обойтись без записей записей типа:

Код AS3:
public function get intelligence() : Number {return _data.intelligence}
Поэтому эффективность такого решения остаётся под вопросом.
А для чего дублировать все поля в Character, если можно дать ему всего один метод чтения

Код AS3:
private var _data:CharacterData;
 
public function get data():CharacterData {
    return _data;
}
Не зацикливайся на наследовании. Очень часто композиция может быть более эффективной. Особенно в играх. Ты уже выделил компонент CharacterData. Попробуй весь класс Character разбить на такие маленькие узкоспециализированные компоненты, а самому Character давать только ссылки на эти компоненты, без каких-либо знаний о них.

Почитай какие-нибудь статьи на тему плюсов и минусов композиции/наследования. Посмотри на Ash. Это довольно простой фреймворк, реализующий шаблон проектирования Entity-Component-System. Он основывается на использовании композиции вместо наследования. Чаще всего применяется именно для создания игр. Изучи принцип, по которому работает данный фреймворк и наверняка ты пересмотришь свои взгляды на многие вещи, касающиеся организации своего кода.

Старый 26.06.2018, 14:46
Appleman вне форума Посмотреть профиль Отправить личное сообщение для Appleman Найти все сообщения от Appleman
  № 6  
Ответить с цитированием
Appleman
 
Аватар для Appleman

Регистрация: Dec 2014
Адрес: Санкт-Петербург
Сообщений: 479
Цитата:
Сообщение от RedHead90 Посмотреть сообщение
Не зацикливайся на наследовании. Очень часто композиция может быть более эффективной.
Дык у меня как раз и использован композиционный подход как в приведённом в первом сообщении примере (который ты цитируешь), так и в последней реплике на счёт скилл-менеджера.
__________________
Не сломано - не чини!

Старый 26.06.2018, 16:07
RedHead90 вне форума Посмотреть профиль Отправить личное сообщение для RedHead90 Найти все сообщения от RedHead90
  № 7  
Ответить с цитированием
RedHead90

Регистрация: Apr 2018
Сообщений: 42
Appleman, Ну смотри. У тебя есть Character, который использует CharacterData. Да, это композиция, но...

В текущей реализации ты дублируешь все поля CharacterData в Character. Это значит, что изменяя CharacterData, тебе обязательно придется лезть в Character, повторяя все изменения также и в нем. Нарушается принцип черного ящика. Ты неизбежно будешь получать ошибки и добавишь себе кучу рутинной работы. Ты, конечно, можешь поручить рутину своей среде разработки, если она это умеет, используя интерфейсы или функцию делегирования методов, как, например в IntelliJ IDEA. Но даже это полностью не избавит тебя от рутины.

Чтобы избавить себя от рутины и кучи потенциальных ошибок, ты можешь сделать Character наследником CharacterData. Все изменения во втором автоматически будут менять и первый. Но тогда теряются все достоинства композиции.

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

Ну и четвертый вариант. Ты можешь реализовать все по шаблону ECS, как в Ash. Тогда у тебя вообще исчезнут такие классы, как Character, CharacterNPC, CharacterPlayer и т.д. в том виде, какие они сейчас. У тебя будут сущности, как контейнеры для всевозможных компонентов и будут узлы, которые определяют связки конкретных компонентов. Например, если сущность имеет такие компоненты, как CharacterData, Equipment, Inventory, Player, View, Position, то движок работает с ней, как с классом героя. Заменяем компонент Player на NPC, и это уже бот. Берем уже готовые компоненты Inventory, View, Position и добавляем ItemContainer, и эта сущность становится сундуком. Далее происходит магия. Все эти сущности имеют компоненты View и Position. Этих данных достаточно для того, чтобы поместить объект на игровую карту. Т.е. система, отвечающая за отображение объектов на карте может работать со всеми ими одинаково. И ей плевать, сундук это, дерево или NPC. Данные полностью отделяются от логики, зависимости между компонентами находятся не в самих компонентах, а выносятся на уровень выше. Раздутых классов нет, все лежит на своих местах и занимается одной определенной задачей. В общем это все долго можно расписывать, лучше погугли сам. Даже на этом сайте где-то были уроки по данному подходу.

Старый 26.06.2018, 18:03
ZergMaster вне форума Посмотреть профиль Отправить личное сообщение для ZergMaster Найти все сообщения от ZergMaster
  № 8  
Ответить с цитированием
ZergMaster
 
Аватар для ZergMaster

Регистрация: May 2008
Адрес: Питер
Сообщений: 385
Отправить сообщение для ZergMaster с помощью ICQ Отправить сообщение для ZergMaster с помощью Skype™
что-то я не вижу связи - каким образом ECS подход отменяет Наследование?) CharacterNPC и CharacterPlayer все равно будут наследоваться от Character - либо будет копипаста кода
__________________
while(live()) { hope(); }

Старый 27.06.2018, 13:21
RedHead90 вне форума Посмотреть профиль Отправить личное сообщение для RedHead90 Найти все сообщения от RedHead90
  № 9  
Ответить с цитированием
RedHead90

Регистрация: Apr 2018
Сообщений: 42
ZergMaster, а я и не утверждал, что ECS отменяет наследование. Хотя, в данном конкретном случае можно организовать все так:

класс Character - это узел;
классы CharacterNPC и CharacterPlayer его наследники, но их различия всего в одной строке (ссылке на частные компоненты);
классы NPC и Player - компоненты, не имеющие общих корней и описывающие все те данные, что присущи лишь одной из сущностей.

Код AS3:
class Character extends Node {
    var commonComponent_1;
    var commonComponent_2;
    ...
    var commonComponent_N;
}
 
class CharacterPlayer extends  Character {
    var player:Player;
}
 
class CharacterNPC extends  Character {
    var npc:NPC;
}
Т.е. наследование есть, но оно номинально.

Добавлено через 56 минут
Вообще, я сейчас прочитал все то, что написал и понял, что не совсем верно изложил свои мысли. Вся эта писанина относится не к выбору между наследованием и композицией(ECS), а именно к затронутой автором проблеме раздутого класса и способах её решения. Тут были предложены варианты решения через цепочку наследования. Я же предложил решение через композицию. Но, естественно, говоря "композиция", я имею в виду "композиция и наследование". Как мне кажется, если решать "проблему раздутого класса" чисто с помощью цепочки наследования, то мы получим некое разросшееся дерево, с высокой глубиной и при этом один корень будет иметь 2-3 класса. А если решать через ECS, то у нас будет несколько маленьких деревьев с одним корнем и одним поколением наследников. Разросшееся дерево в этом случае может получиться только у цепочки узлов, но это настолько маленькие классы, что запутаться в таком дереве будет очень сложно.


Последний раз редактировалось RedHead90; 27.06.2018 в 14:19.
Старый 27.06.2018, 14:54
Appleman вне форума Посмотреть профиль Отправить личное сообщение для Appleman Найти все сообщения от Appleman
  № 10  
Ответить с цитированием
Appleman
 
Аватар для Appleman

Регистрация: Dec 2014
Адрес: Санкт-Петербург
Сообщений: 479
RedHead90, спасибо за объяснения и примеры.

Цитата:
класс Character - это узел;
классы CharacterNPC и CharacterPlayer его наследники, но их различия всего в одной строке (ссылке на частные компоненты);
классы NPC и Player - компоненты, не имеющие общих корней и описывающие все те данные, что присущи лишь одной из сущностей.
Интересный подход. Но в случае если число свойств, различных для Player и NPC, невелико, то проблему раздутого класса не решает, а геморроя добавляет.

Я сам изначально вводил приватные переменные в классы-наследники (т.е. делал всё то же, но не в форме отдельного класса, а просто свойствами в имеющийся), а потом надоело мучиться и ловить исключения при обращении за ними в суперкласс Character. В итоге объявил всё в супере, а дальше уже разруливаю по ситуации переопределяемыми в наследниках сеттерами/геттерами.
__________________
Не сломано - не чини!

Создать новую тему Ответ Часовой пояс GMT +4, время: 12:54.
Быстрый переход
  « Предыдущая тема | Следующая тема »  
Опции темы
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.


 


Часовой пояс GMT +4, время: 12:54.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.