Coqueteando con los cambios climáticos

Sticks & Stones – juego difícil e iterativo, donde prima nuestra habilidad y aprendizaje versus la memorización.

Bajo esta sencilla premisa, siempre hemos tenido en el horizonte a un Goliat con su mirada intimidante e inquietante, advirtiéndonos: variedad, variedad, variedad.

En un juego en el que perder está a la orden del día, y avanzar se hace duro pero muy satisfactorio, se presentan retos a nivel de diseño. Y la palabra variedad es de las que mejor definen muchas de las soluciones.

No es la primera vez que os hablamos sobre esto en el Blog:

Y volvemos a la carga del barco variedad coqueteando con los cambios climáticos… a ver qué os parecen:

¿Qué os parecen?

Si os gusta, compartid entre vuestros amigos.

El diseño de los alijos de supervivencia

deathSticks & Stones es un Rogue-like, y como tal, la elevada dificultad y la permadeath (muerte permanente) forman parte de sus características principales. Vamos a tener que jugar una y otra vez para conseguir ganar este reality show.

A nivel de diseño esto nos pone en un aprieto, pues tenemos que conseguir que la experiencia de juego no se haga pesada o aburrida.

A grandes rasgos, la estructura del videojuego se puede resumir en planificación+combate. La parte de combate no es problema, es pura acción y la diversión está asegurada. Sin embargo, en la parte de planificación, que incluye: exploración, supervivencia, recolección y crafting, es donde debemos andar con más cuidado, porque son mecánicas lentas. Es aquí donde entran en juego los alijos de supervivencia.game-structure

Los alijos de supervivencia serán una de las mecánicas, entre otras tantas, que ayudarán a que la parte de planificación sea más entretenida. Lo que se pretende con los alijos, es potenciar la exploración añadiendo un pequeño componente aleatorio que otorgará ventajas al concursante entre partidas.

karmaA un nivel contextual, los alijos de supervivencia son creados por los mismos concursantes, como una forma para ayudarse entre ellos. Aquí jugamos un poco con el Karma, pues la idea es que si escondes algo bueno en un alijo para que otro se lo encuentre, tu Karma será positivo y es posible que alguien te devuelva ese favor. Aterrizando este concepto en el juego, la idea es que si en una partida escondes algo de valor, en tu siguiente partida encontrarás algo de valor equivalente.

Esta mecánica es una de las pocas que transciende entre partidas, ofreciendo al jugador una manera de comunicarse entre ellas y otorgandole ventajas que le permitan avanzar cada vez más lejos. Además está perfectamente integrada en el contexto, para que el jugador pueda sentir lo duro que es el reto que propone el reality show y decida si quiere ayudar a otros—o a sí mismo-—a obtener el premio.

Hasta aquí el concepto, pero ahora explico como resolvemos esta mecánica y que se va a encontrar el jugador durante la partida.

En la zona central, especialmente diseñada para planificarse y donde comienza la partida, el concursante tendrá que explorar, recolectar y construir cosas. En definitiva, preparase para la supervivencia y el combate. Durante la exploración, si se presta la suficiente atención al entorno, podremos encontrar el alijo, que se descubrirá por emitir un sutil brillo intermitente. Al situarnos en el origen del brillo se habilitará una opción para cavar en el suelo. No será necesario ninguna herramienta, el concursante cavará con sus propias manos poniendo al descubierto el botín, del que podremos coger todo lo que queramos y/o dejar cosas para que el próximo concursante las encuentre.

Internamente los objetos del juego tienen un valor, que se utiliza para calcular el valor total del alijo.

Por ejemplo, un alijo de valor 18 podría contener objetos que sumen hasta un total de 18:

1 × Lanza de madera (8) 1*8 = 8  wooden-spear
1 × Cuchillo de hueso (10)  1*10 = 10  bone-knife
wooden-spearbone-knife = 18

4 × Flecha de madera (3)  4*3 = 12  wooden-arrowswooden-arrowswooden-arrowswooden-arrows
1 × Cuerda (4) 1*4 = 4  rope
wooden-arrowswooden-arrowswooden-arrowswooden-arrows rope = 16

Este factor aleatorio, permitirá que el jugador pueda encontrar y utilizar objetos que no conoce, incentivando la experimentación con el crafting y añadiendo un punto de emoción al encontrar el alijo.

Esto es todo por ahora en cuanto los alijos de supervivencia, pero tenemos varios puntos abiertos, como por ejemplo si hay límite máximo en el valor de un alijo o si hay un número máximo de objetos que puede contener. También nos preguntamos: ¿se debe enterrar el alijo para considerase válido?

Te animo a que opines sobre está mecánica y nos ayudes a decidirnos por las dudas pendientes que nos quedan.

¡Muchas gracias!

 

Generación Procedural o cómo generar escenarios sexys

Querido lector. Si tienes curiosidad de ver cómo las matemáticas pueden ayudar a generar mapas aleatorios tan hermosos como el siguiente, ¡este es tu articulo! Agárrate que vienen curvas.

central-zone-2

En cada partida de Sticks & Stones, los mapas son diferentes, para que el jugador no pueda memorizarlos y haga uso de la habilidad y su experiencia para superarlos. Además, es un juego difícil, donde se repite una y otra vez lo mismo. Por lo tanto, la variedad es una característica esencial en la generación de mapas.

Crear pantallas lo más mutables, bonitas y orgánicas posibles, es una muy buena manera de conseguir esta variedad ¿Pero cómo se puede lograr esto? Con algo llamado generación procedural:

En computación generación procedural es el método de creación de datos con algoritmos en lugar de forma manual

Es decir, que para generar las pantallas es necesario recurrir a las matemáticas y a sus fórmulas. ¡Ah! Las matemáticas… yo siempre me preguntaba de pequeño que para qué diablos sirven las matemáticas en la vida… 😛

Volviendo al tema, para nuestra generación de mapas, hemos usado un algoritmo llamado Perlin Noise. Para entender su funcionamiento, vamos a imaginarnos una onda de radio. Y que mediante 3 parámetros (frecuencia, amplitud y octavas), podemos llegar a cambiar su apariencia. En el siguiente gráfico hay algunos ejemplos visuales:

perlinnoise

  • La frecuencia determina el número de ondas
  • La amplitud determina la altura de las ondas
  • Las octavas determina la suavidad de las ondas

Una vez explicado esto, vayamos a cómo implementar esta onda a nuestra generación de mapas. Para ello, primero vamos a crear celdas con texturas totalmente planas. En nuestro ejemplo vamos a usar un mapa de 3×3.

1

A continuación se genera un Perlin Noise de 3 dimensiones XYZ para cada una de las celdas. Donde la XY son las coordenadas del píxel, y la Z es la ALTURA.

En la siguiente animación tenéis 3 muestras de 3 generaciones distintas de Perlin Noise. Fijaos en lo diferentes que pueden llegar a ser unos mapas de otros. Para facilitar su visualización, se ha establecido una franja de color distinto a cada una de las ALTURAs.

5-7-6

Ahora solo falta establecer una relación entre ALTURATEXTURA. En este caso usamos la misma que utilizan los mapas topográficos,

world_map

Para nuestro ejemplo vamos a usar la siguiente relación:

[∞ .. 300] = Hierba
[0 .. -200] = Tierra
[-500 .. -700] = Tierra con piedras
[-900 .. ∞] = Agua (transparencia del 10%) 

Y estas son las texturas que vamos a usar para la generación:

texturas

Si metemos en una coctelera el Perlin Noise, las franjas de terreno y las texturas, obtenemos este resultado:

5-7-6

Si este proceso lo repetimos, variando únicamente uno de los valores base como puede ser la frecuencia, la amplitud o las octavas, el resultado puede llegar a cambiar bastante.

Amplitud = 1 (menos diferencia entre altura mínima y máxima)

5-1-6

5-1-6

Frecuencia = 1 (espacio entre cambios de altura más espaciados)

1-7-6

1-7-6

Octavas = 1 (transición entre alturas mucho más suavizada)

5-7-1

5-7-1

Mediante este sencillo proceso de configurar un Perlin Noise, estableciendo las franjas de terreno y texturas,  podemos obtener resultados como estos:

magma

meadowsnowedswampdesertwater

Llegados a este punto, lo único que falta por hacer es colocar objetos en el terreno, y para ello volveremos a usar la ALTURA y vincular en este caso OBJETOS (usando un % de aparición).

Veamos un ejemplo:

[∞ .. 500] = Árboles
[500..0] = Plantas, flores
[0 .. -200] = Rocas
[-500 .. -700] = Piedras, árboles secos
[-900 .. ∞] = Juncos

¡¡Y aquí tenéis el resultado!! forest

¿No es fascinante lo natural que parecen los objetos una vez colocados?

La ventaja de configurar todo el escenario en base al concepto de la ALTURA, es que si se desplaza la base de esta ALTURA, el escenario cambia:

forest

Si ahora le aplicamos un efecto de desenfoque en la parte superior e inferior, además de unos bonitos rayos de luz, obtenemos el aspecto que tiene en la actualidad el juego:

central-zone-2

Si te ha gustado el artículo: comenta y comparte.

¡Gracias!

Traje, hologramas y realidad aumentada

En Sticks & Stones eres un concursante en un show televisivo que mezcla supervivencia y acción. El concursante debe enfrentarse a enemigos un tanto especiales: las Holo-Balls, unas esferas que se desplazan por el suelo y proyectan hologramas.

gunslinger
Arte de juego – Gunslinger

gunslinger
Ilustración – Gunslinger

El concursante va equipado con un traje especial que le permite interactuar con las Holo-Balls, y unas lentillas de realidad aumentada que proporcionan una experiencia de combate segura.

El traje está conectado a un sistema de monitorización que controla las constantes vitales y la interacción con las Holo-Balls. Si durante el reto sufre algún desmayo o pérdida de conocimiento, el traje lo notifica al sistema y una unidad de emergencia viene a socorrerlo, provocando automáticamente su derrota. El traje también reconoce un impacto causado por algún objeto de realidad aumentada (proyectil) o Holo-Ball. Los impactos reducen los puntos de golpe de los que dispone el traje y si llegan a 0 el concursante también es derrotado.

ferguson-suit
Arte de juego – Concursante

Los proyectiles que lanzan algunas Holo-Balls son creados mediante la realidad aumentada, incapaces de causar daño físico al concursante. Existen varios tipos de proyectiles o balas y su aspecto determina sus características.

Cuando un holograma o artefacto de realidad aumentada se ve de color rojizo indica que está potenciado, mejorando sus propiedades normales.

super-bullet-normal Super bala
super-bullet-chargedSuper bala potenciada

 mega-bullet-normal  Mega bala
mega-bullet-charged Mega bala potenciada

 

Sticks & Stones te pone a prueba en un reto único y difícil, mezclando naturalezatecnología. ¿Podrás abrirte paso hasta el final partiendo de los palos y piedras que encuentres en el camino?

 

Nueva Holoball: Torpigdo

Hace unas semanas mostramos algunas de las Holoballs. Hoy te enseñamos a Torpigdo, un cerdo montado en un cañón que dispara balas muy poderosas.

Se dice que Torpigdo fue un aviador reconocido que acabó pilotando un cañón de tierra… que no te engañe su aspecto, detrás de sus gafas de aviador se esconde un poderoso enemigo al que respetar.

Su ataque es a distancia (range). Durante un breve periodo de tiempo cargará un poderoso disparo que lanzará a alta velocidad a través de su cañón. Su poder ofensivo es compensado con su falta de movilidad.


Test chamber: Torpigdo – Indie DB

Si quieres probar enfrentarte a Torpigdo, ¡descarga nuestra build y ponlo a prueba! Te retamos a derrotarlo devolviéndole sus disparos con el escudo holográfico.

Si te gusta, ¡compartelo!

 

 

El reto de combinar ciertas mecánicas – Solución

