Post news RSS How We Did It - Survival and Consumables

This time we delve into how we've designed the survival and the consumables system.

Posted by on

Since it's a somewhat subject, we'll divide the whole post in three parts:

  1. Survival—where we speak about how we developed the mechanics of using the consumables.
  2. Consumables—how the consumables were implemented.
  3. ¡Color!—how colors are for each survival indicator.

Survival:

This is the first mechanic that means entertainment for the player. The first Conflict that puts a distance between the player and the wining condition. The player will suffer from hunger, lack of sleep and dehydration, all three in the form of backward-counters that he'll have to replenish with whatever he can find in the environment: hunting, drinking water, sleeping, gathering fruits, mushrooms and whatnot:

Hunger:

Dehydration:

Sleep:

Each being shown as bars, like:

survival status


Survival Properties

Survival is linked only to organic characters (such as our contestant Ferguson) having only two factors for each:

  • Time it takes to pass out. (Remember, in the context of our TV show, there's no dying, only passing out and losing).
  • How many points does it need to be completely restored.

The following code shows our OrganicCharacter class, setting up the survival properties:

/// <summary>
/// Any Organic character, be it a contestant or an animal.
/// </summary>
public abstract class OrganicCharacter : CharacterBase
{

...

  /// <summary>
  /// Time to faint from hunger (seconds).
  /// </summary>
  public abstract float TimeToFaintFromHunger { get; protected set; }

  /// <summary>
  /// Time to faint from dehydration (seconds).
  /// </summary>
  public abstract float TimeToFaintFromDehydration { get; protected set; }

  /// <summary>
  /// Time to faint from lack of sleep (seconds)
  /// </summary>
  public abstract float TimeToFaintFromSleep { get; protected set; }

  /// <summary>
  /// Points required to satiate hunger
  /// </summary>
  public abstract ushort PointsToSatiateHunger { get; protected set; }

  /// <summary>
  /// Points requiered to satiate thirst
  /// </summary>
  public abstract ushort PointsToHydrate { get; protected set; }

  /// <summary>
  /// Points requiered to satiate lack of sleep
  /// </summary>
  public abstract ushort PointsToAlleviateSleep { get; protected set; }

  /// <summary>
  /// Time required for full sleep recovery (seconds)
  /// </summary>
  public abstract float SleepTimeForFullRecovery { get; protected set; }

...

}

How Characters Consume Stuff

Now, the following code shows some of the methods our organic characters use to interact with consumables, or to check the current survival status.


...

 /// <summary>
 /// Checks if the character has passed out
 /// </summary>
 /// <returns>'True' if he has, 'False' if he hasn't</returns>
 public bool IsFaint()
 {
   return (CurrentFeedingPoints <= 0 || CurrentHydrationPoints <= 0 || CurrentStimulationPoints <= 0);
 }

 /// <summary>
 /// Eat or drink food, water or stimulants
 /// Will change the survival status of the character
 /// </summary>
 /// <param name="consumable">Object to consume</param>
 public void Consume(Consumable consumable)
 {
   if (consumable != null)
   {
     // Food
     float estimatedFeedingPoints = (consumable.FeedingPoints + CurrentFeedingPoints);

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

     CurrentFeedingPoints = estimatedFeedingPoints;

    // Water
    float estimatedHydrationPoints = (consumable.HydrationPoints + CurrentHydrationPoints);

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

    CurrentHydrationPoints = estimatedHydrationPoints;

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

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

    CurrentStimulationPoints = estimatedStimulationPoints;
  }
 }

 /// <summary>
 /// Drink a drink
 /// Will change the survival status of the character, especially affecting the hydration 
 /// </summary>
 /// <param name="drinkSource">Drink source</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>
 /// Drink or sip from a drink cotainer, like a canteen.
 /// </summary>
 /// <param name="drinkContainer">Drink container</param>
 public void Drink(DrinkContainer drinkContainer)
 {
   if (drinkContainer != null)
   {
     if (!drinkContainer.IsEmpty())
     {
       drinkContainer.Use();
       Drink(drinkContainer as IDrinkSource);
     }
   }
 }

 /// <summary>
 /// Sleep for a limited time
 /// Will modify the sleep indicator.
 /// </summary>
 /// <param name="sleepTime">Sleep time (in seconds).</param>
 public void Sleep(float sleepTime)
 {
   float calculatedPointsBySecond = sleepTime * SurvivalStatusEngine.GetRecoveryPointsWhenSleep(this);
   float estimatedStimulationPoints = (CurrentStimulationPoints + calculatedPointsBySecond);

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

   CurrentStimulationPoints = estimatedStimulationPoints;
 }

...

}

This means that the three Survival indicators have, for each character, an initial value that is counting down to zero, which signals that the contestant has passed out and lost.

Consumables:

In Sticks & Stones, everything that can be eaten or drank is called a Consumable (meat, water, fruits). They all have what we called Survival Points.

Survival Points

Each consumable gives Survival Points to the character. If each indicator is in the end nothing but a countdown clock (each one with its own pace), Survival Points, allows for adding "seconds" to each clock. Survival Points have then, three kinds:

  1. For satiating hunger: Food Points.
  2. For hydration: Hydration Points.
  3. For resting: Stimulant Points.

For example: Blackberries provide 7 Food Points (how we got to that number, well, that's another issue...). In game, that's shown like:

Survival Points example


It becomes obvious that it's completely possible for a good to give not only food points, but also Hydration and Stimulant Points at the same time, like an orange, for example.

Expiration

Expiration means that goods COULD have their own internal countdown, after which, once they're zero, they couldn't be consumed. However, for entertainment purposes, we currently have this option disabled. We've figured that it can become tiresome rather than fun, so we're not using it right now. However, if we decided to activate it, it could certainly derive in other interesting mechanics, like, if the expired good is in your backpack, then it could spread to others nearby, and then those spread to others, and then others, and then everything rotting inside your backpack in a few minutes, only because you forgot about that last piece of apple... Yeah... But for the time being, there's no expiration so everything's fine.

Code:

For Sticks & Stones, we have our Consumable class: it has mainly four properties and two methods. The properties handle the aforementioned Food, Hydration and Stimulant points, with one extra for expiration time; the methods regard if the consumable is expired or not. Water, for example, even though it's a consumable, doesn't have any expiration time.

/// <summary>
/// Anything that can be consumed: drink, food, stimulants, 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>
 /// Percentage range that the expiration time can vary. (percentage).
 /// </summary>
 protected const float ExpirationTimeMargin = 10;

 /// <summary>
 /// Food points given when consumed
 /// Good for hunger and malnourishment
 /// </summary>
 public abstract ushort FeedingPoints { get; }

 /// <summary>
 /// Hydration points given when consumed
 /// Good for thirst
 /// </summary>
 public abstract ushort HydrationPoints { get; }

 /// <summary>
 /// Stimulant points
 /// Good to play on without sleep, like caffeine
 /// </summary>
 public abstract ushort StimulationPoints { get; }

 /// <summary>
 /// <para>Base time that it takes to expire,
 /// Once it is expired, it can't be consumed (seconds).
 /// </para>
 /// <para>The real expiration time is difined for a small range that can decrease or increase      /// the total duration.</para>
 /// <para>0 means it won't expire</para>
 /// </summary>
 public abstract float BaseExpirationTime { get; }

 /// <summary>
 /// Time left before the item expires (in seconds).
 /// </summary>
 public float RemainingExpirationTime { get; set; } = 0;

 /// <summary>
 /// <para>Real time it takes to expire</para>
 /// <para>This time is not constant, and it depends on a small range internally defined that ///can decrease or increase the expiration.</para>
 /// <para>Ver 'ExpirationTimeMargin'</para>
 /// </summary>
 public float ExpirationTime { get; protected set; } = 0;
 #endregion

 /// <summary>
 /// Current expiration time percentage
 /// Percentage of how much time the good has until expiration
 /// </summary>
 /// <returns>Percentage before expiring</returns>
 public float GeCurrentExpirationPercent()
 {
   return (RemainingExpirationTime / ExpirationTime) * 100f;
 }

 /// <summary>
 /// Checks if the good has expired.
 /// </summary>
 /// <returns>'True' if it has, 'False' if it hasn't</returns>
 public bool IsExpired()
 {
   return (IsExpirable() && RemainingExpirationTime <= 0);
 }

 /// <summary>
 /// <para>Checks if the item has expiration time</para>
 /// <para>Note: A consumable with expiration base time  0 is considered non-expirable.</para>
 /// </summary>
 /// <returns>'True' if it can expire, 'False' if it can't.</returns>
 public bool IsExpirable()
 {
   return (BaseExpirationTime > 0);
 }
}

For example, for an Orange, we could implement it as in:

/// <summary>
/// <para>Natural orange.</para>
/// <para>Nutritional value (just for reference): Calories: 47; Water: 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
}

(yeah: the orange can be hurled ;) )

Color:

Each survival item has been assigned it's own color (with a little help from Adobe Color CC — formerly Adobe Kuler, which sounded much cooler if you ask me...Pun intended HAHA!) They are not only used in game for the survival status indicators, but also to show the Survival Points for each item in the player's inventory. It's nothing too fancy. Just some constants separated in a different Constants class, that keeps it all organized and neat. The Flash file from which we inject the icons has them in White, which allows us to change the color at any time without problems.

Hunger: Dehydration: Sleep:


/// <summary>
/// Hunger color
/// </summary>
public static Color HungerColor = new Color(139, 127, 89);
 
/// <summary>
/// Hydration color
/// </summary>
public static Color HydrationColor = new Color(140, 190, 178);
 
/// <summary>
/// Sleep color
/// </summary>
public static Color SleepColor = new Color(198, 168, 201);

So, that's basically it regarding the design of our survival system and the consumables. What do you think? Do you think the expiration should be available, even if it means slowing down the game's pace?

Post a comment
Sign in or join with:

Only registered members can share their thoughts. So come on! Join the community today (totally free - or sign in with your social account on the right) and join in the conversation.