Siempre creciendo, siempre aprendiendo. Cultura maker.

¡Saludos!. ¡Pasad!, ¡pasad!. ¿Qué?. ¿No habéis tenido bastante?. ¿Venís a por más?. ¡Estupendo!. ¡Sigamos trabajando entonces!.

Hoy vamos a hablar de la Programación Orientada a Objetos (POO, si queréis). Esta filosofía de programación enfoca la generación de algoritmos de cualquier tipo de modo que intenta ahorrar todo tipo de repetición innecesaria en nuestro programa. La idea básica es que, a lo largo del tiempo de ejecución de mi código, se van a realizar muchas tareas repetidamente de modo muy parecido, y muchas veces de modo simultáneo.

Un ejemplo es el de cualquier videojuego. Sea cual sea su argumento, una cantidad enorme de veces todo se reduce a un personaje manejado por el jugador contra múltiples enemigos. Durante el transcurso del juego, todos los enemigos tendrán una serie de características comunes: modo de moverse, nivel de energía, apariencia... ¡No sé!. En los videojuegos de carreras automovilísticas, por otro lado, todos los coches serán distintos pero tendrán una serie de características comunes que regirán su comportamiento: velocidad, aceleración, cantidad disponible de combustible... si bien estas características tendrán valores distintos en cada objeto del programa, serán propiedades comunes que deberemos tener previstas para cada uno de ellos.

La filosofía de la POO se basa en diseñar clases abstractas, que tienen unas variables y unos métodos cuyas características podremos definir, y que luego regirán el comportamiento de unos objetos pertenecientes a dicha clase y que irán apareciendo a medida que el programa genere instancias (objetos) de dicha clase.

Mira, en el cuadro de abajo, por ejemplo, sólo hay un recuadro de color beige. Pero si empiezas a hacer click con el ratón en la pantalla, verás que aparecen múltiples esferas de colores que van rebotando. Todas ellas son objetos concretos de una clase pelota que iremos definiendo a lo largo de los ejemplos que ilustran este artículo.

 ¡HAZ CLICK EN LA PANTALLA!

Empecemos con un ejemplo.

Como decíamos antes, cuando queremos que un programa repita varias veces comportamientos parecidos en uno u otro aspecto, la POO nos ofrece la posibilidad de recoger varios parámetros en una clase, que luego podrán configurarse en cada objeto perteneciente a dicha clase. En nuestro caso, queremos dibujar varias pelotas, de colores y tamaños diferentes, que se desplazan a distintas velocidades por la pantalla.

 

Todas las clases empiezan con la palabra reservada class y se encierran entre dos llaves. En una clase hay atributos (los parámetros que habrá que definir para cada objeto que pertenece a nuestra clase) y métodos (las funciones que corresponderán a dichos objetos). En nuestro caso, vamos a crear una clase llamada pelota con tres atributos: dos coordenadas xy, y una coordenada radio. La clase tendrá también un método que nos dibujará el objeto de clase pelota, y que en un alarde de originalidad vamos a llamar dibujaPelota(). ¿Cómo quedaría el código en Processing?. Pues más o menos así:


class pelota{
 int x; 
int y;
int radio;
pelota (int a, int b, int c){
 x=a;
 y=b;
radio=c;
} 
void dibujaPelota(){
ellipse(x,y,radio,radio); 
}
}

Como podéis ver, mis pequeños padawan, en dibujaPelota() sólo hay una orden: ellipse(), que nos dibujará una elipse en las coordenadas x, y, y del tamaño radio que se ha especificado en el método pelota(), que es lo que se conoce como el constructor de la clase.

Normalmente, todas las clases incluyen un método con su mismo nombre, denominado CONSTRUCTOR. Este método es el que utiliza nuestro programa para inicializar todos y cada uno de los objetos que pertenecen a dicha clase. En nuestro objeto, el método se  llama pelota, y hay que pasarle tres parámetros, correspondientes a los tres atributos (coordenada x, coordenada y, tamaño radio) que hemos dicho que debe tener, como mínimo, cada objeto de esta clase.

