Inputs y outputs en Hammer: conectar un botón con una puerta
Inputs y outputs en Hammer, explicados: los campos de la pestaña Outputs, un ejemplo de botón y puerta, !activator, logic_relay y depuración con ent_fire.
En Hammer conectas las entities mediante outputs e inputs: un output de una entity dispara un input de otra, y el destino se localiza por su nombre (targetname). Para un botón que abre una puerta, le pones un nombre a la puerta y añades al func_button el output OnPressed con el input Open de la puerta. Una conexión así suele fallar por un nombre mal escrito o duplicado, por un destino que se ha eliminado o por dos outputs que llegan en el mismo momento.
Cómo encajan los outputs y los inputs
Un output es un evento que comunica una entity: se ha pulsado un botón, una puerta está abierta del todo, alguien ha entrado en un trigger. Un input es una orden que una entity acepta, como Open, Close o Kill. Qué outputs e inputs tiene una clase está definido en el FGD.
Cada output disparado se coloca en una cola de eventos dentro del motor y se entrega tras el retardo que hayas fijado. Solo en ese momento busca el motor el destino por su nombre. Con él viajan dos datos: el activator, normalmente la entity que inició la cadena (en un botón, el jugador), y el caller, la entity cuyo output se está disparando.
El nombre lo pones en el campo Name de las Object Properties (Alt+Enter). Una entity sin nombre puede enviar outputs, pero como destino solo se puede llegar a ella mediante su classname o un destino especial como !activator. Lo básico del editor está en primeros pasos con el Hammer Editor.
Los campos de la pestaña Outputs
En la pestaña Outputs de las Object Properties creas una conexión con Add.... En el Hammer que viene con GMod, los campos de edición y las columnas de la lista tienen etiquetas ligeramente distintas:
| Campo | Columna | Significado |
|---|---|---|
My output named | My Output | El evento de esta entity que activa la conexión |
Targets entities named | Target Entity | Nombre del destino o, como alternativa, un classname, un comodín o un destino especial |
Via this input | Target Input | La orden que ejecuta el destino |
With a parameter override of | Parameter | Valor para el input, si espera uno |
After a delay in seconds of | Delay | Tiempo de espera entre el evento y la entrega |
Fire once only | Once | La conexión se borra después de dispararse una vez |
Hammer++ sustituye la casilla por el campo Limit to this many fires ( -1 = infinite ) y la columna «Refires». La pestaña Inputs muestra el sentido contrario, es decir, todas las conexiones que apuntan a esta entity. Mark salta al otro extremo.
Ejemplo: un botón abre una puerta y la vuelve a cerrar
- Construye la puerta como brush, pulsa
Ctrl+Ty eligefunc_door. Llámaladoor_1. - Pon
Move Direction (Pitch Yaw Roll)en la dirección de apertura yDelay Before Reset (-1 stay)en-1. Así la puerta se queda abierta. - En la pestaña
Flags, desmarcaTouch Opens. Viene marcado por defecto y abriría la puerta al tocarla. - Construye el botón como brush y conviértelo en un
func_button. El flagUse Activatesya está activado. ConDelay Before Reset (-1 stay)en5, no se puede volver a pulsar hasta pasados al menos cinco segundos. - En la pestaña
Outputsdel botón, añade dos conexiones:
My Output Target Entity Target Input Parameter Delay
OnPressed door_1 Open 0.00
OnPressed door_1 Close 5.00
La puerta se abre al instante y se cierra cinco segundos después de la pulsación. También funciona sin la segunda conexión: una puerta con Delay Before Reset en 5 se cierra sola. Cómo llamar a un ascensor con el mismo esquema lo explica el artículo sobre ascensores y trenes.
Destinos especiales y bloques de lógica
En lugar de un nombre, el campo de destino puede contener un destino especial:
| Destino | Significado |
|---|---|
!activator | La entity que inició la cadena, como el jugador que está en el botón |
!caller | La entity cuyo output se está disparando |
!self | Como destino, lo mismo que !caller; como parámetro, la entity que recibe el input |
!player | El jugador; en multijugador, el primero que entró en el servidor |
!picker | La entity que está bajo la mira, pensado para depurar |
Por eso, en un servidor !player casi nunca es lo correcto. Usa !activator para el jugador que disparó la cadena, o el classname player para todos. Los comodines solo funcionan al final: door_* coincide con todos los nombres que empiezan por door_. Un patrón como *_door no funciona, porque el motor ignora todo lo que va después del *. Un classname como func_door también es un destino válido, salvo que alguna entity lleve exactamente ese texto como nombre.
Con unas pocas entities de lógica, esto se convierte en circuitos más grandes:
logic_relayagrupa varios outputs bajo un solo nombre: inputTrigger, outputOnTrigger.Disableinterrumpe la cadena,CancelPendingdescarta los eventos del relay que siguen esperando. Mientras haya outputs con retardo pendientes no se puede volver a disparar, salvo que esté activado el flagAllow fast retrigger.logic_timerdisparaOnTimeral ritmo de suRefire Interval.math_countercuenta conAddySubtracty disparaOnHitMaxuOnHitMinen cuanto se alcanza un límite.trigger_multiplees un volumen conOnStartTouch,OnEndTouchyOnTrigger. Los flags yFilter Name, con unfilter_activator_nameo unfilter_activator_class, definen quién puede activarlo.
Depurar dentro del juego
Antes de compilar, Map > Check for Problems (Alt+P) enumera las entities con conexiones no válidas. Dentro del juego ayudan estos comandos de consola:
developer 2
ent_messages_draw 1
ent_fire door_1 Open
ent_fire !picker Kill
developer 2imprime en la consola cada output y cada input.dumpeventqueueenumera los eventos que siguen esperando su retardo.ent_messages_draw 1muestra los mismos mensajes en el mundo 3D, junto a las entities.ent_fire <target> <input> [parameter] [delay]dispara un input a mano. Así puedes probar el destino con independencia de lo que lo active.
Prueba en modo de un jugador o como host, porque según la wiki de Valve un cliente conectado a un servidor no puede usar ent_fire. Si la consola pide cheats, escribe antes sv_cheats 1. Con developer 2, las líneas de la consola siguen el patrón de abajo. El bloque está montado a partir de los formatos de mensaje de la server.dll de GMod, con los nombres del ejemplo y un tiempo inventado. No es una grabación de una sesión real:
(12.50) output: (func_button,button_1) -> (door_1,Open)()
(12.50) input button_1: door_1.Open()
unhandled input: (Open) -> (door_2), from (func_button,button_1); target entity not found
La primera línea es el output que se dispara, la segunda su entrega. La tercera es la que hay que buscar: aparece cuando ninguna entity lleva el nombre del destino en el momento de la entrega, en esta ilustración door_2.
Errores típicos
- Falta el nombre o está mal escrito: Hammer muestra el destino en rojo, y «Check for Problems» avisa de la conexión. No uses ahí «Fix» a ciegas: según el texto del error, Hammer borra las conexiones defectuosas en vez de repararlas. Los comodines y los classnames también aparecen en rojo aunque funcionan en el juego.
- Nombre usado dos veces: Hammer muestra el destino en negrita. El input va a todas las entities con ese nombre. Eso es lo que se busca con las dos hojas de una puerta, no con una puerta copiada en la sala de al lado.
- Destino eliminado:
Killborra una entity junto con sus hijas en la jerarquía de parents. Los outputs con retardo que llegan después no van a ninguna parte, y la entity eliminada no dispara outputs nuevos. Lo mismo pasa con!activatorsi el jugador sale antes del servidor. - Carreras de tiempos: Dos outputs con el mismo retardo llegan en el mismo tick. No te fíes de su orden. Si B depende de A, dale a B un pequeño retardo. La wiki de Valve usa al menos 0,02 segundos en un caso así.
Los outputs también pueden activar una lua_run, que entonces ejecuta código Lua. El Map Scanner encuentra entities de riesgo de este tipo en un BSP. Hay más comprobaciones en el artículo sobre revisar un BSP antes de publicarlo.
Preguntas frecuentes
¿Puede un output dirigirse a varias entities a la vez?
Sí. El input va a todas las entities con las que coincide el destino: un nombre compartido, un comodín como door_* o un classname.
¿Qué diferencia hay entre OnPressed y OnIn en un func_button?
OnPressed se dispara en cuanto se pulsa el botón. OnIn solo se dispara cuando el botón ha llegado a su posición final de pulsado, y OnOut cuando ha vuelto a su posición inicial.
¿Puedo cambiar outputs mientras el juego está en marcha?
Sí, con el input AddOutput. El formato es <output> <target>:<input>:<parameter>:<delay>:<max times to fire>. El FGD lo califica de «very dangerous, use with care», y los circuitos que se recablean solos dentro del juego son difíciles de seguir más adelante.