En la entrada de hoy, vamos a explicar cómo hemos afrontado el mayor (por lo menos, hasta la fecha) de los problemas de diseño de Sticks & Stones.

Allá por noviembre publicamos una entrada en la que hablábamos del reto de combinar ciertas mecánicas, y de cómo el hecho de aventurarnos a experimentar con ellas nos ocasionó ciertos problemas:

Rogue-like – Empezar desde cero – VS – Crafting – Construcción progresiva

El problema aquí es que puede llegar a cansar el tener que pasar siempre por las mismas fases del crafting, recolectar ingredientes, craftear, recolectar ingredientes, craftear, …

Rogue-like– Zonas pequeñas – VS – Supervivencia – Sensación de exploración

El problema aquí casi casi se lee solo. El juego tendrá zonas más bien pequeñas, pero la sensación de supervivencia se consigue con ambientes muy grandes para potenciar la necesidad de explorar.

En el artículo actual, desarrollaremos la solución que diseñamos para paliar estos problemas, que aunque, si bien no los elimina por completo, los minimiza bastante.

– ANTES –

Para poder explicar esto,  volvamos al  esquema de juego que teníamos hasta entonces:

gamerunold

 

El desarrollo del juego era muy sencillo, el jugador aparecía en la Zona A y tenía que pasar por todas las demás zonas hasta llegar a la última. Todas y cada una de las zonas tenía partes de exploración, recolección de recursos y combate.

– ACTUAL –

En la actualidad hemos apostado por el siguiente esquema :

gamerunnew

 

El desarrollo aquí se concentra en  empezar en la zona central y recorrer las zonas A, B, C y D en el orden que se quiera, para finalmente ir a la zona final. Con la peculiaridad que la zona central es exclusivamente de exploración y recolección, mientras que las zonas A, B, C y D se centran, casi en exclusiva, en combate. Así, para acceder a la zona final, antes habrá que finalizar las zonas de la A a la D.

ANALISIS

A priori parece una solución bastante sencilla – solo se mueven 4 cajitas y flechitas entre ambos esquemas. Pero este nuevo enfoque aporta un montón de ventajas, mejoras y mitiga bastante los problemas descritos anteriormente.

Vayamos a ver punto por punto lo que nos aporta este nuevo enfoque:

 

PLANIFICACIÓN

Empezamos el juego en la zona central, llena de recursos y sin ninguna amenaza. Somos nosotros los que decidimos el tiempo que invertimos para recolectar recursos y construir nuestras propias armas. Cuando nos sintamos preparados para entrar en combate, solo será necesario coger uno de los 4 ascensores disponibles (A, B, C o D).

¿Qué nos aporta este cambio de enfoque y el nuevo esquema? Pues que el juego ya NO marca el camino ni el ritmo de jugador, sino que cada uno escogerá el suyo propio, haciendo el juego más estratégico, más frenético o más conservador, dependiendo del momento y las necesidades.

Aparte de eso, la construcción está basada de tal modo que, esta primera zona sea la que concentre la mayoría de recursos en todo el juego, lo que hace que tengamos que medir muy bien su uso.

En el anterior esquema, el hecho de que era necesario avanzar de forma obligatoria por las zonas (sin poder volver atrás), hacía que la recolección y la exploración fuera más necesaria e incierta. Ya que no sabías nunca qué recursos ibas a encontrarte en las futuras zonas, y muchas veces acababas con el síndrome de Diógenes recogiendo todo lo que podías cargar en la mochila.

REPETICIÓN MÁS VARIADA

La posibilidad de afrontar cualquiera de las 4 zonas desde el minuto 1, permite que las partidas sean más variadas, ya que cada zona tiene sus propios enemigos, retos, peculiaridades y dificultades que hacen que se tengan que afrontar de forma muy distinta, y llegar a ellas con recursos diversos.

En el anterior esquema, el orden de las zonas era prefijado y las partidas, al conocerse el camino a seguir, podían convertirse en una repetición de acciones de forma sistemática. Adicionalmente nos enfrentábamos a una limitación a nivel de diseño, que consistía en desarrollar el entorno de tal modo que todas las zonas tuvieran recursos suficientes para poder ir avanzando en el juego.

RUN NO LINEAL

El talón de Aquiles de cualquier diseño Rogue-like es la linealidad y puede crear frustración. Los que estén familiarizados con el concepto de juego rogue-like saben que la base siempre es la misma: jugar-morir-aprender, jugar-morir-aprender, jugar-morir-aprender… Así hasta acabar el juego (o no).

Por lo que el empezar cada partida siempre desde el principio, puede convertirse en un camino de frustración, que, en consecuencia, lleva a la triste verdad: el juego cae en el olvido a la primera de cambio.

Creemos que este nuevo esquema de orden no-lineal que se traduce en poder finalizar las 4 zonas en el orden que se quiera, debería ayudar mucho a afrontar de forma más amena el desarrollo del juego. Además, claro está, de toda la variedad que se le pueda dar a nivel de contenido (selector de concursantes, sponsors, ventajas, etc…)

¿Qué os parece esta solución? ¿Creéis que es un esquema y una vuelta de tuerca que podría  funcionar?

Nos encantaría saber vuestras impresiones y leer cualquier comentario al respecto, tanto si creéis que es buen enfoque como no.

Muchas gracias por leernos.

 

¡Cómo molan las Holoballs!

El concepto de Holoball nos ha acompañado desde muy temprano, nació en los inicios del proyecto Sticks & Stones. Ha ido tomado varias formas, colores y comportamientos, pero su esencia se ha mantenido.

La idea es tan simple como atractiva:

Holoball – bola que proyecta un holograma sobre si misma.

Partiendo de esta base tan sencilla, las Holoballs se presentan como los enemigos de los concursantes, impidiéndoles que alcancen su objetivo – finalizar el show.

Centrémonos en qué son exactamente las Holoballs. Por una parte, su exterior está recubierto de un material parecido al caucho que le otorga omniresistencia (resistencia a prácticamente todo). Y por otra banda, las tripas de estas bolas albergan un sistema de engranajes que les permiten coger tracción sobre cualquier superficie conocida.

