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

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

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

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

Камрады!

Сразу признаюсь, я пока порядочный чайник в ООП в целом и в AC3 в частности, но тема меня искренне прёт, поэтому честно пытаюсь грызть гранит. Последние полгода потихоньку писал диздок простенькой игрушки, параллельно штудируя Колина Мука со товарищи. Сейчас почувствовал, что абстрактно продумывать и читать надоело, хочется хоть немножко реализовать. Начал проектировать первый же пользовательский класс и сразу застрял. Need help.

Имеем класс Character, экземплярами которого станут игровые персонажи. В его составе планировался ряд свойств для ключевых игровых показателей: условно "сила", "ловкость", "красота" и т.п. Изначально не видел в этой части никаких проблем - придумывай имена свойств, да добавляй set и get методы. Потом, почесав репу, тормознул. Мне подумалось, что каждое из этих свойств - само представляет некую систему, т.к. для каждого предусмотрено игровое название и текст всплывающей подсказки, которые нужно где-то хранить, словесное описание величины, которое будет разным для различных свойств ("слабак", "силач" для силы, "дурак", "гений" для ума и т.д.). По всему просится создание отдельного класса, потрохами чую. А вот как реализовать - не понимаю.

То ли сделать отдельный класс и связать его с классом Character отношением композиции. Но в этом случае выходит, что каждый экземпляр класса Character должен иметь несколько различных экземпляров нового класса (для тех же "силы", "ловкости", ума" и т.п.) и работать с ними независимо. Не могу понять, возможно ли это реализовать в рамках ООП и правильно ли это делать?

Или сделать некий единый класс для игровой характеристики, а все реально создаваемые унаследовать от него? Тогда получается чёткое присваивание каждой переменной в экземпляре класса персонажа конкретного подкласса (т.е. "сила" = new Strength, "ум"= new Wisdom и т.п.), никакой путаницы. Но как-то громоздко выглядит.

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

Надеюсь, понятно объяснил. Буду признателен за комментарии. Уверен, что описанный мною вопрос - "общее место" для опытных ООП программистов. Спасибо.

Старый 06.09.2017, 10:52
Wolsh вне форума Посмотреть профиль Отправить личное сообщение для Wolsh Найти все сообщения от Wolsh
  № 2  
Wolsh
Нуб нубам
 
Аватар для Wolsh

модератор форума
Регистрация: Jan 2006
Адрес: Бердск, НСО
Сообщений: 6,445
Цитата:
Мне подумалось, что каждое из этих свойств - само представляет некую систему, т.к. для каждого предусмотрено игровое название и текст всплывающей подсказки, которые нужно где-то хранить, словесное описание величины, которое будет разным для различных свойств ("слабак", "силач" для силы, "дурак", "гений" для ума и т.д.). По всему просится создание отдельного класса, потрохами чую. А вот как реализовать - не понимаю.
Выглядит как класс-список строковых констант. Сразу бросается в глаза, что эти данные — справочные, то есть они не участвуют в рассчетах, в математике (скорости движений, нанесенного ущерба, сопротивления атаке и т.п.). В контексте персонажа его свойства — это числа, величины. Мало того, эти текстовые описания ОДИНАКОВЫ для всех, а величины свойств — индивидуальны для каждого. Налицо необходимость абстрагировать все эти descriptions в отдельный класс-хранитель описаний (не обязательно константами — если есть желание в будущем сделать возможность выбора языка интерфейса, то может быть тексты будут подгружаться извне в виде XML). Этот же класс может реализовывать несколько статических методов для получения описаний, получающих идентификатор свойства и, если надо, его величину, и возвращающих строку описания.
Ну, типа Descriptions.getHint(propID:String, propValue:int):String
Нет никакого смысла хранить в экземпляре персонажа библиотеку со словарями. Это ВООБЩЕ к персонажу не относится, это относится, если хотите, к интерфейсу, к показу на экране описаний того, что происходит. Этим не персонаж занимается, это не его ответственность.
__________________
Reality.getBounds(this);

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

Регистрация: Dec 2014
Адрес: Санкт-Петербург
Сообщений: 479
Цитата:
Сообщение от Wolsh Посмотреть сообщение
Это ВООБЩЕ к персонажу не относится, это относится, если хотите, к интерфейсу, к показу на экране описаний того, что происходит. Этим не персонаж занимается, это не его ответственность.
Огромное спасибо! Очень доходчиво, логику уловил. Ох и тяжко же укладывается в голову философия ООП...

Цитата:
Сразу бросается в глаза, что эти данные — справочные, то есть они не участвуют в рассчетах, в математике (скорости движений, нанесенного ущерба, сопротивления атаке и т.п.). В контексте персонажа его свойства — это числа, величины.
То есть получается, касательно класса Character спокойно оставляем обычные свойства со своими значениями у экземпляров этого класса, к которым будем обращаться или изменять в процессе, верно?

Цитата:
Налицо необходимость абстрагировать все эти descriptions в отдельный класс-хранитель описаний (не обязательно константами — если есть желание в будущем сделать возможность выбора языка интерфейса, то может быть тексты будут подгружаться извне в виде XML)
Да, всё понятно. Наверное, пока константами, а потом уже можно будет и мультиязычность добавить. Здесь такой встречный вопрос. Что, по твоему экспертному мнению, правильнее хранить в подобном классе-хранителе описаний: только инфо по однотипным объектам (в нашем примере игровым характеристикам персонажа) или вообще все описания, встречающиеся в игре? Какова общепринятая практика: собирать все текста приложения в некий общий класс, продумав систему идентификаторов, и по ним выдёргивать, или хранить описания в разных классах? Сейчас подумалось, что если у персонажа есть имя и фамилия (сейчас это свойства firstName и lastName в классе Character), то следуя приведённой логике, это тоже лишняя инфо, место которой в неком отдельном классе, откуда мы будем доставать её исключительно для вывода пользователю в нужный момент. Верно рассуждаю?

Цитата:
Этот же класс может реализовывать несколько статических методов для получения описаний, получающих идентификатор свойства и, если надо, его величину, и возвращающих строку описания.
Ну, типа Descriptions.getHint(propID:String, propValue:int):String
Я правильно понимаю, что свой propID должен назначаться каждой игровой характеристике в классе Character как статическое свойство? Тогда получается, что в метод getHint класса Descriptions мы передаём propID нужной характеристики из класса Character и текущее значение данной характеристики из экземпляра класса Character (propValue)? По ним лезем в двумерный массив с текстовыми описаниями (типа первый индекс - наименование свойства, второй - нужная расшифровка для полученного значения) и вытаскиваем оттуда нужный текст, верно?

Здесь у меня тоже возникает уточняющий вопрос. Насколько я понимаю, все идентификаторы правильнее задавать константами, дабы не путаться. Продолжая наш пример, сделаем propID для силы константу const CHARACTER_STRENGTH_ID = 1, и т.д., чтобы потом не запоминать, где ноль, а где 25. Вопрос, учитывая систему областей видимости, в каком месте программы должны объявляться подобные константы? Ведь они будут использоваться уже как минимум в 2х отдельных классах: Character и Description.

Старый 06.09.2017, 22:34
Nooob вне форума Посмотреть профиль Отправить личное сообщение для Nooob Найти все сообщения от Nooob
  № 4  
Nooob
 
Аватар для Nooob

Регистрация: Mar 2007
Сообщений: 319
Еще рекомендую почитать "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
__________________
RocketJump


Последний раз редактировалось Nooob; 06.09.2017 в 23:15.
Старый 06.09.2017, 23:15
Wolsh вне форума Посмотреть профиль Отправить личное сообщение для Wolsh Найти все сообщения от Wolsh
  № 5  
Wolsh
Нуб нубам
 
Аватар для Wolsh

модератор форума
Регистрация: Jan 2006
Адрес: Бердск, НСО
Сообщений: 6,445
Цитата:
Продолжая наш пример, сделаем propID для силы константу const CHARACTER_STRENGTH_ID = 1
Сделать константы нормально, но только "1" это бессмысленная вещь. Подумай о том, как ты будешь это использовать, в какой ситуации? По этой константе ты сможешь вытянуть описание из класса Descriptions, но сможешь ли вытянуть значение из Персонажа? Нет)) Для этого надо знать название свойства. Так что логичнее было бы хранить в такой константе название свойства, например строку "strength".
Да, во многих играх используются идентификаторы-числа, типа 0xFD12A8. Для всего подряд, для классов предметов: оружия, одежды; для свойств персонажа; для квестов и их уровней (эпизодов), для локаций и т.п. Но в этом случае как раз 0xFD12A8 является ключом, а строка — значением, а не наоборот, как у тебя)) Это довольно сложная система, она оправдывает себя когда в игре тысячи понятий, но прямого отношения к ООП не имеет, просто один из инструментов наведения порядка в огромном хаосе. Суть ООП в правильном абстрагировании, распределении уникальности и общности.
Цитата:
По ним лезем в двумерный массив с текстовыми описаниями (типа первый индекс - наименование свойства, второй - нужная расшифровка для полученного значения) и вытаскиваем оттуда нужный текст, верно?
Вообще, я не зря предложил оформить это как метод — то есть предполагается, что результат не является однозначным, как константа. Допустим, тебе надо вывести описание уровня Интеллекта для данного персонажа, тогда ты напишешь
Код AS3:
_hintTXT.text = Descriptions.getHint(Hash.CHARACTER_INTELLIGENCE, _character[Hash.CHARACTER_INTELLIGENCE]);
Метод getHint() должен будет ВЫБРАТЬ описание из нескольких вариантов (массива), подходящее для данного значения свойства, и вернуть его. Скажем, если _character.intelligence больше 60, то метод вернет строку "умный был детина", если < 30 — "вовсе был дурак", иначе — "и так и сяк".
Цитата:
если у персонажа есть имя и фамилия (сейчас это свойства firstName и lastName в классе Character), то следуя приведённой логике, это тоже лишняя инфо, место которой в неком отдельном классе, откуда мы будем доставать её исключительно для вывода пользователю в нужный момент. Верно рассуждаю?
Да, если имя и фамилия это игровая константа, как Гордон Фримен в Half Life. Но, например в Oblivion или Skyrim игрок сам придумывает имя своему протагонисту. (В Обливион игрок мог даже придумывать собственные названия результатам крафтинга — зачарованным им доспехам и оружию, сваренным им эликсирам и ядам, и даже заклинаниям, которые составил сам). Так что на уровне кода это переменные, которые хранятся только в сейвах игрока, не в коде игры)
__________________
Reality.getBounds(this);

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

