[TP] tlo - efekt spadających gwiazd - pomoc


(Patrykkyky) #1

Witam, pisze gre statki kosmiczne w turbo pascalu 7.0 i próbuje do niej napisać jakieś tło, gra nie jest na zbyt zaawansowanym poziomie, głowie sie juz nad tym troche jednak nie chce wyjść tak jak chce. Miało by to polegać na tym ze na ekranie o ograniczniku np (X>=15) and (X<=45) cały obraz "stałby w miejscu" a w losowych miejscach poruszały by sie tylko gwiazki od dołu ekranu w góre tj Y:=Y+1. Ktoś mógłby pomóc i napisać krótki kod który wykonywałby to co mam na mysli? (system winxp)


(Ryan) #2

W trybie graficznym czy tekstowym? (zgaduję, że tekstowym, ale lepiej zapytać)


(Patrykkyky) #3

Tak tak, trybie tekstowym. Próbowałem już dość troche, ale jakoś nie umiem napisać odpowiedniej skłądni


(Ryan) #4

Nie programowałem w Pascalu od 10 lat i nie mam TP, ale poniższy kod działa we FreePascalu, więc powinien działać i Tobie.

Pierwsza rzecz jaką należy wykonać pisząc ten kawałek kodu, to przemyśleć jak chcemy przechowywać informację o pozycji gwiazd. Wybrałem najłatwiejsze rozwiązanie: tablica. Ponieważ każda gwiazda ma dwie współrzędne (X i Y), użyłem tablicy dwuwymiarowej (pierwszy wymiar: numer gwiazdy; drugi wymiar: 1=X, 2=Y). Pozycje wygenerowałem losowo (Randomize/Random).

program

  Stars;


uses

  Crt;


var

  StarsArray : array [1..20, 1..2] of Integer;

  Counter : Integer;


begin

  { Prepare screen for drawing }

  TextBackground(Black);

  TextColor(White);

  ClrScr;


  { Generate stars }

  Randomize;

  for Counter := 1 to 20 do

  begin

    StarsArray[Counter][1] := 1 + Random(80);

    StarsArray[Counter][2] := 1 + Random(25);

  end;


  { Display stars }

  for Counter := 1 to 20 do

  begin

    GotoXY(StarsArray[Counter][1], StarsArray[Counter][2]);

    Write('*');

  end;

  GotoXY(1,1);


  { Wait some time before exit }

  Delay(5000);

  TextColor(LightGray);

  ClrScr;

end.

Ok, mamy statyczne giwazdy. Teraz należy nimi poruszać. W tym celu co "klatkę" animacji odejmiemy od Y coraz to większą wartość.

program

  Stars;


uses

  Crt;


var

  StarsArray : array [1..20, 1..2] of Integer;

  Counter : Integer;

  Offset : Integer;


begin

  { Prepare screen for drawing }

  TextBackground(Black);

  TextColor(White);

  ClrScr;


  { Generate stars }

  Randomize;

  for Counter := 1 to 20 do

  begin

    StarsArray[Counter][1] := 1 + Random(80);

    StarsArray[Counter][2] := 1 + Random(25);

  end;


  { Display stars }

  Offset := 0;


  repeat

    for Counter := 1 to 20 do

    begin

      GotoXY(StarsArray[Counter][1], StarsArray[Counter][2] - Offset);

      Write('*');

    end;

    GotoXY(1,1);

    Delay(250);

    Offset := Offset + 1;

  until KeyPressed;


  { Cleanup and exit }

  TextColor(LightGray);

  ClrScr;

end.

Tą wartością jest oczywiście aktualizowany co klatkę Offset. Coś się jednak dzieje niedobrego! Program się wysypuje. Przyczyną tego jest operacja "StarsArray[Counter][2] - Offset" - w wyniku jej działania wartość podawana do GotoXY może być ujemna. Należy to poprawić. Przy okazji zmienimy kilka drobnych rzeczy. Aha - zwracam uwagę na repeat ... until KeyPressed - z jakiejś przyczyny nie działało to w moim FP poprawnie (opiszę na końcu o co chodzi).

