Tuesday, November 29, 2016

Code Test, Part 2

hey all,

Here is a second test to see if I can get more code to show up nicely. I'm super new to HTML coding, and getting C# code to show up nicely on the web is not the easiest thing in the world. So, here goes nothing. Check below all the code for some commentary.

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

//set up a callback registration with emotions, moods, and personality

//amount of sleep
//amount of food
//sickness
//injuries
//exercise
public class PhysicalState : ScriptableObject
{

 PhysicalNeed[] needs = new PhysicalNeed[6];
 BodyState bodyState = BodyState.Resting;

 List < injury > injuries;

 List < disease > diseases;

 #region Setters and Getters


 public BodyState Body
 {
  get { return bodyState; }
  protected set { }
 }

 public float Hunger
 {
  get { return needs[(int)PhysicalNeedType.Food].severity; }
  protected set { }
 }

 public float Tiredness
 {
  get { return needs[(int)PhysicalNeedType.Rest].severity; }
  protected set { }
 }

 public float Thirst
 {
  get { return needs[(int)PhysicalNeedType.Hydration].severity; }
  protected set { }
 }

 public float Lust
 {
  get { return needs[(int)PhysicalNeedType.Sex].severity; }
  protected set { }
 }

 public float Comfort
 {
  get { return needs[(int)PhysicalNeedType.Comfort].severity; }
  protected set { }
 }

 public float Cleanliness
 {
  get { return needs[(int)PhysicalNeedType.Cleanliness].severity; }
  protected set { }
 }

 #endregion


 //use PhysicalEventArgs here
 public delegate void PhyscialStateChangeHandler(object source, PhysicalEventArgs args);//set up delegate
 public event PhyscialStateChangeHandler PhysicalStateChanged;//define event based on event
                 //raise the event

 float timer = 5f;

 // Use this for initialization
 void Start ()
 {
  injuries = new List < injury > ();
  diseases = new List < disease > ();
  SetupPhysicalState();
 }
 