Si empezamos con este código, así, tal cual, veremos que Processing no nos devuelve nada en pantalla, si bien tampoco nos da ningún error en la ejecución. ¿Por qué es eso?. Por que en realidad, no hemos hecho nada mal. Hemos definido nuestra primera clase. No hay ningún error ni en la definición de los atributos ni en los métodos (el constructor o dibujaPelota()) que hemos definido.

Para ser completamente funcional, nuestro código, como en ocasiones anteriores, deberá incluir las rutinas setup() y draw() que entendemos como irrenunciables en cualquier programa en Processing. La única diferencia es que en esta ocasión, también introduciremos el código correspondiente a la definición de nuestra clase pelota{ }. Además, deberemos declarar al menos un objeto (nosotros declararemos dos) de esta clase, al igual que declaramos variables de tipo int o boolean, por ejemplo. Asímismo, en setup() inicializaremos dichos objetos usando el constructor con tres atributos x, y y radio distintos para cada uno. En draw() utilizaremos el método dibujaPelota() para cada objeto.

Para declarar los objetos, utilizaremos las variables balon1 y balon2 (por ejemplo):

pelota balon1, balon2;

 

Para inicializar ambos objetos, utilizaremos el constructor pelota(). Supongamos, por ejemplo, que queremos al balon1 en las coordenadas 400,300 con un tamaño de 100 píxeles, y que balón2 estará en 700,50 con un tamaño de sólo 30 píxeles. Esto suele hacerse en setup():

balon1=new pelota(400,300,50);
balon2=new pelota(700,50,30);

 Por último, en draw(), sólo nos quedará llamar al método dibujaPelota() para cada objeto con el objetivo de dibujarla:

balon1.dibujaPelota();
balon2.dibujaPelota();

 Nuestro primer código completo trabajando con objetos

Bueno, pues más o menos este sería el código:

pelota balon1, balon2;
void setup(){
size(800,600);
balon1=new pelota(400,300,50);
balon2=new pelota(700,50,30);
}
void draw(){
balon1.dibujaPelota();
balon2.dibujaPelota();
}
class pelota{
int x; 
int y; 
int radio; 
pelota (int a, int b, int c){
 x=a; 
y=b; 
radio=c; } 
void dibujaPelota(){ 
ellipse(x,y,radio,radio); 
} 
}

El resultado no es muy espectacular:

objetosTuto1

Pero cumple con lo especificado: ha construido dos objetos de la clase que hemos redactado. Aunque, claro, necesitaríamos algo un poco más "potente" para mostrar la utilidad de la POO.

Otro paso más: uso de bucles y arrays para generar varios objetos de una misma clase

Veamos. Veamos. Veamos. Supongamos que queremos generar, digamos, 50 objetos de la clase pelota que nos ocupa. Para ello, nada como nuestros viejos amigos, los bucles for, que comentábamos en un artículo anterior. Necesitaremos declarar 50 objetos de tipo pelota en setup() asociados a un array.

Un array, por definición, es una pila de variables (o, en este caso, objetos) asociadas a un mismo nombre, y localizadas específicamente por el orden en que han sido colocadas. Así, por ejemplo, si queremos un array de tres números a los que vamos a llamar numbers, declararíamos dicho array (matriz, en cristiano), lo llenaríamos de valores, y según los fuéramos introduciendo, cada valor respondería a los nombres numbers[0], numbers[1], numbers[2], etc... (partimos siempre del valor 0).

Un ejemplo de array de números enteros:

int[] numbers = { 90, 150, 30 };
println (numbers[0]);
println (numbers[1]);
println (numbers[2]);

 Nos devolvería en consola, sucesivamente, los valores 90, 150, 30.

En el ejemplo que nos ocupa, necesitamos declarar un array de cincuenta objetos de tipo pelota, que llamaremos balon[]

pelota balon[]=new pelota[50];

En setup(), inicializaremos cada objeto del array balon, asignándole un valor aleatorio entre 0 y el ancho de la pantalla para la coordenada X, otro entre 0 y el alto de pantalla para Y y otro entre 10 y 50 para el radio de cada elipse que definirá al objeto de tipo pelota correspondiente:

for (int i=0;i<50;i++){
balon[i]=new pelota((int)random(width), (int)random(height), (int)random(10,50)); 
}

 

