Jak zrobić animowany szkielet ludzika wykorzystując Canvas?

0

Witam, zastanawiam się jak zrobić poruszającą się figurkę używając polecenia canvas. Mam tak:

 var
  Form1: TForm1;
  Szkielet: Array[1..5] of Array[1..3] of TPoint;

implementation

{$R *.dfm}

procedure Ludzik;
const
  LewaReka = 1;
  PrawaReka = 2;
  LewaNoga = 3;
  PrawaNoga = 4;
  Cialo = 5;

begin
Szkielet[LewaReka ][1] := Szkielet[Cialo][2];
Szkielet[PrawaReka ][1] := Szkielet[Cialo][2];
Szkielet[LewaNoga ][1] := Szkielet[Cialo][3];
Szkielet[PrawaNoga][1] := Szkielet[Cialo][3];
end;
end.

I pytanie jest jak połączyć to używając canvas. Rysowanie danej figury nie jest problemem ale robienie to 5 razy z przemieszczającymi się koordynatami kończyn troszkę kłopotliwe więc wole przydzielić poszczególnym stawom pozycję, a następnie używając pętli ożywić aby się poruszała.

0

zastanawiam się jak zrobić poruszającą się figurkę używając polecenia canvas

Canvas to nie żadne polecenie tylko klasa TCanvas, do której dostęp umożliwia odpowiednia właściwość lub zdarzenia komponentu;

Rysowanie danej figury nie jest problemem ale robienie to 5 razy z przemieszczającymi się koordynatami kończyn troszkę kłopotliwe więc wole przydzielić poszczególnym stawom pozycję, a następnie używając pętli ożywić aby się poruszała.

No a gdzie ta pętla?

Dopowiem tyle, że punkty "stawów" możesz w każdej iteracji pętli przesuwać i rysować pomiędzy nimi linie, dzięki czemu uzyskasz dla każdej iteracji pętli jedną klatkę; Współrzędne punktów dla każdej klatki musisz gdzieś mieć zapisane (lub obliczać je ze wzorów), więc dopóki tego nie zrobisz nie będzie o co oprzeć reszty kodu.

0

Czyli tak, pierwsza klatka koślawca:

with form1.Canvas do begin
ellipse(350,100,450,200);
moveto(400,200);
lineto(400,200);
lineto(400,400);
lineto(480,500);
moveto(400,400);
lineto(370,450);
lineto(310,500);
moveto(400,250);
lineto(470,330);
moveto(400,250);
lineto(370,300);

Co do pętli: dana liczba dzielona przez MOD i wyświetlanie danych współrzędnych gdy liczba będzie podzielna bez reszty itp. Tylko, że ona jest użyteczna tylko wtedy jak zrobię współrzędne 5 klatek tak jak zrobiłem to z tą jedną. Natomiast wolał bym to zrobić w taki sposób, że podaje/zmieniam tylko i wyłącznie liczbę przydzieloną do stawu, a on się przemieszcza, czy jakoś tak...

0

Natomiast wolał bym to zrobić w taki sposób, że podaje/zmieniam tylko i wyłącznie liczbę przydzieloną do stawu, a on się przemieszcza, czy jakoś tak...

No to zamiast robić to na stałe w kodzie - zadeklaruj jakąś macierz dwuwymiarową, w której każdy element będzie zawierał punkt, a kazdy wiersz zestaw punktów (gdzie ilość punktów w zestawie jest równa ilości wszystkich klatek); Wierszy ma być tyle, ile stawów;

Jeśli już taką tablicę będziesz miał to wystarczy zaprogramować pętlę, w której pobierzesz z macierzy współrzędne wszystkich stawów dla danej klatki (której indeks będzie iteratorem pętli) i narysujesz pomiędzy tymi punktami linie;

Dzięki temu algorytm rysujący ludzika będzie mógł być jeden, a macierz będziesz mógł modyfikować do woli.

0

Aaaaaaahaaa, emmm wrócę chyba jutro do tego po gruntownym dokształceniu ;p

1

Musisz zadeklarować sobie macierz dwuwymiarową, gdzie jeden wymiar oznacza indeks klatki, a drugi indeks "stawu" - ruchomego punktu; Poniżej masz przykładową definicję takiej macierzy:

const
  GUY_POINTS_COUNT = 5;
  FRAMES_COUNT = 6;