program

  Stars;


uses

  Crt;


const

  ScreenWidth = 80;

  ScreenHeight = 20;

  ScreenX = 1;

  ScreenY = 1;

  StarCount = 20;


var

  StarsArray : array [1..StarCount, 1..2] of Integer;

  Counter : Integer;

  Offset : Integer;

  NewY : Integer;


begin

  { Prepare screen for drawing }

  TextBackground(Black);

  TextColor(White);

  ClrScr;


  { Generate stars }

  Randomize;

  for Counter := 1 to StarCount do

  begin

    StarsArray[Counter][1] := Random(ScreenWidth);

    StarsArray[Counter][2] := Random(ScreenHeight);

  end;


  { Display stars }

  Offset := 0;


  repeat

    for Counter := 1 to StarCount do

    begin

      NewY := StarsArray[Counter][2] + ScreenHeight - Offset;

      NewY := (NewY mod ScreenHeight) + ScreenY;

      GotoXY(StarsArray[Counter][1] + ScreenX, NewY);

      Write('*');

    end;


    GotoXY(1,1);

    Delay(250);

    Offset := Offset + 1;

    if Offset >= ScreenHeight then

      Offset := Offset - ScreenHeight;

  until KeyPressed;


  { Cleanup and exit }

  TextColor(LightGray);

  ClrScr;

end.

Pierwsze co się rzuca w oczy to kilka stałych (const) - definiują one wielkość ekranu na którym wyświetlane są gwiazdy i jego przesunięcie względem lewego górnego rogu (1:1 oznacza brak przesunięcia - pozycja indeksowana jest od jedynki) oraz ilość gwiazd. Pojawiły się dwa fragmenty kodu:

NewY := StarsArray[Counter][2] + ScreenHeight - Offset;

      NewY := (NewY mod ScreenHeight) + ScreenY;

Liczymy tutaj nowe Y gwiazdy jako stare Y pomniejszone o Offset i powiększone o wysokość ekranu. Dlaczego powiększone o wysokość ekranu? Bo dzielenie modulo (mod) we FP (i pewnie w TP też) nie radzi sobie z liczbami ujemnymi. Drugi fragment:

Offset := Offset + 1;

    if Offset >= ScreenHeight then

      Offset := Offset - ScreenHeight;

Tutaj upewniamy się, że Offset nigdy nie przekroczy wysokości ekranu, tym samym zabezpieczenie dla dzielenia modulo z poprzedniego fragmentu będzie działało poprawnie także po przewinięciu gwiazd o więcej niż jeden ekran. Pozostał jeszcze jeden problem: gwiazdy się nie usuwają z poprzedniej klatki. Można to rozwiązać na dwa sposoby: czyścić cały ekran albo czyścić same gwiazdy. Czyszczenie całęgo ekranu przy użyciu ClrScr spowoduje migotanie obrazu, co nie wygląda najlepiej. Dlatego przed aktualizacją Offsetu w miejscu gwiazd narysujemy spację. W tym celu dokonamy drobnej refaktoryzacji kodu i rysowanie gwiazd przeniesiemy do osobnej procedury. Także generowanie gwiazd przeniesiemy do procedury, bo czemu nie? Wreszcie powiększymy tablicę odpowiedzialną za informacje o gwiazdach dodając "typ" gwiazdy (który potem użyjemy przy rysowaniu gwiazd). Finalna wersja:

program

  Stars;


uses

  Crt;


const

  ScreenWidth = 80;

  ScreenHeight = 20;

  ScreenX = 1;

  ScreenY = 1;

  StarCount = 20;


var

  StarsArray : array [1..StarCount, 1..3] of Integer;

  Counter : Integer;

  Offset : Integer;

  NewY : Integer;



  procedure GenerateStars;

