Malowanie obramowania regionu składającego się z kwadratowych pól

0

Mamy region składający się z kwadratowych pól – w rodzaju siatki. Niektóre pola tego regionu (zawsze kwadratowe) stykają się ze sobą bokami, inne wierzchołkami, a jeszcze inne nie stykają się w ogóle (luźne wysepki, których liczba może być dowolna). To które pole siatki jest puste a które zajęte określa dwuwymiarowa macierz wartości logicznych. Pola zajęte mają się znaleźć w regionie, a puste nie.

W Lazarusie mogę stworzyć taki region za pomocą klasy Graphics.TRegion i dodać pola metodą TRegion.AddRectangle, odpowiednio obliczając ich obszary. W ten sposób mogę zbudować region posiadający wiele osobnych wysepek zbudowanych z dowolnej liczby pól.

W jaki sposób namalować samo jego obramowanie w kolorze białym, linią ciągłą i bez wypełnienia?


Co prawda tak przygotowany region mogę wypełnić kolorem za pomocą funkcji LCLIntf.FillRgn lub po prostu FillRgn (z biblioteki systemowej) i ładnie namaluje wszystkie pola wszystkich wysepek, nawet jeśli nie są ze sobą połączone, jednak do samego obramowania nie znalazłem absolutnie niczego…

Ma ktoś pomysł jak to ugryźć? Ostatecznie sam se napiszę taką metodę, ale warto najpierw zapytać. ;)

0

Cholerka, nie ma jej zdefiniowanej w LCLIntf, jest tylko w Windows… :/

1

No to sobie "ukradnij" definicję ;)

Tak na wszelki wypadek sprawdziłem, jak jest to zrobione w Delphi:

function FrameRgn; external gdi32 name 'FrameRgn';

{$EXTERNALSYM FrameRgn}
function FrameRgn(DC: HDC; hrgn: HRGN; hbr: HBRUSH; Width, Height: Integer): BOOL; stdcall;

screenshot-20190220224849.png

A tak poza tym to przyznaj się, co kombinujesz :P

0

Nie muszę kraść importu, bo ten jest w module Windows. Widać ta funkcja albo nie istnieje na innych platformach, albo nie została (jeszcze) powiązana z widgetsetem. Muszę sobie do dokładnie sprawdzić.

cerrato napisał(a):

A tak poza tym to przyznaj się, co kombinujesz :P

A to co zwykle ostatnio – platformer. Potrzebuję dodać opcję wyświetlania zgodnego z hitboksami konturu platform bieżącej warstwy. Brakuje mi tej opcji, a bardzo pomogłaby mi tworzyć poziomy. ;)

0

Z tego co kojarzę, regiony to jest wynalazek typowo WinAPI, więc ciężko by było to portować na inne platformy. To, co daje Lazarus (albo Delphi) to jedynie ładne opakowanie mechanizmu stricte Windowsowego.

W temacie różnic między platformami - z dobrą godzinę walczę już (właściwie to chyba skończyłem.. obawiam się, że tak po prostu musi być) z Lazarusem oraz ShowModal. Mam okienko główne - na cały ekran, bez obramowania, które wywołuje dialog. Niestety, działa tylko Show, przy ShowModal okienka nie ma, albo się pojawia gdzieś w tle i nie da się na nie przełączyć. W Windowsie działa, w Delphi działa, w Lazarusie na Windows - tak, Lazarus na Linux - nie. Metodą kombinowania, prób i błędów oraz szukania w necie (chociaż bardzo mało było sensownych wypowiedzi) ustaliłem, że w przypadku Linuksa ShowModal działa, ale TYLKO, jeśli okno posiada obramowanie. Okienka full-screen sobie z tym nie radzą.

Podejrzewam, że z regionami by było podobnie - jakby ktoś chciał to przerzucić, to osoby starające się skorzystać na innych platformach z tego, bardzo szybko by były bardzo mocno furious ;)

1

