Processing/Zmienne
Pamięć bywa zawodna
edytujNie ma co ukrywać - pamięć ludzka jest świetna, lecz niezwykle krótkotrwała. Pisząc nasze programy często będziemy zapominać, co jest czym i skąd wzięło się akurat tutaj. Dlatego nasz kod wypełniony jest komentarzami, dlatego również nazwy funkcji (choć w języku angielskim) są proste i oznaczają to, co robią. Funkcje funkcjami, ale co z tamik przykładem?
size( 400, 400 );
background( 255, 230, 230 );
smooth();
noStroke();
fill( 250, 200, 200 );
// kolo w centrum okna
// o wielkosci jednej czwartej szerokosci okna
ellipse( 200, 200, 100, 100 );
Łatwo się domyśleć, że kółko o średnicy jednej czwartej szerokości okna narysowane jest w połowie długości i połowie szerokości tego okna. Wszystko gra. Ale czy nie lepiej byłoby tak?
size( 400, 400 );
background( 255, 230, 230 );
smooth();
noStroke();
fill( 250, 200, 200 );
// kolo w centrum okna
// o wielkosci jednej czwartej szerokosci okna
ellipse( width / 2, height / 2, width / 4, width / 4 );
Teraz od razu wiadomo, że chodziło mi o koło wielkości jednej czwartej szerokości okna i znajdujące się w jego środku. To coś trochę innego niż koło o wielkości 100x100 i położeniu 200x200.
Zmieniaj raz, zmieniaj wszędzie
edytujSpójrzmy na ten przykład raz jeszcze - tym razem jednak zmienimy wielkość okna z 400x400 na 500x300:
size( 500, 300 );
background( 255, 230, 230 );
smooth();
noStroke();
fill( 250, 200, 200 );
// kolo w centrum okna
// o wielkosci jednej czwartej szerokosci okna
ellipse( width / 2, height / 2, width / 4, width / 4 );
Jak widać nasz fragment kodu wciąż możemy przeczytać w taki sam sposób:
Koło w połowie szerokości, w połowie wysokości o średnicy jednej czwartej szerokości okna
To właśnie jest ta różnica w znaczeniu między "200x200" a "W centrum okna". Czym więcej w naszym programie znaczących i zrozumiałych treści, tym lepiej dla nas i samego programu.
Zmienne...
edytujTak samo, jak wielkość szerokości okna zapisać możemy jako width, tak samo zapisać możemy dowolną wartość, z której korzystać będziemy w naszym programie. Jak to działa? Wyobraźmy sobie taki przykład:
size( 500, 300 );
background( 255 );
smooth();
// wielkosc kola
int circleSize = 100;
// duze kolo
ellipse( width / 2, height / 2, circleSize, circleSize );
// male kolo
ellipse( width / 2, height / 2, circleSize / 2, circleSize / 2 );
Dzięki wprowadzeniu specjalnej nazwy (circleSize, czyli wielkość koła), która w naszym programie zastępuje wartość 100 z daleka widzimy, że drugie kółko będzie o połowę mniejsze od pierwszego, a do tego, jeśli przyjdzie nam zmienić wielkość obu kół wystarczy zrobić to raz w jednym miejscu. Znów zatem dotykamy tego samego rozróżnienia między tym, co znaczy "dwa koła o średnicach - odpowiednio - 100 i 50 pikseli", a tym, co znaczy "dwa koła - jedno o połowę mniejsze od drugiego". To ważna różnica, na którą warto zwracać uwagę.
...i ich typy
edytujW powyższym fragmencie kodu nasza zmienna (circleSize) poza nazwą oraz przypisaną jej wartością (100) posiada również dodatkową informację na temat swojego typu reprezentowaną przez słowo int (od angielskiego integer, czyli liczba całkowita). Takie oznaczenie typu jest w Processingu konieczne, żeby programowi łatwiej było zorganizować sobie pamięć operacyjną komputera. Co to jednak oznacza dla nas, programistów? Przede wszystkim to, że musimy każdej zmiennej nadać typ (jak choćby int) oraz przestrzegać własnej deklaracji. Jeśli tworzymy zmienną typu całkowitego to nie możemy przypisać jej ułamka:
int x = 3.14; // B Ł Ą D ! ! !
int y = 3; // D O B R Z E
Co mamy do wyboru
edytujTypów zmiennych w Processingu jest kilkanaście. Niektóre z nich są proste, jak int, inne bardzo złożone jak PImage (odpowiedzialny za pracę z obrazkami - dojdziemy do tego). Trzy podstawowe typy, z których będziemy korzystali na początku to:
int x = 1; // liczby całkowite
float y = 3.14; // liczby z przecinkiem
color c = color( 200, 100, 100 ); // kolor - w tym przypadku odcień czerwieni
Pierwsze dwa typy są proste i na razie nie musimy się na ich temat rozpisywać (przyjdzie na to pora, jak będziemy pisali o pamięci operacyjnej i bardziej zaawansowanej pracy z kolorami i obrazem). Teraz przyjrzyjmy sie zmiennej typu color.
color
edytujZmienna typu color może przechowywać dla nas informację o kolorze. To coś jak puszka z farbą:
size( 400, 300 );
background( 255, 230, 230 );
smooth();
int circleSize = 100;
int horizontalCenter = width / 2;
int verticalCenter = height / 2;
color darkColor = color( 100, 50, 50 );
color lightColor = color( 250, 200, 200 );
noStroke();
fill( darkColor );
ellipse( horizontalCenter, verticalCenter, circleSize, circleSize );
fill( lightColor );
ellipse( horizontalCenter, verticalCenter, circleSize / 2, circleSize / 2 );
ellipse( width, verticalCenter, circleSize / 4, circleSize / 4);
fill( darkColor );
ellipse( 0, verticalCenter, circleSize, circleSize );
Nie trzeba znać się na programowaniu, by od razu widzieć, która część kodu odpowiada za rysowanie których elementów na ekranie. Najpierw rysujemy ciemne (darkColor) i duże (circleSize) koło (linia 15), później jasne (lightColor) i małe (circleSize / 2) na wierzchu (linia 18), następnie to po prawej (linia 20), a na końcu to po lewej (linia 23). Wyraźnie też widać, jak korzystamy z naszych zmiennych typu color - w liniach 14, 17 i 22 wybieramy nową puszkę z farbą i malujemy nią kolejne koła.
Kto razem, kto osobno
edytujPrzyjrzyjmy się fragmentom powyższego kodu w dwóch wersjach:
// omijamy wielkość okna i inne deklaracje
// kola w centrum
fill( darkColor );
ellipse( horizontalCenter, verticalCenter, circleSize, circleSize );
fill( lightColor );
ellipse( horizontalCenter, verticalCenter, circleSize / 2, circleSize / 2 );
// kola po bokach
ellipse( width, verticalCenter, circleSize / 4, circleSize / 4);
fill( darkColor );
ellipse( 0, verticalCenter, circleSize, circleSize );
oraz
// omijamy wielkość okna i inne deklaracje
// ciemne kola
fill( darkColor );
ellipse( horizontalCenter, verticalCenter, circleSize, circleSize );
ellipse( 0, verticalCenter, circleSize, circleSize );
// jasne kola
fill( lightColor );
ellipse( horizontalCenter, verticalCenter, circleSize / 2, circleSize / 2 );
ellipse( width, verticalCenter, circleSize / 4, circleSize / 4);
Jak widać ten sam kod pogrupowaliśmy w dwa różne sposoby i choć efekt wizualny jest taki sam, to zupełnie inaczej czyta się oba fragmenty. Ciężko określić, który z nich jest lepszy - to z jednej strony sprawa gustu programisty, z drugiej większego kontekstu programu. Ważne jest jednak, by pamiętać o dobrej, czytelnej i znaczącej organizacji kodu źródłowego, bo programy, które będziemy pisali w przyszłości mogą mieć kilkaset lub nawet kilka tysięcy linii kodu źródłowego i wtedy logiczna organizacja to podstawa.
Wracamy do coloru
edytujPisaliśmy, że zmienne typu kolor przechowują informację o kolorze i że przypisujemy im wartość w następujący sposób:
color black = color( 0 ); // czarny
color white = color( 255 ); // bialy
color lightRed = color( 255, 230, 230 ); // jasny czerwony
color niceGrey = color( 80, 80, 80 ); // fajny szary
color nicerGrey = color( 50 ); // jeszcze fajniejszy szary
Co jednak, gdybyśmy chcieli nasz lightRed zrobić nieco ciemniejszym? Mamy na to dwie metody:
color lightRed = color( 255, 230, 230 ); // jasny czerwony
lightRed = color( 200, 150, 150 ); nowy, trochę ciemniejszy czerwony
lub
color lightRed = color( 255, 230, 230 ); // jasny czerwony
float r = red( lightRed );
float g = green( lightRed );
float b = blue( lightRed );
lightRed = color( r - 55, g - 80, b - 80 );
Pierwszy fragment jest prosty i wymaga wyjaśnienia tylko jednej rzeczy: w każdym programie zmienne deklarowane są tylko raz! To znaczy, że w pierwszej linii kodu występuje deklaracja typu color, a w drugiej nie, bo Processing wie już, że lightRed jest zmienną typu color - po prostu przypisujemy jej nową wartość za pomocą funckji color( int r, int g, int b ).
W drugim fragmencie dzieje się więcej, bo mamy kilka dodatkowych zmiennych oraz wykorzystujemy funkcje red( color c ), green( color c ), blue( color c ), które pozwalają nam na wydobycie informacji o trzech składowych danego koloru c. Moglibyśmy zapisać go następująco:
tworzę zmienną:
zmienna typu kolor --> lightRed
lightRed --> jasno czerwony ( RGB: 255, 230, 230 )
tworzę zmienną:
zmienna typu ułamkowego --> r
r --> ilość czerwieni w lightRed
tworzę zmienną:
zmienna typu ułamkowego --> g
g --> ilość zieleni w lightRed
tworzę zmienną:
zmienna typu ułamkowego --> b
b --> ilość niebieskiego w lightRed
przypisuję wartość:
tworzę nowy kolor --> R: wartość r minus 55, G: wartość g minus 80, B: wartość b minus 80
lightRed --> nowy kolor ( RGB: 200, 150, 150 )
Nic skomplikowanego, a pozwala nam się dostać do składowych koloru, co moglibyśmy wykorzystać np. tak:
size( 400, 300 );
background( 255, 230, 230 );
smooth();
int circleSize = 100;
int horizontalCenter = width / 2;
int verticalCenter = height / 2;
color lightRed = color( 255, 230, 230 ); // jasny czerwony
float r = red( lightRed );
float g = green( lightRed );
float b = blue( lightRed );
color middleRed = color( r / 2, g / 2, b / 2 ); // sredni kolor: polowa wszystkich wartosci z lightRed
color darkRed = color( r / 3, g / 3, b / 3 ); // ciemny kolor: jedna trzecia wszystkich wartosci z lightRed
noStroke();
fill( darkRed );
ellipse( horizontalCenter, verticalCenter, circleSize, circleSize );
fill( middleRed );
ellipse( horizontalCenter, verticalCenter, circleSize / 2, circleSize / 2 );
fill( lightRed );
ellipse( horizontalCenter - ( circleSize / 2 ), verticalCenter, circleSize / 3, circleSize / 3 );
ellipse( horizontalCenter, verticalCenter, circleSize / 4, circleSize / 4 );
ellipse( horizontalCenter + ( circleSize / 2 ), verticalCenter, circleSize / 3, circleSize / 3 );
Słowo o przyjaźni
edytujZmiennych będziemy używali do końca podręcznika w literalnie każdym programie, więc wszelkie niejasności wyjdą w praniu. Wrócimy do nich również, w rozdziale poświęconym Javie i programowaniu obiektowemu. Zmienne bowiem, to jeden z przyjaciół programisty.