Регистрация: Dec 2014
Адрес: Санкт-Петербург
Сообщений: 479
Цитата:
Сообщение от Nooob Посмотреть сообщение
Еще рекомендую почитать "ActionScript 3.0. Шаблоны проектирования (Сандерс Уильям, Кумаранатунг Чандима)" или
"Приёмы объектно-ориентированного проектирования. Паттерны проектирования (Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес)" старая правда, но неплохо так вставляет.
За книжки спасибо, уже качнул, почитаю в спокойном режиме. Но всё остальное, если честно, больше запутало, чем внесло ясности.

Цитата:
Data - библиотека классов статической константной информации, к которым можно обратиться по ссылке, не изменная и общедоступная, в целом это то что Wolsh назвал Description
Как понимать "библиотека классов"? Это фигура речи или есть какая-то специальная реализация подобных вещей? В любом случае, это получается несколько классов со статичной информацией, типа справочников, из которых определённым образом выдёргиваются нужные элементы. Я правильно понимаю?

Цитата:
Итого получается следующая иерархия:
-data
--BattleData содержится информация например сколько должно быть персонажей, время боя и тд.
--CharacterData содержится информация о первоначальном здоровье, скорости, силе и тд.
Извини, если вопрос покажется идиотским, но я во второй раз о терминах. "Иерархия" - это условное понятие для проектировщика или ты имеешь в виду иерархию типа наследования классов?

