Herramientas de usuario

Herramientas del sitio


contenedores

Contenedores

Los contenedores son cosas que pueden contener otras cosas, y en donde es posible poner nuevas cosas o quitar las que ya hay.

Así, los contenedores se pueden utilizar para modelar objetos que pueden tener a otros en su interior, como un baúl o una maleta; o también para objetos que pueden tener otros encima, como una mesa.

Definición y uso de contenedores

Definir un contenedor con PUCK no podría ser más sencillo: simplemente creamos una cosa y, en la ficha “General” de su panel de entidad, marcamos la casilla que pone “Contenedor”. Con eso, la cosa queda marcada como contenedor y se podrán meter otras cosas en ella.

Para que el contenedor empiece el juego conteniendo alguna cosa, no tenemos más que usar PUCK para crear una relación estructural de tipo “contiene” que vaya del contenedor a la cosa contenida. Éste es el tipo de relación que se crea por defecto cuando creamos una flecha que va de una cosa a otra, así que no necesitaremos ir a ningún panel sino que con crear la flecha será suficiente.

Por defecto, los usuarios pueden usar las órdenes poner/meter cosa en contenedor y coger/sacar/quitar cosa de contenedor para poner y quitar cosas de los contenedores. En el caso de que un contenedor sea cerrable, esto sólo se podrá hacer cuando está abierto.

A la hora de capturar estas acciones en métodos de análisis de la entrada, es útil saber que los verbos “quitar” y “sacar” se traducen automáticamente a “coger”, con lo cual con capturar el verbo “coger” es suficiente. Del mismo modo, el verbo “meter” se traduce automáticamente a “poner”, así que capturar este último es suficiente.

A la hora de definir la descripción de un contenedor, a menudo interesará que en ella se muestren los objetos que contiene. Por ejemplo, puede interesar que la descripción de un baúl abierto sea “Es un bonito baúl. Está abierto. En su interior hay una espada y un escudo.” Para conseguir este efecto, lo más sencillo es utilizar en las descripciones del contenedor la palabra clave %INVENTORY, que se sustituirá por la descripción de su inventario. Así, una descripción como la anterior se conseguiría poniendo como descripción “Es un bonito baúl. Está abierto. En su interior hay %INVENTORY”. Jugando con las descripciones dinámicas y el método isClosed() visto en la sección sobre cosas abribles y cerrables, podemos conseguir que los objetos que hay dentro del baúl sólo se muestren cuando está abierto. Pero el mecanismo es muy flexible: si en su lugar tuviésemos una caja transparente, podríamos poner la palabra clave %INVENTORY tanto en la descripción que se muestra cuando está abierta como en la correspondiente a cuando está cerrada, para que se puedan ver los objetos que contiene a través del cristal.

En aventuras que utilicen contenedores, a menudo nos interesará consultar y manipular su contenido mediante código BeanShell: igual que en la sección sobre manipulación básica de entidades veíamos cómo enterarnos de qué cosas había en una habitación o en el inventario de una criatura, así como ponerlas y quitarlas; lo mismo se puede hacer con los contenedores. El siguiente método BeanShell es útil para ello:

/*clase Item*/ Inventory getContents ( )

Invocado sobre un contenedor c, este método nos devuelve un inventario de su contenido. Este inventario se representa mediante un objeto de la clase Inventory (véase Inventario (Inventory)), y podemos utilizar los métodos de dicha clase no sólo para comprobar si una cosa dada está dentro del contenedor, sino también para añadirle y quitarle cosas.

Nótese que esto contrasta con añadir y quitar cosas de los inventarios de habitaciones y criaturas, donde vimos que era mejor hacerlo mediante métodos específicos en lugar de trabajar con la clase Inventory.

Con esto tenemos todo lo necesario para crear juegos que trabajen con contenedores. Sin embargo, es recomendable tener en cuenta una advertencia: no es recomendable abusar de este recurso. Aunque los contenedores son interesantes cuando se integran de forma natural en el argumento de un juego o en los acertijos que plantea (por ejemplo, si es necesario encontrar la llave de un baúl para obtener una poderosa arma); usados en exceso para modelar en el mundo cosas que no los necesitan tiende a hacer que los juegos resulten menos naturales y más incómodos para los jugadores.

Por ejemplo, si tienes un mundo donde para cortar un chorizo es necesario “coger cuchillo de mesa”, “coger tabla de cajón”, “poner tabla en mesa”, “poner chorizo en tabla” y “cortar chorizo con cuchillo”; en la mayoría de los casos ese nivel de detalle no va a aportar nada al argumento o a los acertijos salvo aburrir al jugador. Sería un mejor diseño no usar contenedores en absoluto y que se pudiese “cortar chorizo con cuchillo” directamente, poniendo si se quiere un texto descriptivo que diga que el jugador ha usado una tabla.

Este sobreuso de contenedores es, en la opinión personal del autor, uno de los vicios de diseño más comunes que se cometen al crear juegos basados en texto (e incluso otros tipos de juego); y debería evitarse utilizando los contenedores sólo cuando sean estrictamente necesarios y aporten algo al argumento del juego que no sería posible sin ellos. Es decir, hablando en plata, casi nunca.

Acciones sobre objetos contenidos

Por defecto, sobre una cosa que está dentro de un contenedor no se puede hacer nada sin sacarla antes del contenedor, excepto mirarla: por ejemplo, si hay un libro dentro de un baúl, el jugador tendrá que primero coger libro o sacar libro de baúl para a continuación poder leerlo, no podrá hacerlo directamente.

Esto quiere decir que, por defecto, las únicas acciones que se pueden llevar a cabo sobre objetos dentro de contenedores son las acciones coger y mirar por defecto de AGE, el resto no funcionarán. Esto incluye también a las acciones que se definan o redefinan mediante los métodos de análisis de la entrada vistos en capítulos anteriores.

Sin embargo, si se quieren definir acciones que sí funcionen sobre objetos que estén dentro de contenedores, también se puede hacer. Aunque no se recomienda utilizar esta funcionalidad en general porque suele ser síntoma de un diseño con contenedores innecesarios como se explicaba en la sección anterior, existe la posibilidad de hacerlo. Para ello, en los menús del PUCK, cada uno de los métodos de análisis de la entrada que hemos visto en los capítulos anteriores tiene una versión que pone entre paréntesis (para contenedores y objetos contenidos). Ésta es la versión que debemos seleccionar para definir acciones que actúen directamente sobre cosas que estén dentro de contenedores.

Por ejemplo, supongamos que queremos que se pueda “cortar chorizo con cuchillo” a pesar de que el chorizo esté en un contenedor (por ejemplo, una tabla de cortar). Para ello, si queremos definir la acción en el chorizo (también podríamos alternativamente hacerlo en el cuchillo), vamos a su campo de código y, en los menús, seleccionamos Insertar código → Redefinir métodos de cosa → Método de análisis de la entrada (para contenedores y objetos contenidos) → Referente a ésta y otra cosa, en ese orden. Obtenemos la siguiente plantilla:

/*Método de análisis sintáctico de la entrada referida a dos cosas, que pueden o no estar dentro de otras*/
/*Este método se ejecuta:
  - Cuando el jugador invoca una orden sobre dos objetos, el primero de los cuales es éste (estén o no estos objetos dentro de un contenedor).
  - Cuando el jugador invoca una orden sobre dos objetos, el primero de los cuales está contenido en éste.
*/
void parseCommandOnContentsObj1 ( Mobile aCreature , String verb , String args1 , String args2 , List path1 , List path2 ,  Entity obj2  )
{
 
	//aCreature: criatura que introduce un comando.
	//verb: comando que introduce, por ejemplo "afilar"
	//args1: parte de la orden que se refiere a un primer objeto, por ejemplo "el cuchillo". Ese primer objeto es éste o está contenido
		//en éste.
	//args2: parte de la orden que se refiere a un segundo objeto, por ejemplo "con el afilador"
	//path1: camino de contenedores desde el primer objeto al que referencia la orden. Por ejemplo, si introdujo "afilar el cuchillo con el
		//afilador" y el cuchillo está en una caja, será [cuchillo, caja].
	//path2: camino de contenedores desde el segundo objeto al que referencia la orden. Por ejemplo, si introdujo "afilar el cuchillo con el
		//afilador" y el afilador no está dentro de nada, será [afilador].
	//obj2: segundo objeto al que se refiere la acción del jugador (en el ejemplo, el objeto afilador).
 
 
	//terminar con end(): interceptamos la frase, no se ejecuta lo que se tenga que ejecutar
	//por defecto ante ella
	//terminar normal: después de nuestro procesado, se lleva a cabo el análisis normal del
	//comando y ejecución de la acción correspondiente
 
}

