7 days of WordPress themes, graphics & videos - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Game Development
  2. Game Design

Cómo codificar puertas y cerraduras

Scroll to top
Read Time: 16 mins

Spanish (Español) translation by Esther (you can also view the original English article)

En juegos hechos de habitaciones conectadas como The Legend of Zelda, el más reciente The Binding of Isaac, o cualquier tipo de Roguelike o incluso Metroidvania, las puertas juegan un papel esencial en la navegación y el progreso del jugador.

Las puertas permiten al jugador viajar de una sala o nivel a otro, por lo que ocupan un lugar importante en la navegación y la conexión de las distintas salas entre sí, y en la definición del mapa como mundo abierto o planta de mazmorra. También pueden actuar como bloqueos temporales que el jugador deberá desbloquear mediante una mecánica específica (como la obtención de una llave o la activación de un interruptor).

En este tutorial, mostraré varias mecánicas de bloqueo y propondré formas de implementarlas en tus juegos. No se trata en absoluto de las únicas o mejores implementaciones, sino de ejemplos prácticos.

Las demostraciones interactivas de este tutorial fueron realizadas con la herramienta de creación de juegos HTML5 Construct 2 y deberían ser compatibles con su versión gratuita. (Los archivos CAPX están disponibles en la descarga de la fuente.) Sin embargo, este tutorial debería ayudarte a aprender cómo implementar la lógica de las puertas y cerraduras en cualquier motor que te apetezca. Una vez que tengas la idea detrás de la lógica, todo depende de tu propio conocimiento de tu herramienta/lenguaje de codificación y de la forma en que quieras adaptarlo al juego que estás haciendo.

¡Vamos a sumergirnos!

La mecánica básica

Una puerta es básicamente un bloque de escenario que no se puede pasar, impidiendo que el personaje del jugador lo atraviese hasta que se desbloquee. La puerta puede tener diferentes estados: bloqueada o desbloqueada, cerrada o abierta.

Debe haber una representación obvia de esto último; el jugador debe ser capaz de decir que la puerta es realmente una puerta y si está en su estado cerrado o desbloqueado.

En las siguientes demostraciones, las puertas se presentan a través de dos gráficos :

Esta es una puerta cerrada.
Esta es una puerta abierta.

También he utilizado diferentes colores para representar los distintos materiales de los que pueden estar hechas las puertas, pero, para ser sincero, el aspecto gráfico depende de ti, de tu juego y de su universo. Lo más importante es que la puerta sea claramente identificable como tal, y que sea obvio si va a bloquear la progresión del jugador o si se abre y conduce al resto del nivel o del mundo.

Cuando está cerrada o bloqueada, la puerta debe ser un bloque de estado sólido. Cuando se abre, el estado sólido debe desactivarse, permitiendo que los personajes la atraviesen. Asegúrate de que, sea cual sea tu motor de colisión, te permita modificar este estado sobre la marcha con bastante facilidad.

Desde el punto de vista de la programación, el objeto puerta debe contener o estar vinculado a una variable booleana is_locked. En función del valor de esta variable, se puede determinar qué sprite mostrar y si el bloque debe ser sólido o no.

Para desbloquear la puerta, el personaje debe contener una variable booleana has_key cuando el jugador haya cogido una llave: true si la tiene, false si no la tiene.

En esta mecánica básica, la llave actúa como parte del inventario del personaje, y una llave abre todas las puertas. Usar la llave en una puerta no la consume; la llave permanece en el inventario del personaje.

Para visualizarlo, podemos simplemente mostrar una imagen de la llave en el HUD para que el jugador sepa que "posee" una llave que podría abrir las puertas una vez que el personaje la haya recogido (moviendo el personaje sobre la llave en la habitación).

Considera el siguiente ejemplo básico:

Haz clic en la demo para darle foco, luego controla al personaje con las teclas de flecha de tu teclado y realiza acciones con la barra espaciadora. (En este ejemplo, la acción es "abrir una puerta").

Las paredes son bloques sólidos que no permiten que el personaje las atraviese al chocar con ellas. Las puertas cerradas también son sólidas.

Para abrir una puerta, el personaje debe estar a menos de 64 píxeles de la puerta y poseer una llave (es decir, la variable booleana has_key que determina si el personaje tiene la llave en su inventario debe ser true).

Con estas condiciones, cuando el jugador pulsa la barra espaciadora, el estado de la puerta correspondiente cambia. Su variable booleana locked se pone en false, y su estado "sólido" se desactiva.

En pseudocódigo, esto sería algo así como :

Recordatorio: Este código no representa un lenguaje específico; deberías ser capaz de implementarlo en cualquier lenguaje que quieras.