Цитата:
Сообщение от Wolsh Посмотреть сообщение
Сделать константы нормально, но только "1" это бессмысленная вещь. Подумай о том, как ты будешь это использовать, в какой ситуации? По этой константе ты сможешь вытянуть описание из класса Descriptions, но сможешь ли вытянуть значение из Персонажа? Нет)) Для этого надо знать название свойства. Так что логичнее было бы хранить в такой константе название свойства, например строку "strength".
Это я понял. Естественно, отдельный класс с соответствующим методом для выбора по идентификатору и значению. У меня логика следующая. Если думать о будущей поддержке нескольких языков, то нужен некий идентификатор не только для описаний тех или иных значений свойства персонажа: "умный", "дурак", "так и сяк", но и для самого названия игровой характеристики ("Ум" в русской версии, "Intelligence" в английской). Поэтому я рассудил, что для каждой игровой характеристики должен быть уникальный идентификатор, по которому можно будет вытащить как её название для интерфейса, так и описание к возможным значениям. Получается, что в классе Description метод getHint будет вызываться с передачей в него ID характеристики и её значении для конкретного персонажа (featureID:uint, value:number). И он из первого массива featureNames с названиями самих характеристик по featureID=1 со второй позиции вытащит "ум", чтобы вывести пользователю, а затем обратится в массив с описаниями, где по этому же featureID=1 выдаст нам ещё один массив с пороговыми значениями и описаниями (10, "полный кретин", 20, "дурачок", 30, "имбецил"), из которого мы по value подберём нужный вариант.