Los métodos de análisis de la entrada para contenedores y objetos contenidos se parecen a sus análogos estándar vistos anteriormente, pero son más complejos. En concreto, las diferencias son éstas:

  1. La primera es la diferencia obvia: el método no sólo se ejecutará cuando el jugador teclee una acción referente al objeto en que lo definimos y éste esté en el inventario del jugador o en el de la habitación en la que está éste; sino también cuando el objeto esté en un contenedor que a esté en estos inventarios (o bien en un contenedor que a su vez esté en otro contenedor que esté en estos inventarios, etc.)
  2. Además, el método se nos ejecutará tanto cuando el jugador teclee una acción referente al objeto en que lo definimos (siempre que esté en uno de los lugares dichos anteriormente) como cuando teclee una acción referente a un objeto contenido en aquél en que lo definimos. Esto nos da la flexibilidad de permitir capturar acciones sobre todo lo que esté contenido en un contenedor (por ejemplo, que al coger cualquier objeto que esté dentro de un baúl, salte una trampa en dicho baúl).
  3. Los métodos de análisis para contenedores y objetos contenidos nos pasan unos parámetros path que nos dan el camino completo de contenedores que va desde el objeto al que se refirió el jugador hasta el contenedor más externo. Por ejemplo, si el jugador ha puesto “cortar chorizo con cuchillo” y el chorizo está en una caja que a su vez está dentro de un baúl, path1 sería una lista de longitud 3 tal que path1.get(0) sería el Item chorizo, path1.get(1) sería la caja y path1.get(2) sería el baúl. En ese mismo ejemplo, path2 sería una lista de longitud 1 que sólo contendría el cuchillo (si no está contenido en nada).
  4. Para obtener las entidades a las que realmente se refirió el jugador, podemos utilizar path1.get(0) y path2.get(0). Nótese que path1.get(0) no necesariamente coincide con self (puede ser en su lugar un objeto contenido en self, por razón de lo explicado en el punto 2). Así pues, si queremos asegurarnos de definir una acción que afecte sólo al objeto en el que se define y no a los que puedan estar contenidos en él, tendríamos que hacer una comprobación tipo equals(path1.get(0),self).

El resto de métodos de análisis de la entrada para contenedores y objetos contenidos se comportan análogamente a éste, teniendo dos parámetros tipo List en el caso de métodos para dos entidades, o uno en el caso de métodos para una entidad.

De este modo, podríamos definir la acción de cortar el chorizo con el cuchillo de la siguiente manera (IMPORTANTE: no debe tomarse este ejemplo como recomendación del autor de cómo hacer un juego, ya que el autor es de la opinión de que este tipo de usos barrocos de los contenedores no se debería utilizar nunca; pero lo incluye en esta documentación por completitud):

void parseCommandOnContentsObj1 ( Mobile aCreature , String verb , String args1 , String args2 , List path1 , List path2 ,  Entity obj2  )
{
 
  if ( equals ( self , path1.get(0) ) ) //acción referida al chorizo
  {
    if ( equals ( path1.size() , 1 ) || !equals ( item("tabla") , path1.get(1) ) ) //si el chorizo no está en la tabla
    {
      aCreature.writeDenial("Para cortar el chorizo, deberías ponerlo en una tabla primero.\n");
      end(); 
    }   
    else if ( equals ( path2.get(0) , item("cuchillo") ) ) //si está en la tabla y cortamos con el cuchillo
    {
      if ( equals ( path2.size() , 1 ) ) //el cuchillo no está dentro de nada
      { 
        aCreature.writeAction("Cortas el chorizo con el cuchillo limpiamente sobre la tabla.\n");
        item("tabla").getInventory().removeItem(item("chorizo"));
        item("tabla").getInventory().addItem(item("chorizo troceado"));
        set ( item("tabla") , "manchadaDeChorizo" , true );
        end(); 
      } 
      else //el cuchillo está dentro de algo
      {
        aCreature.writeDenial("Primero tendrías que sacar el cuchillo de " + path2.get(1).getSingName(aCreature));
        end();  
      }   
    }
    else //si está en la tabla y no cortamos con el cuchillo
    {
      aCreature.writeDenial("Para cortar el chorizo, necesitarías un cuchillo.\n");
      end(); 
    }   
  } 
 
}

Si además quisiéramos mirar si la tabla está en una mesa, tendríamos que irnos a mirar si el tamaño de path1 es al menos 3 y si path1.get(2) es la mesa, complicando más el código así como la vida del sufrido jugador.

contenedores.txt · Última modificación: 2011/01/16 16:35 por al-khwarizmi