A nivel técnico, la superficie de las Holoballs está llena de micro-cámaras, que tienen la capacidad de proyectar hologramas representando cualquier forma y color. Y en el caso de necesidad, es posible proyectar hologramas a distancia, aunque siempre hasta cierto límite.

Cada una de las Holoballs puede ser programada para tener un aspecto y un comportamiento totalmente distinto. Y aunque técnicamente son casi indestructibles, están preparadas para recibir y procesar cualquier tipo de daño, tanto físico como holográfico.

Aquí tenéis un pequeño esquema de las tripas de las astutas Holoballs:

holoball

Una vez explicado esto, os dejamos un pequeño avance de algunas Holoballs que tenemos desarrolladas hasta ahora.

El pistolero (Gunslinger)

gunslinger

Esta Holoball centra su ataque a distancia (range). Siempre lleva consigo su revólver con 6 disparos en la recámara. Su mecánica es perseguir al concursante y rodearlo mientras le dispara sin parar.

El Carnero Salvaje (Wild Ram)

wildram

El carnero ataca cuerpo a cuerpo (melee) con su embestida. Tiene poca tracción y sus movimientos son un poco impredecibles a la vez que ágiles. Es crítico calcular sus movimientos y estudiarlos para saber cuando se le puede atacar.

 

En la medida que vayamos desarrollando más Holoballs, os las mostraremos aquí.

Y como siempre, si os ha gustado el artículo: ¡compartidlo!

Diseño y programación – Supervivencia y consumibles

En este post contaré cómo hemos diseñado y programado el sistema de supervivencia y consumibles, que afectan directamente a la mecánica de supervivencia.

El post se divide en 3 apartados:

  1. Supervivencia: la mecánica relacionada con los consumibles.
  2. Consumibles: los objetos que representan el concepto de consumibles.
  3. Colores: los colores que identifican los diferentes indicadores de supervivencia.

 

Supervivencia

Es una de las mecánicas del videojuego que sirve para hacer más difícil el reto al jugador. Durante la partida, sufrirá de hambre, deshidratación y sueño. Para lidiar con ello, tendrá que buscarse la vida, sobre todo utilizando el entorno natural en el que se encuentra. Podrá cazar animales, beber agua, dormir, recoger frutos silvestres, fruta, setas y otras tantas cosas.

Por tanto, la supervivencia mide tres factores:

  • Hambre hunger-icon
  • Deshidratación dehydration-icon
  • Sueño sleep-icon

Cada uno de estos factores tiene su indicador y se dividen en varias propiedades, que permite tener el control del estado de supervivencia de los personajes.

survival-status

Propiedades de supervivencia

La supervivencia está vinculada a los personajes orgánicos, como los concursantes, de manera que cada personaje define dos métricas importantes para cada uno de los factores de supervivencia (hambre, deshidratación y sueño):

  • El tiempo que tarda en desmayarse
  • La cantidad de puntos que necesita para quedar saciado

El código que verás a continuación, es parte del script de un personaje orgánico, donde definimos las propiedades de supervivencia.

  • Tiempo desmayo por hambre: indica el tiempo para desmayarse por hambre o desnutrición, expresado en segundos.
  • Tiempo desmayo por deshidratación: indica el tiempo para desmayarse por deshidratación, expresado en segundos.
  • Tiempo desmayo por sueño: indica el tiempo para desmayarse por sueño, expresado en segundos.
  • Puntos para saciar el hambre: puntos necesarios para saciar completamente el hambre.
  • Puntos para hidratarse: puntos necesarios para hidratarse completamente.
  • Puntos para aliviar sueño: puntos necesarios para aliviar el sueño completamente.
  • Tiempo durmiendo para recuperar sueño: tiempo necesario que se necesita dormir para recuperar completamente el sueño, expresado en segundos.
/// <summary>
/// Cualquier personaje del juego que sea orgánico, por ejemplo un concursante o un animal de la fauna.
/// </summary>
public abstract class OrganicCharacter : CharacterBase
{

...

  /// <summary>
  /// Tiempo para desmayarse por hambre o desnutrición (en segundos).
  /// </summary>
  public abstract float TimeToFaintFromHunger { get; protected set; }

  /// <summary>
  /// Tiempo para desmayarse por deshidratación (en segundos).
  /// </summary>
  public abstract float TimeToFaintFromDehydration { get; protected set; }

  /// <summary>
  /// Tiempo para desmayarse por sueño (en segundos).
  /// </summary>
  public abstract float TimeToFaintFromSleep { get; protected set; }

  /// <summary>
  /// Puntos necesarios para nutrirse al completo y saciar el hambre.
  /// </summary>
  public abstract ushort PointsToSatiateHunger { get; protected set; }

  /// <summary>
  /// Puntos necesarios para hidratarse al completo y saciar la sed.
  /// </summary>
  public abstract ushort PointsToHydrate { get; protected set; }

  /// <summary>
  /// Puntos necesarios para aliviar el sueño.
  /// </summary>
  public abstract ushort PointsToAlleviateSleep { get; protected set; }

  /// <summary>
  /// Tiempo necesario para recuperarse por completo del sueño,
  /// desde un estado completamente agotado o cansado por sueño (en segundos).
  /// </summary>
  public abstract float SleepTimeForFullRecovery { get; protected set; }

...

}

Métodos de consumo de los personajes

El código que añado a continuación, muestra algunos de los métodos de los personajes orgánicos, que permiten interactuar con los consumibles o consultar el estado de supervivencia.

  • Comprobar desmayo: indica si el personaje se ha desmayado por alguna de las propiedades de supervivencia.
  • Consumir: actualiza los indicadores de supervivencia del personaje para un consumible.
  • Beber una bebida: actualiza los indicadores de supervivencia de un personaje para una bebida.
  • Beber de un contenedor: da un sorbo a la bebida contenida en el contenedor de bebidas.
  • Dormir: actualiza el indicador de supervivencia que corresponde con el sueño aumentando los puntos correspondientes al tiempo dormido.