También puedes notar que hacemos una comprobación para cuando el jugador no tiene la llave esperada, y mostramos un mensaje de retroalimentación explicando por qué la puerta no fue desbloqueada. Puedes manejar este tipo de comprobaciones de la forma que mejor se adapte a tu juego, pero ten en cuenta que siempre es bueno informar al jugador de que su acción se ha registrado y explicar la razón por la que no se ha completado.

Esta es una lógica muy básica de puertas y cerraduras y cómo implementarla. En el resto del tutorial, veremos otros sistemas de cierre que son variantes de este sistema básico.

Diferentes sistemas de cierre

Hemos visto el sistema básico en el que la llave forma parte del inventario del personaje y una llave abre todas las puertas y puede reutilizarse para abrir varias puertas. Vamos a construir sobre esto.

Ejemplo de KeyStack

En el siguiente ejemplo, el personaje tendrá un montón de llaves en su inventario. Aunque hay varios colores de puertas, la diferencia es estrictamente gráfica: el objeto puerta es lógicamente el mismo que en el ejemplo básico, y un tipo de llave puede abrir cualquiera de ellas. Sin embargo, esta vez, cada vez que usas una llave para abrir una puerta, esa llave se retira de la pila.

En lo que respecta a la codificación, esta modificación es principalmente a nivel del personaje. En lugar de tener una variable booleana has_key, querrás tener una variable numeric que contenga el número de llaves que el personaje tiene "en stock".

Cada vez que el personaje coge una llave, añade 1 a esta variable para representar que la pila sube. Cada vez que el personaje abre una puerta, resta 1 a esta variable para representar el uso de una llave. (En la tierra de los videojuegos, las llaves se destruyen en cuanto se usan una vez).

Otra modificación es para cuando el jugador pulsa la barra espaciadora: en lugar de comprobar que una variable booleana has_key es true, en realidad queremos comprobar que el valor de KeyStack es mayor que cero, para poder consumir una tecla después de abrir la puerta.

En pseudocódigo, esto se ve algo así:

Ejemplo de WhichKey

En este nuevo ejemplo, consideraremos un escenario en el que diferentes tipos de puertas requieren diferentes tipos de llaves para ser desbloqueadas.

Aquí, como en el primer ejemplo básico, las llaves formarán parte del inventario del personaje. Volveremos a utilizar variables booleanas para determinar si el personaje ha recogido las llaves necesarias. Y como tendremos diferentes llaves, también tendremos diferentes tipos de puertas (puerta negra, puerta roja, puerta dorada) que también requerirán una llave adecuada para poder abrirlas (llave negra, llave roja, llave dorada, respectivamente).

Los objetos puerta utilizarán diferentes sprites para mostrar su material, y contendrán una variable numérica llamada WhichKey que indicará el tipo de tecla que se espera, así como el tipo de gráfico que debe mostrar. Los diferentes valores de las teclas están contenidos como variables constantes, para una mejor legibilidad.

En pseudocódigo:

Esta es una variación del ejemplo básico que permite varios tipos de llaves y puertas y que no consume llaves para abrir puertas. Una vez que tienes la llave, forma parte de tu inventario, de las "estadísticas" del personaje.

Ejemplo de Switch (interruptor)

Esta vez, en lugar de actuar directamente sobre las puertas, el jugador tiene que activar un interruptor específico para abrir o cerrar una puerta concreta.

Las puertas aquí son esencialmente el mismo objeto que en el ejemplo básico. Podrían mostrar gráficos diferentes, pero la lógica del objeto sigue siendo la misma. Sin embargo, hay un añadido: añadimos dos variables numéricas DoorID y SwitchID, que utilizamos para saber qué interruptor está ligado a qué puerta.

Los Switches (interruptores) son un nuevo tipo de objetos que he decidido hacer sólidos (pero no es necesario). Contienen una variable booleana, Activated, y variables numéricas DoorID y SwitchID que, como puedes adivinar, utilizamos para determinar qué interruptor está ligado a qué puerta.

Así que cuando un interruptor tiene Activated: True, la puerta "vinculada" está configurada para tener Locked: False. Nuestra acción con la barra espaciadora va a ocurrir entonces cuando esté junto a un interruptor, en lugar de junto a una puerta. Observa la ausencia de una llave en este ejemplo, ya que los interruptores actúan como llaves:

Podríamos utilizar un código simple que compruebe si los enlaces puerta-interruptor están en la misma habitación (ya que este ejemplo muestra tres puertas e interruptores en la misma habitación), pero más adelante veremos que podemos tener interruptores que actúan sobre puertas que están en otra habitación, por lo que su acción no se producirá en el momento exacto en que el jugador active el interruptor; se producirá más tarde, cuando se cargue la nueva habitación.

Por esta razón, necesitamos persistencia. Una opción para esto es utilizar arrays para mantener un registro de datos como el estado de los interruptores (es decir, si cada interruptor está activado o no).