Finalmente, en draw(), apelaremos al método objetoCorrespondiente.dibujaPelota() en otro bucle que recorrerá el array definido para dibujar todas y cada una de las elipse que definen a cada balón:

for (int i=0;i<50;i++){
balon[i].dibujaPelota();
}

 

El código completo sería:


pelota balon[]=new pelota[50];
void setup(){
size(800,600);
for (int i=0;i<50;i++){
 balon[i]=new pelota((int)random(width), (int)random(height), (int)random(10,50)); 
}
}
void draw(){
for (int i=0;i<50;i++){
balon[i].dibujaPelota();}
}
class pelota{
int x; 
int y; 
int radio; 
pelota (int a, int b, int c){
 x=a; 
y=b; 
radio=c; } 
void dibujaPelota(){ 
ellipse(x,y,radio,radio); 
} 
}

 processingTuto4b

Pero esto está aún muy muerto... ¡Quiero movimiento!

¡Tus deseos son órdenes!. Si ya hemos entendido la naturaleza de las clases que se encarnan en distintos objetos, y si tenemos una cierta idea de lo que suponen los conceptos de atributo y método dentro de esta filosofía, vamos a hacer lo siguiente:

  1. Vamos a introducir dos nuevos atributos para cada objeto de la clase pelota: velocidad horizontal vx y velocidad horizontal vy, ambos enteros, de tipo int. Tal y como explicamos en otros artículos de esta serie, vamos a asignar un valor inicial a ambos atributos(por ejemplo, 5 píxeles).
  2. Vamos a instaurar un nuevo método que llamaremos muevePelota(). Cada vez que llamemos a este método en cada objeto de la clase que estemos manejando, sumaremos a sus atributos x e y los correspondientes vx y vy. Este método tendrá en cuenta la posibilidad de que la pelota se nos vaya por la izquierda o la derecha de la pantalla (x<0 ó x>width) o por arriba o abajo (y<0 ó y>height), en cuyo caso invertiremos el signo de la velocidad correspondiente (de modo que la coordenada horizontal/vertical de la pelota invierta su crecimiento/decrecimiento).
  3. En el bloque principal de programa, loop(), usaremos un background al principio para facilitar la ilusión óptica del movimiento que explicábamos en el primer artículo de esta serie. A continuación, antes de llamar a los métodos dibujaPelota() de cada objeto (tal y como hacíamos en un bucle en el anterior ejemplo), ejecutaremos muevePelota()

El método muevePelota(), instaurados los nuevos atributos vx y vy, sería algo así:

El nuevo constructor (fijáos que los nuevos atributos vxvy no necesitan argumento que los defina, ya que les hemos dado un valor fijo) y el nuevo método serán así:

