549 lines
22 KiB
Text
549 lines
22 KiB
Text
[#_lesson_24_physics]
|
|
== Lección 24: Física
|
|
|
|
image::learn/24-physics.png[La lección terminada, 480]
|
|
|
|
Hasta ahora, todo lo que se movía en tus juegos se movía porque tú hacías las
|
|
cuentas. Un número subía de dos en dos en cada fotograma; si se pasaba de un
|
|
borde, le dabas la vuelta. Es la forma honrada de hacer que un juego se mueva,
|
|
y la mayoría de los juegos están hechos de eso.
|
|
|
|
Esta lección le pasa las cuentas a otro. Un *motor de física* simula el
|
|
mundo -- la gravedad, las caídas, los deslizamientos, las pilas, los rebotes,
|
|
el rodar y las cosas que chocan entre sí -- y mueve tus nodos por ti. Al final
|
|
tendrás una bandeja de cajas y un cañón que las tira. La última sección trata de
|
|
cuándo no hacer nada de esto, y es la parte que hay que leer dos veces.
|
|
|
|
[#_what_it_does_and_what_it_does_not]
|
|
=== Lo que hace y lo que no
|
|
|
|
Dale un *cuerpo* a un nodo y la simulación se hace cargo de él. A partir de
|
|
entonces cae por la gravedad, aterriza sobre lo que tenga debajo, se vuelca
|
|
cuando le dan descentrado, descansa en una pila, se desliza hasta pararse, y el
|
|
nodo va allí donde lo ponga la simulación. Tú dejas de fijar su posición. Ese es
|
|
el trato: entregas la posición y la rotación de ese nodo, y a cambio obtienes un
|
|
comportamiento que no has tenido que escribir.
|
|
|
|
Lo que no hace es nada que tenga que ver con tu juego. No sabe qué es una
|
|
puntuación, ni que la caja roja es la que da puntos, ni que caerse por el borde
|
|
cuesta una vida. No anima a un personaje que camina, no sabe cómo debería
|
|
sentirse un salto y no hará que un objeto se mueva de una forma que puedas
|
|
predecir con exactitud. La física es una máquina de producir líos verosímiles.
|
|
|
|
Las unidades son metros, kilogramos y segundos. La gravedad es `0, -9.81, 0`
|
|
salvo que digas otra cosa, que es la de la Tierra, hacia abajo, en metros por
|
|
segundo al cuadrado. Una caja de una unidad de ancho mide un metro de ancho, y
|
|
cae como tal. Si tu escena está construida a otra escala, todo parecerá caer a
|
|
través de almíbar o dentro de unos dibujos animados, y el arreglo es construir
|
|
en metros.
|
|
|
|
[#_a_floor_and_one_crate]
|
|
=== Un suelo y una caja
|
|
|
|
Crea una carpeta, crea dentro `crates.singe` y escribe esto. Es una escena, una
|
|
losa, una caja encima de la losa y tres líneas de física.
|
|
|
|
[source,lua]
|
|
----
|
|
sceneEnable(true)
|
|
sceneSetBackground(16, 18, 26)
|
|
sceneSetAmbient(40, 42, 50)
|
|
|
|
local stone = materialNew()
|
|
materialSetColor(stone, 110, 110, 120)
|
|
materialSetRoughness(stone, 0.9)
|
|
|
|
local wood = materialNew()
|
|
materialSetColor(wood, 190, 140, 80)
|
|
materialSetRoughness(wood, 0.8)
|
|
|
|
local floor = nodeNew()
|
|
nodeSetMesh(floor, meshBox(16, 0.4, 16), stone)
|
|
nodeSetPosition(floor, 0, -0.2, 0)
|
|
bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.4, 16)
|
|
|
|
local crate = nodeNew()
|
|
nodeSetMesh(crate, meshBox(0.8, 0.8, 0.8), wood)
|
|
nodeSetPosition(crate, 0.3, 4, 0)
|
|
nodeSetRotation(crate, 20, 0, 15)
|
|
bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.8, 0.8, 0.8)
|
|
bodySetMass(crate, 4)
|
|
|
|
local sun = lightNew(LIGHT_DIRECTIONAL)
|
|
nodeSetPosition(sun, -5, 9, 6)
|
|
nodeLookAt(sun, 0, 0, 0)
|
|
lightSetIntensity(sun, 1.7)
|
|
lightSetShadow(sun, true)
|
|
|
|
local camera = nodeNew()
|
|
nodeSetPosition(camera, 0, 4, 11)
|
|
nodeLookAt(camera, 0, 1, 0)
|
|
cameraSet(camera)
|
|
|
|
|
|
function onOverlayUpdate()
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Ejecútalo con `Singe -R crates`. La caja cae desde cuatro metros de altura,
|
|
aterriza sobre una esquina porque la inclinaste, se balancea hasta quedar sobre
|
|
una cara y se para. Pulsa *F5* para ejecutarlo otra vez y volver a mirarlo.
|
|
|
|
Nada de lo que hay en `onOverlayUpdate` mueve nada. Las dos llamadas a `bodyNew`
|
|
lo han hecho todo.
|
|
|
|
[#_what_just_happened_20]
|
|
=== Qué acaba de pasar
|
|
|
|
[source,lua]
|
|
----
|
|
bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.4, 16)
|
|
----
|
|
|
|
`bodyNew` le da un cuerpo a un nodo. Recibe el nodo, qué tipo de cuerpo es, qué
|
|
forma debe usar la simulación para él y las medidas de esa forma.
|
|
|
|
Hay tres tipos, y elegir el correcto es la mayor parte de la habilidad.
|
|
|
|
* `BODY_STATIC` no se mueve nunca. Se coloca donde está el nodo cuando creas el
|
|
cuerpo, y ahí se queda para siempre. Suelos, paredes, el terreno, el armario,
|
|
el nivel. Mover su nodo después no mueve el cuerpo, lo cual es una trampa que
|
|
vale la pena recordar: estático significa estático.
|
|
* `BODY_DYNAMIC` lo mueve la simulación. Cae, rebota, se desliza, recibe
|
|
empujones y arrastra a su nodo consigo. Esto es una caja, un barril, una
|
|
pelota, un trozo de escombro.
|
|
* `BODY_KINEMATIC` va donde *tú* pones su nodo, y aparta de su camino a los
|
|
cuerpos dinámicos sin que nada lo empuje nunca a él. Una plataforma móvil, una
|
|
pala, una puerta sobre un raíl, el puño de un personaje que lanza un golpe. Es
|
|
el puente entre tus cuentas y la simulación: tú lo manejas a él, y él los
|
|
maneja a ellos.
|
|
|
|
La forma no es la malla. Es un sustituto aproximado que usa la simulación porque
|
|
puede responder preguntas sobre una caja muchísimo más rápido que sobre
|
|
novecientos triángulos. `SHAPE_BOX` recibe un ancho, un alto y un fondo;
|
|
`SHAPE_SPHERE` recibe un radio; `SHAPE_CAPSULE` y `SHAPE_CYLINDER` reciben un
|
|
radio y una altura. Está `SHAPE_HULL`, que envuelve como un film retráctil la
|
|
malla que haya bajo el nodo -- la respuesta habitual para un modelo -- y
|
|
`SHAPE_MESH`, los triángulos exactos, que puede usar el decorado estático y no
|
|
puede usar un objeto que cae.
|
|
|
|
Las medidas te toca acertarlas a ti. Nada comprueba que el cuerpo que has
|
|
descrito coincida con la malla que has dibujado, y una caja dibujada a `0.8` con
|
|
un cuerpo creado a `2` flotará por encima del suelo y tirará cosas a distancia.
|
|
Cuando algo se comporta como si tuviera otro tamaño, es que lo tiene.
|
|
|
|
[source,lua]
|
|
----
|
|
bodySetMass(crate, 4)
|
|
----
|
|
|
|
La masa de un cuerpo dinámico sale de su forma con la densidad del agua, lo que
|
|
hace que las cosas pesen mucho más de lo que esperas: una caja del tamaño de una
|
|
puerta pesa unos ciento cuarenta kilos, y nada que le puedas lanzar la moverá.
|
|
Fija la masa de todo lo que deba parecer ligero. Cuatro kilos es una caja que
|
|
podrías levantar.
|
|
|
|
La masa importa porque las fuerzas se reparten según ella. El mismo empujón
|
|
mueve más lejos una cosa ligera, y un impulso de masa por velocidad es lo que
|
|
hace falta para poner algo en movimiento a esa velocidad. Ajusta primero la masa
|
|
y después las fuerzas, o acabarás persiguiéndote la cola.
|
|
|
|
[#_making_things_move]
|
|
=== Hacer que las cosas se muevan
|
|
|
|
Tres llamadas empujan un cuerpo dinámico, y no son intercambiables.
|
|
|
|
`bodySetVelocity(node, x, y, z)` dice «ahora vas a esta velocidad», y sustituye
|
|
lo que estuviera haciendo. Esto es un saque, un lanzamiento, algo que aparece
|
|
ya con algo de velocidad.
|
|
|
|
`bodyApplyImpulse(node, ix, iy, iz)` es un golpe: una patada, una explosión, un
|
|
bate que se encuentra con una pelota. Se suma a lo que el cuerpo ya estaba
|
|
haciendo. Dale un punto del mundo como tres números más y golpea *ahí* en lugar
|
|
de en el centro, así que una caja golpeada cerca de arriba se vuelca en lugar de
|
|
deslizarse.
|
|
|
|
`bodyApplyForce(node, fx, fy, fz)` es un empujón que dura: un cohete, un
|
|
ventilador, una cinta transportadora. Una fuerza aplicada durante un fotograma
|
|
casi no hace nada, lo que sorprende a quien esperaba un impulso.
|
|
|
|
La gravedad en sí es `physicsSetGravity(x, y, z)` y vale para todo el mundo. La
|
|
luna es `physicsSetGravity(0, -1.62, 0)`, el espacio es todo ceros, y un juego
|
|
con la gravedad de lado está a una línea de distancia.
|
|
|
|
Dos ajustes más completan cómo se siente un objeto.
|
|
`bodySetBounce(node, amount)` va de `0`, que se para en seco, a `1`, que vuelve
|
|
con todo lo que traía; por defecto es un `0.1` que casi ni se nota.
|
|
`bodySetFriction(node, amount)` es `0` para el hielo y `0.5` por defecto. Los
|
|
dos se combinan entre las dos cosas que se tocan, así que un suelo helado
|
|
necesita el número bajo en el suelo, no en todo lo que camina por encima.
|
|
|
|
[#_knowing_when_things_touch]
|
|
=== Saber cuándo se tocan las cosas
|
|
|
|
[source,lua]
|
|
----
|
|
function onCollision(nodeA, nodeB, x, y, z, speed)
|
|
if speed > 4 then
|
|
knocks = knocks + 1
|
|
end
|
|
end
|
|
----
|
|
|
|
Ahí, `knocks` es una variable tuya, que espera al principio del archivo con
|
|
`local knocks = 0`, y el juguete de más abajo la imprime.
|
|
|
|
`onCollision` es un callback, como `onOverlayUpdate`: lo escribes tú y el motor
|
|
lo llama. Llega una vez por cada contacto nuevo -- dos cuerpos que siguen
|
|
tocándose no vuelven a avisar -- con los dos nodos, el punto del mundo donde se
|
|
encontraron y lo rápido que se encontraron. La velocidad es lo que permite que
|
|
un golpecito y un choque suenen distinto, y es la razón de que la comprobación
|
|
de arriba ignore cómo se asienta suavemente una pila.
|
|
|
|
La otra mitad de la pareja es `onTrigger`. Convierte un cuerpo en activador con
|
|
`bodySetTrigger(node, true)` y deja de empujar cosas: se convierte en una zona
|
|
que informa de lo que entra y sale de ella a través de
|
|
`onTrigger(trigger, other, entered)`. Una línea de meta, una puerta, un foso, un
|
|
objeto que recoger. Es el descendiente en 3D de las comprobaciones de colisión
|
|
de la lección once, y para cualquier cosa del tipo «¿ha llegado aquí el
|
|
jugador?» es la llamada que quieres en lugar de un cuerpo sólido.
|
|
|
|
[#_the_toy]
|
|
=== El juguete
|
|
|
|
Ahora todo junto. Parte del script de arriba y conviértelo en esto: una bandeja
|
|
con cuatro paredes, seis cajas en pirámide y una bola de acero que puedes
|
|
apuntar y disparar. Todas las piezas son cosas que ya conoces.
|
|
|
|
Todo lo que recuerda el código nuevo va al principio del archivo, encima de la
|
|
escena:
|
|
|
|
[source,lua]
|
|
----
|
|
local crates = {}
|
|
local balls = {}
|
|
local aimX = 0
|
|
local aimLeft = false
|
|
local aimRight = false
|
|
local knocks = 0
|
|
local showShapes = false
|
|
----
|
|
|
|
Con los materiales van dos handles compartidos más: un metal pulido para la
|
|
bola y para el marcador de puntería, y una malla de caja para las seis cajas.
|
|
|
|
[source,lua]
|
|
----
|
|
local steel = materialNew()
|
|
materialSetColor(steel, 200, 205, 215)
|
|
materialSetMetallic(steel, 1)
|
|
materialSetRoughness(steel, 0.25)
|
|
|
|
local crateMesh = meshBox(0.8, 0.8, 0.8)
|
|
----
|
|
|
|
Las paredes de la bandeja son cuatro cajas estáticas, creadas en un bucle a
|
|
partir de una tabla de posiciones, que es la lección seis haciendo un trabajo
|
|
honrado:
|
|
|
|
[source,lua]
|
|
----
|
|
local wallMesh = meshBox(16, 1, 0.4)
|
|
|
|
for _, side in ipairs({ { 0, -8, 0 }, { 0, 8, 0 }, { -8, 0, 90 }, { 8, 0, 90 } }) do
|
|
local wall = nodeNew()
|
|
nodeSetMesh(wall, wallMesh, stone)
|
|
nodeSetPosition(wall, side[1], 0.5, side[2])
|
|
nodeSetRotation(wall, 0, side[3], 0)
|
|
bodyNew(wall, BODY_STATIC, SHAPE_BOX, 16, 1, 0.4)
|
|
end
|
|
----
|
|
|
|
Cada elemento es una X, una Z y un giro alrededor de Y. Dos paredes van de lado
|
|
a lado, al fondo y delante; dos están giradas noventa grados para formar los
|
|
laterales. Una malla sirve para las cuatro, y el cuerpo se crea después de
|
|
colocar y girar el nodo, porque un cuerpo estático toma la posición y la
|
|
rotación del nodo en el momento en que se crea.
|
|
|
|
Las cajas salen de una función, para que seis cajas sean seis líneas:
|
|
|
|
[source,lua]
|
|
----
|
|
local function crateAt(x, y)
|
|
local crate = nodeNew()
|
|
|
|
nodeSetMesh(crate, crateMesh, wood)
|
|
nodeSetPosition(crate, x, y, -2)
|
|
bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.8, 0.8, 0.8)
|
|
bodySetMass(crate, 4)
|
|
crates[#crates + 1] = crate
|
|
end
|
|
----
|
|
|
|
Guarda cada caja que crea en la tabla `crates`, que es como el callback de
|
|
dibujo puede hacerles una pregunta a todas más tarde. Seis llamadas construyen
|
|
la pirámide, tres en el suelo, dos encima de esas y una arriba del todo:
|
|
|
|
[source,lua]
|
|
----
|
|
crateAt(-0.9, 0.4)
|
|
crateAt(0, 0.4)
|
|
crateAt(0.9, 0.4)
|
|
crateAt(-0.45, 1.25)
|
|
crateAt(0.45, 1.25)
|
|
crateAt(0, 2.1)
|
|
----
|
|
|
|
Las filas de arriba quedan a un dedo de las de abajo y caen a su sitio en cuanto
|
|
empieza el juego. Dejar un hueco pequeño es más fácil que apilar con exactitud,
|
|
y es más amable con la simulación que darle un montón que ya está apretado.
|
|
|
|
Disparar crea una bola nueva cada vez:
|
|
|
|
[source,lua]
|
|
----
|
|
local function fire()
|
|
local ball = nodeNew()
|
|
|
|
nodeSetMesh(ball, meshSphere(0.35, 24), steel)
|
|
nodeSetPosition(ball, aimX, 1.2, 6)
|
|
bodyNew(ball, BODY_DYNAMIC, SHAPE_SPHERE, 0.35)
|
|
bodySetMass(ball, 3)
|
|
bodySetBounce(ball, 0.3)
|
|
bodySetVelocity(ball, 0, 2, -14)
|
|
balls[#balls + 1] = ball
|
|
if #balls > 5 then
|
|
nodeDelete(balls[1])
|
|
table.remove(balls, 1)
|
|
end
|
|
end
|
|
----
|
|
|
|
Fíjate en el orden. El nodo se crea, recibe una forma que dibujar y se
|
|
*coloca*, y solo entonces recibe un cuerpo. Un cuerpo dinámico empieza donde
|
|
está su nodo, y a partir de ahí el tráfico va en el otro sentido: el cuerpo
|
|
mueve el nodo. Fijar tú después la posición de un nodo dinámico es pelearte con
|
|
la simulación, y la simulación gana. Cuando haya que mover algo, dale una
|
|
velocidad, golpéalo con un impulso o haz lo que hace esto -- crea uno nuevo y
|
|
deja ir el viejo.
|
|
|
|
Dejar ir el viejo es `nodeDelete`, que libera el nodo, todo lo que cuelga de él
|
|
y el cuerpo que iba montado en él. Sin esas tres líneas, cada disparo se
|
|
quedaría en el mundo para siempre, y unos cientos de bolas después la simulación
|
|
estaría trabajando muchísimo en cosas que nadie puede ver. Limpiar forma parte
|
|
de usar la física.
|
|
|
|
Los controles y lo que se muestra en pantalla son el resto:
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
if what == SWITCH_BUTTON1 then
|
|
fire()
|
|
elseif what == SWITCH_BUTTON3 then
|
|
showShapes = not showShapes
|
|
if showShapes then
|
|
physicsSetDebug(DEBUG_SHAPES + DEBUG_CONTACTS)
|
|
else
|
|
physicsSetDebug(DEBUG_NONE)
|
|
end
|
|
elseif what == SWITCH_LEFT then
|
|
aimLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
aimRight = true
|
|
end
|
|
end
|
|
----
|
|
|
|
La barra espaciadora dispara, Mayús activa y desactiva la vista de la física, y
|
|
las flechas, mantenidas pulsadas, desplazan el punto de lanzamiento. Una placa
|
|
plana de metal sin ningún cuerpo, que se mueve en cada fotograma a `aimX`,
|
|
muestra de dónde saldrá la siguiente bola. El script completo, con el callback
|
|
de soltar y el resto de la preparación, es `24-physics.singe` en la carpeta
|
|
`learn`.
|
|
|
|
`physicsSetDebug` es la llamada más útil de esta lección. Dibuja lo que la
|
|
simulación tiene de verdad -- las formas reales en alámbrico, y una cruz roja
|
|
allí donde dos cuerpos se han tocado en este paso -- por encima de tu escena. La
|
|
primera vez que una caja flote a un palmo del suelo, o lo atraviese, o tire algo
|
|
desde dos metros de distancia, activa esto y la respuesta estará en la
|
|
pantalla. Añade `DEBUG_STATIC` para ver también el suelo y las paredes. No
|
|
cuesta nada mientras está desactivado.
|
|
|
|
Por último, la cuenta:
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
local over = 0
|
|
local settled = true
|
|
|
|
if aimLeft then
|
|
aimX = math.max(aimX - 0.08, -5)
|
|
end
|
|
if aimRight then
|
|
aimX = math.min(aimX + 0.08, 5)
|
|
end
|
|
nodeSetPosition(marker, aimX, 0.05, 6)
|
|
|
|
for _, crate in ipairs(crates) do
|
|
local pitch, _, roll = nodeGetRotation(crate)
|
|
|
|
if math.abs(pitch) > 30 or math.abs(roll) > 30 then
|
|
over = over + 1
|
|
end
|
|
if not bodyIsResting(crate) then
|
|
settled = false
|
|
end
|
|
end
|
|
|
|
overlayClear()
|
|
overlayPrint(2, 2, "Arrows aim. Space fires. Shift shows the shapes.")
|
|
overlayPrint(2, 4, "knocked over " .. over .. " of " .. #crates .. " hard hits " .. knocks)
|
|
if settled then
|
|
overlayPrint(2, 6, "everything has settled")
|
|
end
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Una caja cuenta como derribada cuando se inclina más de treinta grados, que es
|
|
una regla del juego escrita en una línea de aritmética por encima de lo que
|
|
haya hecho la simulación. `nodeGetRotation` da tres ángulos y aquí se quieren el
|
|
primero y el tercero, así que el del medio se recoge en una variable llamada
|
|
`_`. Ese guion bajo es un nombre normal y corriente sin nada de especial, que se
|
|
usa por convención para decir «este lo ignoro».
|
|
|
|
`bodyIsResting` dice si la simulación ha dormido un cuerpo porque ha dejado de
|
|
moverse. Así es como sabes que un tiro ha terminado: ni un temporizador ni una
|
|
suposición, sino todas las cajas dormidas. Un juego de bolos puntúa su tirada
|
|
justo ahí.
|
|
|
|
[#_when_not_to_use_it]
|
|
=== Cuándo no usarla
|
|
|
|
La física es la herramienta más tentadora de cualquier motor y la que más fines
|
|
de semana hace perder. Hay tres cosas ciertas sobre ella, y ninguna es culpa del
|
|
motor.
|
|
|
|
*Es cara.* Cada cuerpo dinámico es trabajo en cada paso, sesenta veces por
|
|
segundo, lo esté mirando alguien o no. Cuarenta cajas está bien. Cuatro mil no,
|
|
y en una máquina pequeña cuatrocientas tampoco.
|
|
|
|
*Es impredecible.* Ejecuta tu juguete dos veces y las cajas caen de forma
|
|
distinta. Eso es lo que hace que parezca vivo, y también es la razón de que un
|
|
juego no pueda confiar en ella para nada que tenga que salir igual cada vez. Una
|
|
puerta que tiene que abrirse, una plataforma que tiene que llegar, un salto que
|
|
el jugador tiene que poder hacer: eso es aritmética, o una interpolación, o un
|
|
cuerpo cinemático sobre un recorrido que has escrito tú. Nunca dejes algo que el
|
|
jugador tiene que hacer en manos de una simulación que puede dejarlo caer detrás
|
|
de una caja.
|
|
|
|
*Es difícil de ajustar.* La masa, la fricción, el rebote, la forma y el tamaño
|
|
tiran unos de otros, y «se siente raro» rara vez tiene una sola causa. Cada hora
|
|
que pasas consiguiendo que una caja con física se sienta bien es una hora que no
|
|
has pasado con el juego.
|
|
|
|
Así que recurre a ella cuando el lío *es* lo que buscas: cosas que se
|
|
derrumban, escombros que vuelan, un montón que se asienta, una pelota que
|
|
rebota por una habitación, un vehículo que rueda por un terreno irregular, un
|
|
ragdoll que cae a trompicones por una escalera. Nadie puede escribir eso a mano
|
|
y nadie puede decirte exactamente qué aspecto debería tener.
|
|
|
|
No recurras a ella cuando sabes lo que debe pasar. Una bala que vuela recta son
|
|
dos líneas de la lección dos. Una plataforma que sube y baja es `math.sin`. Un
|
|
jugador que camina y salta como los jugadores esperan quiere un controlador de
|
|
personaje (`playerNew`, en la referencia), porque una persona no es una caja y
|
|
nunca se comporta como tal. Un enemigo que patrulla quiere un recorrido. Un menú
|
|
que entra deslizándose quiere una interpolación.
|
|
|
|
La regla que mejor funciona: física para las cosas que el jugador mira, y
|
|
aritmética para las cosas que el jugador hace.
|
|
|
|
[#_try_it_24]
|
|
=== Pruébalo
|
|
|
|
. *Cambia el mundo.* Añade `physicsSetGravity(0, -1.62, 0)` cerca del principio
|
|
y vuelve a disparar. Luego prueba `0, -30, 0`. Las mismas cajas, otro planeta,
|
|
una línea.
|
|
. *Cambia la bola.* Haz la bola diez veces más pesada con
|
|
`bodySetMass(ball, 30)`, y luego diez veces más ligera con `0.3`, manteniendo
|
|
la misma velocidad. Después devuelve la masa a su valor y cambia en su lugar
|
|
el `-14`. Dos formas de golpear más fuerte, y no se sienten igual.
|
|
. *Haz que las cajas sean de hielo.* Añade `bodySetFriction(crate, 0.02)` en
|
|
`crateAt`, y añade una línea que le dé también al suelo una fricción baja.
|
|
Mira cómo la pila se niega a mantenerse en pie desde el principio.
|
|
. *Equivoca la forma a propósito.* En `crateAt`, cambia las medidas del cuerpo
|
|
a `2, 2, 2` mientras la malla se queda en `0.8`. Ejecútalo, mira cómo las
|
|
cajas flotan y se empujan unas a otras desde la nada, y luego pulsa Mayús y
|
|
mira cómo la vista de depuración lo explica en una sola imagen.
|
|
. *Puntúalo como es debido.* Usa `bodyIsResting` en cada caja para darte cuenta
|
|
de cuándo ha terminado el tiro, y solo entonces cuenta cuántas han caído y
|
|
súmalo a una puntuación. Esa es la mitad de un juego de bolos que lleva la
|
|
puntuación.
|
|
|
|
[#_break_it_on_purpose_23]
|
|
=== Rómpelo a propósito
|
|
|
|
Añade una línea debajo del `bodyNew` del suelo para que el suelo sea pesado, que
|
|
es algo razonable de creer que quieres:
|
|
|
|
[source,lua]
|
|
----
|
|
bodySetMass(floor, 200)
|
|
----
|
|
|
|
Singe detiene el juego con algo así:
|
|
|
|
----
|
|
32:bodySetMass: Node 1 is not a dynamic body, or the mass is not positive.
|
|
----
|
|
|
|
El primer número es la línea de tu propio archivo, así que el tuyo será
|
|
distinto.
|
|
|
|
El número que va después de `Node` es el handle del nodo, el número que te
|
|
devolvió `nodeNew`. La queja es exacta: la masa es cosa de lo que mueve la
|
|
simulación, y a un cuerpo estático no lo mueve nada, así que no tiene masa que
|
|
fijar. Pedirlo es señal de que el cuerpo es del tipo equivocado para lo que
|
|
tenías en mente -- y si de verdad quieres un suelo pesado que se pueda mover, lo
|
|
que quieres es un cuerpo dinámico con mucha masa, o uno cinemático que muevas
|
|
tú.
|
|
|
|
El mismo mensaje aparece con una masa de `0`, que la gente escribe queriendo
|
|
decir «sin peso». Sin peso no es una masa. Es un mundo sin gravedad, o
|
|
`playerSetGravityScale` en un controlador de personaje.
|
|
|
|
[#_what_you_learned_24]
|
|
=== Lo que has aprendido
|
|
|
|
* Un cuerpo se une a un nodo, y desde ese momento el nodo y el cuerpo son una
|
|
sola cosa.
|
|
* El estático no se mueve nunca, al dinámico lo mueve la simulación, y al
|
|
cinemático lo mueves tú y aparta todo lo demás.
|
|
* La forma de colisión es un sustituto de la malla, y eres tú quien responde de
|
|
que sus medidas coincidan con lo que has dibujado.
|
|
* La masa por defecto es el volumen de la forma con la densidad del agua, así
|
|
que fíjala en todo lo que deba parecer ligero.
|
|
* `bodySetVelocity` sustituye el movimiento de un cuerpo, `bodyApplyImpulse` es
|
|
un golpe y `bodyApplyForce` es un empujón que dura.
|
|
* No fijes la posición de un nodo dinámico; bórralo y crea uno nuevo cuando
|
|
tengas que hacerlo empezar de nuevo.
|
|
* `onCollision` informa de cada contacto nuevo con su punto y su velocidad, y
|
|
un cuerpo activador informa de las llegadas y las salidas a través de
|
|
`onTrigger`.
|
|
* `bodyIsResting` te dice cuándo se ha parado todo, que suele ser cuando ha
|
|
terminado un turno.
|
|
* `physicsSetDebug` dibuja lo que ve la propia simulación, y responde a la
|
|
mayoría de las preguntas sobre la física más rápido que leer código.
|
|
* La física es cara, irrepetible y delicada de ajustar. Úsala para el lío, no
|
|
para las cosas de las que depende el juego.
|
|
|
|
[#_next_time_24]
|
|
=== La próxima vez
|
|
|
|
Las cajas que caen son un tipo de espectáculo. El otro tipo está hecho de mil
|
|
cosas diminutas que viven medio segundo: chispas, humo, fuego, lluvia y polvo.
|
|
La próxima lección trata de los emisores, y termina con una explosión. Esa es la
|
|
lección veinticinco.
|