Прочитав написанное тобой, возникло два очень важных вопроса:
1. Категорически не понял, как можно по строковому идентификатору вытащить значение из персонажа? Получается, что если в классе персонажа прописано свойство public var strength, то в другом месте программы мы можем создать строковую переменную var feature:String = "strength" и, написав, 'имя экземпляра'.feature, мы сможем обратиться к экземпляру класса? А как же использование Set и Get методов, инкапсуляция и всё такое?
2. Вторая идея, почему я предпочёл использовать в качестве идентификаторов целые положительные числа uint, заключается в том, что я не могу представить себе иного способа организованно хранить данные и обращаться к ним, кроме как с помощью массивов. А индекс массива - это всегда число типа uint. Даже в твоём примере ты обращаешься к массиву, т.е. должен в какой-то момент перейти от строки к числу. Или нет?

Цитата:
Да, если имя и фамилия это игровая константа, как Гордон Фримен в Half Life. Но, например в Oblivion или Skyrim игрок сам придумывает имя своему протагонисту.
А в чём принципиальная разница? Опять же, если задавать для персонажа только идентификатор, а имена хранить в отдельном классе, то значения в этот класс могут попасть как из заранее заготовленного XML на нужном языке, так и в результате ввода пользователем. Разве одно мешает другому? Этот же идентификатор может использоваться и для подтягивания графики (условно портрета в интерфейс), и всего остального. Главное - обеспечить, чтобы во всех классах, по которым разбросаны данные, связанные с персонажем, соблюдалось единство этих самых идентификаторов. Поэтому я и спрашивал, где объявлять соответствующие константы или переменные.

Старый 07.09.2017, 01:59
Wolsh вне форума Посмотреть профиль Отправить личное сообщение для Wolsh Найти все сообщения от Wolsh
  № 7  
Wolsh
Нуб нубам
 
Аватар для Wolsh

модератор форума
Регистрация: Jan 2006
Адрес: Бердск, НСО
Сообщений: 6,445
Цитата:
1. Категорически не понял, как можно по строковому идентификатору вытащить значение из персонажа?
Я даже использовал синтаксис в своем примере: _character[Hash.CHARACTER_INTELLIGENCE]
то есть это равнозначно обращению _character.intelligence, если константа CHARACTER_INTELLIGENCE в классе Hash имеет значение "intelligence". И да, это может быть геттер или сеттер.

Цитата:
2. Вторая идея, почему я предпочёл использовать в качестве идентификаторов целые положительные числа uint, заключается в том, что я не могу представить себе иного способа организованно хранить данные и обращаться к ним, кроме как с помощью массивов. А индекс массива - это всегда число типа uint.
Кроме массивов, есть еще как более низкоуровневые хэши, например Object, так и более сложные, например Dictionary.
То есть, ты предлагаешь ВООБЩЕ ВСЁ хранить в одном бесконечном массиве, а константы класса, вместо того чтобы просто содержать строку "strength", будут содержать индекс массива, по которому из него можно вытащить этот "strength"? Это уместно, когда "strength" может оказаться не "strength", например при смене языка мы заменяем весь массив текстов, а идентификаторы фраз остаются в коде как были. Но если ты начнешь на каждую фразу создавать константу, хранящую ее uint-идентификатор, то это будет адский перебор. Потому что этой константой ты будешь пользоваться ровно в одном месте, где прекрасно обошелся бы самим uint, если надо — с комментарием что это за фрукт)).

