Delphi плюс Android? Есть идея! Tap#3
Эта заметка является продолжением предыдущих. Tap#1, Tap#2.
И прежде чем приступить к анимации, стоит определиться с масштабированием персонажа. То есть необходимо настроить фрейм таким образом, чтобы, задавая его размеры “сверху”, все объекты персонажа автоматически подгоняли свой размер, и персонаж (по своим внешним размерам) оказался вписанным во фрейм.
Сделать это можно очень просто. Для самой фреймы устанавливаем свойство Align = alFit (чтобы сохранить пропорцию у соотношения сторон фреймы), а всем контролам на фрейме – Align = alScale. Тогда, при изменении размеров фреймы, контролы будут масштабироваться автоматически. Оговорюсь, что размеры и положения контролов в FM задаются вещественными числами, поэтому масштабирование будет плавным.
Однако есть такой нюанс: платформа FM масштабирует контролы относительно их текущих размеров. Что это значит? А это значит, что при задании маленьких размеров фреймы, наш персонаж может оказаться очень маленьким. Настолько маленьким, что при последующем увеличении фреймы персонаж перестанет масштабироваться.
Правда если рассчитывать только на мобильное устройство, в котором размер экрана фиксирован и приложение всегда развёрнуто на весь экран, то можно и не беспокоиться. Автоматическое масштабирование нам подойдёт, т.к. оно будет происходить:
- один раз при запуске приложения;
- при повороте экрана.
Однако я хочу, чтобы приложение можно было так же запускать и в окне в Windows. А в оконном режиме существует ненулевая вероятность того, что пользователь захочет “поиграться” с размерами окна и уменьшит его до очень маленького размера…
Поэтому масштабирование всех контролов я буду делать вручную, в обработчике OnResize. При этом, я хочу ориентироваться на исходный размер контролов, т.е. после загрузки фреймы из fmx-ресурса, необходимо сохранить позиции и размеры контролов.
Для этого создаём простую запись:
type TDesignPosition = record Left, Top, Width, Height: Single; RotationAngle: Single; FontSize: Single; end;
И класс-наследник от TList<T>:
type TDesignPositions = class(TList<TDesignPosition>) private function IndexByControl(AControl: TControl): Integer; function GetItemByControl(AControl: TControl): TDesignPosition; public procedure AddByControl(AControl: TControl); property ItemByControl[AControl: TControl]: TDesignPosition read GetItemByControl; default; end; { TDesignPositionsList } function TDesignPositions.IndexByControl(AControl: TControl): Integer; begin Result := AControl.Tag; end; function TDesignPositions.GetItemByControl(AControl: TControl): TDesignPosition; begin Result := Items[IndexByCOntrol(AControl)]; end; procedure TDesignPositions.AddByControl(AControl: TControl); var DP: TDesignPosition; begin DP.Left := AControl.Position.X; DP.Top := AControl.Position.Y; DP.Width := AControl.Width; DP.Height := AControl.Height; DP.RotationAngle := AControl.RotationAngle; if AControl is TTextControl then DP.FontSize := TTextControl(AControl).Font.Size; Add(DP); end;
Тут TDesignPositions – это простой список записей типа TDesignPosition. Так как я не планирую в Run-Time добавлять или удалять контролы, то (для простоты) соответствие между элементом в списке и контролом задаётся свойством TControl.Tag.
Заполняется список так:
procedure TNPC.SavePositionsTo(AList: TDesignPositions); var i: Integer; begin AList.Clear; i := 0; ForEach( procedure (AControl: TControl) begin AControl.Tag := i; Inc(i); AList.AddByControl(AControl); end ); end;
Здесь TNPC – это базовый фрейм для персонажа, от него будут наследоваться все персонажи в будущем. ForEach – это специальный метод, который вызывает переданную анонимную функцию и перебирает все контролы фреймы. Метод я объявил в специальном хелпере, т.к. планирую его использовать ещё не один раз. (Идею с хелпером я подсмотрел у Bioan Mitov.)
type TControlHelper = class helper for TControl public procedure ForEach(AProc: TProc<TControl>); end; { TControlHelper } procedure TControlHelper.ForEach(AProc: TProc<TControl>); var i: Integer; begin AProc(Self); for i := 0 to ControlsCount - 1 do Controls[i].ForEach(AProc); end;
Т.к. мы контролы вкладывали друг в друга, то здесь используется рекурсия. Метод ForEach сначала вызывает указанную процедуру, а потом перебирает дочерние контролы. Таким образом, в список TDesignPositions сначала добавляются размеры самой фреймы, а потом уже размеры её контролов. Я этим воспользуюсь, чтобы определить коэффициент масштабирования:
procedure TNPC.RecalcScaleFactor; var dw, dh: Single; begin dw := Width / FDesignPositions.Items[0].Width; dh := Height / FDesignPositions.Items[0].Height; if dw < dh then FScaleFactor := dw else FScaleFactor := dh; end;
На текущий момент базовый фрейм для персонажа у меня выглядит примерно так:
TNPC = class(TFrame) private FControls: TControls; FDesignPositions: TDesignPositions; FScaleFactor: Single; FIsLoaded: Boolean; protected procedure Loaded; override; procedure Resize; override; procedure SaveDesignPositions; virtual; procedure SaveControls; procedure ScaleControls; virtual; public procedure SavePositionsTo(AList: TDesignPositions); property IsLoaded: Boolean read FIsLoaded; property ScaleFactor: Single read FScaleFactor; // Controls - простой массив для быстрого доступа ко всем контролам на фрейме // нулейвой элемент соответствует самой фрейме // у каждого контрола свойство Tag соответствует индексу в этом массиве property Controls: TControls read FControls; // DesignPositions - координаты и размеры всех контролов, заданных в дизайнере // нулейвой элемент соответствует самой фрейме property DesignPositions: TDesignPositions read FDesignPositions; end; .. procedure TNPC.Loaded; begin inherited; SaveDesignPositions; SaveControls; FIsLoaded := True; end; procedure TNPC.SaveDesignPositions; begin FDesignPositions := TDesignPositions.Create; SavePositionsTo(FDesignPositions); end; procedure TNPC.SaveControls; var i: Integer; begin SetLength(FControls, DesignPositions.Count); i := 0; ForEach( procedure (AControl: TControl) begin FControls[i] := AControl; end ); end; procedure TNPC.Resize; begin inherited; if IsLoaded then begin BeginUpdate; try RecalcScaleFactor; ScaleControls; finally EndUpdate; end; end; end; procedure TNPC.ScaleControls; begin // do nothing end;
Самый интересный метод – ScaleControls, он отвечает за масштабирование. Как раз здесь можно использовать DesignPositions и ScaleFactor. Но на уровне базовой фреймы я решил, что он будет пустым, т.к. у персонажа будет ещё анимация и будут внутренние состояния. И именно от текущего состояния будут рассчитываться положения объектов. Другими словами, свойствами DesignPositions и ScaleFactor мы будем активно пользоваться в наследниках.
А в следующий раз уже поговорим об анимации.
UPD. Для удобства я сделал ещё такую вещь: все контролы с фреймы сохранил в линейный массив Controls.
Delphi плюс Android? Есть идея! Tap#2
Эта заметка является продолжением предыдущей. Сегодня я расскажу, как я рисовал персонаж для будущей игры.
На самом деле, я перепробовал несколько вариантов. Сначала даже пробовал в 3D – я подумал, что можно сделать что-то красивое-объёмное, потом поиграться с тенями и освещением… но это занимает очень много времени, а в результате, при большом количестве объектов, на Android-устройстве всё это подтормаживает.
В 2D я тоже пробовал по-разному… От растровых картинок отказался сразу. Потому что векторную графику можно и отмасштабировать без потери качества, и анимировать отдельные объекты труда не составит. От своего собственного объекта-наследника от TShape, в котором закодировать (хардкорно) всю прорисовку – тоже отказался, т.к. это довольно трудоёмко. В конце-концов решил, что пусть это будет набор из готовых Shape-компонентов, размещённых в отдельной фрейме. Это и проще, и нагляднее. (Спасибо разработчикам FM, что вспомнили про TFrame, т.к. в Delphi XE2 в FireMonkey фрейм не было.)
Дальше я нашёл в интернете картинку с зелёным роботом, и положил её на фрейму в обычный TImage. Картинку выбрал с высоким разрешением, чтобы легче было накладывать на неё объекты. Затем, через меню Tools Options… –> Form Designer, выключил выравнивание по сетке в дизайнере, сняв флажок Snap to grid.
Теперь начинаем формировать персонаж. Тут тоже всё просто. Кидаем поверх картинки TRectangle, подгоняем размер до пикселя. Создаём следующий объект, кидаем поверх картинки и т.д. Чтобы легче было подогнать, делаем объект полупрозрачным, задав в его свойствах Opacity = 0,5.
Т.к. я планирую объекты анимировать, т.е. двигать (меняя Position), крутить (меняя RotationAngle), сжимать-растягивать (меняя Width и Height), то я применил такую хитрость: объекты кидаются не на фрейму, а в промежуточный TRectangle (размером в одну точку). Это нужно чисто для удобства, чтобы координаты объекта рассчитывались не относительно фреймы, а относительно некоторой точки (в 3D для этих целей есть TDummy, аналога для 2D я не нашёл). На картинке из предыдущей заметки Вы можете увидеть объекты с именами RoboCenter, LegRightCenter и т.п. – это как раз и есть “опорные” для анимации точки.
Когда робот готов – фоновая картинка больше не нужна. (Но я удалять её не стал, а просто создал отдельную фрейму, на которую скопировал получившегося персонажа.) С этой новой фреймой и буду работать дальше.
Теперь мне нужно получившийся персонаж научить каким-нибудь анимациям. Об этом – в следующий раз.
Прежде чем делать анимацию, в следующий раз мы поговорим о масштабировании персонажа.
Delphi плюс Android? Есть идея! Tap#1
В рамках обозначенного конкурса решил я что-нибудь смастерить на FM под Андроид.
Забегая чуточку вперёд обозначу, что буду делать незаурядное приложение – игру на логическое мышление, обязательно с анимацией. В игре будет персонаж (или несколько персонажей?), в зависимости от своего состояния персонаж будет выглядеть по-разному. Соответственно переходы из состояния в состояние будут анимированными. Приложение будет в 2D, т.к. игровое поле само по себе плоское. А пока, буду описывать сам процесс…
Итак, начинаем мастерить. Для начала надо дать какое-то черновое название проекту. Я решил, что пусть это будет “Тап-Тап!” (от английского “Tap” – тап-тап в экран пальцем). Создаём проект в Delphi: File New FireMonkey Mobile Application – Delphi, и далее выбираем Blank Application. Сохраняем проект: для формы задаём имя frmMain, для проекта – TapTap. Теперь сделаем несколько телодвижений, чтобы облегчить себе работу.
Во-первых, понятно, что я приложение буду запускать очень много раз, каждый раз делать это на Android-устройстве (или эмуляторе) – очень накладно по времени. Поэтому сразу выбираем в Project Manager’е Target Platform –> 32 bit Windows. То есть, хоть мы и указали, что приложение будет мобильным, но сначала компилировать и отлаживать мы будет прям в ОС, в которой установлена Delphi. В будущем, достаточно будет переключить Target Platform обратно на Android и выбрать нужное Android-устройство. По-моему, это очень удобно. (Вот она, сила RAD!)
Во-вторых, в главной форме приложения я отключу эту рамочку вокруг формы со скином девайса, под который я якобы разрабатываю интерфейс. Отключу по двум причинам: а) мне банально эта рамочка не нравится, б) эта рамочка фиксирует размер формы. В конце-концов, я хочу, чтобы приложение было универсальным и не зависело от форм-фактора конечного устройства. По крайней мере, пока у нас черновик, я вполне могу захотеть изменять размер формы как угодно и размещать на ней временные объекты.
Отключить довольно просто: правой кнопкой мыши на форму, выбираем View as Text и меняем свойство DesignerMobile с True на False. Далее жамкаем Alt+F12 – возвращаемся обратно к дизайнеру.
В-третьих, конструируя игрового персонажа и настраивая его анимацию, у нас будет много объектов. Я буду постоянно экспериментировать со свойствами объектов, мне надо будет постоянно между ними переключаться. Для удобства я расположу панели Structure и Object Inspecotr рядом друг с другом, слева-направо. Переключаться же между Project Manager’ом и Tool Palette я буду гораздо реже, поэтому их расположим друг-в-друге, вкладками. Всё это у меня выглядит так:
Как видите на картинке, персонаж у нас вполне себе предсказуемый для Android-приложения :). Чуть попозже я расскажу, как я его рисовал.
А пока сделаем ещё одно действие – настроим иконку для приложения. Можно, конечно, отложить это на потом; иконка – это творческий элемент приложения, когда надоело писать код, а сделать что-то надо, самое-то отвлечься на иконку.
Для создания иконок я использую приложение AWicons Pro. Приложение платное, но оно того стоит. Разобраться в нём труда не составит, поэтому процесс создания иконок останется за кадром – на всё про всё у меня ушло не более трёх минут. Иконки, для удобства, сохраняем в отдельный каталог, для Windows-сборки нам нужен файл с расширением .ico (причём один файл будет содержать внутри себя несколько иконок разных размеров), а для Android’а нам нужно несколько png-файлов с разным разрешением.
HINT: Кстати сказать, если для Android платформы не указать иконки, то приложение под Android собрать не получится.
Далее связываем иконки с приложением через меню Project Options –> Application. Выбираем Target и прописываем связи, у меня это выглядит так:
На заметку: диск F у меня ссылается на каталог с проектами Delphi. Очень удобно.
Пожалуй, на этом я закончу. Продолжение следует.
Добавление ресурсов в программу
В этой публикации я побуду Капитаном Очевидность и расскажу о паре способах добавления ресурсов в программу. Определение: Ресурсы — данные, встроенные в EXE, DLL, CPL
Добавление ресурсов в программу
В этой публикации я побуду Капитаном Очевидность и расскажу о паре способах добавления ресурсов в программу.
Определение: Ресурсы — данные, встроенные в EXE, DLL, CPL и (начиная с Windows Vista) MUI-файлы. Доступ к этим данным можно получить через функции Windows API. (из Википедии).
В Delphi XE5 аналогичный механизм используется и для хранения ресурсов в Android приложениях. Насчёт iOS приложений я не проверял, но полагаю, что должно работать и там.
Вообще, ресурсы в Delphi – это фундамент, на котором строится вся программа. Именно в ресурсы пакуются все ваши DFM файлы (видели же строку “{$R *.DFM}” в .pas файлах форм? это оно самое), иконки, курсоры, и строки (resourcestring). Именно благодаря ресурсам работает локализация программ с помощью Translation Manager. Всё это происходит…
[[ This is a content summary only. Visit my website for full links, other content, and more! ]]
Карта памяти процесса
Задумывались ли вы над тем, как именно используется память, доступная вашей программе, да и вообще, что именно размещается в этих двух-трех гигабайтах виртуальной памяти, с
1. Пишем детскую программку под Android: план
Delphi для Android. О сколько радостных предвкушений вызывает у меня сочетание этих слов. Я давно хотел сделать что-нибудь под Android. Для начала что-нибудь простенькое, но
1. Пишем детскую программку под Android: план
Delphi для Android. О сколько радостных предвкушений вызывает у меня сочетание этих слов. Я давно хотел сделать что-нибудь под Android. Для начала что-нибудь простенькое, но свое.
Дочке сейчас полтора года, и ей очень нравятся телефоны, компьютеры и планшеты. Любая чёрненькая коробочка с кнопочками и лампочками влечёт её почище магнита. Но особое предпочтение доча отдаёт устройствам с тачскрином. “Папа, дай мне пожалуйста свой телефон, я хочу с ним поиграть, а заодно я раскидаю твои иконки так, что не сможешь потом ничего найти, поменяю звонок, переставлю время, запущу тебе кучу разных программ и позвоню парочке случайных знакомых”, хочет она сказать, но произносит только “Алё-алё! На-на-на!”. Ключевое для дочки, конечно, поиграть. Больше всего она любит программки с картинками, которые…
[[ This is a content summary only. Visit my website for full links, other content, and more! ]]