const
  TOP_INDEX  = 0;
  LEFT_KNEE  = 1;
  LEFT_FOOT  = 2;
  RIGHT_KNEE = 3;
  RIGHT_FOOT = 4;
const
  GUY_FRAMES: array [0 .. FRAMES_COUNT - 1, 0 .. GUY_POINTS_COUNT - 1] of TPoint =
    (
      // TOP_INDEX   |  LEFT_KNEE    |  LEFT_FOOT    |  RIGHT_KNEE   |  RIGHT_FOOT
      ((X: 23; Y: 0), (X: 14; Y: 26), (X: 02; Y: 45), (X: 35; Y: 26), (X: 44; Y: 47)), // FRAME 0
      ((X: 23; Y: 0), (X: 17; Y: 27), (X: 04; Y: 45), (X: 32; Y: 27), (X: 36; Y: 47)), // FRAME 1
      ((X: 23; Y: 0), (X: 24; Y: 27), (X: 14; Y: 43), (X: 28; Y: 27), (X: 29; Y: 47)), // FRAME 2
      ((X: 23; Y: 0), (X: 29; Y: 27), (X: 22; Y: 43), (X: 24; Y: 27), (X: 24; Y: 47)), // FRAME 3
      ((X: 23; Y: 0), (X: 31; Y: 27), (X: 31; Y: 43), (X: 20; Y: 28), (X: 16; Y: 47)), // FRAME 4
      ((X: 23; Y: 0), (X: 33; Y: 26), (X: 38; Y: 45), (X: 13; Y: 27), (X: 05; Y: 47))  // FRAME 5 
    );

Każda kolumna zawiera współrzędne danego punktu ciała w poszczególnych klatkach, a każdy wiersz zawiera wszystkie punkty jednej klatki (po jednym punkcie dla każdego punku ciała - oznaczone komentarzami); Współrzędne punktów musisz sobie ustalić sam - ja dobrałem współrzędne pięciu punktów ("miednica", lewe kolano, lewa stopa, prawe kolano i prawa stopa) dla sześciu klatek;

Teraz do klasy formularza dodajesz pole typu np. TBitmap, które tworzysz w konstruktorze formularza; Ustawiasz mu odpowiedni rozmiar i kolor tła; Dodajesz także pole przechowujące indeks aktualnej klatki, po czym w konstruktorze formularza przypisujesz mu wartość 0 (czyli indeks pierwszej klatki);

Następnie dodajesz do formularza komponent klasy TTimer i ustawisz mu odpowiedni interwał (w zależności od tego ile klatek ma być wyświetlanych na sekundę); W zdarzeniu OnTimer tego komponentu oprogramowujesz rysowanie jednej klatki; Wszystko rysujesz na tymczasowej klatce (dodatkowe pole klasy formularza) - linie wszystkich kawałków ludzika, ewentualnie możesz dodać kwadraciki w miejscach stawów, aby polepszyć efekt; W przykładowej aplikacji wygląda to tak:

procedure TForm1.tmrAnimationTimer(Sender: TObject);

  procedure BodyLine(ACanvas: TCanvas; const AFirst, ASecond: TPoint);
  begin
    // line from AFirst to ASecond with offset
    ACanvas.MoveTo(FRAME_OFFSET + AFirst.X, FRAME_OFFSET + AFirst.Y);
    ACanvas.LineTo(FRAME_OFFSET + ASecond.X, FRAME_OFFSET + ASecond.Y);
  end;

  procedure BodyPoint(ACanvas: TCanvas; const APoint: TPoint);
  begin
    // draw rectangle at body point
    ACanvas.Rectangle(FRAME_OFFSET + APoint.X - 2, FRAME_OFFSET + APoint.Y - 2,
                      FRAME_OFFSET + APoint.X + 1, FRAME_OFFSET + APoint.Y + 1);
  end;