Цитата:
Даже в твоём примере ты обращаешься к массиву, т.е. должен в какой-то момент перейти от строки к числу. Или нет?
К "числу" переходит метод, и переходит он, анализируя величину свойства персонажа. Из первого параметра метод находит массив, относящийся во-первых к понятию "хинт", а во-вторых к параметру "интеллект". Далее из величины второго параметра метод определяет индекс, по которому берет из массива строку и возвращает ее. Как находит массив по строковому идентификатору? Да может быть просто объект _hints (или статическая константа HINTS:Object) содержащая массивы вариантов по строковым ключам. То есть HINTS["intelligence"] это массив ["дурак", "ни так ни сяк", "умный"].
__________________
Reality.getBounds(this);

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

Регистрация: Mar 2007
Сообщений: 319
Под иерархией проекта я говорю про содержимое проекта, то какие классы в нем содержатся и какие пакеты есть

минималистичный пример:
Код AS3:
package
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.geom.Point;
 
	import game.data.BattleData;
	import game.data.BattleDataSpawnPoint;
	import game.data.CharacterData;
	import game.model.BattleModel;
	import game.view.BattleView;
 
	[SWF(frameRate="60", width="800", height="600")]
	public class Game extends Sprite
	{
		private const _model:BattleModel = new BattleModel();
		private const _view:BattleView = new BattleView();
 
		public function Game()
		{
			//init data
			var battle_1:BattleData = BattleData.find("battle_1");
			battle_1.time = 60 * 30;
 
			battle_1.spawnPoints = new Vector.<BattleDataSpawnPoint>(4, true);
			battle_1.spawnPoints[0] = new BattleDataSpawnPoint(new Point(-100, -100), CharacterData.find("character_1"));
			battle_1.spawnPoints[1] = new BattleDataSpawnPoint(new Point(100, 150), CharacterData.find("character_2"));
			battle_1.spawnPoints[2] = new BattleDataSpawnPoint(new Point(-100, 100), CharacterData.find("character_3"));
			battle_1.spawnPoints[3] = new BattleDataSpawnPoint(new Point(180, -100), CharacterData.find("character_4"));
 
			var character_1:CharacterData = CharacterData.find("character_1");
			character_1.damage = 3;
			character_1.health = 10000;
			character_1.speed = 0.4;
			character_1.aggroRadius = 260;
			character_1.attackRadius = 20;
			character_1.color = 0xff0000;
 
			var character_2:CharacterData = CharacterData.find("character_2");
			character_2.damage = 2;
			character_2.health = 70;
			character_2.speed = 0.3;
			character_2.aggroRadius = 160;
			character_2.attackRadius = 25;
			character_2.color = 0x00ff00;
 
			var character_3:CharacterData = CharacterData.find("character_3");
			character_3.damage = 3;
			character_3.health = 700;
			character_3.speed = 0.1;
			character_3.aggroRadius = 130;
			character_3.attackRadius = 40;
			character_3.color = 0x0000ff;
 
			var character_4:CharacterData = CharacterData.find("character_4");
			character_4.damage = 4;
			character_4.health = 300;
			character_4.speed = 0.1;
			character_4.aggroRadius = 100;
			character_4.attackRadius = 35;
			character_4.color = 0xff00ff;
 
			//start battle
			_model.start(BattleData.find("battle_1"));
 
			//init view
			_view.setup(_model);
			addChild(_view);
 
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}
 
		private function onEnterFrame(event:Event):void
		{
			_model.update();
 
			_view.x = stage.stageWidth / 2;
			_view.y = stage.stageHeight / 2;
			_view.update();
		}
	}
}
 
package game.data
{
	import flash.utils.Dictionary;
 
	public class BattleData
	{
		private static const map:Dictionary = new Dictionary();
		public static function find(key:String):BattleData
		{
			return map[key] ||= new BattleData(key);
		}
 
		public var key:String;
		public var spawnPoints:Vector.<BattleDataSpawnPoint>;
		public var time:int;
 
		public function BattleData(key:String)
		{
			this.key = key;
		}
	}
}
 
