Всем доброго дня!
Давно не писал в журнал, так как навалилась целая лавина дел, которые никак нельзя отложить, заморозить или просто делать в расслабленном режиме. Психологически нелегкое выдалось время, но я стараюсь разобраться со всем изо всех сил. А сегодня я решил побаловать себя, нашел капелюшочку сил и выделил несколько часов на описание демки Drag’n’drop (в рамках создания gui-демонстраций). Точнее сказать, это еще не окончательная программа, а всего лишь заготовка; но тем проще читателям будет понять те или иные моменты работы законченной демки, а мне намного удобнее разбивать описание работы сложных классов на несколько сообщений.
Итак, встречайте!
Сегодня делаем первую часть - нормальное перетаскивание объектов по экрану. То есть схватили мышкой и перетянули в другое место.
Начало
Для начала определимся, что в итоге нам будуь нужны два базовых класса:
- TDragObject - объект, который мы можем перетаскивать
- TDragTarget - место, куда мы можем перетаскивать объект TDragObject
После наимучительных непродолжительных раздумий, я решил отнаследовать их от совсем простенького класса TBaseDrag, который будет совмещать в себе функции двух вышеперечисленных классов.
Так как на сегодня у нас заготовлено не так много, поэтому описание совсем коротенькое:
TDragTarget = class; TBaseDrag = class fSprite: TGlHudSprite; end; TDragObject = class(TBaseDrag) fInTarget: TDragTarget; end; TDragTarget = class(TBaseDrag) fObjectInPlace: TDragObject; end;
Первая строка (TDragTarget = class;) является неким обходным маневром для рекуррентного использования классов в получившейся связке TDragObject <--> TDragTarget. То есть полем у одного класса является объект другого класса, и наоборот. А наш обходной маневр позволяет объявить класс TDragTarget в самом верху и уже спокойно использовать как корректное поле для класса TDragObject (я нормально объяснил?)
Так как объектов на экране будет много, то для удобства создадим класс-список, который будет хранить набор объектов типа TBaseDrag. Я назвал этот утилитарный класс TDragList, сейчас он выглядит совсем просто и умеет совсем немного:
TDragList = class(TList) Function GetObjectByIndex(aIndex: Integer): TBaseDrag; Function GetObjectRect(aIndex: Integer): TVector; Function GetObjectByPos(aPos: TVector): TBaseDrag; end;
Еще раз оговорюсь, сегодня делаем только передвижение объектов, без привязки к конечному месту, куда они кладутся; таким образом класс TDragTarget сегодня не пригодится, да и многое из того, что описано в текущей демке, возможно будет перерабатываться - но тем интереснее будет наблюдать за эволюцией кода, структуры и логики работы тех или иных моментов программы.
Класс TDragManager
Приступаем к основному классу, который будет руководить перемещениями объектов, их расположением, а также хранением всех наших драг-объектов и драг-целей. Встречайте TDragManager:
TDragManager = class (TGlDummyCube) protected fTargets: TDragList; fObjects: TDragList; fLButtonDown: Boolean; fDragingObject: TDragObject; fDragingPos: TVector; fMousePos: TVector; Function GetObjectByIndex(aIndex: Integer): TDragObject; public Procedure DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean); override; Procedure DoProgress(const progressTime: TProgressTimes); override; Procedure SetMousePos(aMousePos: TVector); Function AddDragObject(aPos: TVector; aMaterialName: String): TDragObject; Constructor Create(AOwner: TComponent); override; end;
Взгляд вперед
Как видно, это уже заготовка под большой класс, с помощью которого можно будет размещать драг-объекты (TDragObject'ы) по набору драг-целей (TDragTarget'ы). В результате, я надеюсь, что с помощью этого класса можно будет довольно быстро и легко накидать итоговую демку, в которой будем снаряжать персонажа различными объектами. Для видения конечного результата, я собрал картинку в FireWorks'е, обращаю внимание - это не скриншот:
Вверху - объекты для расстановки. Посередине - символический человек, его руки, ноги и голова. Справа - корзина для уничтожения неверно расположенного объекта. Но вернемся к нашему менеджеру - что сейчас он умеет делать?
Работа TDragManager'а
С помощью метода AddDragObject() можно с легкостью добавить в список драг-объектов еще один в заданных координатах и с определенной текстурой. Перемещением всех объектов заведует перекрытый метод DoProgress(), внутри которого, в зависимости от состояния мыши, производятся манипуляции с объектами. На текущий момент это выглядит так:
Procedure TDragManager.DoProgress(const progressTime: TProgressTimes); var Obj: TDragObject; begin if IsKeyDown(VK_LBUTTON) and not fLButtonDown then begin Obj := TDragObject(fObjects.GetObjectByPos(fMousePos)); if Obj <> nil then begin fDragingObject := Obj; fDragingPos := VectorSubtract(Obj.fSprite.Position.AsVector, fMousePos); end; end; if fDragingObject <> nil then fDragingObject.fSprite.Position.SetPoint(VectorAdd(fDragingPos, fMousePos)); if not IsKeyDown(VK_LBUTTON) and (fDragingObject <> nil) then fDragingObject := nil; fLButtonDown := IsKeyDown(VK_LBUTTON); end;
Немного поясню, сказал, что в fDragingPos записывается смещение мыши от центра объекты в момент клика. Это необходимо, чтобы при движениях мыши сохранялось относительное положение объекта и курсора.
Предварительный итог
Теперь, в основной программе нам достаточно выполнить всего несколько действий (комментарии помещаю внутрь):
//создаем менеджер и устанавливаем библитеку материалов fDragManager := TDragManager.CreateAsChild(fMainDummy); fDragManager.Material.MaterialLibrary := fMatLib; //добавляем нужное нам количество объектов в заданных координатах и с нужным материалом fDragManager.AddDragObject(VectorMake(100, 100, 0), 'dnd_item'); fDragManager.AddDragObject(VectorMake(200, 150, 0), 'dnd_item');
А в основном обработчике OnCadencerProgress() передаем координаты курсора мыши:
fDragManager.SetMousePos(VectorMake(MPos.x, MPos.y, 0));
И... все! В итоге наш менеджер сам позаботится о перетаскивании созданных объектов. Сейчас демка выглядит так:
Схватит мышкой, можем перемещать квадратики по экрану... Просто? Дальше - больше, обещаю! :)
Итак, текущие изменения ознаменованы 14ой ревизией в репозитории демок. Отдельно файл uDragManager можно подсмотреть здесь.
p.s. Кстати, забыл сказать - во многих демках утекает память, так как не везде я позаботился о правильном освобождении выделенной памяти (все-таки демонстрации же). В ближайших сообщениях о постараюсь осветить момент, как я борюсь с утечками и обязательно почищу все демки на этот счет!
Классный компонент. Теперь можно с легкостью делать функциональные инвентари в играх.
ОтветитьУдалитьНа каком этапе ты получаешь fMousePos ?
ОтветитьУдалить"я нормально объяснил?" ага =)
ОтветитьУдалитьпотрясно! уже хочется продолжения :)
gltrinix, спасибо! постараюсь сделать его максимально удобным...
ОтветитьУдалитьLOLO, у менеджера есть метод SetMousePos(), который вызывается "снаружи", прямо из главного каденсера демки...
Radical Ed,
здорово! продолжение постараюсь выложить в ближайшее время...