pelota (int a, int b, int c){
 x=a; 
y=b; 
radio=c; 
vx=5;
vy=5;}
void muevePelota(){
 x+=vx;
 y+=vy;
 if ((x<0)||(x>width)){
  vx=-vx; 
 } 

 Y en loop(),  justo después de poner un background() que borre el dibujo anterior, y dentro del bucle for() que recorre el array de objetos, añadiremos el método muevePelota();:

for (int i=0;i<50;i++){
balon[i].muevePelota();
  balon[i].dibujaPelota();
}

 

¡Quiero verlo ya!. ¡Ejecutémoslo!

¡Claro!. ¡Enseguida!. Sólo dejadme añadir algo... ¿Y si quisiéramos que cada objeto/balón de esta clase tuviera, por ejemplo, colores distintos?. ¡Exacto!. El protocolo a seguir sería el mismo:

  1. Definimos un atributo para cada característica que queramos añadir a nuestra clase. En nuestro ejemplo, tres valores rojo, verde, azul de tipo int.
  2. Decidimos cómo darle un valor a cada atributo en nuestro método constructor. En este caso, optaré por valores aleatorios, con los comandos (int)random(255);
  3. En este caso en particular, añadiríamos un fill(rojo,verde,azul); al principio del método dibujaPelota(), al objeto de obtener el color que corresponda.

Veeengaaa, vaaaa... No os aburro más. Éste es el código completo:

pelota balon[]=new pelota[50];
void setup(){
size(800,600);
for (int i=0;i<50;i++){
 balon[i]=new pelota((int)random(width), (int)random(height), (int)random(10,50)); 
}
}
void draw(){
  background(255,255,255);
for (int i=0;i<50;i++){
balon[i].muevePelota();
  balon[i].dibujaPelota();
}
}
class pelota{
int x; 
int y; 
int vx;
int vy;
int rojo, verde, azul;
int radio; 
pelota (int a, int b, int c){
 x=a; 
y=b; 
radio=c; 
vx=5;
vy=5;
rojo=(int)random(255);
verde=(int)random(255);
azul=(int)random(255);

} 
void dibujaPelota(){ 
  fill(rojo, verde, azul);
ellipse(x,y,radio,radio); 
} 
void muevePelota(){
 x+=vx;
 y+=vy;
 if ((x<0)||(x>width)){
  vx=-vx; 
 }
  if ((y<0)||(y>height)){
  vy=-vy; 
 }
}
}

 ¿Queréis verlo? ¡Aquí lo tenéis!

 ¿A que mola?.

Aprendiz, nos has engañado... Éste no es el ejemplo con el que empezabas.

¡Bufff!. Sois durillos conmigo, ¿eh?. Efectivamente, en el método setup() generamos automáticamente los objetos que rellenan el array de pelotas de nuestro ejemplo, mientras que en el ejemplo del principio cada balón se va generando a medida que hacemos click en el ratón, es decir, cada vez que vamos llamando a la función mousePressed() a la que hacíamos referencia en artículos anteriores.

Para conseguirlo, necesitaremos modificar nuestro código según las siguientes orientaciones:

  1. Generamos un array de tamaño 50, y una variable de tipo int llamada indice, que empieza siendo 0. En setup(), para evitar problemas con arrays vacíos, generaremos un primer objeto (aunque todavía no se verá).
  2. En la función mousePressed(), cada vez que la llamemos, indice sumará una unidad, hasta un máximo de 50, al tiempo que genera un nuevo objeto que se añadirá al array balon [].
  3. En loop(), siempre y cuando índice sea mayor que cero, se recorrerá el array y se moverá y dibujará cada pelota de acuerdo a lo ya explicado.

El nuevo código (esta vez completo) sería así:

pelota balon[]=new pelota[50];
int indice=0;
void setup(){
size(800,600);

 balon[0]=new pelota((int)random(width), (int)random(height), (int)random(10,50)); 

}
void draw(){
  background(255,255,255);
  if (indice>0){
for (int i=0;i<indice;i++){
balon[i].muevePelota();
  balon[i].dibujaPelota();
}
  }
}
void mouseClicked(){
    if (indice<50){
  balon[indice]=new pelota(mouseX,mouseY,
 (int)random(10,50));

 indice++;
  }
}
class pelota{
int x; 
int y; 
int vx;
int vy;
int rojo, verde, azul;
int radio; 
pelota (int a, int b, int c){
 x=a; 
y=b; 
radio=c; 
vx=5;
vy=5;
rojo=(int)random(255);
verde=(int)random(255);
azul=(int)random(255);

} 
void dibujaPelota(){ 
  fill(rojo, verde, azul);
ellipse(x,y,radio,radio); 
} 
void muevePelota(){
 x+=vx;
 y+=vy;
 if ((x<0)||(x>width)){
  vx=-vx; 
 }
  if ((y<0)||(y>height)){
  vy=-vy; 
 }
}
}

 

 ¡Y eso es todo por hoy!

¡Lo sé, lo sé!. ¡Hemos trabajado con muchísimas cosas a la vez!. ¡Os váis a casa con mucho en qué pensar, y mucho código que probar por vuestra cuenta!. ¡Ésa es la esencia del aprendizaje! No os preocupéis, en lo sucesivo seguiremos trabajando para afianzar gran parte de las cosas que hemos ido explicando hoy por aquí. Así que, ¡ya sabéis!. Id saliendo por orden. Podéis volver por aquí cuando queráis. ¡Sed felices!. ¡Siempre creciendo!. ¡Siempre aprendiendo!. ¡Cultura maker!.