Я наконец сделал это!
Сегодня продолжаем делать маленькие хитрости для интерфейса, список которых я старался составить в зависимости от нужд и интересов читателей. И на очереди у нас перетаскивание объектов. На нерусском это называется "Drag and drop". Всем знаком процесс копирования файлов - перетягиваем файл из одной папки в другую. Наверно буду банален, сказав, что это и есть самый простой пример drag'n'drop'а. |
Где такое встречается в играх?! Ситуаций много:
- Копаясь в инвентаре мы перемещаем объекты с одного места на другое, более удобно располагая ту или иную вещь
- При торговле зачастую используют систему drag'n'drop - игрок самостоятельно перетягивает объекты у торговца к себе в мешок, при этом плата снимается автоматически
- В играх вроде Colonization экипировка корабля в порту осуществляется закидыванием грузов простым перетаскиванием иконок вроде Серебро или Табак в трюм корабля - удобно!
- Во флешке Gem Craft можно совмещать драгоценные камни, наделяя их новыми способностями; таким образом можно получить "замедляюще-ядовитый камень"; объединение камней происходит как раз drag'n'drop'ом одного на другой
Как видим, примеров огромное количество, поэтому иметь такую штуковину в своем арсенале обязательно нужно...
хм... Приступим!
В самом начале рекомендую прочитать первую часть рассказа о разработке этой демки. А сегодня мы, наконец, доделаем нашу программу, создав рабочий вид класса TDragManager. В принципе, дело за малым - наполнить модуль uDragManager различными вкусностями, вроде "приемников" для перемещения объектов, срабатывания сообщений на основные события и остальные мелочи, так необходимые для удобного использования.
Чтобы не перенасыщать сообщение излишним кодом, который в итоге "спрятан" внутри нашего TDragManager'а, я буду стараться при возможности рассказывать, как использовать этот класс "снаружи", умалчивая про то, как же это устроено внутри. Надеюсь, бывалых программистов это не отпугнет :)
Сейчас вы меня спросите: "Не тяни, что же у нас нового, кэп?".
Итак, у нас появился вполне самостоятельный метод AddDragTarget(), который требует позицию цели, в которую можно будет класть драг-объект:
Function TDragManager.AddDragTarget(aPos: TVector; aMaterialName: String): TDragTarget;
После вызова этого метода, на экране появится драг-цель, в которую без труда можно будет поместить любой драг-объект, добавленный в менеджер с помощью AddDragObject()
(в первой части рассказа только метод AddDragObject и был, теперь же появился и AddDragTarget; а вместе они дополняют друг друга).
Стоит отметить, что прежде активной областью драг-объекта была вся текстура, таким образом, если на текстуре 128х128 пикселей в центре был маленький объект размерами 70х70, то его можно было "таскать" по экрану за его прозрачные границы. Сейчас же, чтобы такого недоразумения не происходило, я добавил метод SetActionRect(), который принимает четыре значения для указания прямоугольника, реагирующего на клики пользователя. К примеру, передача четырех значения -1, -1, 1, 1 укажет объекту, что необходимо использовать целиковую текстуру, то есть драг-объект будет реагировать как и раньше.
С учетом этого изменения, добавление драг-объектов и драг-целей на экран в нашей демке выглядит довольно красиво:
// объявляем вспомогательную функцию Function AddCloneDragObject(aLeft, aTop: Single; aMatName: String; aPercents: Single): TDragObject; begin result := fDragManager.AddDragObject(VectorMake(aLeft, aTop, 0), aMatName); with result do begin Cloning := true; SetActionRect(-aPercents, -aPercents, aPercents, aPercents); end; end; ... // добавляем драг-цели fDragManager.AddDragTarget(VectorMake(300, 120, 0), 'dnd_item'); ... // добавляем драг-объекты AddCloneDragObject(50, 40, 'cup', 0.5); ...
Правда просто?
Наверно сразу возник вопрос, что делает строка
Cloning := true
Дело в том, что зачастую требуется перетаскивать объект, создавая его копию при старте перетаскивания. Достаточно вспомнить торговцев из RPG-игр с нескончаемым запасом товара. Покупаешь у него блестящий меч, но у него в лавке заветный предмет никуда не пропадает, можно купить еще и еще!
Так вот у нас также! Выставляя свойство Cloning в true, мы указываем объекту на необходимость создания нового экземпляра при старте драга. Скриншот результата:
Прямо-таки нашествие варежек!
Справа виднеется корзина, в которую можно положить ненужный объект, который тотчас же пропадет в неизвестном направлении. Добиться подобного эффекта можно отслеживанием различных событий. Нам достаточно слушать конец драга:
fDragManager.OnEndDrag := OnEndDrag;
А в обработчике как раз проверяем, куда мы кладем драг-объект; если это корзина, убиваем объект из списка:
Procedure TfrmMain.OnEndDrag(aManager: TDragManager; aDragObject: TDragObject; aDragTarget: TDragTarget; aPreviousTarget: TDragTarget); begin // если кладем объект в корзину... if aDragTarget = fBasket then begin // ...то навеки убиваем его! aManager.DeleteByObject(aDragObject, true); aDragObject := nil; end; // если мы пытаемся положить объект "высоко", то также убиваем его! if (aDragObject <> nil) and (aDragObject.fSprite.Position.y < 80) then aManager.DeleteByObject(aDragObject, true); end;
Комментарии уже внутри, но все равно повторюсь, что в приведенном куске кода мы также запрещаем перетаскивать объекты, не сильно смещенные от их источника драга (объекта, со свойством Cloning).
Также на данный момент в арсенале нашего менеджера имеются и другие события:
// on start drag property OnBeginDrag: TOnDragBaseEvent // on end drag property OnEndDrag: TOnDragProceedEvent // on moving drag-object property OnDragProceed: TOnDragBaseEvent // on change target while dragging property OnDragProceedTarget: TOnDragProceedEvent
Пока мы используем только OnEndDrag, но остальные тоже могут пригодиться в любой момент! Если появится необходимость - сообщайте в комментариях, с радостью постараюсь добавить!
Экипируем своего персонажа:
На этом, пожалуй, все... Качаем демку (560kb) или обновляемся из репозитория (18ая ревизия).
Видео происходящего:
Видео происходящего:
Выше всяких похвал. Браво. И как всегда очень мило)))
ОтветитьУдалитьgltrinix,
ОтветитьУдалитьспасибо за добрые слова!
правда всего лишь две демки за три месяца с момента составления списка - это как-то долговато... а впереди ведь много всего интересного!
Отлично!
ОтветитьУдалитьВ твоих статьях и примерах привлекает стильный и простой дизайн :)
Замечательно!
ОтветитьУдалитьЕсли в занятое поле положить объект из верхней части, то в верхней панельке объекты "накладываются" друг на друга. Что-то мне говроит, что так изначально не было задумано...(маленький баг-фикс)
Спасибо за демку) может в скором времени понадобиться.
perfect daemon,
ОтветитьУдалитьблагодарю! я считаю, что арт для игры - это не рисование, это дизайн... стараюсь не падать в грязь лицом!
Ulop,
очень рад, если эти демки будут полезными!
баг принят на заметку - как будет время, постараюсь поправить! большое спасибо!