package game.data
{
	import flash.geom.Point;
 
	public class BattleDataSpawnPoint
	{
		public var point:Point;
		public var character:CharacterData;
 
		public function BattleDataSpawnPoint(point:Point, character:CharacterData)
		{
			this.point = point;
			this.character = character;
		}
	}
}
 
package game.data
{
	import flash.utils.Dictionary;
 
	public class CharacterData
	{
		private static const map:Dictionary = new Dictionary();
		public static function find(key:String):CharacterData
		{
			return map[key] ||= new CharacterData(key);
		}
 
		public var key:String;
		public var health:uint;
		public var speed:Number;
		public var damage:Number;
		public var aggroRadius:Number;
		public var attackRadius:Number;
		public var color:uint;
 
		public function CharacterData(key:String)
		{
			this.key = key;
		}
	}
}
 
package game.model
{
	import game.data.BattleData;
	import game.data.BattleDataSpawnPoint;
 
	public class BattleModel
	{
		private var _data:BattleData;
		public const characters:Vector.<CharacterModel> = new Vector.<CharacterModel>();
 
		public function BattleModel()
		{
		}
 
		public function start(data:BattleData):void
		{
			_data = data;
			for each (var point:BattleDataSpawnPoint in data.spawnPoints) 
			{
				characters.push(new CharacterModel(this, point.character, point.point));
			}
		}
 
		public function finish():void
		{
			characters.length = 0;
			_data = null;
		}
 
		public function update():void
		{
			for each (var character:CharacterModel in characters) 
			{
				character.update();
			}
		}
 
		public function findEnemy(character:CharacterModel):CharacterModel
		{
			var distance:Number = Number.MAX_VALUE;
			var target:CharacterModel = null;
			for each (var testCharacter:CharacterModel in characters) 
			{
				if(testCharacter == character || testCharacter.health == 0) continue;
 
				var deltaX:Number = character.x - testCharacter.x;
				var deltaY:Number = character.y - testCharacter.y;
				var testDistance:Number = deltaX * deltaX + deltaY * deltaY;
				if(testDistance < distance)
				{
					distance = testDistance;
					target = testCharacter;
				}
			}
			return target;
		}
	}
}
 
package game.model
{
	import flash.geom.Point;
 
	import game.data.CharacterData;
 
	public class CharacterModel
	{
		private var _battle:BattleModel;
		private var _data:CharacterData;
		private var _health:uint;
		private var _speed:Number;
		private var _damage:uint;
		private var _x:Number;
		private var _y:Number;
		private var _rotation:Number;
		public function get data():CharacterData
		{
			return _data;
		}
		public function get health():uint
		{
			return _health;
		}
		public function get x():Number
		{
			return _x;
		}
		public function get y():Number
		{
			return _y;
		}
		public function get rotation():Number
		{
			return _rotation;
		}
 
		public function CharacterModel(battle:BattleModel, data:CharacterData, position:Point)
		{
			_battle = battle;
			_data = data;
			_health = data.health;
			_speed = data.speed;
			_damage = data.damage;
			_x = position.x;
			_y = position.y;
			_rotation = 0;
		}
 
		public function update():void
		{
			if(_health == 0) return;
			var enemy:CharacterModel = _battle.findEnemy(this);
			if(enemy == null) return;
 
			var deltaX:Number = enemy.x - x;
			var deltaY:Number = enemy.y - y;
			var distance:Number = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
			if(distance < _data.aggroRadius)
			{
				_rotation = Math.atan2(deltaY, deltaX);
				if(distance < _data.attackRadius)
				{
					enemy.hit(_damage);
				}
				else
				{
					deltaX /= distance;
					deltaY /= distance;
 
					_x += deltaX;
					_y += deltaY;
				}
			}
		}
 
		public function hit(damage:uint):void
		{
			if(_health <= damage)
			{
				_health = 0;
			}
			else
			{
				_health -= damage;
			}
		}
	}
}
 
package game.view
{
	import flash.display.Sprite;
 
	import game.model.BattleModel;
	import game.model.CharacterModel;
 
	public class BattleView extends Sprite
	{
		private var _model:BattleModel;
		private const _characters:Vector.<CharacterView> = new Vector.<CharacterView>();
 
		public function BattleView()
		{
		}
 
		public function setup(model:BattleModel):void
		{
			_model = model;
			for each (var characterModel:CharacterModel in model.characters) 
			{
				var characterView:CharacterView = new CharacterView(characterModel);
				_characters.push(characterView);
				addChild(characterView);
			}
		}
 
		public function update():void
		{
			for each (var characterView:CharacterView in _characters) 
			{
				characterView.update();
			}
		}
	}
}
 