begin
  with FFrame do
  begin
    // clear background
    Canvas.FillRect(Canvas.ClipRect);

    // draw all body lines
    Canvas.Pen.Color := clGray;
    BodyLine(Canvas, GUY_FRAMES[FFrameIdx, TOP_INDEX], GUY_FRAMES[FFrameIdx, LEFT_KNEE]);
    BodyLine(Canvas, GUY_FRAMES[FFrameIdx, LEFT_KNEE], GUY_FRAMES[FFrameIdx, LEFT_FOOT]);
    BodyLine(Canvas, GUY_FRAMES[FFrameIdx, TOP_INDEX], GUY_FRAMES[FFrameIdx, RIGHT_KNEE]);
    BodyLine(Canvas, GUY_FRAMES[FFrameIdx, RIGHT_KNEE], GUY_FRAMES[FFrameIdx, RIGHT_FOOT]);

    // draw all body points
    Canvas.Pen.Color := clRed;
    BodyPoint(Canvas, GUY_FRAMES[FFrameIdx, TOP_INDEX]);
    BodyPoint(Canvas, GUY_FRAMES[FFrameIdx, LEFT_KNEE]);
    BodyPoint(Canvas, GUY_FRAMES[FFrameIdx, LEFT_FOOT]);
    BodyPoint(Canvas, GUY_FRAMES[FFrameIdx, RIGHT_KNEE]);
    BodyPoint(Canvas, GUY_FRAMES[FFrameIdx, RIGHT_FOOT]);

    // copy temporary frame
    imgGuy.Picture.Bitmap.Assign(FFrame);
    // clear temporary frame
    Canvas.FillRect(Canvas.ClipRect);
  end;

  // increment frame index
  Inc(FFrameIdx);

  if FFrameIdx = FRAMES_COUNT then
    FFrameIdx := 0;
end;

Po ich namalowaniu "kopiujesz" tymczasową klatkę do komponentu metodą Assign (czy rysujesz ją bezpośrednio na formularzu), po czym ją czyścisz metodą FillRect (Brush tymczasowej klatki ustalasz na początku przy jej tworzeniu w pamięci w konstruktorze klasy okna); Po skopiowaniu klatki inkrementujesz licznik aktualnej klatki i sprawdzasz, czy się przekręcił; Jeśli tak - przypisujesz mu wartość 0, aby animacja ruszyła od początku;

To wszystko - przykładowe klatki animacji dla samych nóg:

frames.png

Jak widać nie wygląda źle; Możesz sobie jeszcze dodać kilka fiuczerów do np. manipulowania ilością wyświetlanych klatek na sekundę (dodać komponent np. klasy TSpinEdit) lub inne dodatki jak np. linia podłoża:

simple_animated_guy.png

Do swojej wersji musisz zwiększyć ilość punktów (zwiększyć drugi wymiar macierzy GUY_FRAMES i odpowiednio ustawić stałą GUY_POINTS_COUNT) i ew. ilość klatek dla większej płynności animacji; W załaczniku dodaję aplikację widniejącą na powyższym obrazku - zobacz sobie jak działa i dostosuj pod swoje wymagania.

0

Oczekiwałem tylko paru wskazówek, a jakoś bym sobie z tym poradził prędzej czy później, natomiast dostałem prawie, że gotowca na talerzu. W każdym razie doceniam wkład włożonej pracy. Dzięki wytłumaczeniu i kodzie jestem wstanie prześledzić każdy punkt tego co napisałeś. Dopracuje to i spróbuje udoskonalić, a następnie pokaże wyniki. Na pewno dzięki tobie się sporo nauczyłem i nauczę budując drugą cześć szkieletu mojego ludzika ^^

0

Może faktycznie nieco się zapędziłem, ale ciężko to wytłumaczyć tak, abyś zrozumiał z samego tekstu (co widać po Twoich poprzednich postach); Sporo w tym kodzie można zmienić, a na pewno trzeba będzie zmodyfikować macierz zawierającą punkty ludzika, która jest w sumie w tym programie najważniejsza; Uprzedziłem tym samym Twoje pytania dotyczące rozszerzenia macierzy (dodając nowe punkty i nowe klatki) no i pytania dotyczące jej obsługi przez pętle; Aby nie tracić kontroli nad aplikacją lepiej jest wykorzystać TTimer, który nie blokuje programu (o to pewnie też byś zapytał) i tak pokazałem w aplikacji testowej;