begin

  Randomize;

  for Counter := 1 to StarCount do

  begin

    StarsArray[Counter][1] := Random(ScreenWidth);

    StarsArray[Counter][2] := Random(ScreenHeight);

    StarsArray[Counter][3] := Random(2);

  end;

end;



procedure DrawStars(ClearStars: Boolean);

begin

    for Counter := 1 to StarCount do

    begin

      NewY := StarsArray[Counter][2] + ScreenHeight - Offset;

      NewY := (NewY Mod ScreenHeight) + ScreenY;

      GotoXY(StarsArray[Counter][1] + ScreenX, NewY);

      if ClearStars = True then

        Write(' ')

      else

      begin

        if StarsArray[Counter][3] = 0 then

          Write('*')

        else

          Write('.');

      end;

    end;

end;



begin

  { Prepare screen for drawing }

  TextBackground(Black);

  TextColor(White);

  ClrScr;


  { Generate stars }

  GenerateStars;


  { Display stars }

  Offset := 0;


  repeat

    { Draw, wait, clear }

    DrawStars(False);

    GotoXY(1,1);

    Delay(250);

    DrawStars(True);


    { Move stars offset }

    Offset := Offset + 1;

    if Offset >= ScreenHeight then

      Offset := Offset - ScreenHeight;

  until KeyPressed;


  { Cleanup and exit }

  TextColor(LightGray);

  ClrScr;

  WriteLn;

  WriteLn('Bye!');

  WriteLn;

end.

No i wreszcie o co chodzi z tym KeyPressed. Jeśli mnie pamięć nie myli repeat ... until Keypressed; powinno się wykonywać tak długo, jak długo nie wciśnie się klawisza. Mój laptop ma jednak coś z klawiaturą i... same mi się klawisze wciskają. Serio. W związku z tym program się kończy po pierwszej klatce. U innych powinno być ok. Gdyby nie było - zamienić czasowo KeyPressed na True i poszukać samemu rozwiązania. :wink:

Aha i jeszcze taka uwaga - piszesz, że chcesz spadających gwiazd, podajesz równanie Y:=Y+1, ale z opisu wynika, że mają się poruszać "od dołu ekranu w górę". To jak ma w końcu być? ;]


(Patrykkyky) #5

Stokrotne dzięki ! Pierwszy raz widze tak łądnie opisane działanie programu ! : ) Podejrzewam że mniej więcej "tak samo" będzie wyglądała funkcja na przemieszczające sie i strzelające statki przeciwnika + dodanie kolizji i strzału ?

PS co do spadających giwazd i Y+1 - mój błąd : P Ten program to jest napisany idealnie tak jak miałem to w wizji


(Ryan) #6

Tak, w pętli repeat ... until powinieneś umieścić wszystkie inne "rysowane" rzeczy, np (konieczna własna implementacja DrawShip oczywiście):

{ Draw, wait, clear }

    DrawStars(False);

    DrawShip(False);

    GotoXY(1,1);

    Delay(250);

    DrawStars(True);

    DrawShip(True);

Gdzieś po sekcji { Move stars offset } powinieneś też umieścić obsługę klawiatury, w stylu:

if KeyPressed then

  begin

    KbdChar := ReadKey;

    case KbdChar of

      #0 :

      begin

        KbdChar := ReadKey; { Scan code }

        case KbdChar of

          #75 : { Left }

          #77 : { Right }

        end;

      end;

      #32 :

        { Fire }

      #27 :

        Done = True;

    end;

  end;

Dodatkowo warunek repeat ... until pewnie zmieni się z KeyPressed na Done.

Aha, i jeszcze taka jedna wskazówka: w trybie tekstowym nie możesz nic napisać w prawym dolnym rogu ekranu (GotoXY(80, 25); Write('x'); przy standardowym trybie tekstowym) gdyż Write przesuwa kursor w prawo. Jako że to ostatnia linia, cały obraz przesunie się o linię w górę, co najprawdopodobniej nie jest pożądane.