Nieważne już, skrobne sobie swoją metodę, która po prostu malować będzie pojedyncze linie. W końcu jednorazowo trzeba będzie ich namalować co najwyżej kilkadziesiąt, co w połączeniu z tylnym buforowaniem nie będzie stanowić praktycznie żadnego narzutu. ;)


cerrato napisał(a):

Metodą kombinowania, prób i błędów oraz szukania w necie (chociaż bardzo mało było sensownych wypowiedzi) ustaliłem, że w przypadku Linuksa ShowModal działa, ale TYLKO, jeśli okno posiada obramowanie. Okienka full-screen sobie z tym nie radzą.

A weź wyświetl za ten dialog normalnie za pomocą Show, ale najpierw zablokuj główne okno za pomocą Enabled, a po zamknięciu dialogu odblokuj z powrotem. Nie zmieniaj też właściwości WindowState i FormStyle. W razie czego wspomóż się BringToFront.

Potestuj aktywowanie aplikacji z poziomu klawiatury (odpowiednik windowsowego Alt+Tab, jeśli istnieje) oraz z poziomu różnych systemowych funkcjonalności. Bo może być tak, że po takich zabawach okno dialogowe schowa się pod to główne.

0

A weź wyświetl za ten dialog normalnie za pomocą Show, ale najpierw zablokuj główne okno za pomocą Enabled, a po zamknięciu dialogu odblokuj z powrotem. Nie zmieniaj też właściwości WindowState i FormStyle. W razie czego wspomóż się BringToFront

No dokładnie w ten sposób to załatwiłem, widać wybitne umysły działają podobnie ;)

WindowState w ogóle nie tykam. Jak pisałem wcześniej - główne okno jest bez obramowania, fsStayOnTop, rozmiar ustawiony na cały ekran (robię to ręcznie w OnCreate, żadne wsFullScreen). Dialogowi dodałem w obsłudze deactivate wspomniane przez Ciebie BringToFront - jedyny minus to mrugnięcie podczas przełączania. Kiedy kliknę na oknie głównym, zanim zadziała BringToFront, na ułamek sekundy dialog znika, przez co mam mrugnięcie. Ale w sumie to i tak to wszystko jest podane bardziej na zasadzie ciekawostki, gdyż sobie piszę na Linuksie (gdzie te "zjawiska" występują), ale klient będzie korzystać z tego na Windowsach - a tam ShowModal działa poprawnie, nawet jeśli okno główne nie posiada obramowania.

0
cerrato napisał(a):

Kiedy kliknę na oknie głównym, zanim zadziała BringToFront, na ułamek sekundy dialog znika, przez co mam mrugnięcie.

A to dość dziwne, bo jeśli okno jest zablokowane to nie powinno w ogóle reagować na kliknięcia. Tym bardziej, jeśli próbuje się przełączać pomiędzy oknami tego samego procesu. Przynajmniej tak to działa pod Windows – system nie podbija zablokowanego okna na wierzch, a włącza animację migania obramowania aktywnego okna.

No, to tyle jeśli chodzi o off-top. ;)

1

No dobra, zrobiłem własny mechanizm malowania konturu, który dla każdego mieszczącego się w kamerze hitboksa maluje linie jego ścian. Narzut wynikający z renderowania linii wynosi raptem 1%, ale to i tak nieważne, bo ta funkcja jest dostępna tylko w trybie debugowania. ;)


Aby można było malować linie, najpierw trzeba znać zakres kafli widocznych na ekranie. Klasa kamery przechowuje widoczny obszar liczony w pikselach, więc dodałem sobie nową właściwość, która na jego podstawie oblicza i zwraca obszar liczony w kaflach:

type
  TCamera = class(TObject)
  {..}
  private
    function GetHitBoxesZone(): TRect;
  public
    property HitBoxesZone: TRect read GetHitBoxesZone;
  end;
  