/// <summary>
/// Cualquier personaje del juego que sea orgánico, por ejemplo un concursante o un animal de la fauna.
/// </summary>
public abstract class OrganicCharacter : CharacterBase
{
 
...

 /// <summary>
 /// Indica si el personaje se ha desmayado por falta de comida, hidratación o cansancio.
 /// </summary>
 /// <returns>'Verdadero' si se ha desmayado, 'Falso' en caso contrario.</returns>
 public bool IsFaint()
 {
   return (CurrentFeedingPoints <= 0 || CurrentHydrationPoints <= 0 || CurrentStimulationPoints <= 0);
 }

 /// <summary>
 /// Consumir algún alimento, bebida o estimulante.
 /// Modificará el estado de supervivencia del individuo.
 /// </summary>
 /// <param name="consumable">Objeto a consumir.</param>
 public void Consume(Consumable consumable)
 {
   if (consumable != null)
   {
     // Alimentación
     float estimatedFeedingPoints = (consumable.FeedingPoints + CurrentFeedingPoints);

     if (estimatedFeedingPoints > PointsToSatiateHunger)
     {
       estimatedFeedingPoints = PointsToSatiateHunger;
     }

     CurrentFeedingPoints = estimatedFeedingPoints;

    // Hidratación
    float estimatedHydrationPoints = (consumable.HydrationPoints + CurrentHydrationPoints);

    if (estimatedHydrationPoints > PointsToHydrate)
    {
      estimatedHydrationPoints = PointsToHydrate;
    }

    CurrentHydrationPoints = estimatedHydrationPoints;

    // Estimulante
    float estimatedStimulationPoints = (consumable.StimulationPoints + CurrentStimulationPoints);

    if (estimatedStimulationPoints > PointsToAlleviateSleep)
    {
      estimatedStimulationPoints = PointsToAlleviateSleep;
    }

    CurrentStimulationPoints = estimatedStimulationPoints;
  }
 }

 /// <summary>
 /// Consumir una bebida.
 /// Modificará el estado de supervivencia del individuo, normalmente afectando a su hidratación.
 /// </summary>
 /// <param name="drinkSource">Fuente o surtidor de bebida.</param>
 public void Drink(IDrinkSource drinkSource)
 {
   if (drinkSource != null)
   {
     GameObject drinkCandidate = GameObjectFactory.GetSample(drinkSource.DrinkType);

     if (drinkCandidate is Drink)
     {
       Drink drink = (Drink)drinkCandidate;
       Consume(drink);
     }
   }
 }

 /// <summary>
 /// Beber o dar un sorbo de un contenedor de bedida, por ejemplo, una cantimplora.
 /// </summary>
 /// <param name="drinkContainer">Contenedor de bedida del que se va a dar un sorbo.</param>
 public void Drink(DrinkContainer drinkContainer)
 {
   if (drinkContainer != null)
   {
     if (!drinkContainer.IsEmpty())
     {
       drinkContainer.Use();
       Drink(drinkContainer as IDrinkSource);
     }
   }
 }

 /// <summary>
 /// Dormir un tiempo limitado.
 /// Modificará el indicador de sueño del individuo.
 /// </summary>
 /// <param name="sleepTime">Tiempo de descanso o sueño (en segundos).</param>
 public void Sleep(float sleepTime)
 {
   float calculatedPointsBySecond = sleepTime * SurvivalStatusEngine.GetRecoveryPointsWhenSleep(this);
   float estimatedStimulationPoints = (CurrentStimulationPoints + calculatedPointsBySecond);

   if (estimatedStimulationPoints > PointsToAlleviateSleep)
   {
     estimatedStimulationPoints = PointsToAlleviateSleep;
   }

   CurrentStimulationPoints = estimatedStimulationPoints;
 }

...

}

Los tres indicadores de supervivencia (hambre, deshidratación y sueño) tienen un valor que va disminuyendo con el tiempo, cuando cualquiera de estos llega a 0, el concursante se desmaya y pierde la partida. Para impedir que eso ocurra, el concursante debe consumir ciertas cosas para aumentar dicho valor. Estas cosas son las que antes he llamado consumibles.

 

Consumibles

En el videojuego, un elemento consumible es cualquier cosa que se puede consumir, como por ejemplo carne, agua, setas, fruta… Todos los consumibles tienen lo que llamamos puntos de supervivencia.

Puntos de supervivencia

Los consumibles otorgan puntos de supervivencia al consumirlos, aumentando el tiempo que el personaje puede aguantar sin desmayarse. Cuando hablamos de puntos usamos la siguiente nomenclatura:

  • Para saciar el hambre: puntos de alimento
  • Para hidratarse: puntos de hidratación
  • Para aliviar el sueño: puntos de estimulación

Por ejemplo, consumir moras añade 7 puntos de alimento a los indicadores de supervivencia del personaje. Esto aumentará el tiempo que tiene el concursante antes de desmayarse por hambre.

survival-stats-example

Es posible que un consumible pueda dar puntos a más de un indicador de supervivencia. Por ejemplo una naranja, otorga puntos de alimentación e hidratación.

Caducidad

Los consumibles pueden caducar, lo que significa que transcurrido su tiempo de caducidad se echan a perder y no se pueden consumir.

Actualmente tenemos deshabilitada esta opción por temas de jugabilidad. Creemos que es una mecánica más pesada que divertida y por el momento no la utilizamos.

Esto abre la puerta a otras mecánicas interesantes, como por ejemplo: si un consumible caducado se encuentra en un contenedor con otros consumibles, podría contagiar al resto y acelerar el tiempo que tardan en echarse a perder… Pero como he dicho, es una mecánica que de momento no vamos a utilizar 😛

El código de los consumibles

En nuestro código disponemos de una clase que representa este concepto de objeto consumible, a la que llamamos Consumable.

