hoguera

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!

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *