Nuestra estrella central tiene una extensión de 30 @ 30 que
necesitamos utilizar en varios lugares del código. Por lo tanto, tiene
sentido definir un método específico para responder a este valor:
CentralStar>>morphExtent ^ `30 @ 30`
Ejemplo 7.3: Extensión de la estrella central
Una expresión rodeada por comillas invertidas
'`'solo se evalúa una vez, cuando el método se guarda y compila por primera vez. Esto crea un valor literal compuesto y mejora el rendimiento del método, ya que la expresión no se evalúa cada vez que se llama al método: en su lugar, se utiliza el valor precompilado.
Como aprendiste anteriormente, un morph se dibuja a sí mismo desde su
método drawOn:. Dibujamos la estrella como una elipse con
radios x e y que fluctúan aleatoriamente:
CentralStar>>drawOn: canvas
| radius |
radius := self morphExtent // 2.
canvas ellipseCenter: 0 @ 0
radius: (radius x + (2 atRandom - 1)) @ (radius y + (2 atRandom - 1))
borderWidth: 3
borderColor: Color orange
fillColor: Color yellow
Ejemplo 7.4: Una estrella con un tamaño fluctuante
Los diámetros de la estrella en las direcciones x e y fluctúan independientemente entre 0 y 2 unidades. La estrella no parece perfectamente redonda.
Figura 7.9: Una estrella con un tamaño fluctuante
Al igual que una nave espacial, cuando se instancia un torpedo, su proa apunta hacia la parte superior de la pantalla y sus vértices vienen dados por la Figura 7.11.
Figura 7.11: Diagrama de torpedo al inicio del juego
Dados los vértices proporcionados por la Figura 7.11, ¿cómo escribirías su método
morphExtent?
Ejercicio 7.5: Extensión del torpedo
¿Cómo escribirás el método
drawOn:deTorpedo?
Ejercicio 7.6: Dibujo de torpedo
Como habrás observado, los métodos drawOn: de SpaceShip
y Torpedo comparten la misma lógica: dibujar un polígono a
partir de sus vértices. Probablemente queramos trasladar esta lógica
común a su antecesor común, la clase Mobile. Necesita conocer
sus vértices, por lo que quizá queramos añadir una variable de instancia
vertices inicializada en sus subclases con una matriz que
contenga los puntos:
PlacedMorph subclass: #Mobile
instanceVariableNames: 'acceleration color velocity vertices'
classVariableNames: ''
poolDictionaries: ''
category: 'Spacewar!'
SpaceShip>>initialize
super initialize.
vertices := {0@-15 . -10@15 . 0@10 . 10@15}.
self resupply
Torpedo>>initialize
super initialize.
vertices := {0@-4 . -2@4 . 2@4}.
lifeSpan := 500.
acceleration := 3000
Sin embargo, esto no es una buena idea. Imagina el juego con 200 torpedos: ¡la matriz de vértices se duplicaría 200 veces con los mismos datos!
En ese tipo de situación, lo que se necesita es una variable de clase definida en el lado de la clase, en contraste con el lado de la instancia, donde hemos estado programando hasta ahora.
Aprovechamos el hecho de que todos los objetos son instancias de alguna
clase. ¡La Mobile class es una instancia de la clase
Class!
Torpedo) en lugar de a
sí misma o a otra entidad.
class
situado debajo de la lista de clases.
En el navegador del sistema, hacemos clic en el botón class y
luego declaramos nuestra variable en la definición de la
Mobile class – Figura 7.12:
Mobile class instanceVariableNames: 'vertices'
Ejemplo 7.6: vertices como variable instanciada
en la Mobile class
A continuación, escribimos un método de acceso en Mobile class,
para que las instancias SpaceShip y Torpedo puedan
acceder a él:
Mobile class>>vertices ^ vertices
Figura 7.12: El lado de clase del Browser del sistema
A continuación, cada subclase es responsable de inicializar
correctamente vertices con su método de clase
initialize:
SpaceShip class>>initialize
"SpaceShip initialize"
vertices := {0@-15 . -10@15 . 0@10 . 10@15}
Torpedo class>>initialize
"Torpedo initialize"
vertices := {0@-4 . -2@4 . 2@4}
Ejemplo 7.7: Inicializar una clase
Cuando se instala una clase en Cuis-Smalltalk, se ejecuta su método de clase
initialize. También puedes seleccionar el método y ejecutalo
con Ctrl-d.
Experimenta en un espacio de trabajo para comprender cómo se comporta una variable de instancia de clase:
SpaceShip vertices. ⇒ nil SpaceShip initialize. SpaceShip vertices. ⇒ #(0@-15 -10@15 0@10 10@15) Torpedo vertices. ⇒ nil Torpedo initialize. Torpedo vertices. ⇒ #(0@-4 -2@4 2@4)
Ejemplo 7.8: El valor de una variable de clase no es compartido por las subclases
Este es realmente el comportamiento que queremos: las instancias
SpaceShip y Torpedo tienen un diagrama diferente. Sin
embargo, todas las instancias de una SpaceShip tendrán el mismo
diagrama, haciendo referencia a la misma matriz vertices (es
decir, la misma ubicación en la memoria del ordenador).
Cada instancia pregunta a su lado de clase con el mensaje #class:
aTorpedo class ⇒ Torpedo self class ⇒ SpaceShip
El método drawOn: de Torpedo se reescribe para
acceder a los vértices en su lado de clase:
Torpedo>>drawOn: canvas | vertices | vertices := self class vertices. canvas line: vertices first to: vertices second width: 2 color: color. canvas line: vertices third to: vertices second width: 2 color: color. canvas line: vertices first to: vertices third width: 2 color: color
¿Cómo reescribirías
drawOn:deSpaceShippara utilizar los vértices de su lado de clase?
Ejercicio 7.7: Acceso de la nave espacial a su diagrama en el lado de la clase
Hasta ahora, seguimos teniendo esta redundancia en los métodos
drawOn:. Lo que queremos es que Mobile se encargue de
dibujar el polígono dada una matriz de vértices: self drawOn:
canvas polygon: vertices.
El método drawOn: de SpaceShip y Torpedo se
escribirá simplemente como:
Torpedo>>drawOn: canvas
self drawOn: canvas polygon: self class vertices
SpaceShip>>drawOn: canvas
| vertices |
vertices := self class vertices.
self drawOn: canvas polygon: vertices.
"Draw gas exhaust"
acceleration ifNotZero: [
canvas line: vertices third to: 0@35 width: 1 color: Color gray]
¿Cómo escribirías el método
drawOn:polygon:enMobile? Sugerencia: utiliza el iteradorwithIndexDo:.
Ejercicio 7.8: Dibujar en Mobile
Una variable de clase se escribe en mayúsculas en el argumento
de la palabra clave classVariableNames::
PlacedMorph subclass: #Mobile instanceVariableNames: 'acceleration color velocity' classVariableNames: 'Vertices' poolDictionaries: '' category: 'Spacewar!'
Ejemplo 7.9: Vertices una variable de clase Mobile
Como variable de instancia de clase, se puede acceder a ella directamente desde el lado de la clase y las instancias solo tienen acceso mediante mensajes enviados al lado de la clase. A diferencia de una variable de instancia, su valor es común en toda la jerarquía de clases.
En Spacewar!, una variable de clase Vertices tendrá el mismo
diagrama para una nave espacial y un torpedo. Esto no es lo que
queremos.
SpaceShip>>vertices
^ `{0@-15 . -10@15 . 0@10 . 10@15}`
Usar una variable de clase en el diseño actual del juego es un poco excesivo. Fue una excusa para presentar el concepto de variables de clase. Si el juego incluyera un editor en el que el usuario rediseñara los diagramas de la nave y los torpedos, tendría sentido guardar los vértices en una variable. Pero nuestros vértices de los diagramas de la nave espacial y los torpedos son constantes. No los modificamos. Al igual que hicimos con la masa de la nave espacial –Ejemplo 3.16–, podemos utilizar un método que devuelva una colección, rodeada de llaves para mejorar la eficiencia.
SpaceShip>>vertices
^ `{0@-15 . -10@15 . 0@10 . 10@15}`
Torpedo>>vertices
^ `{0@-4 . -2@4 . 2@4}`
Ejemplo 7.10: Vértices devueltos por un método de instancia
Luego, en los métodos de dibujo, reemplazamos self class
vertices por self vertices.
En Ejemplo 4.23, tenemos un enfoque muy ingenuo para la colisión entre la estrella central y las naves, basado en la distancia entre los morphs. Era muy impreciso.
En los juegos de bitmaps, una forma clásica de detectar colisiones es
observar las intersecciones de los rectángulos que rodean los objetos
gráficos. El mensaje #displayBounds enviado a un morph responde
a sus límites en la pantalla, un rectángulo que abarca el morph dada su
rotación y escala.
Figura 7.13: Los límites de visualización de una nave espacial
Al examinar la clase Rectangle, se aprende que el mensaje
#intersects: nos indica si dos rectángulos se superponen. Esto es
lo que necesitamos para una detección de colisiones más precisa entre la
estrella central y las naves espaciales:
SpaceWar>>collisionsShipsStar
ships do: [:aShip |
(aShip displayBounds intersects: centralStar displayBounds) ifTrue: [
aShip flashWith: Color red.
self teleport: aShip]]
Ejemplo 7.11: Colisión (superposición de rectángulos) entre las naves y el Sol.
Sin embargo, utilizamos el framework VectorGraphics, que conoce la forma
de cada morph que está renderizando. Utiliza este conocimiento para la
detección de colisiones con precisión de píxeles. Reescribimos nuestro
método de detección de forma más sencilla con el mensaje
#collides::
SpaceWar>>collisionsShipsStar
ships do: [:aShip |
(aShip collides: centralStar) ifTrue: [
aShip flashWith: Color red.
self teleport: aShip]]
Ejemplo 7.12: Colisión (precisión de píxel) entre las naves y el Sol
Reescribe los tres métodos de detección de colisiones entre naves espaciales, torpedos y la estrella central.
Ejercicio 7.9: Detección precisa de colisiones