If you're seeing this message, it means we're having trouble loading external resources on our website.

Jeżeli jesteś za filtrem sieci web, prosimy, upewnij się, że domeny *.kastatic.org i *.kasandbox.org są odblokowane.

Główna zawartość

Ruch wektorowy

Cała ta matematyka, te wektory, powinniśmy je pamiętać z lekcji matematyki i geometrii, ale właściwie po co? Czy rzeczywiście pomogą nam napisać dobry kod? Prawda jest taka że musimy być cierpliwi. Upłynie trochę czasu, zanim ogarniemy piękno klasy PVector w całej okazałości.
Tak dość często się zdarza, jeśli uczysz się nowej konstrukcji. Na przykład, kiedy pierwszy raz dowiadujesz się o macierzach, może się wydawać że to jest sztuczna i niepotrzebna konstrukcja, o ile prościej jest mieć po prostu różne nazwy dla zmiennych oznaczających różne rzeczy. Szybko jednak przekonujesz się do macierzy, gdy zaczynasz myśleć o setkach, tysiącach i dziesiątkach tysięcy "rzeczy".
Tak samo będzie z klasą PVector. To, co na razie wydaje Ci się dodatkową komplikacją, okaże się bardzo pomocne w przyszłości. I nie będzie trzeba specjalnie długo na to czekać, nagroda przyjdzie już w następnym rozdziale.

Prędkość

Na razie jednak skupmy się na czymś prostym. Co oznacza zaprogramować ruch za pomocą wektorów? Widzieliśmy coś takiego w przykładzie o odbijającej piłce. Obiekt na ekranie ma swoje położenie (tzn. informację, gdzie znajduje się w danym momencie) oraz prędkość (instrukcję jak powinien się poruszać w danej chwili). Prędkość jest dodawana do położenia:
position.add(velocity);
Po czym rysujemy obiekt w tym położeniu:
ellipse(position.x, position.y, 16, 16);
Oto podstawy ruchu:
  • Dodaj prędkość do aktualnego położenia
  • Narysuj obiekt w tym położeniu
W przykładzie z piłką, cały program znajdował się wewnątrz funkcji draw ProcessingJS. Teraz chcemy przenieść całą logikę odpowiadającą za ruch do wnętrza obiektu. Dzięki temu będziemy w stanie stworzyć podstawę do programowania przemieszczających się obiektów we wszystkich naszych programach używających ProcessingJS.
Tym razem utwórzmy ogólną klasę Mover która będzie opisywać, jak rzeczy przemieszczają się na ekranie. Musimy więc rozważyć następujące dwa pytania:
  • Jakie dane ma mieć klasa Mover?
  • Jakie funkcjonalności musi zapewniać?
Przedstawiony przez nas algorytm podstaw ruchu odpowiada na te pytania. Obiekt typu Mover przechowuje dwie informacje: położenie oraz prędkość, które są obiektami typu PVector. Możemy zacząć od napisania funkcji konstruktora który zainicjuje te dane za pomocą odpowiednich, losowych wartości:
var Mover = function() {
  this.position = new PVector(random(width), random(height));
  this.velocity = new PVector(random(-2, 2), random(-2, 2));
};
Jego funkcjonalność również jest prosta. Mover musi być w stanie się przemieszczać i być widoczny. Zaimplementujemy te funkcje jako metody nazwane update() oraz display(). Będziemy przechowywać logikę przemieszczania się w update() oraz rysować obiekt za pomocą display().
Mover.prototype.update = function() {
  this.position.add(this.velocity);
};