Las propiedades más relevantes son:

  • Puntos de alimentación: cantidad de puntos de alimentación que se obtiene al consumirlo.
  • Puntos de hidratación: cantidad de puntos de hidratación que se obtienen al consumirlo.
  • Puntos de estimulante: cantidad de puntos de estimulante que se obtienen al consumirlo.
  • Tiempo de caducidad: tiempo total que tarda en caducar.

Los métodos más relevantes son:

  • Comprobar estado de caducidad: indica si el consumible ha caducado.
  • Comprobar si caduca: indica si el consumible caduca. Hay consumibles a los que no aplica la caducidad en el videojuego, como por ejemplo el agua.
/// <summary>
/// Cualquier elemento que se puede consumir, como por ejemplo bebida, comida, estimulantes, etc.
/// </summary>
public abstract class Consumable : GameObject
{
 #region Constructor
 protected Consumable() : base()
 {
   if (BaseExpirationTime > 0)
   {
     float minExpirationTime = BaseExpirationTime * ((100f - ExpirationTimeMargin) / 100f);
     float maxExpirationTime = BaseExpirationTime * ((100f + ExpirationTimeMargin) / 100f);
     float expirationTime = CustomConstants.Random.Next((int)minExpirationTime, (int)maxExpirationTime);
     ExpirationTime = expirationTime;
     RemainingExpirationTime = expirationTime;
   }
 }
 #endregion

 #region Properties
 /// <summary>
 /// Margen en el que el tiempo de caducidad definido puede variar (en porcentaje).
 /// </summary>
 protected const float ExpirationTimeMargin = 10;

 /// <summary>
 /// Puntos de alimento que se obtienen al consumir esto.
 /// Sirven para saciar el hambre y mitigar la desnutrición.
 /// </summary>
 public abstract ushort FeedingPoints { get; }

 /// <summary>
 /// Puntos de hidratación que se obtienen al consumir esto.
 /// Sirven para saciar la sed y mitigar la deshidratación.
 /// </summary>
 public abstract ushort HydrationPoints { get; }

 /// <summary>
 /// Puntos de estimulación contra el sueño que se obtienen al consumir esto.
 /// Sirven para combatir el sueño, como la cafeína.
 /// </summary>
 public abstract ushort StimulationPoints { get; }

 /// <summary>
 /// <para>Tiempo base que tarda esto en echarse a perder, con el tiempo
 /// expirado no se puede consumir (en segundos).
 /// </para>
 /// <para>El tiempo real de caducidad queda definido por un margen pequeño
 /// que puede acortar o alargar la duración total.</para>
 /// <para>El valor 0 indica que no caduca.</para>
 /// </summary>
 public abstract float BaseExpirationTime { get; }

 /// <summary>
 /// Tiempo restante, que le queda al consumible para caducar o echarse a perder (en segundos).
 /// </summary>
 public float RemainingExpirationTime { get; set; } = 0;

 /// <summary>
 /// <para>Tiempo real que tarda esto en echarse a perder.</para>
 /// <para>Este tiempo no es constante, depende de un pequeño margen definido
 /// internamente que puede acortar o alargar la caducidad.</para>
 /// <para>Ver 'ExpirationTimeMargin'</para>
 /// </summary>
 public float ExpirationTime { get; protected set; } = 0;
 #endregion

 /// <summary>
 /// Obtiene el porcentaje actual de caducidad del consumible.
 /// Representa porcentualmente cuanto tiempo le queda para caducarse.
 /// </summary>
 /// <returns>Porcentaje restante antes de caducar.</returns>
 public float GeCurrentExpirationPercent()
 {
   return (RemainingExpirationTime / ExpirationTime) * 100f;
 }

 /// <summary>
 /// Indica si el consumible ha caducado.
 /// </summary>
 /// <returns>'Verdadero' si ha caducado, 'Falso' en caso contrario.</returns>
 public bool IsExpired()
 {
   return (IsExpirable() && RemainingExpirationTime <= 0);
 }

 /// <summary>
 /// <para>Indica si el consumible tiene tiempo de caducidad.</para>
 /// <para>Nota: Un consumible con una tiempo base de caducidad de 0 se considera no-caduco.</para>
 /// </summary>
 /// <returns>'Verdadero' si puede caducar, 'Falso' en caso contrario.</returns>
 public bool IsExpirable()
 {
   return (BaseExpirationTime > 0);
 }
}

La naranja sería un ejemplo de implementación de un consumible:

/// <summary>
/// <para>Naranja natural. Se obtiene del naranjo.</para>
/// <para>Datos orientativos del fruto: Calorías: 47; Agua: 87%</para>
/// </summary>
public class Orange : Consumable, IInventariable, IDamageDealer
{

...

 #region Inventariable
 public InventariableType InventariableType { get; } = InventariableType.Orange;

 public bool Stackable { get; } = true;

 public float Weight { get; } = 0.2f;
 #endregion

 #region Consumable
 public override float BaseExpirationTime { get; } = 0f;

 public override ushort FeedingPoints { get; } = 28;

 public override ushort HydrationPoints { get; } = 10;

 public override ushort StimulationPoints { get; } = 0;
 #endregion

 #region Damage Dealer
 public DamageSource DamageSource { get; } = new DamageSource(
 new Dictionary<DamageType, int>()
   {
     { DamageType.Crushing, 1 }
   },
   null);

 public bool CanBeHurled { get; } = true;
 #endregion
}

 

Colores

A cada factor de supervivencia le hemos asociado un color que permita identificarlo.

hunger-icon                        dehydration-icon                     sleep-icon
Hambre        
Deshidratación        Sueño

Utilizamos una clase que centraliza estos valores en forma de constantes, de manera que podamos cambiar estos valores de forma fácil si lo necesitamos.

Para elegir los colores hemos utilizado Adobe Kuler, una herramienta muy útil e interesante para buscar sintonía entre colores.

/// <summary>
/// Color base relacionado con el hambre.
/// </summary>
public static Color HungerColor new Color(139, 127, 89);
 
/// <summary>
/// Color base relacionado con la deshidratación.
/// </summary>
public static Color HydrationColor new Color(140, 190, 178);
 
/// <summary>
/// Color base relacionado con el sueño.
/// </summary>
public static Color SleepColor new Color(198, 168, 201);

Eso es todo (a alto nivel) en cuanto a diseño y programación sobre el sistema de supervivencia y los consumibles.

Si tienes dudas o curiosidad por saber el detalle de alguna cosa ¡espero tus comentarios!

 

 

El Motor del Videojuego – III

Contexto

Si acabas de aterrizar en el Blog, te recomiendo que antes de continuar que te dejes caer por aquí:

Así mismo recomiendo leer también las 2 entradas que hablan sobre YokaiEngine – el motor que estamos creando para desarrollar Sticks & Stones:

Auto-cast de objetos

El artículo de hoy se va a centrar en lo que llamo auto-cast de objetos de Flash a C#.

Para que me sea más sencillo de explicar en qué consiste el auto-cast, voy a recurrir de nuevo a nuestro personaje de ejemplo Sigfrid – El Paladín.

Imaginemos que hemos creado una animación en Flash y la llamamos paladin:

paladin_instance

Tal y como hemos hablado en anteriores artículos, el motor es capaz de replicar todos los objetos Flash y sus animaciones de forma recursiva, y reconstruirlos tal cual en C#.

Sólo basta con definir en la Screen de juego una variable del tipo CustomSprite (propia del motor) con el mismo nombre que hemos usado en el Flash:

public class Screen PlayableScreen
{
      public CustomSprite paladin; 
}

Y obtenemos lo siguiente:

paladin

Imaginemos por un momento que queremos acceder a cualquier parte de la animación, por ejemplo a la espada. Para ello deberemos realizar los siguientes pasos:

  1. Dar un nombre identificativo al objeto Flash
  2. Crear jerarquía de clases C# para definir padres-hijos

Vamos a asignar un nombre al objeto de la espada, en este caso le llamaremos weapon.

weapon_instance

Clases tipadas

Sigfrid, nuestro paladin, va a dejar de ser una clase del tipo CustomSprite (que es genérica) para pasar a tener su propia clase – PaladinCharacter. Lo que nos va a permitir poder definir dentro contenido propio:

public class Screen PlayableScreen
{
      public PaladinCharacter paladin; 
}
public class PaladinCharacter CustomSprite
{
      public CustomSprite weapon;
}

De esta manera ya podríamos tener acceso al arma a través del objeto paladin, y por ejemplo hacerla desaparecer:

public class Screen PlayableScreen
{
    public void RemoveWeapon()
    {
        paladin.weapon.Visible = false;
    }

no_weapon

Cuando el motor recrea los objetos del Flash, lo que hace es crear instancias automáticamente al tipo indicado en la definición de la variable. En nuestro caso:

  • paladin lo castea al tipo PaladinCharacter
  • sword lo castea al tipo CustomSprite

Recursividad

Esta técnica de auto-cast es recursiva y puede comprender todos los niveles que se necesiten. Profundizando más en el mismo ejemplo, podríamos tener la siguiente estructura:

public class PaladinCharacter CustomSprite
{
      public Weapon weapon;
      public Helmet helmet;
      public Shield shield;
}
public class Weapon CustomSprite
{
      public Blade blade;
      public Grid grip;    
      public CrossGuard crossGuard;
}
public class GripCustomSprite
{
      public Handle handle;
      public Jewel jewel;    
}

Listas

El auto-cast también funciona con listas. En el caso que tengamos más de un objeto del mismo tipo, por ejemplo los brazos (en este caso 2), en el Flash los identificamos con el mismo nombre acabado en número  (arm0 y arm1):

instance_arm

Ahora solo basta con tiparlo como una lista y ya:

public class PaladinCharacter CustomSprite
{
      public Weapon weapon;
      public Helmet helmet;
      public Shield shield;
      public List<Arm> arm;
}

Generar cualquier estructura jerarquizada de objetos es muy útil para poder realizar animaciones complejas y aún así tener el control total de acceso a cualquier objeto (para poder realizar la transformación).

 

¡Si te ha gustado compártelo! :]

¡Aciertos, aciertos, aciertos!

Siguiendo con los posts de reflexiones, quiero aprovechar la última entrada (larga) del 2016 para señalar las cosas que hicimos bien, (dándonos unas palmaditas en la espalda a nosotros mismos, claro está) y que quede también como registro. Si debemos aprender a apreciar los fallos, también debemos darle valor las veces que la hemos acertado.

Aquí vamos…

Acierto Número 1: Hitos 

That’s how you devour a whale, Doug, one bite at a time… – Frank Underwood

Desde el día, no sé, -40, cuando estábamos en fase incipiente y no teníamos idea de qué queríamos hacer (y ni siquiera sabíamos si podríamos hacerlo), hemos tenido esta mentalidad Orientada a Objetivos. Aún cuando Sticks & Stones ni siquiera se llamaba Sticks & Stones, sino JalapeñoTV, hablábamos de trabajar en capas, de hacer iteraciones. Primero  una cosa, luego la siguiente, luego la siguiente. Poco a poco. Un paso a la vez…

Mientras íbamos avanzando podíamos cómo nuestro concepto original, ya de por sí abierto, iba mutando por cada pieza de feedback (externo e interno) que recibíamos mientras calibrábamos la definición que teníamos de lo que era entretenido, vistoso, excesivo…  Agile, y la orientación a objetivos nos permitía planificar esos cambios a la vez que nos obligaba a mantener los pies en la tierra cuando la creatividad se disparaba hacia la luna. Ha habido entregas, tiempos definidos y requerimientos; ha habido tareas, subtareas e iteraciones. Ha habido demos y ha habido feedback.

Estoy convencido de que si no hubiésemos adoptado esta manera estructurada de trabajar (que tal vez, en realidad es que la tenemos ya inyectada en la sangre)  no habríamos llegado a lo que tenemos ahora.

agiledev

 

Acierto Número 2: Estructura de Clases adecuada

Ya Adri habló de la abstracción que conllevó diseñar la mochila, y explicó los primeros rudimentos de cómo funciona el crafting. Aunque prefiero que él sea quien profundice en los detalles técnicos y los problemas y beneficios que hemos encontrado en el camino, algo que nos ha resultado evidente ha sido el beneficio de diseñar toda las estructuras de clases como representaciones de la realidad, donde la abstracción no llega más lejos que cómo uno, sin usar demasiado la imaginación, haría las cosas…

Uno de los ejemplos más inmediatos viene del diseño del crafting: en Sticks & Stones, hay interfaces

ICraftingIngredient
ICraftingTool

y una clase para encargarse en sí del crafting,

CraftingEngine

que incluye, entre otros, métodos para controlar que una combinación es válida, o para ejecutar las combinaciones de ingredientes con herramientas, o para verificar si una combinación está disponible (dependiendo de la pantalla a la que ha llegado el jugador). Aunque existe el debate de que demasiada abstracción puede ser nociva, un beneficio inmediato que percibimos ha sido el de poder trabajar, de cierta forma, instintivamente;  no tener que pensar demasiado para decidir dónde ubicar una clase (o una variable o un método), y de qué debe heredar;  poder, sin conocer todo lo que hay en el código, utilizar IntelliSense no sólo para apoyarnos con la sintaxis, sino también con los conceptos propios del juego.  No sólo es por tiempo que se ahorra, sino también por una sensación de fluidez y avance muy agradable y muy provechosa en el momento.

Acierto Número 3: ¡Comunicación, comunicación, comunicación!

En Sticks & Stones tenemos tres canales electrónicos para comunicarnos:  WhatsApp, Hangouts, correo. Tenemos reuniones de seguimiento. Tenemos tareas de discusión. Estamos la mayoría del tiempo sentados uno al lado del otro, al alcance de un “hey tú!” el ocasional “…OTRA VEZ, PERO QUÉ DEMONIOS ESTÁS HACIENDO!?”. Debemos lidiar entre nosotros, cada uno con su propia mochila: esos son 3 conjuntos de anhelos, 3 conjuntos de intereses, 3 visiones del juego final, 3 personalidades, y más importante, 3 universos de inquietudes por entrar en un mundo que sólo conocíamos superficialmente.

Afortunadamente, porque en esencia estamos yendo a ciegas, nos hemos visto obligados a depurar la comunicación mientras ocurre; y eso a ambos lados del mensaje. No sólo los que hablamos hemos tenido que aprender a decir las cosas adecuadamente, cuidando no sólo el lenguaje sino también de usar la inflexión correcta, la que apoye el significado, la que lo haga más efectivo; también mientras escuchamos hemos tenido que aprender a hacerlo desapegadamente, a reconocer las motivaciones detrás, las inquietudes, la personalidad, con empatía y hasta agradecimiento. Todavía no es perfecto (y quién dice si siquiera tiene que serlo), pero gracias a S&S hemos logrado establecernos como Compañeros en Armas, con diferencias siempre pero capaces de tranquilamente confiar entre nosotros, a la vez que hemos aprendido a comunicarnos mejor.

Acierto Número 4: ¿Se suponía que no debía ser divertido?

flow

Mihály Csíkszentmihályi (pronunciado, más o menos, como chik-sent-mi-já-li) definió en su libro  Fluir: La psicología de las experiencias óptimas, el estado de Flujo (que es lo que informática llaman estar en la Zona), un estado mental donde la persona está totalmente sumergida en la actividad que realiza, con una sensación de enfoque activo y de disfrute: es lo que pasa cuando practicas el violín y se hacen las 10 de la noche y tu vecino está ya tumbando las paredes a martillazos y tú ni te habías dado cuenta de que no has cenado aún y has gastado, qué, ¡6 horas!.

Es un pelín más complicado que esto, pero, para conseguir ese estado de flujo, Csíkszentmihályi sugiere que deben cumplirse tres condiciones:

  1. Uno debe saber qué hacer y cómo medir el progreso: una tarea debe estar estructurada con objetivos y resultados, tener formas de saber cuán cerca se está de completarla.
  2. La tarea debe tener feedback inmediato, así la persona puede ajustar sus acciones para mantener el estado de flujo.
  3. Debe haber equilibrio entre la dificultad de la tarea y las habilidades propias de la persona y cómo las percibe, y debe poseer la confianza para completarlas.

Este estado es el que se dice está detrás de ese golpe perfecto cuando se juega golf, el gol espectacular en el fútbol; el desempeño óptimo para un atleta, pero también el genio creativo en la ciencia (como Einstein que le describe a su hijo el secreto de la felicidad, y habla de ese mismo estado donde utiliza sus habilidades al máximo, dos días después de haber postulado la Relatividad General) y el arte. El estado de Flujo aparece incluso, y esa puede ser otra de las razones de por qué lo hacemos, mientras jugamos videojuegos.

Aunque Csíkszentmihályi publica su libro en 1990, el Flujo es un estado que ha sido reconocido a lo largo de la historia y en diferentes culturas, y que ha ganado valor últimamente, no sólo como una herramienta de valor para la satisfacción del individuo sino también como un estado aprovechable por las organizaciones para el aumento de la productividad, la innovación, la creatividad y el desarrollo de sus empleados.

Nosotros ni lo sabíamos, pero si había una señal que nos indicara que estábamos ahí, Fluyendo, en la Zona, era que nuestros días siempre transcurrieron en un abrir y cerrar de ojos (excepto esos de cierre de hito, esos eran eternos…). Algunos estaban llenos de ruido y discusiones y fricción, otros de silencio sepulcral apenas interrumpido por una toz o un crujir de nudillos. Siempre, sin embargo, quedaba la sensación de oh, cuán rápido pasa el tiempo, mañana más, mañana más…  Mientras íbamos avanzando más nos dábamos cuenta de que hacer un videojuego era un proceso de duro aprendizaje. Sin embargo, de alguna manera, siempre estuvimos en sintonía para hacer que cada día fuese una experiencia gratificante. Si había que sufrirlo, nunca se sintió así. Cada día podíamos decir, sí, ¡bien!, estuvimos en la Zona