 // Update is called once per frame
 void Update ()
 {
  timer -= Time.deltaTime;
  if (timer < 0)
  {
   timer = 5f;
   bodyState = (BodyState)UnityEngine.Random.Range(0, 5);
  }
  switch(bodyState)
  {
   case BodyState.Bathing:
    ChangePhysicalState(PhysicalNeedType.Cleanliness, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Comfort, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Rest, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Food, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Hydration, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Sex, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    break;
   case BodyState.Drinking:
    ChangePhysicalState(PhysicalNeedType.Cleanliness, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Comfort, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Rest, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Food, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Hydration, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Sex, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    break;
   case BodyState.Eating:
    ChangePhysicalState(PhysicalNeedType.Cleanliness, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Comfort, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Rest, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Food, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Hydration, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Sex, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    break;
   case BodyState.Resting:
    ChangePhysicalState(PhysicalNeedType.Cleanliness, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Comfort, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Rest, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Food, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Hydration, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Sex, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    break;
   case BodyState.Sexing:
    ChangePhysicalState(PhysicalNeedType.Cleanliness, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Comfort, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Rest, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Food, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Hydration, +Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    ChangePhysicalState(PhysicalNeedType.Sex, -Time.deltaTime * needs[(int)PhysicalNeedType.Cleanliness].changeRate);
    break;
  }
 }

 void SetupPhysicalState()
 {
  //warmth = 100f;
  //lust = 0f;
  //thirst = 0f;
  //hunger = 0f;
  //tiredness = 0f;
  for (int i = 0; i < needs.Length; i++)
  {
   needs[i] = new PhysicalNeed(0f, 1f, 1000f, (PhysicalNeedType)i);
  }
  Debug.Log("Physical needs setup");
 }

 public void ChangePhysicalState(PhysicalNeedType ty, float amount)
 {
  switch (ty)
  {
   case PhysicalNeedType.Food:
    needs[(int)PhysicalNeedType.Food].severity = Mathf.Clamp(needs[(int)PhysicalNeedType.Food].severity + amount, 0f, 100f);
    //needs[(int)PhysicalNeedType.Food].severity += amount;
    break;
   case PhysicalNeedType.Hydration:
    needs[(int)PhysicalNeedType.Hydration].severity = Mathf.Clamp(needs[(int)PhysicalNeedType.Hydration].severity + amount, 0f, 100f);
    //needs[(int)PhysicalNeedType.Hydration].severity += amount;
    break;
   case PhysicalNeedType.Rest:
    needs[(int)PhysicalNeedType.Rest].severity = Mathf.Clamp(needs[(int)PhysicalNeedType.Rest].severity + amount, 0f, 100f);
    //needs[(int)PhysicalNeedType.Rest].severity += amount;
    break;
   case PhysicalNeedType.Sex:
    needs[(int)PhysicalNeedType.Sex].severity = Mathf.Clamp(needs[(int)PhysicalNeedType.Sex].severity + amount, 0f, 100f);
    //needs[(int)PhysicalNeedType.Sex].severity += amount;
    break;
   case PhysicalNeedType.Comfort:
    needs[(int)PhysicalNeedType.Comfort].severity = Mathf.Clamp(needs[(int)PhysicalNeedType.Comfort].severity + amount, 0f, 100f);
    //needs[(int)PhysicalNeedType.Comfort].severity += amount;
    break;
   case PhysicalNeedType.Cleanliness:
    needs[(int)PhysicalNeedType.Cleanliness].severity = Mathf.Clamp(needs[(int)PhysicalNeedType.Cleanliness].severity + amount, 0f, 100f);
    //needs[(int)PhysicalNeedType.Cleanliness].severity += amount;
    break;
   default:
    break;
  }
  OnPhysicalStateChanged();
 }

 //for use with the delegate
 //any thing that changes the physical state should call this function
 protected void OnPhysicalStateChanged()
 {
  if (PhysicalStateChanged  != null)
  {
   //treating it like a function
   //this is used to notify subscribers to this event
   PhysicalEventArgs p = new PhysicalEventArgs();
   p.phys = this;
   PhysicalStateChanged(this, p);
   //Debug.Log("Physical State changed");
  }
 }

 
}

//set up new class for EventArgs
public class PhysicalEventArgs : EventArgs
{
 public PhysicalState phys { get; set; }
}

public class PhysicalNeed:Need
{
 //[Range(0, 100)]
 //public float severity;
 //research the actual rate at which each physical need must be met
 //water: about 250ml every 3 hours, for a total of 2,000ml per day. more could cause problems. but depends on exercise level and temperature.
 //food: 2,000~3,000kC every day, spread between 2~5 meals. also depends on exercise
 //rest: between 6~9 hours per day. 
 //sex:??? once/twice per week?
 //cleanliness: about once per day, depending on exercise
 //comfort: ???increases if:
 //                          standing
 //                          working
 //                          sitting on ground
 //                          
 //
 //
 //public float changeRate;
 public PhysicalNeedType type;

 public PhysicalNeed(float sev, float ch, float mult, PhysicalNeedType ty):base(sev,ch,mult)
 {
  //base.Need(sev, ch, mult);
  type = ty;
 }
}

public enum PhysicalNeedType
{
 Food,
 Hydration,
 Rest,
 Sex,
 Comfort,
 Cleanliness
}

public enum BodyState
{
 Resting,
 Eating,
 Drinking,
 Sexing,
 Bathing
}

public struct Injury
{
 [Range(0, 100)]
 float severity;
 InjuryType type;
 Location loc;
}

public enum InjuryType
{
 Burn,
 Cut,
 Puncture,
 Bruise,
 Fracture
}

public struct Disease
{
 [Range(0, 100)]
 float severity;
 DiseaseVector cause;
 bool isInfectious;
 Location loc;
}

public enum Location
{
 Head,
 Neck,
 Chest,
 Abdomen,
 RightArm,
 LeftArm,
 Back,
 RightLeg,
 LeftLeg
}

public enum DiseaseVector
{
 Bacteria,
 Virus,
 Fungus
}

Hopefully what you saw was a lot of decently formatted C# code. One thing to note is that right now the rate at which each physical need increases is linear. In fact, after one second of time in the appropriate body state, the opposite physical needs will have increased by one.


This is a really boring relationship, and what I'd really like is something more like this:




With this, you set the maximum need level, max, and play around with the steepness and x mid-point values. For example, for water, we need about 250ml every three hours. After drinking, our need for water should be basically zero, and it will slowly increase. Eventually, after long enough without water, our thirst will get worse and worse at an increasing rate, until the derivative of the curve switches sign and we are so close to dying of thirst that a little more time barely changes our thirst level. The max level should be set to 100, but what about the x mid-point value? Assuming that all those medical studies about dehydration are true, lets say the x mid-point value should be 12 hours. What about the steepness level? Again, playing around with some different values, 0.5 produces an OK looking curve. Most probably, I am going to create a separate class to hold an array of floats, then use some form of interpolation to get any values in between them.

The next part of this whole deal is how should the level of need affect one's mood? 

This particular graph here from GameAI.com is the closest I've seen to anyone trying to figure this out in a concrete way. However, please stop and try to figure out what the graph means. What is on the X-axis for instance? Is it displaying the amount of food you have or is it displaying how hunger affects happiness? Either way, it makes no sense. Assuming that the X-axis shows how much food you have, if hunger is at +100, it might be reasonable to assume that it has a small positive affect on mood, but if hunger is at -100, why should it have a large positive affect on mood?  And assuming that mood is on the X-axis, if you have a small amount of food, why would that produce only a small positive affect on mood, while having lots of food would produce a large negative affect on mood? 

This is my own attempt at it and I think it makes a lot more sense. Need is on the X-axis, and mood is on the Y-axis. If your need is below 50, you get a positive bonus to mood, which increases the lower your need. If your need is above 50, however, you get increasing penalties to your mood. 








Why bother with all this? In this talk about a procedural game system, Ken Levine talks about the possibility of having NPCs in the game world that have desires and ambitions that the player can help or frustrate through actions.

 Since I am making the game by myself at this point, I cannot spend hours and hours writing scripted story lines for hundreds of characters. I'd much rather do something like what Ken Levine says, or take yet another page from Mount & Blade: Warband and have NPCs that react to what the player does based on their "wants and desires." The end goal is to have a system where the NPCs can generate quests for the player dynamically based on what they want and need at the moment: like a peasant asking the player to kill some monsters infesting a field, preventing the peasant from farming and getting food to feed the family. The need is for food, but the desire for safety prevents the peasant from going and getting it him/herself.

No comments:

Post a Comment