Mover.prototype.display = function() {
  stroke(0);
  strokeWeight(2);
  fill(127);
  ellipse(this.position.x, this.position.y, 48, 48);
};
Jeśli programowanie obiektowe jest dla Ciebie czymś nowym, jedna rzecz może wydawać się dziwna. Spędziliśmy cały wstęp tego rozdziału na opisie PVector. PVector to szablon do tworzenia obiektów zawierających położenie i prędkość. Więc co robią te rzeczy w kolejnym obiekcie, Mover? Tak na prawdę, to całkiem normalne. Obiekt to po prostu coś, co przechowuje dane (i funkcjonalności). Tymi danymi mogą być liczby, łańcuchy znaków, tablice albo inne obiekty! Zobaczycie to w praktyce wielokrotnie podczas tego kursu. Dla przykładu, w poradniku o cząsteczkach, napiszemy obiekt opisujący system cząsteczek. Obiekt typu ParticleSystem będzie posiadał jako swoje dane tablicę obiektów Particle… a każdy obiekt Particle będzie posiadać w swoich danych kilkanaście obiektów PVector!
Skończmy pisać obiekt Mover poprzez zaimplementowanie zachowania klasy w momencie, gdy dotrze do rogu okna. Tymczasowo zróbmy coś prostego i sprawmy, by wracał po drugiej stronie ekranu:
Mover.prototype.checkEdges = function() {

  if (this.position.x > width) {
    this.position.x = 0;
  } 
  else if (this.position.x < 0) {
    this.position.x = width;
  }

  if (this.position.y > height) {
    this.position.y = 0;
  } 
  else if (this.position.y < 0) {
    this.position.y = height;
  }
};
Teraz, gdy obiekt Mover jest skończony, możemy sprawdzić, co jest nam potrzebne w głównym programie. Wpierw zadeklarujmy i zainicjujmy instancję Movera:
var mover = new Mover();
Po tym wywołujemy odpowiednią funkcję w draw:
draw = function() {
  background(255, 255, 255);

  mover.update();
  mover.checkEdges();
  mover.display(); 
};
Oto działający przykład. Poeksperymentujcie z liczbami, usuwajcie kawałki kodu i sprawdźcie, co się dzieje:

Przyspieszenie

OK. W tym miejscu powinniśmy już wiedzieć dwie rzeczy: (1) czym jest PVector i (2) jak używać PVectora w obiekcie by monitorować jego położenie i ruch. To znakomity początek i należy nam się pochwała. Zanim jednak zaczniemy świętować, musimy zrobić jeszcze jeden, znacznie większy krok naprzód. W końcu oglądanie przykładu ruchu jest raczej nudne—kółko nigdy nie przyspiesza, nigdy nie zwalnia oraz nigdy nie skręca. By stworzyć znacznie ciekawszy ruch, ruch który jest obecny w prawdziwym świecie, musimy dodać jeszcze jeden PVector do naszego obiektu Mover—przyspieszenie.
Definicja przyspieszenia, której będziemy się tutaj trzymać jest następująca: przyspieszenie to tempo zmiany prędkości. Zastanówmy się nad tym przez chwilę. Czy to coś zupełnie nowego? Niezbyt. Prędkość to tempo zmiany położenia. Mówiąc w skrócie tworzymy “układ naczyń połączonych”. Przyspieszenie wpływa na prędkość, która wpływa na położenie (to będzie bardzo ważne na początku następnego rozdziału, gdy zajmiemy się tym, jak siły wpływają na przyspieszenie, które wpływa na prędkość, która wpływa na położenie). W kodzie można to zapisać następująco:
velocity.add(acceleration);
position.add(velocity);
Jako formę ćwiczenia, postanówmy sobie coś w tym momencie. Każdy przykład pisany od teraz przez resztę przykładów będziemy robić bez modyfikowania wartości przyspieszenia i położenia (poza ich inicjalizacją). Innymi słowy, naszym celem podczas nauki programowania ruchu teraz jest wymyślenie algorytmu, który pozwoli nam obliczać przyspieszenie, przez co nasz efekt naczyń połączonych zadziała. (Tak na prawdę znajdziecie wiele powodów by łamać tę zasadę, ale jest ona bardzo istotna przy naszej nauce algorytmu ruchu.) Wymyślmy więc parę sposobów, którymi będziemy liczyć przyspieszenie:
  1. Stałe przyspieszenie
  2. Całkowicie losowe przyspieszenie
  3. Przyspieszenie w kierunku kursora