function TCamera.GetHitBoxesZone(): TRect;
begin
  Result.Left := FViewZone.Left div TILE_SIZE;
  Result.Right := FViewZone.Right div TILE_SIZE;

  Result.Top := FViewZone.Top div TILE_SIZE;
  Result.Bottom := FViewZone.Bottom div TILE_SIZE;

  if FViewZone.Right mod TILE_SIZE <> 0  then Result.Right += 1;
  if FViewZone.Bottom mod TILE_SIZE <> 0 then Result.Bottom += 1;
end;

Następnie do klasy renderera poziomu dodałem metodę umożliwiającą namalowanie wszystkich czterech ścian hitboksu:

type
  TLevelRenderer = class(TObject)
  {..}
  private
    procedure RenderHitBoxContour(ALayer: TLayer; ACamera: TCamera; AColumn, ARow: Integer);
  end;

procedure TLevelRenderer.RenderHitBoxContour(ALayer: TLayer; ACamera: TCamera; AColumn, ARow: Integer);
begin
  if not ALayer.HitBox[AColumn - 1, ARow] then
    Buffers.Master.Canvas.Line(
      ACamera.ToDisplayPoint(AColumn * TILE_SIZE, ARow * TILE_SIZE),
      ACamera.ToDisplayPoint(AColumn * TILE_SIZE, ARow * TILE_SIZE + TILE_SIZE)
    );

  if not ALayer.HitBox[AColumn + 1, ARow] then
    Buffers.Master.Canvas.Line(
      ACamera.ToDisplayPoint(AColumn * TILE_SIZE + TILE_SIZE - 1, ARow * TILE_SIZE),
      ACamera.ToDisplayPoint(AColumn * TILE_SIZE + TILE_SIZE - 1, ARow * TILE_SIZE + TILE_SIZE)
    );

  if not ALayer.HitBox[AColumn, ARow - 1] then
    Buffers.Master.Canvas.Line(
      ACamera.ToDisplayPoint(AColumn * TILE_SIZE, ARow * TILE_SIZE),
      ACamera.ToDisplayPoint(AColumn * TILE_SIZE + TILE_SIZE, ARow * TILE_SIZE)
    );

  if not ALayer.HitBox[AColumn, ARow + 1] then
    Buffers.Master.Canvas.Line(
      ACamera.ToDisplayPoint(AColumn * TILE_SIZE, ARow * TILE_SIZE + TILE_SIZE - 1),
      ACamera.ToDisplayPoint(AColumn * TILE_SIZE + TILE_SIZE, ARow * TILE_SIZE + TILE_SIZE - 1)
    );
end;

oraz ogólną metodę renderującą kontur wszystkich hitboksów mieszczących się na ekranie:

type
  TLevelRenderer = class(TObject)
  {..}
  public
    procedure RenderContour(ALevel: TLevel);
  end;

procedure TLevelRenderer.RenderContour(ALevel: TLevel);
var
  Column, Row: Integer;
begin
  Buffers.Master.Canvas.Pen.Color := COLOR_LAYER_CONTOUR;
  Buffers.Master.Canvas.Pen.Style := psDot;
  Buffers.Master.Canvas.Brush.Style := bsClear;

  for Row := ALevel.Camera.HitBoxesZone.Top to ALevel.Camera.HitBoxesZone.Bottom do
    for Column := ALevel.Camera.HitBoxesZone.Left to ALevel.Camera.HitBoxesZone.Right do
      if ALevel.CurrentLayer.HitBox[Column, Row] then
        RenderHitBoxContour(ALevel.CurrentLayer, ALevel.Camera, Column, Row);

  Buffers.Master.Canvas.Pen.Style := psSolid;
  Buffers.Master.Canvas.Brush.Style := bsSolid;
end;

Niżej efekt końcowy (dwie najbliższe warstwy są ukryte, aby nie przeszkadzały w testowaniu kolizji):

rendering layer contour.png

1 użytkowników online, w tym zalogowanych: 0, gości: 1