En pseudocódigo:

Para este ejemplo concreto, en el que los interruptores están en la misma habitación que las puertas a las que están vinculados, utilizar la técnica del array es una exageración. Si tu juego está configurado de tal manera que cada interruptor que actúa sobre una puerta va a estar posicionado en la misma habitación, entonces por todos los medios ir por el método más simple, deshacerse del array, y comprobar los objetos que están en la pantalla solamente.

Ejemplo de Plate-Switch (interruptor de placas)

Los interruptores de placa son similares a los interruptores, en el sentido de que se activan o no, y que podemos vincularlos a las puertas para bloquearlas o desbloquearlas. La diferencia radica en cómo se activa un interruptor de placa, que es a través de la presión.

En este ejemplo de vista descendente, el interruptor de placa se activará siempre que el personaje esté sobre él. Puedes pulsar la barra espaciadora para dejar caer una piedra sobre el interruptor de placa, dejándolo activado incluso cuando el personaje no esté sobre él.

La implementación de esto es similar al ejemplo anterior, con dos pequeñas modificaciones:

  • Tienes que activar el interruptor de la placa cuando un personaje o una roca esté encima de ella.
  • Tienes que hacer que la barra espaciadora deje caer una piedra (del inventario) sobre el interruptor de placa.

Ejemplo de Mobs (turbas)

Otra posible mecánica de bloqueo es exigir al jugador que se deshaga de todos los enemigos (también conocidos como mobs) de una habitación o zona para activar el desbloqueo de las puertas.

Para este ejemplo, he hecho varias áreas en una sola habitación; cada área tiene una puerta y varios mobs (aunque esos enemigos no se mueven y no hacen daño). Cada zona tiene su propio color.

La barra espaciadora hará que el personaje dispare algunos proyectiles; tres proyectiles matarán a un mob.

Este tipo de mecánica se utiliza en The Legend of Zelda y The Binding of Isaac, y gira en torno a una función que comprueba el número de enemigos vivos en la habitación o zona. En este ejemplo, cada área coloreada contiene un contador de los mobs vivos, iniciado cuando la sala se carga, y está ligado a la puerta. La muerte de cada mob resta 1 a este contador; una vez que baja a 0, el estado Locked de la puerta se cambia a False.

En este ejemplo, un Area es un sprite de color con una variable numérica asociada, AliveMobs, que cuenta el número de mobs que se encuentran en el área. Una vez que todos los mobs de un área son derrotados, la puerta correspondiente se desbloquea (usando la misma mecánica que hemos visto desde el ejemplo básico).

Ejemplo de navegación

Como mencioné en la introducción, las puertas pueden actuar como obstáculos de bloqueo, pero también pueden utilizarse para que el personaje del jugador navegue de una habitación a otra.

En este ejemplo, las puertas estarán desbloqueadas por defecto, ya que nos interesa más el aspecto de la navegación.

La mecánica depende en gran medida del juego que estés haciendo, así como de la forma en que manejes la estructura de datos para tus pisos. No voy a entrar en los detalles de cómo funciona mi implementación aquí, ya que es muy específica para Construct 2, pero puedes encontrarla en los archivos fuente si lo deseas.

Conclusión

A lo largo de este artículo, hemos visto cómo las puertas son obstáculos temporales que requieren llaves o mecánicas de desbloqueo como interruptores, placas o incluso la muerte de mobs. También hemos visto cómo pueden actuar como "puentes", permitiendo la navegación a través de diferentes áreas del mundo del juego.

A modo de recordatorio rápido, he aquí algunas posibles mecánicas de bloqueo :

  • Una llave para todas las puertas, como parte del inventario.
  • Llaves consumibles: cada vez que abres una puerta, se quita una llave de tu pila de llaves.
  • Cada puerta requiere una llave diferente.
  • Interruptores, o interruptores de placa, en los que no se actúa directamente sobre la puerta para desbloquearla, sino a través de un dispositivo separado y vinculado.
  • Matar a todos los mobs de una zona desbloquea automáticamente una puerta.

Si mezclas todas esas mecánicas en un juego, podrías acabar con algo así:

Aquí tenemos una buena selección de diferentes mecánicas de puertas y cerraduras, que requieren que el jugador pase por varias habitaciones para desbloquear las distintas puertas. Para fines de aprendizaje, es posible que quieras reproducir esto en tu propio entorno de programación, utilizando todas las implementaciones anteriores que hemos pasado.

Espero que hayas disfrutado de este artículo y que te haya sido útil, y me gustaría recordar que puedes encontrar el código fuente de todas las demos en Github. Puedes abrirlas y editarlas en la versión gratuita de Construct 2 (versión r164.2 o superior).

Referencias

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.