(Patrykkyky) #7

Hmmm... a dało by rade przerobić w kod który automatycznie rysuje te gwiazdki w innymi miejscu, bo ten program niestety się zapętla, więc w takim razie statki lecące na mnie ciągle tak samo to troche nieciekawa sprawa... Ew. powiększyć rozmiar miejsca od którego zapętlają sie rysowane gwiazdki?


(Ryan) #8

A kto mówi, że statki muszą lecieć na Ciebie zawsze tak samo? Co za problem sprawdzić, czy gwiazdka/statek znika i wylosować jej nową pozycję na miejsce poprzednika?

Generalnie popełniasz klasyczny błąd zadając pytanie. Nie opisujesz tego, co chcesz uzyskać, tylko pytasz jak zrobić coś, co wydaje Ci się, że jest rozwiązaniem Twojego problemu (a zapewne nie jest). Pytam więc: co dokładnie chcesz uzyskać? Jaki ma być efekt końcowy? Nie pisz jak coś chcesz uzyskać, napisz co.


(Patrykkyky) #9

No to postaram sie jaśniej... U góry ekranu ma być mój statek (to już mam), statek ten porusza się po ograniczonym ekranie X>=15 i X<=45. I teraz chce mieć kod który sprawia że u dołu ekranu w losowym miejscu ( od X>=15 aż do X<=45) pojawiają się punkciki (statki wroga i gwiazdy) które przesuwają się w górę ekranu, z tym że ze statkami wroga ma zachodzić kolizja (ale ze statkami juz sobie poradze). Kod ten nie może się zapętlać - chodzi o to że w Twoim programie powyżej kiedy wszystkie gwiazdki przeleciały od dołu do góry to pojawiały się one w tym samym miejscu u dołu ekranu, w tym samym zestawieniu i znowu leciały w góre i tak w kółko, a chce żeby każda gwiazdka która się pojawi na ekranie, miała ciągle inną pozycję.


(Ryan) #10

Co za problem? :slight_smile: Za blokiem { Move stars offset } i zmianą wartości zmiennej Offset dodaj (pisane z palca):

for Counter := 1 to StarCount do

      begin

        NewY := StarsArray[Counter][2] + ScreenHeight - Offset;

        NewY := (NewY Mod ScreenHeight) + ScreenY;

        if NewY = ScreenHeight - 1 then StarsArray[Counter][1] := Random(ScreenWidth);

      end;

W ten sposób jeśli jakaś gwiazda doleci do brzegu ekranu i "przewinie się" na dół, od nowa wygenerowana zostanie dla niej współrzędna X. Tym samym miejsce w tablicy StarsArray, którego używała gwiazda już niewidoczna, zajmie nowa gwiazda. W ten sposób stale masz na ekranie 20 gwiazd.

Oczywiście jeśli chcesz, żeby gwiazdy reprezentowały, powiedzmy, statki przeciwnika i wiesz, że pewnego dnia zapragniesz bardziej złożony ruch niż w górę, należałoby to inaczej rozwiązać. Zacząłbym od zmiany tablicy dwuwymiarowej w jednowymiarową tablicę struktur opisujących statki. Do tego pozbyłbym się zmiennej Offset i zamiast niej co klatkę bazując na logice lotu statku zmienił jego pozycję. Ponadto oznaczałbym statki jako "martwe".

Jest sporo rzeczy, które można robić. Ważne, żeby oddzielić to, co widać, od tego co w kodzie. Przykładowo zbyt silnie wiążesz koncepcję pozycji w tablicy z obiektem. Pozycję w tablicy można ponownie użyć dla innego obiektu (gwiazdy). Gdybyś koniecznie chciał przekomplikowywać kod, mógłbyś użyć kolejki do przechowywania informacji o obiektach (gwiazdach). Tylko po co? :slight_smile: Prosty kod jest piękny a nieintuicyjne zachowanie należy { komentować }.