Недавно один из форумчан glscene.ru спрашивал о способе отображения панельки с изменяемой шириной. Простым масштабированием (Scale) спрайта правильного эффекта не всегда можно добиться. Например, нам нужны закругленные края или небольшая тень от панельки. При масштабировании эти эффекты безобразно увеличатся и будут портить всю картинку. Как же быть в случае, если заранее ширина или высота такой панельки неизвестна и просто нарисовать png-шку нужного размеры мы не сможем? На ум приходит собирать итоговую картинку из нескольких элементов - углы, края и серединка. Вот такую панельку, состоящую из 9ти кусочков мы и будем сегодня делать на GlScene. |
В общем, ничего хитрого, все просто и прямолинейно. Поэтому сегодня будет мало слов, много кода...
Итак, создаем основной класс панельки, опишем его в следующем виде:
TSimplePanel = class (TGLHudSprite) protected fCorner, fHalfCorner: Integer; Procedure MainInit; public Function NCHitTest(aPosition: TVector): TNCSizing; Function IsHit(aPosition: TVector): Boolean; Procedure DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean); override; Constructor Create(AOwner: TComponent); reintroduce; Constructor CreateAsChild(aParentOwner: TGLBaseSceneObject); end;
где NCHitTest() и IsHit() нужны для возможности изменения размеров панельки при помощи мыши в realtime-режиме!
Заглянем в главный метод DoRender() и найдем для себя базовый метод отрисовки 9ти кусочков нашей панельки:
Procedure TSimplePanel.DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean); var left, top: integer; eWidth, eHeight: Single; ePosition: TVector; Procedure RenderHud(x, y, w, h: integer; a, b, c, d: Single); begin Position.SetPoint(left + x, top + y, 0); with Material.GetActualPrimaryTexture do begin MappingMode := tmmObjectLinear; MappingSCoordinates.SetVector(1 / w * a, 0, 0, b); MappingTCoordinates.SetVector(0, 1 / h * c, 0, d); end; Width := w; Height := h; inherited DoRender(ARci, true, false); end; var w, h: integer; begin eWidth := Width; eHeight := Height; ePosition := Position.AsVector; left := Round(Position.x - Trunc(Width / 2)); top := Round(Position.y - Trunc(Height / 2)); w := (Round(Width) - fCorner * 2) div 2; h := (Round(Height) - fCorner * 2) div 2; RenderHud(fHalfCorner, fHalfCorner, fCorner, fCorner, 1/4, 1/8, 1/4, 7/8); // ... и так далее для всех 9ти элементов панели RenderHud(fCorner + w, fHalfCorner, 2 * w, fCorner, 1/2, 0.5, 1/4, 7/8); RenderHud(fCorner + 2 * w + fHalfCorner, fHalfCorner, fCorner, fCorner, 1/4, 7/8, 1/4, 7/8); RenderHud(fHalfCorner, h + fCorner, fCorner, 2 * h, 1/4, 1/8, 1/2, 0.5); RenderHud(fCorner + w, h + fCorner, 2 * w, 2 * h, 0.5, 0.5, 1/2, 0.5); RenderHud(fCorner + 2 * w + fHalfCorner, h + fCorner, fCorner, 2 * h, 1/4, 7/8, 1/2, 0.5); RenderHud(fHalfCorner, 2 * h + fCorner + fHalfCorner, fCorner, fCorner, 1/4, 1/8, 1/4, 1/8); RenderHud(fCorner + w, 2 * h + fCorner + fHalfCorner, 2 * w, fCorner, 1/2, 0.5, 1/4, 1/8); RenderHud(fCorner + 2 * w + fHalfCorner, 2 * h + fCorner + fHalfCorner, fCorner, fCorner, 1/4, 7/8, 1/4, 1/8); Width := eWidth; Height := eHeight; Position.SetPoint(ePosition); end;
Выглядит ужасно, согласен. Но это код из разряда "один раз написал и забыл", поэтому пока трогать и рефакторить его не собираюсь. По сути запоминаем текущие размеры панельки, ренедерим необходимые элементы, и выставляем исходные размеры.
Теперь об использовании данного класса...
Достаточно по обыкновению определить основные свойства и на экране появится симпатичная панелька:
fPanel := TSimplePanel.CreateAsChild(fMainDummy); fPanel.Material.MaterialLibrary := fMatLib; fPanel.Material.LibMaterialName := 'white'; fPanel.Position.SetPoint(600, 200, 0); fPanel.Width := 200; fPanel.Height := 200;
В нашем случае будет что-то вроде такого
Для красоты и интерактивности я позволил себе добавить список различных "скинов". Сам список - это элемент, который мы создали ранее. Поэтому демка принимает следующий вид:
Код самой программы вроде нетруден, поэтому я в дополнение просто приведу методы, которые решил добавить в качестве украшения - панельку можно resize-ить за стороны в любой момент! А также таскать саму панельку за любое произвольное место с зажатой левой кнопкой мыши.
Весь механизм по сути заключается в двух методах (UpdateMoving и UpdateSizing):
Procedure TfrmMain.UpdateSizing(MPos: TVector; DeltaPos: TVector); begin if not fDoSizing then begin fSizing := fPanel.NCHitTest(MPos); if (fSizing = cs_Left) or (fSizing = cs_Right) then fViewer.Cursor := crSizeWE else if (fSizing = cs_Top) or (fSizing = cs_Bottom) then fViewer.Cursor := crSizeNS else fViewer.Cursor := crDefault; end else begin if fSizing = cs_Left then begin fPanel.Width := fPanel.Width - Trunc(DeltaPos[0] / 2) * 2; fPanel.Position.X := fPanel.Position.X + Trunc(DeltaPos[0] / 2); end else if fSizing = cs_Right then begin fPanel.Width := fPanel.Width + Trunc(DeltaPos[0] / 2) * 2; fPanel.Position.X := fPanel.Position.X + Trunc(DeltaPos[0] / 2); end else if fSizing = cs_Top then begin fPanel.Height := fPanel.Height - Trunc(DeltaPos[1] / 2) * 2; fPanel.Position.Y := fPanel.Position.Y + Trunc(DeltaPos[1] / 2); end else if fSizing = cs_Bottom then begin fPanel.Height := fPanel.Height + Trunc(DeltaPos[1] / 2) * 2; fPanel.Position.Y := fPanel.Position.Y + Trunc(DeltaPos[1] / 2); end; end; end; Procedure TfrmMain.UpdateMoving(MPos: TVector; DeltaPos: TVector); begin if fMousePressed and fPanel.IsHit(MPos) and not fDoSizing then with fPanel.Position do begin SetPoint(x + DeltaPos[0], y + DeltaPos[1], 0); if x <= 380 then x := 380 else if x > 700 then x := 700; if y <= 100 then y := 100 else if y > 500 then y := 500; end; end;
Видео происходящего:
Думаю, на этом все, как обычно исходники качаем отсюда...
Всем удачи!
Спасибо, Lampogolovii.
ОтветитьУдалитьgltrinix, всегда рад! если будут идеи еще каких гуи-элементов - буду очень признателен...
ОтветитьУдалитьКорни подхода вечно живут в web-верстке под css <3.0. Поэтому геймдевелоперам, имеющим опыт верстки, такой подход практически родной :)
ОтветитьУдалить