Algorytm 1, stałe przyspieszenie nie jest zbyt ciekawe, ale jest najprostszy i pomoże nam rozpocząć dodawanie przyspieszenia do naszego programu.
Pierwsze, co musimy zrobić to dodać kolejne pole PVector do konstruktora Movera, aby reprezentował przyspieszenie. Zainicjujemy go w (0.001,0.01) i będzie to jego stała wartość, gdyż nasz aktualny algorytm opiera się na stałym przyspieszeniu. Może Ci się wydawać “Rety, ale te wartości są małe!” To prawda, są bardzo małe. Należy pamiętać, że nasze przyspieszenie(liczone w pikselach) będzie zwiększało się wraz ze zmianą prędkości, około 30 razy na sekundę w zależności od tego, ile klatek na sekundę będzie rysował nasz program. Tak więc aby wartość naszej prędkości była w miarę rozsądna, wartość przyspieszenia powinna być i pozostać raczej niewielka.
var Mover = function() {
  this.position = new PVector(width/2,height/2);
  this.velocity = new PVector(0, 0);
  this.acceleration = new PVector(-0.001, 0.01);
};
Zauważcie, że zaczęliśmy od prędkości początkowej 0 - ponieważ wiemy, że prędkość będzie wzrastać podczas pracy programu, dzięki przyspieszeniu. Zrobimy to w metodzie update():
Mover.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
};
Skoro ciągle zwiększamy prędkość, ryzykujemy, że wartość ta stanie się bardzo duża, jeżeli będziemy wystarczająco długo mieć uruchomiony program. Chcemy ograniczyć prędkość do konkretnej wartości maksymalnej. Możemy zrobić to za pomocą pochodzącej z klasy PVector metody ">limit , która ogranicza wektor do zadanej wielkości.
Mover.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.velocity.limit(10);
  this.position.add(this.velocity);
};
Można to przetłumaczyć w sposób następujący:
Jaka jest wartość prędkości? Jeśli jest mniejsza niż 10, nie przejmuj się i zostaw ją taką, jaka jest. Jeżeli jest większa niż 10, zmniejsz ją do 10!
Zobaczmy jak zmienia się obiekt Mover, po dodaniu wartości acceleration oraz limit():
Przejdźmy teraz do drugiego algorytmu, całkowicie losowego przyspieszenia. W tym przypadku, zamiast inicjalizować przyspieszenia w konstruktorze obiektu, chcemy wyznaczać nowe przyspieszenie za każdym razem, czyli gdy tylko metoda update() jest wywołana.
Mover.prototype.update = function() {
  this.acceleration = PVector.random2D();
  this.velocity.add(this.acceleration);
  this.velocity.limit(10);
  this.position.add(this.velocity);
};
Ponieważ losowy wektor jest znormalizowany, możemy spróbować go skalować za pomocą dwóch różnych sposobów:
  1. Skalowanie przyspieszenia do stałej wartości:
    acceleration = PVector.random2D();
    acceleration.mult(0.5);
    
  1. Skalowanie przyspieszenia do losowej wartości:
    acceleration = PVector.random2D();
    acceleration.mult(random(2));
    
Chociaż może się to wydawać w miarę oczywiste, ważne jest, aby zrozumieć, że przyspieszenie nie oznacza tylko zwiększania prędkości albo zmniejszania prędkości ruszającego się obiektu, ale raczej zmianę albo wartości, albo kierunku prędkości. Przyspieszenie jest używane by sterować obiektem, co wielokrotnie zaobserwujemy w przyszłych rozdziałach, gdy zaczniemy programować obiekty które same podejmują decyzję jak mają się ruszać na ekranie.

Ten kurs "Symulacje Natury" jest pochodną z "Natury Kodu " stworzonej przez Daniela Shiffmana, użytej pod licencją Creative Commons Attribution-NonCommercial 3.0 Unported License.

Chcesz dołączyć do dyskusji?

Na razie brak głosów w dyskusji
Rozumiesz angielski? Kliknij tutaj, aby zobaczyć więcej dyskusji na angielskiej wersji strony Khan Academy.