535 lines
21 KiB
Text
535 lines
21 KiB
Text
[#_lesson_11_hitting_things]
|
|
== Lección 11: Chocar con cosas
|
|
|
|
image::learn/11-hitting-things.png[La lección terminada, 480]
|
|
|
|
Tu juego de la lección diez funciona. Los disparos rompen rocas, las rocas
|
|
cuestan vidas y las explosiones suenan donde tienen que sonar. Pero míralo de
|
|
cerca y verás que hace trampa, en las dos direcciones. Una roca que pasa
|
|
rozando la punta de un ala te quita una vida. Un disparo que atraviesa el cielo
|
|
vacío junto a una roca la rompe igualmente.
|
|
|
|
En un juego, dos cosas se tocan cuando los números dicen que se tocan. Nadie
|
|
mira las imágenes. Esta lección hace que los números estén de acuerdo con lo
|
|
que ve el jugador: cambia tu comprobación escrita a mano por la del propio
|
|
motor, encoge las cajas que se comprueban para que se ajusten a las cosas que
|
|
hay dentro y te da un interruptor que te enseña esas cajas. La imagen de arriba
|
|
es el juego con ese interruptor encendido.
|
|
|
|
[#_where_you_are]
|
|
=== Dónde estás
|
|
|
|
El juego pregunta «¿se tocan estas dos cosas?» exactamente en dos sitios, y los
|
|
dos usan `overlapping`, la función que escribiste en la lección nueve. Uno está
|
|
en `updateRocks`, donde una roca se encuentra con la nave:
|
|
|
|
[source,lua]
|
|
----
|
|
if overlapping(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then
|
|
----
|
|
|
|
El otro está en `updateShots`, donde un disparo se encuentra con una roca:
|
|
|
|
[source,lua]
|
|
----
|
|
if not gone and overlapping(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then
|
|
----
|
|
|
|
Todo lo de esta lección cambia esas dos líneas y añade unas cuantas funciones
|
|
pequeñas que las alimentan. Nada más del juego se mueve. El script terminado
|
|
entero está en `learn/11-hitting-things.singe`, por si te pierdes.
|
|
|
|
[#_read_it_once_more]
|
|
=== Léela una vez más
|
|
|
|
La lección nueve ya hizo esta parte: `overlapping` es la colisión de
|
|
rectángulos escrita a mano. Léela una vez más antes de tirarla, porque es la
|
|
idea entera:
|
|
|
|
[source,lua]
|
|
----
|
|
function overlapping(ax, ay, aw, ah, bx, by, bw, bh)
|
|
if ax + aw <= bx then
|
|
return false
|
|
end
|
|
if bx + bw <= ax then
|
|
return false
|
|
end
|
|
if ay + ah <= by then
|
|
return false
|
|
end
|
|
if by + bh <= ay then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
----
|
|
|
|
La pregunta «¿se están tocando estas dos cosas?» es difícil de responder
|
|
directamente y fácil de responder al revés. Dos rectángulos *no* se tocan
|
|
cuando uno de ellos está entero a la izquierda del otro, o entero a la derecha,
|
|
o entero encima, o entero debajo. No hay una quinta forma de fallar. Si no se
|
|
cumple ninguna de esas cuatro, se tocan.
|
|
|
|
Cada `if` es una de las cuatro formas de fallar. `ax + aw` es el borde derecho
|
|
del primer rectángulo, así que `ax + aw <= bx` se lee «el primero termina antes
|
|
de que empiece el segundo», que es lo que significa «entero a la izquierda» en
|
|
números. La misma forma tres veces más, para la derecha, encima y debajo, con
|
|
`y` y las alturas en lugar de `x` y las anchuras, y lo único que queda es
|
|
`true`.
|
|
|
|
Esos son los ocho números que necesita cualquier comprobación de colisión: una
|
|
esquina y un tamaño para cada una de dos cosas. Guarda la forma en la cabeza,
|
|
porque estás a punto de borrarla.
|
|
|
|
[#_the_engine_already_has_it]
|
|
=== El motor ya lo tiene
|
|
|
|
Conociste `collideRects` en la lección siete, donde atrapaba los bloques que
|
|
caían en Dodge. La lección nueve escribió la misma comprobación a mano para que
|
|
pudieras verla por dentro. Ya la has visto por dentro. Borra `overlapping` y
|
|
cambia las dos líneas de *Dónde estás* para que llamen al motor:
|
|
|
|
[source,lua]
|
|
----
|
|
if collideRects(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
if not gone and collideRects(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then
|
|
----
|
|
|
|
Los mismos ocho números, en el mismo orden: la esquina y el tamaño del primer
|
|
rectángulo, y luego los del segundo. Ejecútalo, y el juego funciona exactamente
|
|
igual que antes, que es de lo que se trata. La única diferencia está justo en
|
|
el borde. Tu `<=` decía que dos rectángulos que se encuentran a lo largo de un
|
|
borde no se tocan. `collideRects` dice que sí, y un rectángulo sin anchura o
|
|
sin altura no toca nada en absoluto. Esos casos incómodos son donde las
|
|
versiones escritas a mano fallan sin avisar, y son una de las razones para usar
|
|
la del motor.
|
|
|
|
`collideRects` es una de una familia de comprobaciones de colisión, y la
|
|
familia es una parte de lo que este libro llama el *kit de juego*: un puñado de
|
|
funciones sencillas para las cosas que casi todos los juegos tenían que
|
|
escribirse ellos mismos. Comprobar si dos cosas se tocan es una. Guardar un
|
|
número para la próxima vez es otra, y es la lección doce. Esperar un rato,
|
|
llevar un número suavemente de un valor a otro y mostrar cuánto ha tardado cada
|
|
fotograma son el resto. La referencia le da a cada parte una sección propia:
|
|
Colisión, Partida guardada, Temporizador, Interpolación y Estadísticas.
|
|
|
|
Las comprobaciones de colisión son las más sencillas de todas: preguntas
|
|
sencillas, sin handles que guardar ni nada que preparar o liberar. Y ninguna
|
|
mueve nada. `collideRects` te dice que la nave y la roca están en el mismo
|
|
sitio; qué hacer al respecto lo decides tú, y todo lo interesante de un juego
|
|
ocurre en esa decisión.
|
|
|
|
[#_the_picture_is_not_the_hit_box]
|
|
=== La imagen no es la zona de impacto
|
|
|
|
La lección diez prometió explicar por qué el rectángulo que rodea una roca
|
|
redonda tiene la forma equivocada. El rectángulo que rodea la nave también está
|
|
mal, y por la misma razón.
|
|
|
|
Dibuja en papel el rectángulo de 32 por 24 de la nave y sombrea la nave. Es un
|
|
triángulo con dos aletas, así que hay cielo vacío en las dos esquinas de arriba
|
|
y entre las aletas. La roca es redonda, así que hay cielo vacío en las cuatro
|
|
esquinas de su cuadrado. Una roca que roza la esquina de arriba del rectángulo
|
|
de la nave no ha chocado con la nave. Ha pasado volando junto al ala, y un
|
|
jugador que pierde una vida por eso dirá que el juego ha hecho trampa -- y
|
|
tendrá razón.
|
|
|
|
Así que la caja que compruebas no es la imagen que dibujas. Es más pequeña, y
|
|
está dentro de la imagen. Añade tres números a las constantes de arriba del
|
|
todo de tu archivo:
|
|
|
|
[source,lua]
|
|
----
|
|
local SHIP_PAD_X = 6
|
|
local SHIP_PAD_Y = 5
|
|
local ROCK_PAD = 4
|
|
----
|
|
|
|
El margen es lo lejos de cada borde que empieza la cosa de verdad. Seis
|
|
píxeles menos por cada lado de la nave y cinco menos por arriba y por abajo
|
|
dejan una caja de 20 por 14 en el centro de una imagen de 32 por 24, que es más
|
|
o menos donde está el casco. Cuatro menos por cada lado de la roca dejan 16 por
|
|
16, y eso es casi todo roca.
|
|
|
|
Las dos cajas son un poco más pequeñas que las cosas que representan, y es a
|
|
propósito. Una caja un poco demasiado pequeña le da al jugador escapadas por
|
|
los pelos que parecen habilidad. Una caja un poco demasiado grande le da golpes
|
|
que parecen trampa. Los jugadores perdonan lo primero y nunca lo segundo.
|
|
|
|
En lugar de escribir la aritmética en cada comprobación, escríbela una vez para
|
|
cada tipo de cosa:
|
|
|
|
[source,lua]
|
|
----
|
|
function rockBox(rock)
|
|
return rock.x + ROCK_PAD, rock.y + ROCK_PAD, rockWidth - ROCK_PAD * 2, rockHeight - ROCK_PAD * 2
|
|
end
|
|
|
|
|
|
function shipBox()
|
|
return shipX + SHIP_PAD_X, shipY + SHIP_PAD_Y, shipWidth - SHIP_PAD_X * 2, shipHeight - SHIP_PAD_Y * 2
|
|
end
|
|
----
|
|
|
|
Los tamaños salen de `rockWidth`, `shipWidth` y compañía, las medidas que la
|
|
lección nueve tomó de las imágenes, así que sigue sin haber ni un tamaño en
|
|
píxeles a pelo en el código del juego. Vuelve a dibujar la nave más grande y la
|
|
caja crece con ella.
|
|
|
|
Cada una de esas funciones devuelve cuatro números a la vez, separados por
|
|
comas. Una función puede devolver tantos valores como quiera, y la forma de
|
|
recogerlos es una lista de nombres a la izquierda del `=`:
|
|
|
|
[source,lua]
|
|
----
|
|
function shipHitsRock(rock)
|
|
local sx, sy, sw, sh = shipBox()
|
|
local rx, ry, rw, rh = rockBox(rock)
|
|
|
|
return collideRects(sx, sy, sw, sh, rx, ry, rw, rh)
|
|
end
|
|
----
|
|
|
|
Ahora la comprobación de `updateRocks` se lee como la pregunta que hace:
|
|
|
|
[source,lua]
|
|
----
|
|
if shipHitsRock(rock) then
|
|
----
|
|
|
|
La aritmética de los márgenes está exactamente en dos sitios, y los números que
|
|
usa están arriba del todo del archivo, así que puedes ajustar lo justo que se
|
|
siente el juego cambiando un número en lugar de ir buscando por tus
|
|
comprobaciones.
|
|
|
|
Pon las funciones nuevas en su sitio del orden alfabético, como pedía la
|
|
lección nueve: `rockBox` después de `onSoundCompleted`, y luego `shipBox` y
|
|
`shipHitsRock`.
|
|
|
|
[#_firing_back]
|
|
=== Devolver el fuego
|
|
|
|
Con eso está hecha la parte de la nave. La otra colisión del juego es con la
|
|
que devuelves el fuego: cada disparo contra cada roca, en `updateShots`. Tiene
|
|
el mismo problema que tenía la nave, en pequeño. Un disparo es un rectángulo de
|
|
4 por 10 que se comprueba contra el cuadrado entero de 24 por 24 de la roca,
|
|
así que un disparo que solo pasa por la esquina vacía junto a una roca la rompe
|
|
igualmente.
|
|
|
|
Podrías darle al disparo una caja con márgenes propia. Pero mira antes los
|
|
números. Quítale márgenes a un disparo de cuatro píxeles de ancho y no queda
|
|
casi nada, y comprobar un rectángulo entero para saber si una astilla de luz ha
|
|
tocado algo es más maquinaria de la que merece la pregunta.
|
|
|
|
[#_a_shot_is_almost_a_point]
|
|
=== Un disparo es casi un punto
|
|
|
|
Comprueba la punta en su lugar. `collidePointRect` pregunta si un punto cae
|
|
dentro de un rectángulo -- dos números para el punto, y luego la esquina y el
|
|
tamaño del rectángulo:
|
|
|
|
[source,lua]
|
|
----
|
|
function shotHitsRock(shot, rock)
|
|
local rx, ry, rw, rh = rockBox(rock)
|
|
|
|
return collidePointRect(shot.x + shotWidth / 2, shot.y, rx, ry, rw, rh)
|
|
end
|
|
----
|
|
|
|
`shot.y` es la parte de arriba del disparo y `shot.x + shotWidth / 2` es la
|
|
mitad de su anchura, así que el punto que se comprueba es el centro de la punta
|
|
del disparo. Ahí es donde un jugador cree que está un disparo. El lado de la
|
|
roca en esta pregunta usa el mismo `rockBox` que la nave, así que una roca mide
|
|
lo mismo le dé lo que le dé.
|
|
|
|
Va después de `shipHitsRock`, y la comprobación de `updateShots` queda así:
|
|
|
|
[source,lua]
|
|
----
|
|
if not gone and shotHitsRock(shot, rock) then
|
|
----
|
|
|
|
[#_deciding_what_happened]
|
|
=== Decidir qué ha pasado
|
|
|
|
Ninguna de las comprobaciones de colisión hace nada. Responden, y tu código
|
|
decide. Aquí están los dos sitios que deciden, con sus preguntas nuevas dentro.
|
|
Primero `updateRocks`:
|
|
|
|
[source,lua]
|
|
----
|
|
if shipHitsRock(rock) then
|
|
local channel = soundPlay(boomClip)
|
|
|
|
newRock(rock)
|
|
lives = lives - 1
|
|
if lives <= 0 then
|
|
dying = true
|
|
deathChannel = channel
|
|
if channel < 0 then
|
|
endGame()
|
|
end
|
|
end
|
|
end
|
|
----
|
|
|
|
Y el bucle de dentro de `updateShots`:
|
|
|
|
[source,lua]
|
|
----
|
|
for _, rock in ipairs(rocks) do
|
|
if not gone and shotHitsRock(shot, rock) then
|
|
newRock(rock)
|
|
score = score + HIT_SCORE
|
|
gone = true
|
|
soundPlay(boomClip, 0, 45)
|
|
end
|
|
end
|
|
----
|
|
|
|
Todo lo que hay debajo de las preguntas es exactamente lo que dejó ahí la
|
|
lección diez. Una roca que choca con la nave estalla, vuelve arriba y cuesta
|
|
una vida. Una roca alcanzada por un disparo estalla, vuelve arriba y suma
|
|
puntos. El disparo también desaparece, y el `not gone` impide que un disparo
|
|
rompa dos rocas. Cambia cualquiera de esas cosas y cambias el juego, sin tocar
|
|
ni una sola comprobación de colisión, que es la razón de que las preguntas y
|
|
las respuestas se mantengan separadas.
|
|
|
|
Ejecútalo. Pasa volando cerca de una roca y deja que te roce una aleta:
|
|
conservas la vida. Dispara justo al lado de una roca y el disparo pasa de
|
|
largo. Dispara al centro de una y se rompe, igual que siempre.
|
|
|
|
[#_seeing_the_boxes]
|
|
=== Ver las cajas
|
|
|
|
Las zonas de impacto son invisibles, y eso hace que un error en ellas también
|
|
lo sea. Dibújalas mientras trabajas. Una constante más, arriba del todo:
|
|
|
|
[source,lua]
|
|
----
|
|
local SHOW_BOXES = false
|
|
----
|
|
|
|
Dos funciones más, que van al principio del todo del orden alfabético:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawBox(x, y, width, height)
|
|
overlayBox(x, y, x + width - 1, y + height - 1)
|
|
end
|
|
|
|
|
|
function drawBoxes()
|
|
colorForeground(0, 255, 0)
|
|
drawBox(shipBox())
|
|
for _, rock in ipairs(rocks) do
|
|
drawBox(rockBox(rock))
|
|
end
|
|
end
|
|
----
|
|
|
|
Y en `onOverlayUpdate`, después de dibujar la nave y antes de que la puntuación
|
|
vaya encima:
|
|
|
|
[source,lua]
|
|
----
|
|
spriteDraw(shipSprite, shipX, shipY)
|
|
if SHOW_BOXES then
|
|
drawBoxes()
|
|
end
|
|
drawHud()
|
|
----
|
|
|
|
Cambia `SHOW_BOXES` a `true` y aparecen contornos verdes sobre el arte, como en
|
|
la imagen del principio de esta lección. Vuelve a cambiarlo cuando acabes. Un
|
|
interruptor como este, una palabra arriba del todo de un archivo que enciende
|
|
una imagen de lo que cree el programa, vale más que cualquier cantidad de mirar
|
|
fijamente.
|
|
|
|
[#_what_just_happened_7]
|
|
=== Qué acaba de pasar
|
|
|
|
[source,lua]
|
|
----
|
|
function rockBox(rock)
|
|
return rock.x + ROCK_PAD, rock.y + ROCK_PAD, rockWidth - ROCK_PAD * 2, rockHeight - ROCK_PAD * 2
|
|
end
|
|
----
|
|
|
|
Cuatro valores de un solo `return`, separados por comas. Lua devuelve encantado
|
|
tantos como quieras, y quien llama decide cuántos se queda. Cuando una llamada
|
|
así es lo *último* dentro de los paréntesis de otra llamada, se pasan los
|
|
cuatro, y por eso `drawBox(rockBox(rock))` funciona y le da a `drawBox` sus
|
|
cuatro argumentos. En cualquier otro sitio de una lista de argumentos, solo
|
|
sobrevive el primer valor. Esa regla muerde exactamente una vez, y muerde al
|
|
final de esta lección.
|
|
|
|
[source,lua]
|
|
----
|
|
return collideRects(sx, sy, sw, sh, rx, ry, rw, rh)
|
|
----
|
|
|
|
`collideRects` quiere ocho números: esquina y tamaño, esquina y tamaño.
|
|
Devuelve `true` o `false` y no cambia nada. Como la respuesta sale directamente
|
|
de la función con `return`, `shipHitsRock(rock)` se lee como una pregunta allí
|
|
donde se use.
|
|
|
|
[source,lua]
|
|
----
|
|
return collidePointRect(shot.x + shotWidth / 2, shot.y, rx, ry, rw, rh)
|
|
----
|
|
|
|
`collidePointRect` quiere seis: un punto, y luego un rectángulo. El borde
|
|
cuenta como dentro, así que un disparo justo en el límite acierta. Úsala
|
|
siempre que uno de los lados de la pregunta sea lo bastante pequeño como para
|
|
que su tamaño no importe.
|
|
|
|
[source,lua]
|
|
----
|
|
colorForeground(0, 255, 0)
|
|
overlayBox(x, y, x + width - 1, y + height - 1)
|
|
----
|
|
|
|
Las dos vienen de la lección siete. `overlayBox` recibe dos *esquinas*, no una
|
|
esquina y un tamaño -- el único sitio de esta lección donde los números cambian
|
|
de forma -- y el `- 1` está porque las dos esquinas se incluyen.
|
|
`colorForeground` es un interruptor que se queda puesto, así que todo lo que
|
|
dibuja `drawBoxes` sale verde. Los sprites y `overlayPrint` no le hacen caso,
|
|
así que el resto del juego se ve igual.
|
|
|
|
[#_the_rest_of_the_family]
|
|
=== El resto de la familia
|
|
|
|
`collideRects` y `collidePointRect` bastan para la mayoría de los juegos 2D.
|
|
Las demás están para las formas que los rectángulos describen mal, y cada una
|
|
es el mismo tipo de pregunta sencilla:
|
|
|
|
* `collideCircles` y `collidePointCircle`, para cualquier cosa redonda. Un
|
|
círculo es un centro y un radio.
|
|
* `collideRectCircle`, para una cosa redonda contra una cuadrada.
|
|
* `collidePointPolygon`, para una forma con esquinas propias -- un dragón, un
|
|
continente, un botón con forma rara. Le das una lista plana de números `x`,
|
|
`y`, `x`, `y`.
|
|
* `collideSegments`, para dos segmentos de línea que se cruzan.
|
|
|
|
Si eres sincero, tu roca es redonda, y las comprobaciones de círculos le van
|
|
mejor que cualquier caja. Los márgenes son la respuesta barata y bastan para la
|
|
mayoría de los juegos, y por eso van primero. La respuesta exacta es una de las
|
|
cosas que probar más abajo.
|
|
|
|
`collideSegments` importa más de lo que parece. Tu disparo se mueve seis
|
|
píxeles por fotograma y las rocas tienen dieciséis píxeles de zona de impacto,
|
|
así que un disparo no puede atravesar una sin que se le compruebe dentro de
|
|
ella. Haz el disparo lo bastante rápido -- cuarenta píxeles por fotograma -- y
|
|
salta de debajo de una roca a encima de ella, sin estar ni una sola vez en el
|
|
mismo sitio que la roca, y nunca le da a nada. Trazar la línea desde donde
|
|
estaba el disparo hasta donde está ahora, y preguntarle a `collideSegments` si
|
|
esa línea cruza un borde, es la cura. Las cosas lentas no la necesitan.
|
|
|
|
Hay un callback de colisión en Singe, `onCollision`, y no es para esto.
|
|
Pertenece al motor de física 3D: cuerpos con masa que caen y rebotan unos
|
|
contra otros en tres dimensiones y te dicen dónde se han tocado y con qué
|
|
fuerza. Lo conocerás en la lección veinticuatro. Nada del overlay avisa por sí
|
|
solo, y nada lo necesita. Tus rocas están en una lista, sabes cuántas hay, y
|
|
preguntar es barato.
|
|
|
|
Y cuando quieras saber no solo *si* dos cosas se han encontrado, sino dónde
|
|
debería acabar la que se mueve -- deslizándose a lo largo de una pared en lugar
|
|
de quedarse clavada dentro de ella -- las llamadas de colisión no responden a
|
|
eso, a propósito. Singe incluye una biblioteca llamada `bump` justo para ese
|
|
trabajo, y la sección Bibliotecas incluidas de la referencia dice cómo usarla.
|
|
|
|
[#_try_it_11]
|
|
=== Pruébalo
|
|
|
|
. *Quita los márgenes.* Pon `ROCK_PAD` a `0` y juega un minuto. Luego ponlo a
|
|
`8` y vuelve a jugar. Con uno de los dos parece que el juego te miente.
|
|
. *Mira lo que estás comprobando.* Pon `SHOW_BOXES` a `true` y observa los
|
|
contornos verdes mientras una roca pasa junto a la nave. Ahora pon
|
|
`SHIP_PAD_X` a `16`, juega, y fíjate en que ya nada puede darte. Averigua por
|
|
qué con la aritmética de `shipBox`.
|
|
. *Haz el disparo demasiado rápido.* Pon `SHOT_SPEED` a `40`. Cuenta a cuántas
|
|
rocas de cada diez les das. Averigua con los números por qué pasa eso antes
|
|
de volver a cambiarlo.
|
|
. *Usa el disparo entero.* Reescribe `shotHitsRock` para que use `collideRects`
|
|
con el rectángulo completo del disparo en lugar de `collidePointRect` con su
|
|
punta. Juega con las dos y decide cuál prefieres; no hay respuesta correcta,
|
|
y vale la pena saberlo.
|
|
. *Haz la roca redonda.* Dale a la roca un círculo en lugar de una caja: su
|
|
centro está a `rock.x + rockWidth / 2` en horizontal y a
|
|
`rock.y + rockHeight / 2` en vertical, y un radio de `10` es más o menos lo
|
|
justo. Usa `collideRectCircle` para la nave y `collidePointCircle` para el
|
|
disparo, y dibuja el círculo en `drawBoxes` con `overlayCircle`, que recibe
|
|
un centro y un radio.
|
|
|
|
[#_break_it_on_purpose_10]
|
|
=== Rómpelo a propósito
|
|
|
|
Las dos cajas ya son cuatro valores cada una, así que dan ganas de pasarlas
|
|
directamente y ahorrarse los ocho nombres locales:
|
|
|
|
[source,lua]
|
|
----
|
|
function shipHitsRock(rock)
|
|
return collideRects(shipBox(), rockBox(rock))
|
|
end
|
|
----
|
|
|
|
Parece correcto y está mal. Ejecútalo y el juego se para antes de dibujar nada:
|
|
|
|
----
|
|
186:collideRects: Expected 8 argument(s), got 5.
|
|
----
|
|
|
|
La línea es la que acabas de escribir, así que puede que la tuya no sea la 186.
|
|
El cinco, en cambio, sí. `rockBox(rock)` es lo último dentro de los paréntesis,
|
|
así que entraron sus cuatro valores. `shipBox()` no lo es, así que se recortó a
|
|
uno. Uno más cuatro son cinco, y `collideRects` no va a adivinar los otros
|
|
tres.
|
|
|
|
Este error viene del motor y no de Lua, así que tiene la misma forma que los de
|
|
las lecciones nueve y diez: el número del principio es la línea, luego viene el
|
|
nombre de la función que se ha negado, y luego la queja. Vuelve a poner los
|
|
ocho nombres locales.
|
|
|
|
[#_what_you_learned_11]
|
|
=== Lo que has aprendido
|
|
|
|
* Las colisiones son aritmética con rectángulos, y puedes escribirla tú mismo.
|
|
* Dos rectángulos pueden no tocarse exactamente de cuatro formas; si no se
|
|
cumple ninguna, chocan.
|
|
* `collideRects` recibe una esquina y un tamaño para cada uno de dos
|
|
rectángulos y responde `true` o `false`. Cuenta como que se tocan los bordes
|
|
que solo se rozan.
|
|
* Las comprobaciones de colisión son parte del kit de juego, funciones
|
|
sencillas sin nada que preparar. Partida guardada, Temporizador,
|
|
Interpolación y Estadísticas son el resto.
|
|
* `collidePointRect` pregunta si un punto está dentro de un rectángulo, que es
|
|
la pregunta adecuada para cualquier cosa pequeña.
|
|
* La caja que compruebas es más pequeña que la imagen que dibujas, o el juego
|
|
parecerá injusto.
|
|
* Una función puede devolver varios valores, y solo la última llamada de una
|
|
lista de argumentos los pasa todos.
|
|
* Las comprobaciones de colisión solo responden. Lo que pasa después es cosa de
|
|
tu código, y mantener las dos partes separadas te deja cambiar cualquiera de
|
|
ellas por su cuenta.
|
|
* Dibujar tus zonas de impacto en pantalla convierte un fallo invisible en uno
|
|
visible.
|
|
* `onCollision` es para cuerpos de física 3D, no para sprites en el overlay.
|
|
|
|
[#_next_time_11]
|
|
=== La próxima vez
|
|
|
|
El juego cuenta tu puntuación y te quita vidas, y en cuanto sales, la mejor
|
|
puntuación que nadie haya conseguido desaparece. Empieza en plena acción en el
|
|
instante en que se carga. Y lleva la cuenta de lo que está haciendo con dos
|
|
variables de verdadero o falso, `over` y `dying`, que es exactamente la trampa
|
|
de la que te avisó la lección siete. La lección doce le da una pantalla de
|
|
título, una sola variable en lugar de esas dos y un récord que mañana seguirá
|
|
ahí.
|