Sam system rysowania jest bardzo prosty - wykorzystuje ustawienia Canvas, a dokładniej kolorów ołówka i wypełnienia, malując linie standardowymi metodami MoveTo w połączeniu z LineTo; Opakowałem je w osobne - lokalne procedury żeby nie zaciemniać kodu; W nich wykorzystana jest stała FRAME_OFFSET - u mnie rysowanie odbywa się na komponencie klasy TImage, a Ty potrzebujesz rysować po formularzu, więc jakiś ofset będzie Ci potrzebny; Samo rysowanie na kanwie formularza musisz wykonać metodą Canvas.Draw podając instancję klasy przygotowanej pojedynczej klatki;

Nie uważam tego za gotowca, bo sporo musisz zmodyfikować - przygotować macierz z punktami (zwiększyć ilość klatek i ilość punktów ludzika), zmienić metodę rysowania i zamiast malować na komponencie - malować na formularzu, dodać obsługę rysowania nowych elementów ciała ludzika w zdarzeniu OnTimer, dostosować okno pod Twoją animację itd.; Więc jest co zmieniać i z pewnością nie przekopiujesz mojego kodu do swojego programu; A że trochę mi się nudziło - dostałeś przykład prostej animacji połowy ludzika, druga połowa sprawi Ci nieco problemów, zobaczysz :]

0

Zacząłem się bawić i mam niezły ubaw przy tym. W każdym razie zastanawiam się jak tu głowę narysować... Toż nawet miecz ma ten ludzik ale głowy nadal brak ;p

Edit. Już chyba wiem ^^

0

Skoro Twoja macierz zawiera punkty, to dla głowy także wystarczy punkt, który stanowić będzie środek okregu reprezentującego dyńkę; Bez względu na kształt głowy i dobraną funkcję do jej rysowania będziesz mógł według tego punktu ją narysować;

Zacząłem się bawić i mam niezły ubaw przy tym.

Sam miałem niezły ubaw, dlatego też nabazgrałem cały program z animacją, a nie tylko rzuciłem pomysłami :]

0

Jednak ja pomyślałem o czymś całkowicie innym, jak narysować głowę. Czyli tak: mam const dla głowy, punkty w każdej klatce i teraz bawić mam się w tym miejscu, lub stworzyć nową procedurę dla głowy:

procedure BodyLine(ACanvas: TCanvas; const AFirst, ASecond: TPoint);
  begin
    // line from AFirst to ASecond with offset
    ACanvas.MoveTo(FRAME_OFFSET + AFirst.X, FRAME_OFFSET + AFirst.Y);
    ACanvas.LineTo(FRAME_OFFSET + ASecond.X, FRAME_OFFSET + ASecond.Y);
  end;

Dobrze czaje?

0

A jaki kształt ma mieć głowa?

0

Okrągła=Kulka=Koło ^^

0

No to zrób tak jak napisałem - określ pozycję głowy za pomocą jednego punktu i jeśli chcesz to dorób funkcję przyjmującą w argumencie ten punkt i wywołaj metodę Canvas.Ellipse z odpowiednimi ofsetami dla czterech parametrów, np. w ten sposób:

{
  ACanvas - kanwa do malowania
  APoint  - punkt oznaczający środek głowy
  ARadius - promień okręgu reprezentującego dyńkę
}
procedure Head(ACanvas: TCanvas; const APoint: TPoint; const ARadius: Integer);
begin
  ACanvas.Ellipse(FRAME_OFFSET + APoint.X - ARadius, FRAME_OFFSET + APoint.Y - ARadius,
                  FRAME_OFFSET + APoint.X + ARadius, FRAME_OFFSET + APoint.Y + ARadius);
end;

Dzięki temu nie będziesz musiał kombinować z przerabianiem macierzy dla innego typu, czy tworzeniem głowy na podstawie wielu punktów, aby przypominała okrąg;

Tylko pamiętaj o wczęśniejszym ustawieniu kolorów ołówka (Canvas.Pen.Color) i wypełnienia (Canvas.Brush.Color).

0
procedure Head(ACanvas: TCanvas; const APoint: TPoint; const ARadius: Integer);
begin
  ACanvas.Ellipse(FRAME_OFFSET + APoint.X - ARadius, FRAME_OFFSET + APoint.Y - ARadius,
                  FRAME_OFFSET + APoint.X + ARadius, FRAME_OFFSET + APoint.Y + ARadius);
end;

Zanim edytowałeś zrobiłem prawie, że identycznie, zapomniałem tylko o ARadius ;p = skleroza
...ciąg dalszy nastąpi jutro...

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