package game.view
{
	import flash.display.Sprite;
 
	import game.model.CharacterModel;
 
	public class CharacterView extends Sprite
	{
		private var _model:CharacterModel;
 
		public function CharacterView(model:CharacterModel)
		{
			_model = model;
		}
 
		public function update():void
		{
			if(_model.health == 0)
			{
				visible = false;
				return;
			}
 
			graphics.clear();
			graphics.beginFill(_model.data.color, _model.health / _model.data.health);
			graphics.drawCircle(0, 0, 8);
			graphics.endFill();
			graphics.lineStyle(1, _model.data.color, _model.health / _model.data.health);
			graphics.drawCircle(0, 0, _model.data.aggroRadius);
			graphics.lineStyle(2, _model.data.color, _model.health / _model.data.health);
			graphics.drawCircle(0, 0, _model.data.attackRadius);
			graphics.moveTo(0, 0);
			graphics.lineTo(_model.data.attackRadius, 0);
 
			x = _model.x;
			y = _model.y;
			rotation = _model.rotation * 180 / Math.PI;
		}
	}
}
__________________
RocketJump

Старый 07.09.2017, 13:00
Appleman вне форума Посмотреть профиль Отправить личное сообщение для Appleman Найти все сообщения от Appleman
  № 9  
Appleman
 
Аватар для Appleman

Регистрация: Dec 2014
Адрес: Санкт-Петербург
Сообщений: 479
Цитата:
Сообщение от Nooob Посмотреть сообщение
Под иерархией проекта я говорю про содержимое проекта, то какие классы в нем содержатся и какие пакеты есть. минималистичный пример:
Фига себе минималистичный! Надеюсь, это ты не за чашкой кофе за 10 минут ночью налабал?

Получается, что в пакете data - вся справочная информация, в model - вся игровая механика, расчёты и т.п., а view - пакет для визуализации всего этого хозяйства. Понятно. Вопрос, в чём преимущество создания отдельных пакетов и постоянного импорта двух других в каждый, по сравнению с созданием единого пакета для всей игры, и настройки взаимодействия на уровне отношений исключительно между классами?

И ещё ряд вопросов по коду:

Код AS3:
public class Game extends Sprite
	{
		private const _model:BattleModel = new BattleModel();
		private const _view:BattleView = new BattleView();
Я так понимаю, что _model и _view = это экземпляры классов BattleModel и BattleView для быстрого обращения к математике и графике. Почему в виде констант?

Код AS3:
public class BattleData
	{
		private static const map:Dictionary = new Dictionary();
		public static function find(key:String):BattleData
		{
			return map[key] ||= new BattleData(key);
		}
Получается, что у тебя тут некое хранилище данных, организованное с помощью класса Dictionary, и весь функционал класса - это вытащить и вернуть нужное значение по полученному key. Опять не понимаю, почему map записана в форме константы. И выражение return map[key] ||= new BattleData(key); взрывает мой мозг До логического OR более-менее понимаю (это судя по всему как раз обращение к экземпляру map класса Dictionary по ключу key), а вот в чём соль правой части - совсем не понятно.

Последний мини-вопрос. Обратил внимание, что у тебя и у Wolsh многие переменные в коде записаны с символа "_", но не все. Какая тут логика? Я такое в некоторых книгах встречал (не у Мука).

Старый 07.09.2017, 13:30
undefined вне форума Посмотреть профиль Отправить личное сообщение для undefined Найти все сообщения от undefined
  № 10  
undefined

Регистрация: Oct 2006
Сообщений: 2,281
Цитата:
Почему в виде констант?
Это называется константный указатель. Т.е.view и model всегда гарантировано будут ссылаться на экземпляр указанный при инициализации. А если где-то появится еще одна запись
Код AS3:
_model= new BattleModel();
Компилятор выдаст ошибку. Защита от дурака.

Создать новую тему Закрытая тема Часовой пояс GMT +4, время: 15:58.
Быстрый переход
  « Предыдущая тема | Следующая тема »  

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

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


 


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


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