![]() |
Вопрос по проектированию
Камрады!
Сразу признаюсь, я пока порядочный чайник в ООП в целом и в AC3 в частности, но тема меня искренне прёт, поэтому честно пытаюсь грызть гранит. Последние полгода потихоньку писал диздок простенькой игрушки, параллельно штудируя Колина Мука со товарищи. Сейчас почувствовал, что абстрактно продумывать и читать надоело, хочется хоть немножко реализовать. Начал проектировать первый же пользовательский класс и сразу застрял. Need help. Имеем класс Character, экземплярами которого станут игровые персонажи. В его составе планировался ряд свойств для ключевых игровых показателей: условно "сила", "ловкость", "красота" и т.п. Изначально не видел в этой части никаких проблем - придумывай имена свойств, да добавляй set и get методы. Потом, почесав репу, тормознул. Мне подумалось, что каждое из этих свойств - само представляет некую систему, т.к. для каждого предусмотрено игровое название и текст всплывающей подсказки, которые нужно где-то хранить, словесное описание величины, которое будет разным для различных свойств ("слабак", "силач" для силы, "дурак", "гений" для ума и т.д.). По всему просится создание отдельного класса, потрохами чую. А вот как реализовать - не понимаю. То ли сделать отдельный класс и связать его с классом Character отношением композиции. Но в этом случае выходит, что каждый экземпляр класса Character должен иметь несколько различных экземпляров нового класса (для тех же "силы", "ловкости", ума" и т.п.) и работать с ними независимо. Не могу понять, возможно ли это реализовать в рамках ООП и правильно ли это делать? Или сделать некий единый класс для игровой характеристики, а все реально создаваемые унаследовать от него? Тогда получается чёткое присваивание каждой переменной в экземпляре класса персонажа конкретного подкласса (т.е. "сила" = new Strength, "ум"= new Wisdom и т.п.), никакой путаницы. Но как-то громоздко выглядит. Наконец, может быть я зря вообще пытаюсь городить огород и усложнять без необходимости? Возможно, нужно сделать, как изначально и планировалось - ограничиться свойствами класса Character, а всё "мясо", связанное с игровыми характеристиками, сделать-таки отдельным классом, но не пихать в класс персонажа, а связывать их посредством каких-то других способов (т.е. если нужно вывести инфо о силе персонажа, то отдельно выдёргиваем значение соответствующего свойства экземпляра класса персонажа и отдельно всю "воду", соответствующую этой характеристике из экземпляра класса игровых характеристик)? Надеюсь, понятно объяснил. Буду признателен за комментарии. Уверен, что описанный мною вопрос - "общее место" для опытных ООП программистов. Спасибо. |
Цитата:
Ну, типа Descriptions.getHint(propID:String, propValue:int):String Нет никакого смысла хранить в экземпляре персонажа библиотеку со словарями. Это ВООБЩЕ к персонажу не относится, это относится, если хотите, к интерфейсу, к показу на экране описаний того, что происходит. Этим не персонаж занимается, это не его ответственность. |
Цитата:
Цитата:
Цитата:
Цитата:
Здесь у меня тоже возникает уточняющий вопрос. Насколько я понимаю, все идентификаторы правильнее задавать константами, дабы не путаться. Продолжая наш пример, сделаем propID для силы константу const CHARACTER_STRENGTH_ID = 1, и т.д., чтобы потом не запоминать, где ноль, а где 25. Вопрос, учитывая систему областей видимости, в каком месте программы должны объявляться подобные константы? Ведь они будут использоваться уже как минимум в 2х отдельных классах: Character и Description. |
Еще рекомендую почитать "ActionScript 3.0. Шаблоны проектирования (Сандерс Уильям, Кумаранатунг Чандима)" или
"Приёмы объектно-ориентированного проектирования. Паттерны проектирования (Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес)" старая правда, но неплохо так вставляет. Я предлагаю для начала архитектуру разделить на 3 основных компонента. Data - библиотека классов статической константной информации, к которым можно обратиться по ссылке, не изменная и общедоступная, в целом это то что Wolsh назвал Description Model - классы логики игры, все сущности, весь жизненный цикл, вся логика всех сущностей View - отображение текущего состояния Model, является наблюдателем В основном для всех сущностей есть классы во всех этих трех компонентах. Затем нужно игру разбить на сущности, скорее всего есть сцена/бой/симуляция которая знает все обо всем, и есть сущности которые содержат в себе уникальную логику, например это Character Итого получается следующая иерархия: -data --BattleData содержится информация например сколько должно быть персонажей, время боя и тд. --CharacterData содержится информация о первоначальном здоровье, скорости, силе и тд. -model --BattleModel здесь игровой цикл, старт боя, создание остальных сущностей, обновление, завершение, знает о BattleData, создает CharacterModel --CharacterModel здесь логика перемещения, атаки, расчет урона, взаимодействия с остальными сущностями, знает о BattleModel и CharacterData -view --BattleView отображение боя, изометрия/3d/вид сверху, знает о BattleModel, создает CharacterView --CharacterView отображение персонажа, его анимация, эффекты, знает о BattleView и CharacterModel Далее с расширением функциональности, к примеру появляется сущность сундук добавляешь СhestData, ChestModel, ChestView. затем понимаешь что у модели сундука и персонажа есть одинаковые поля например position, делаешь родительский класс например EntityModel и наследуешь сущности от него (не рекомендую делать иерархию наследования больше 2-3). Для того что-бы понять как правильно: делаешь все в лоб, затем улучшаешь уменьшая количество полей/методов, затем раскидываешь область ответственности персонаж знает только о персонаже, сундук знает только о себе, бой знает обо всех сущностях и скрываешь все в них что не должны знать другие. Ты сначала правильно сделал, но зачем-то начал усложнять, не усложняй, разделил на Data/Model/View достаточно, все дополнительные финтиплюшки накручивай по необходимости, нужно отображать health над персонажем тогда и сделал get health у CharacterModel, не раньше, будет чище и сам во всем разберешься. что-бы строить чистую-правильную архитектуру надо прогнозировать, что-бы прогнозировать нужен опыт, нету опыта скрывай все поля/методы пока не понадобятся, наработаешь опыт объективного предсказания будешь наперед делать все как надо. Если хочешь сопровождай этот тестовый проект на github, я (может кто-то еще) могу оставлять комментарии, рекомендации на рефакторинг С Descriptions.getHint идея конечно хорошая, функциональный подход все дела, но я бы не рекомендовал так хранить константные данные, так как все превратиться в одномерную таблицу, лучше сделать нормальную иерархию классов-сущностей и её использовать, туда потом также можно накрутить десериализацию из json/xml/yaml, логику/стратегию чтения каких-то данных, приятнее будет с этим работать и очевиднее Можешь еще почитать про Entity-component-system, но с ней сложнее работать в угоду концепции компонентов в качестве множественного наследования CharacterModel extends IMovable, IAttackable |
Цитата:
Да, во многих играх используются идентификаторы-числа, типа 0xFD12A8. Для всего подряд, для классов предметов: оружия, одежды; для свойств персонажа; для квестов и их уровней (эпизодов), для локаций и т.п. Но в этом случае как раз 0xFD12A8 является ключом, а строка — значением, а не наоборот, как у тебя)) Это довольно сложная система, она оправдывает себя когда в игре тысячи понятий, но прямого отношения к ООП не имеет, просто один из инструментов наведения порядка в огромном хаосе. Суть ООП в правильном абстрагировании, распределении уникальности и общности. Цитата:
Код AS3:
Цитата:
|
Цитата:
Цитата:
Цитата:
Цитата:
Прочитав написанное тобой, возникло два очень важных вопроса: 1. Категорически не понял, как можно по строковому идентификатору вытащить значение из персонажа? Получается, что если в классе персонажа прописано свойство public var strength, то в другом месте программы мы можем создать строковую переменную var feature:String = "strength" и, написав, 'имя экземпляра'.feature, мы сможем обратиться к экземпляру класса? А как же использование Set и Get методов, инкапсуляция и всё такое? 2. Вторая идея, почему я предпочёл использовать в качестве идентификаторов целые положительные числа uint, заключается в том, что я не могу представить себе иного способа организованно хранить данные и обращаться к ним, кроме как с помощью массивов. А индекс массива - это всегда число типа uint. Даже в твоём примере ты обращаешься к массиву, т.е. должен в какой-то момент перейти от строки к числу. Или нет? Цитата:
|
Цитата:
то есть это равнозначно обращению _character.intelligence, если константа CHARACTER_INTELLIGENCE в классе Hash имеет значение "intelligence". И да, это может быть геттер или сеттер. Цитата:
То есть, ты предлагаешь ВООБЩЕ ВСЁ хранить в одном бесконечном массиве, а константы класса, вместо того чтобы просто содержать строку "strength", будут содержать индекс массива, по которому из него можно вытащить этот "strength"? Это уместно, когда "strength" может оказаться не "strength", например при смене языка мы заменяем весь массив текстов, а идентификаторы фраз остаются в коде как были. Но если ты начнешь на каждую фразу создавать константу, хранящую ее uint-идентификатор, то это будет адский перебор. Потому что этой константой ты будешь пользоваться ровно в одном месте, где прекрасно обошелся бы самим uint, если надо — с комментарием что это за фрукт)). Цитата:
|
Под иерархией проекта я говорю про содержимое проекта, то какие классы в нем содержатся и какие пакеты есть
минималистичный пример: Код AS3:
|
Цитата:
Получается, что в пакете data - вся справочная информация, в model - вся игровая механика, расчёты и т.п., а view - пакет для визуализации всего этого хозяйства. Понятно. Вопрос, в чём преимущество создания отдельных пакетов и постоянного импорта двух других в каждый, по сравнению с созданием единого пакета для всей игры, и настройки взаимодействия на уровне отношений исключительно между классами? И ещё ряд вопросов по коду: Код AS3:
Код AS3:
Последний мини-вопрос. Обратил внимание, что у тебя и у Wolsh многие переменные в коде записаны с символа "_", но не все. Какая тут логика? Я такое в некоторых книгах встречал (не у Мука). |
Цитата:
Код AS3:
|
Цитата:
Цитата:
Код AS3:
Цитата:
|
Цитата:
Цитата:
Другой класс - для имён, где хранятся имена, фамилии и прозвища. Там идентификаторов служит ID персонажа. Фактически тоже пачка массивов, забитых именами (на нужном языке). И да, я почитав твои предыдущие советы задумался о том, что "strength" может быть и "силой", а значит и само наименование свойства нужно не задавать жёстко, а брать откуда-то по некоему идентификатору. Хотя, насколько я понимаю, использование строковых идентификаторов такому подходу тоже не противоречит. Цитата:
|
Я вот вообще не хотел опускаться в разговоре до конкретного кода, просто потому что всегда есть 100500 способов решить задачу, и какой из них оптимальный, зависит от множества факторов. А когда начинаешь приводить код, выглядит так что ты настаиваешь на конкретно таком вот решении. А я вообще никак не настаиваю, просто показываю возможный вариант, ОК?
Код AS3:
|
Уважаемые Nooob и Wolsh!
Спасибо за ваши подробные объяснения. Всё прочитал, осознал и пошёл переваривать и экспериментировать. Думается мне, что для первого опыта программирования я в слишком сложную телегу впрягся :) По крайней мере я уже третий день туплю и даже не могу начать вообще что-либо программировать. Хотя понимание того, что я в любом случае всего сразу не предусмотрю, уже появилось. Поэтому думаю выбрать какой-то отдельный и законченный участок, который можно создать, и сконцентрируюсь на нём. Цитата:
Вот кстати ещё один методологический вопрос, над которым я размышляю. У всех персонажей есть одинаковые свойства, что логично. При этом у главного героя их больше всего (ибо ему условно не только руками махать, но и девок охмурять), у второстепенных NPC их поменьше, а у "проходных" - буквально 3-4 типа имени с фамилией. Вопрос, как в данной ситуации правильнее организовать: наследовать классы и потом воспользоваться преимуществами полиморфизма (ибо расчёты для центральных персонажей производятся иначе, чем для второстепенных) или создавать единый класс и просто не заполнять неиспользуемые поля значениями для второстепенных персонажей? От чего может зависеть решение? И вопрос камраду Nooob. Я правильно понял логику, что условно все шаблоны и правила расчёта игровой механики "живут" в data, а в model производятся только непосредственные "живые" расчёты для конкретных игровых ситуаций? То есть если действие "пендель" условно наносится с расстояния полметра, а его урон зависит от массы тапка персонажа и его умения бить ногами, то всё это хозяйство прописывается в качестве методов класса game.data.pendel, чтобы потом быть импортированным в model для расчёта по актуальным значениям. Или нет? |
Цитата:
|
Цитата:
твой пример с пенделем можно располагать в data, но если в игре появляются бафы которые влияют на массу тапка и его умения и его урон, то это перестает быть константными данными, и по большей части все данные уже берутся из модели, и логичнее всего такой метод переложить в model |
Nooob,
Можно узнать ваш способ организаций ответа на действия пользователя? (Controller) Как именно вы реализуете этот процесс. На любом простом примере, например, кнопка изучить скилл. |
Цитата:
Потихоньку разбирался и пробовал, в итоге из всех 100500 разнообразных решений для хранения нужных мне строковых данных выбрал всё-таки предложенный тобой код, что не удивительно :) Сделал в целом похожую конструкцию, она работает. Возникло 2 вопроса. Во-первых, каково преимущество статических переменных и методов перед обычными для подобных классов-"справочников"? В своём примере коллега Nooob, например, сразу после объявления класса создавал в нём константу с экземпляром класса-"справочника" и потом обращался непосредственно к ней. Во-вторых, я попробовал в классе Object с массивами расшифровок заменить прямое указание строковых идентификаторов ("agility", "strength") на константы, содержащие те же значения (попытка записи типа [GlobalValues.FEATURES_STRENGTH], имеющей значение "strength"). Ничего не вышло, выдаёт ошибку. Есть способ непрямого указания идентификаторов в теле Object? |
Цитата:
|
Спасибо, понял. А по второму вопросу есть какие-нибудь идеи?
|
Код AS3:
|
ZackMercury, обалдеть, кратко и эффективно. Всё заработало, спасибо!
Пошёл восхищаться и дальше учить мат. часть. Добавлено через 3 часа 29 минут Ещё раз поклон камраду ZackMercury, реализовал его совет в классе-хранителе игровых имён с выдачей оных в нужном падеже. Там же решено было складировать фамилии, наименования локаций и все прочие имена существительные, которые придётся по ходу игры склонять. Сначала написал несколько однотипных методов-получателей элементов созданных объектов по идентификатору и номеру падежа. Примерно так: Код AS3:
Код AS3:
|
Вообще, как правило, подобное не хранят в коде. Эта инфа выгружается в какой-то декларативный язык, вроде JSON/XML. Но как синтаксически возможный вариант:
Код AS3:
|
Спасибо, будем пробовать.
Согласен, что правильнее в XML. Но это мой первый проект, и так башка пухнет даже от простейших вещей типа синтаксиса. И если я могу вручную забить несколько примеров типа выложенных выше и поэкспериментировать с кодом, то XML формировать - целое отдельное дело. Насколько я понимаю, никто его руками и не формирует обычно. Так что это оставлю на будущее. :) |
Руками не формируют, в AS3 есть специальный класс для этого.
http://help.adobe.com/ru_RU/ActionSc...0204-7ff5.html |
Цитата:
Еще "поиграться": Код AS3:
|
Wolsh, ну ты просто добрый волшебник! :drinks: Я как раз начал задумываться о том, как можно технологично предусмотреть подстановку строковых переменных в заранее созданные куски текста. Спасибо.
Скажи, а добавлять в текст заготовки под будущую подстановку значений в виде *$n1 *$N1 - это чисто твой креатив или подобный формат *$ - результат требований AC3 или какого-либо соглашения, принятого среди программистов? Код AS3:
Цитата:
|
Цитата:
Цитата:
Цитата:
|
Цитата:
На счёт своего вопрос я имел в виду другую твою реплику, которую не процитировал: Цитата:
|
Цитата:
Цитата:
Скажем, окну настроек Персонажа не нужно знать названий локаций. Да если и нужно, дело не в этом. Просто люблю банальный порядок, когда мухи отдельно, котлеты отдельно. Да, с технической стороны они может быть одно и то же. Ну тогда и названия одежды, и еды в холодильнике, и товаров у торговца, и документов в шкафу у следователя. Тогда все в кучу? Фу. |
Цитата:
Я с позволения задам ещё пару вопросов по проектированию. К сожалению, пока никакого чутья на это дело нет, и перед реализацией каждой даже самой проходной задачи приходится долго тупить на предмет того, в какой класс разместить код или вообще создавать новый класс и т.п. Рекомендации уважаемых Nooob и Wolsh - ощутимо помогают (хоть отчасти противоречат друг другу), то пока тоже не смогли успокоить мою душу. Уже кратко спрашивал, теперь постараюсь подробнее сформулировать суть моего вопроса и альтернатив. Продумывая игровых персонажей и их механики, вывел 3 группы: npc, ключевые npc и сам игрок. Количество свойств увеличивается от npc до игрока. Когда дошла очередь до проектирования классов, я нарисовал табличку, где в столбцы поставил эти группы, а в строки - свойства. А дальше ставил на пересечении галочки, используется ли данное свойство или нет. По итогу получился такой расклад: совсем небольшое количество базовых свойств попали во все три группы (имя, ID, пол и т.п.), некоторое количество оказалось уникальным для персонажа игрока, ещё небольшая группа оказалась общей для npc и ключевых npc, наконец, пачка уникальных свойств только для ключевых npc. Итого 4 "профиля вхождения". Они определили вот такую структуру наследования классов: Код AS3:
Теперь смотрю на эту структуру и сомневаюсь, правильно ли я начал делать и чем мне это аукнется в скором времени. Свойств много, часть из них полностью повторяется, поэтому делать совсем независимые отдельные классы для каждой группы - точно не вариант. Что остаётся? Первое, оставить как есть сейчас, для расчётов игровой механики писать классы с абстрактными методами для каждого из подклассов персонажей, т.е. воспользоваться прелестями полиморфизма (или зря я столько книжек прочитал!). Но смущает, что во-первых, дерево наследование кривое получается (вопрос, повлияет ли этот факт на работу абстрактных методов и вообще, насколько подобный перекос допустим), плюс много писанины - по 3 подкласса на каждый класс с расчётами. При условии, что ни в близком, ни в отдалённом времени я не планирую вводить другие группы персонажей (для которых было бы как раз удобно добавить отдельных потомков), всё это выглядит перебором и необоснованным усложнением. Вторая альтернатива - забить на наследование, собрать все свойства в один класс Character, а для того, чтобы различать, кто есть ху, добавить в класс соответствующее свойство-указатель, типа "player", "npc" или "key_npc" и присваивать его при создании каждого нового персонажа. Избавляемся от всего головняка с наследованием, но обрекаем себя на добавление конструкции switch или подобной во все методы, различающиеся для групп персонажей. Наконец, третий вариант - оставить классы как они есть, но не париться с полиморфизмом, а делать единые классы с методами расчётов и проверять на старте, какой из подклассов персонажей поступил. В зависимости от того, кто это оказался, запускать соответствующие версии функций. Буду признателен за развёрнутый ответ. |
Цитата:
|
Цитата:
|
Друзья!
Я продолжил свои изыскания в проектировании классов будущей игры и возник новый вопрос. Появился он при переходе от базовых свойств персонажа к специфическим: которые выделяются для пола, профессии и т.п. Пусть условно для мужчин учитывается длина бороды, а для женщин - объём талии. У меня получилось следующее. Я создал новый класс Gender с единственным свойством genderID, которое может принимать значения 0 для женского пола и 1 для мужского, и унаследовал от него два класса GenderMale и GenderFemale. В первом соответственно свойство beardLength, во втором - waistVolume, плюс set- и get-методы для каждого. Тогда в классе персонажа Character добавился такой set-метод: Код AS3:
Вопрос такой. Чтобы задать либо получить значения свойства, связанного с полом, приходится писать такой код. Например, для нашей бороды: В классе Character: Код AS3:
Код AS3:
Код AS3:
|
1. Если у тебя в Gender объявлена function set beardLength() то она будет и у Женщин, так?
2. Зачем в конструкторы Мужского и Женского пола передается еще и идентификатор? Ты же не собираешься делать экземпляр Мужчины со значением "женщина"? Да вроде и switch не позволит. Тогда зачем? 3. Конвенции просят не называть параметр сеттера как Бог черепаху, но всегда просто "value". Почему так? Потому что название сеттера уже несет необходимый смысл, а если нет то это плохое название сеттера. Он принимает значение для свойства с этим названием — ведь он хоть и функция, но прикидывается свойством. Поэтому как-то еще глубокомысленно называть параметр, который станет этим самым свойством с этим самым названием как-то очень странно. Так же принято называть внутреннее хранилище (приватную переменную, к которой доступ через геттер/сеттер) точно так же, как геттер/сеттер (только с подчеркиванием), потому что она собственно и есть это самое СВОЙСТВО. У тебя, в частности, грубейшее нарушение этого принципа: есть сеттер gender типа uint, а свойство _gender имеет тип Gender. Таким образом у твоего характера снаружи "гендер" это просто число 1 или 0, а внутри это целый экземпляр Мужика с бородой и талией. Теперь о смысловой нагрузке. Я думаю ты и сам видишь, что вместо абстрагирования получилась какая-то каша. До тех пор, пока пол Характера не имеет значения, все прекрасно, ты можешь обращаться с ним как просто с Человеком. Но как только встает вопрос пола, начинается катавасия — тебе приходится протаскивать в класс Характер ВСЕ половые признаки, и мужские и женские, и совершенно непонятно зачем вообще был нужен класс Gender, да еще и с подклассами Джентельмен и Мадмуазель, если ВСЕ их свойства вынужден представлять класс Характер, и даже как транспортный дата-обжект (для обмена данными) эти гендеры использовать нельзя, ибо доступ к ним как объектам наглухо закрыт. Лично мне ошибкой видится именно эта паническая инкапсуляция Гендера. Надо открыть к нему доступ напрямую, а не прокидывать все сеттеры/геттеры в Характер. В чем смысл, если доступ к этим свойствам ВСЕ-РАВНО есть, только с бубном вокруг деревни. Скрывать надо то, что другим НЕ НУЖНО знать. В чем смысл сокрытия гендера, мне непонятно. Сама идея собирать какие-то свойства определенного назначения в отдельные блоки нормальная. Это используется в нативных классах, например такие свойства визуальных объектов как .filters или .transform, или .defaultTextFormat у TextField. |
Wolsh, спасибо за очередную порцию ликбеза. Отвечаю по пунктам:
Цитата:
Цитата:
Код AS3:
Код AS3:
Цитата:
Код AS3:
Цитата:
Теперь появилась проблема. Если раньше было легко по идентификатору вытащить значения свойства, написав character[propID], где строковая переменная propID содержит название соответствующей переменной в классе Character, например "intelligence", то теперь, указав в propID = "gender.beardLength", выдаёт ошибку :( Можно как-то подтянуть? |
Цитата:
Код AS3:
По поводу character[propID] — ну чтож, теперь надо считаться с тем что это свойства не Характера а Гендера, то есть character.gender[propID]. |
Цитата:
Цитата:
Цитата:
Код AS3:
|
Цитата:
Во второй строке вызывается сеттер самого TextField: "_textField.defaultTextFormat = " и ему отдается объект TextFormat, полученный из геттера _textField.defaultTextFormat. То есть тот самый объект, в котором значение свойства .bold теперь true. Cеттер это функция, она не просто присваивает этот ТекстФормат внутреннему хранилищу (он итак там лежит). Сеттер делает что-нибудь еще: считывает свойства переданного ему объекта ТекстФормат и "применяет" их "прямо сейчас". (может пример с ТекстФорматом и не совсем корректен ввиду того что там не требуется это "прямо сейчас", но вот если ты захочешь рычажком слайдера менять длину бороды в редакторе персонажа, то просто менять одно из свойств объекта Гендер будет недостаточно, чтобы видеть изменение в реальном времени). Цитата:
|
Цитата:
Мне тут ещё один вариант придумался. Может быть в классе AttributeHints первым делом выяснять, в каком классе "живёт" нужное нам свойство, и в зависимости от результата формировать обращение либо в character, либо в character.gender. Единственное, что смог найти, это метод hasOwnProperty(), но он работает только для классов типа Object. Есть ли что-то подобное для использования в своих классах? |
| Часовой пояс GMT +4, время: 17:54. |
Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.