Теоретическая подготовка - описание файлов (комментарии)
Это сообщение сгенерировано автоматически.
Доброго времени суток...
Случайно заглянул - и решил влезть со своими комментариями... так что заранее прошу прощения за занудство...
>>Какими свойствами должны обладать эти объекты? Каждый экземпляр такого класса должен помнить координаты, где будет изображен, свой размер, помнить
>> с какой скоростью будет перемещаться (то есть на какую дистанцию переместиться за минимальный отрезок времени), также, если его скорость будет
>> изменяться (ускоряться или замедляться), то он должен помнить свой коэфициент торможения/ускорения. Далее - каждый экземпляр класса может быть
>> текстурирован своей уникальной текстурой, то есть он должен помнить идентификатор своей текстуры.
все не так... точнее, не совсем все... что значит "каждый экземпляр класса" - даже при условии, что речь идет о простых четырехугольничках, которые, вдобавок, рисуются в чистом 2D? а если нужно этих объектов - одинаковых, но в разных местах экрана 1000...000 - много, короче, нужно... и что, теперь для каждого держать (и, заметь, обрабатывать - иначе зачем они еще нужны) - все эти данные? а не слишком ли...
основных соображений здесь два:
1) инстансинг - рулит, даже если он организован чисто программно - достаточно создать 1 (один) прототип объекта - который содержит все необходимое для его отображения в произвольном месте - и в дальнейшем использовать ссылки на этот объект с указанием конкретных координат
2) объект (по крайней мере на уровне графического движка НЕ должен знать о том как и почему он движется. хотя бы просто потому, что нельзя заранее предвидеть, какое именно движение для него потребуется. вот у тебя есть координаты объекта - отлично, без них мы его не отрисуем. дальше ты хочешь начать его двигать... добавляешь скорость... скорость тоже может меняться - добавляешь ускорение... а он еще и тормозиться может, добавляем трение... а какая именно модель трения нам нужна? а фиг его знает, добавим самую крутую, которая может учитывать все... а дальше на объект начинают действовать переменные силы - и для расчета ускорений нужна масса, а потом выясняется, что и масса может меняться... надеюсь, уже понятно, к чему я веду? раз мы не можем (не хотим?) заранее заложиться на ВСЕ возможные варианты поведения объекта - не надо закладываться на них вообще. может розвучать парадоксально, но это правильно. если очень хочется предусмотреть внутри самго графического движка перемещение объектов по неким законам - бог с ним, это можно сделать... но не так... просто добавь к каждому объекту ссылку на НЕЧТО, что будет управлять его движением - а именно модифицировать координаты объекта. больше самому ГРАФИЧЕСКОМУ движку ничего не интересно - он не рисует ни скоростей, ни ускорений, ни прочего... массу информации на эту тему можно найти в описаниях различных реализаций систем частиц. воть... двигать объекты как-то надо, но это все таки должно происходить снаружи - в зависимости от (условно) игровой модели. а то для 100000 статических объектов умножение нулевых ускорений на dt, потом умножение нулевых скоростей на dt, а потом еще и прибавление нулевого смещения к координатам способно сожрать все рессурсы процессора... и не одного...
ЗЫ : все, что я тут написал - в достаточной мере очевидно. так что если все это было принято в расчет - и, тем не менее, сделано так, как сделано - прошу извинить. хотя мне почему-то так не кажется...
Привет!
Спасибо за конструктивную критику. Думаю, все правильно - я слишком увлекся "астральной байдой" - ООП ( ;-) ). Просто хотелось сделать все такое объектное-разобъектное. А еще это и от того, что я создаю от простого к сложному - от 1 объекта, потом целый список тех же самых объектов. А можно использовать один и тот же объект для рисования кучи прямоугольников - лишь передавая новые координаты. Как подумал, что один и тот же код повториться в каждом экземпляре класса...
Да, наверное, ты прав - графический движок должен уметь только рисовать. Но информация о свойствах объекта (те же координаты) где-то должна храниться. Все-равно надо будет создавать список (массив) с этими данными. Просто я думал создать 2 режима: отрисовка (render) и модификация (update) объектов. Перед каждой отрисовкой в объектном варианте брались бы данные самого объекта (значения перемещения), если не равны нулю - то прибавлялись бы к значениям координат. Впрочем, для этого достаточно и записей. А вообще, я только описал в статьях для простоты только небольшой список свойств объектов - Более полный список:
TAPActorProperties = record {for save in File StartProperties}
X,Y:glFloat; //Начальные координаты
Xmove,Ymove:glFloat; //Возможно, объект сразу после создания начинает движение
XAngle,YAngle,ZAngle:single; //Вращение объекта
ScaleX,ScaleY:glFloat; //Масштабирование объекта
ZOrder:Integer; //Порядок отображения (глубина внутрь экрана)
TexturResID:Integer; //(Идентификатор текстуры)
CurrentTile:Integer; //Номер текущего тайла
CurrentMovie:Integer; //номер текущего списка тайлов
Static:Boolean; //Не реагирует на трансформации
Visible:Boolean; //Видимость
IsControl:Boolean; //выполняет роль визуального Контрола (например, кнопка)
IsBackground:Boolean; //Только изображение - не выделяется, не участвует в проверках на столкновение
end;
Все объекты заносятся в один список, а далее создаются отдельные списки - для Контролов (элементы графического интерфейса), для Бекграундов-фоновые изображения, список отсортированных объектов (так как теста глубины применять не буду, то объекты отображаются по мере отрисовки от дальнего (первого) к ближнему (последнему в списке).
Antalis
на самом деле проблема не в оопе... она, скорее, в выборе деления на объекты - а именно в выборе того, что именно является объектом и какую функциональность он должен предоставлять. и далеко не всегда стоит идти по пути "а запихну-ка я в этот класс всю возможную функциональность" - скорее наоборот, ее надо по возможности ограничивать.
кстати, раз ты такой поклонник ооп, то хотя бы для приличия сделал бы координаты и прочие скорости если не классами, так хоть структурами... оно и удобнее, и читабельность кода возрастает намного...
теперь конкретный пример - что я, собственно, имел в виду...
итак - есть некий прототип рендера объекта. обзовем его для определенности RenderProto. у него есть по большому счету один метод (например виртуальный) - Render(params : RenderParameters); что именно лежит в этих params - зависит от твоей конкретной задачи. например там наверняка будет жить матрица трансформации - явно или неявно (например как координаты + повороты). что именно рендерится - тоже не суть важно, это может быть прямоугольничек, набор линий или еще что-то - ты просто делаешь нужные тебе прототипы и все.
в самом же объекте у тебя лежат как раз эти параметры и ссылка на прототип.
идем дальше - объект получился статический, и надо его как-то "задвИгать" - то есть изменять его положение, ориентацию и что-то еще. кто этим должен заниматься? ну уж точно не сам объект - он ни в жизнь не догадается, чего от него в данный конкретный момент нужно. он у нас тупой. и простой - что немаловажно.
вместо этого берем и создаем аффектор - нечто, что у нас будет двигать объекты. этот аффектор тоже по большому счету имеет один метод - и тоже виртуальный - что-то вроде ApllyTo(object);
соответственно, эти аффекторы тоже могут быть какими угодно - т.е. воздействовать на объекты совершенно по разному.
более того, совершенно необязательно делать по одному аффектору на каждый объект - вместо этого сам аффектор может содержать целую пачку ссылок на те объекты, на которые он воздействует + необходимые для этого данные.
например простому "двигателю" достаточно ссылки на объект + его скорость.
а если аффектор реализует графвитацию, то ему понадобится масса объекта. и так далее - с легкостью добавляется все, что угодно.
что дает подобный подход - на мой взгляд массу всего интересного...
первое - простота реализаци. все получается предельно прозрачно - объекты простые, аффекторы простые, и алгоритмы обработки у них тоже простые...
второе - эффективность обработки. если у нас статический объект - он создается раз и навсегда, и больше его никто не трогает. и даже память на всякие его скорости-ускорения не тратится. более того, если нам вдруг понадобилось его задвигать - му его просто подлючаем к нужному аффектору - и вуаля... не нужно больше двигать - отключаем его от аффектора.
сам аффектор тоже простой - если к нему прицепили объект - он его обработает. а пор все остальные он знать ничего не знает - и ему это не нужно.
то есть если есть 100000 объектов, и из них только 100 движутся - ТОЛЬКО эти 100 объектов и будут обработаны. а остальные мы даже трогать не будем, даже проверять флаги - статический\динамический. их вообще обрабатывать не придется... а как известно самый быстрый код - это тот, который вообще не исполняется (в смысле у него время выполнения - ноль) 8)
третье - гибкость. мало того, что один и тот же объект можно одновременно прицепить к разным аффекторам - чтобы получить комплексный результат (например один двигает, а второй вращает) - мы получаем возможность добавить новый, произвольно сложный аффектор НЕ МЕНЯЯ ничего в самой существующей структуре объектов - этот новый аффектор можно хоть как плагин подключать.
вот так вот... может, несколько сумбурно, но надеюсь, понятно...
Я исправил код и по-этому поводу написал статейку:
http://www.gamedev.ru/community/ants/articles/notObj
Скачать исходники можно:
http://www.gamedev.ru/download/?id=4205
Спасибо! Очень аргументированно!
Про создание собственного типа для координат и тд... А зачем усложнять простое? НУ и что что X,Y а не Coord.X,Coord.Y... ;-) Но это не принципиально, а вот над архитектурой - оптимальной организацией можно и нужно подумать...
Динамику я планировал делать через метод Менеджера Сцены. В данную процедуру будет передаваться массив свойств объектов. В одном списке будут храниться статичные, в другом - динамичные объекты. И в твоем случае все-равно придется сортировать объекты по их свойствам (проверять флаги) - только либо это делать заранее (готовить списки), либо проверять флаг на входе в аффектор. Единственно не пойму, как (зачем) аффектор делать классом, ведь изменение данных - всего-лишь одна процедура... хотя можно разбить и на несколько - одна будет масштабировать, вторая - перемещать, еще одна вращать... и тд... Я планировал все эти обязанности нагрузить на Менеджера Сцены.
видимо, все-таки было излишне сумбурно...
что касается координат - дело вкуса, по большому счету... хотя, имхо, запись position = position + velocity * dt выглядит предпочтительнее, чем
positionX = positionX + velocityX * dt
positionY = positionY + velocityY * dt
...
то это так, к слову...
что касается аффекторов...
во-первых - почему класс... это просто удобно. дело в том, что хотя у него и будет одна торчащая наружу процедура (обработать во-он тот объект, ну или во-он тот список объектов) - к ней могут прилагаться дополнительные данные - общие для всех объектов. причем какие именно - зависит от типа этого аффектора.
например, если ом обсчитывает гравитацию - ему понадобится ускорение свободного падения. а если еще и учитывает торможение о воздух - то какой-то коэффициент сопротивления воздуха. при этом для самой системы - ну, в твоем случае это будет, например, менеджер сцены - совершенно неважно, что именно это за аффектор. то есть выглядеть это будет примерно так (си-подобный псевдокод, а то паскалевский синтакс я уже лет 15 как забыл, не буду позориться)
class AbstractAffector // прототип всех аффекторов на свете { public: void AddObject(TheObject * obj); // добавить объект к списку процессируемых объектов void RemoveObject( TheObject * obj); // удалить объект из списка virtual void ApplyToObject( TheObject * obj) = 0; // виртуальный метод - обработать объект. переопределяется для создания нового типа аффектора void ProcessAllObjects( ) { for ( int i=0; i<m_affectedObjects.size( ); ++i) ApplyToObject( m_affectedObjects[i]); } protected: SomeObjectContainer m_affectedObjects; } class SomeAffeactor : public AbstractAffector { public: virtual void ApplyToObject( TheObject * obj); // реальный обработчик protected: // какие-то данные, нужные этому конкретному аффектору };
после этого в твоем сцене-менеджере будет что-то вроде
class SceneManager { public: // чего-то нужное void AddAffector(AbstarctAffector * affector); void RemoveAffector( AbstractAffector * affector); void ProcessObjects( ) { for ( int i=0; i<m_affectors.size( ); ++i) m_affectors[i]->ProcessAllObjects( ); } protected: AbstractAffectorList m_affectors; }; // где-то в коде AbstractAffector * affector = CreateGravityAffector( параметры); sceneManager.AddAffector( affector); // добавили гравитацию affector = CreateWindAffector( параметры); sceneManager.AddAffector( affector); // добавили ветер // и так далее
и все! что касается проверок флагов - то в нормальной ситуации ее просто нет. если какой-то объект прицеплен к каким-то аффекторам - они его обработают.
если не прицеплен никуда - то его и обрабатывать некому, он статический. фактически, эта проверка производится только один раз - при создании объекта (ну, или при изменении его состояния - например камень летел - на него действовала гравитация и сопротивление воздуха. упал - отцепили его от аффекторов - и больше на него ничего не действут, пнули) - но уж никак не в каждом цикле апдэйта.
а вот какие именно аффекторы делать - это целиком зависит от задачи. отдельно двигать объект, а отдельно вращать - может это и не самая лучшая идея - скорее всего это стоит объединить... я это чисто в качестве примера привел... но на общую структуру обработки это никак не влияет - ведь самому SceneManager-у глубоко пофигу, ЧТО ИМЕННО происходит с объектами - ему важно, что ЧТО-ТО происходит.
Спасибо Lion007 !
Я сегодня на работу приехал к 12 часам, и пока добирался до офиса все время размышлял над идеей аффекторов. (Я их для себя назвал модификаторами).
Наконец, я понял их выгоду. Действительно незачем каждый раз просчитывать какую-то трансформацию, которая не вносит изменения. Также плюс, что можно создать разные модификаторы (для разных действий, и использовать их по-надобности), еще выйгрыш - когда на все объекты должно оказывать влияние какое-то общее воздействие - гравитация, или сопротивление воздуха и тд... хранить одно и то же значение (величину гравитации например) во всех объектах (в каждой записи) смысла нет - нерациональное использование памяти. Я уже обдумал сам код реализации, но ты меня опередил - выложил свой код. Спасибо!
Вечером приеду домой и напишу код.
Был у меня еще вопрос - как ты будешь подготавливать объекты и выяснять с каким объектом какие трансформации производить. Понятно, что нужно рассовывать по разным спискам, но где эти списки хранить, и как их готовить...Но теперь все понятно - ты заносишь объект во внутренний список модификатора (аффектора) - прицепляешь объект.
Lion007
Вот реализую твой совет - создавать разные модификаторы. Залил исходники:
http://www.gamedev.ru/download/?id=4231
Написал статью - описал свои действия:
http://www.gamedev.ru/community/ants/articles/usemod2
Еще раз спасибо за дельный совет!
Тема в архиве.