Really busy week at work and outside of it as well. Outside, I've been doing research on Voronoi diagrams, how to simulate diffusion, and I've also done a complete revamping of my code for needs. Instead of having Physical Needs and Emotional Needs treated separately, I've decided to make a BaseNeed class, and then have the NPCs have a Needs class which sets them up, initializes them, and takes care of keeping them up to date. The main reason for this is that when I started to write my code for updating mood based on Physical and Emotional Needs, I needed to write some extra functions and variables for dealing with their contributions separately, and then combining them.
More to come.
using UnityEngine;
using System.Collections;
using Gamelogic.Extensions.Algorithms;
public class BaseNeed:IBaseNeed
{
[Range(0, 100)]
float severity;
float maxTime;
MeetNeedEvent typicalMNE;
//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;
NeedType needType;
ResponseCurveFloat curve;
float timeSinceMet;
bool isBeingMet;
//used for determining the effect during decision making
float multiplier;
//public PhysicalNeedType type;
#region Getters and Setters
public NeedType Type
{
get { return needType; }
protected set {; }
}
public float Severity
{
get {return severity; }
protected set {; }
}
public bool IsBeingMet
{
get { return isBeingMet; }
protected set {; }
}
public float Multiplier
{
get { return multiplier; }
protected set {; }
}
//this is probably not needed, as the response curve is only ever referenced internally
public ResponseCurveFloat Curve
{
get { return curve; }
protected set {; }
}
#endregion
public BaseNeed(NeedType ty, float sev, ResponseCurveFloat rc, float maxT, MeetNeedEvent mne ,float mult)
{
severity = sev;
curve = rc;
multiplier = mult;
isBeingMet = false;
timeSinceMet = 0f;
typicalMNE = mne;
maxTime = maxT;
needType = ty;
}
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
}
public void IncreaseNeed(float timePassed)
{
if (!isBeingMet)
{
timeSinceMet += timePassed;
severity = curve[timeSinceMet];
//Debug.Log("Need increased to " + severity.ToString());
}
}
public IEnumerator MeetNeed(MeetNeedEvent mne=new MeetNeedEvent())
{
//this is because the default parameter must be a constant at compile time
//so if a value is not passed in, use the typical MeetNeedEvent for this need
if (mne.eventLength <= 0)
{
mne = typicalMNE;
}
isBeingMet = true;
float t = 0f;
//Debug.Log("meeting need");
//Debug.Log("mne length is" +mne.eventLength);
//Debug.Log("typical mne length is" + typicalMNE.eventLength);
while (t < mne.eventLength)
{
t += Time.deltaTime;
//Debug.Log("value of t in MeetNeed is " + t);
//Debug.Log("change in severity is " + (t / mne.eventLength) * mne.potency);
//this value needs to be clamped!!!
severity = Mathf.Clamp(severity - ((t / mne.eventLength) * mne.potency), 0f, 100f);
//severity -= (t / mne.eventLength) * mne.potency;
yield return null;
}
//Debug.Log("This should only be called when finished");
isBeingMet = false;
//Debug.Log("isBeingMet is now " + isBeingMet.ToString());
timeSinceMet = Mathf.Clamp(maxTime-(maxTime*mne.potency/100f),0f,maxTime);
//Debug.Log("Time since met is " + timeSinceMet);
}
}
public struct MeetNeedEvent
{
public float eventLength;
[Range(0,100)]
public float potency;
public MeetNeedEvent(float el=-1f, float p =-100f)
{
eventLength = el;
potency = p;
}
}
public enum NeedType
{
Food,
Hydration,
Rest,
Sex,
Comfort,
Cleanliness,
Shelter,
Intimacy,
Fun,
Safety,
Respect,
MAX
}
public interface IBaseNeed
{
//(get) accessor functions for:
//severity
//type
NeedType Type
{ get; }
//Multiplier
float Multiplier
{ get; }
}
using UnityEngine;
using System;
using Gamelogic.Extensions.Algorithms;
using System.Collections.Generic;
public class Needs : MonoBehaviour
{
const int numberOfNeeds = (int)NeedType.MAX;
////this holds the response curve for how each need will affect mood
////does this really belong here or should it go in mood?
List < ResponseCurveFloat > moodResponseCurve = new List < ResponseCurveFloat > ();
BaseNeed[] needs = new BaseNeed[numberOfNeeds];
NPCState npcState;
public delegate void NeedStateChangeHandler(object source, NeedEventArgs args);//set up delegate
public event NeedStateChangeHandler NeedStateChanged;//define event based on event
#region Getters and Setters
public int NumberOfNeeds
{
get { return numberOfNeeds; }
protected set { }
}
public ResponseCurveFloat MoodResponse(int i)
{
return moodResponseCurve[i];
}
public float Hunger
{
get { return needs[(int)NeedType.Food].Severity; }
protected set { }
}
public float Tiredness
{
get { return needs[(int)NeedType.Rest].Severity; }
protected set { }
}
public float Thirst
{
get { return needs[(int)NeedType.Hydration].Severity; }
protected set { }
}
public float Lust
{
get { return needs[(int)NeedType.Sex].Severity; }
protected set { }
}
public float Comfort
{
get { return needs[(int)NeedType.Comfort].Severity; }
protected set { }
}
public float Cleanliness
{
get { return needs[(int)NeedType.Cleanliness].Severity; }
protected set { }
}
public float Shelter
{
get { return needs[(int)NeedType.Shelter].Severity; }
protected set { }
}
public float Intimacy
{
get { return needs[(int)NeedType.Intimacy].Severity; }
protected set {; }
}
public float Fun
{
get { return needs[(int)NeedType.Fun].Severity; }
protected set {; }
}
public float Safety
{
get { return needs[(int)NeedType.Safety].Severity; }
protected set {; }
}
public float Respect
{
get { return needs[(int)NeedType.Respect].Severity; }
protected set {; }
}
#endregion
//the steepness, emults, and xMidpoints need to be adjusted for each emotional need
void SetupNeeds()
{
double[] steepnesses = new double[numberOfNeeds] { 0.2f, 0.4f, 0.4f, 0.1f, 0.4f, 0.1f, 0.2f, 0.2f, 0.4f, 0.4f, 0.1f };
double[] eMults = new double[numberOfNeeds] { 1f, 1f, 10f, 1f, 10f, 0.1f , 1f , 1f, 1f, 10f, 1f };
double[] xMidpoints = new double[numberOfNeeds] { 24, 12, 16, 84, 10, 72, 24, 24, 12, 16, 84 };
double yMax = 100;
double yMaxforCalc = 99.99999f;
for (int i = 0; i < needs.Length; i++)
{
//Debug.Log("need " + ((PhysicalNeedType)i).ToString());
double maxX = -((System.Math.Log((yMax / yMaxforCalc) - 1) - System.Math.Log(eMults[i])) / steepnesses[i]) - xMidpoints[i];
//Debug.Log("double max x is " + maxX);
//Debug.Log("float max x is " + maxX);
double dx = maxX / 10f;
List < float > xVals = new List < float > ();
List < float > yVals = new List < float > (); //use this as the input for the mood response curve
float yv;
float myv;
List < float > moodYVals = new List < float > (); // use this as the output for the mood response curve
//Debug.Log("need " + ((PhysicalNeedType)i).ToString());
for (int xs = 0; xs < 10; xs++)
{
xVals.Add((float)(xs * dx));
//Debug.Log(xs * dx);
if (xs == 0)
{
yv = 0f;
}
else if (xs == 9)
{
yv = (float)yMax;
}
else
{
yv = (float)(yMax / (1 + eMults[i] * (System.Math.Pow(System.Math.E, -steepnesses[i] * ((xs * dx) - xMidpoints[i])))));
}
yVals.Add(yv);
//Debug.Log(yMax / (1 + eMults[i] * (System.Math.Pow(System.Math.E, -steepnesses[i] * ((xs * dx) - xMidpoints[i])))));
if (yv > = 50f)
{
//gives a penatly to mood
//make sure to clamp this value
myv = Mathf.Clamp(-(Mathf.Pow(yv - 50f, 3) / 1000f), -100, 100);
}
else
{
//gives a bonus to mood
//make sure to clamp this value
myv = Mathf.Clamp(-(Mathf.Pow(yv - 50f, 3) / 50000f) + 5f, -100, 100);
//Debug.Log(-(Mathf.Pow(yv - 50f, 3) / 50000f) + 5f);
}
moodYVals.Add(myv);
}
moodResponseCurve.Add(new ResponseCurveFloat(yVals, moodYVals));
float time = 10f;
float potency = 100f;
MeetNeedEvent mne = new MeetNeedEvent(time, potency);
needs[i] = new BaseNeed((NeedType)i, 0f, new ResponseCurveFloat(xVals, yVals), (float)maxX, mne, 100f);
//needs[i] = new PhysicalNeed(0f, 1f, 1000f, (PhysicalNeedType)i);
}
Debug.Log("Needs setup");
}
public void ChangeNeedState(NeedType ty, float amount)
{
switch (ty)
{
case NeedType.Food:
//Debug.Log("Trying to increase food need");
needs[(int)NeedType.Food].IncreaseNeed(amount);
break;
case NeedType.Hydration:
//Debug.Log("Trying to increase hydration need");
needs[(int)NeedType.Hydration].IncreaseNeed(amount);
break;
case NeedType.Rest:
//Debug.Log("Trying to increase rest need");
needs[(int)NeedType.Rest].IncreaseNeed(amount);
break;
case NeedType.Sex:
//Debug.Log("Trying to increase sex need");
needs[(int)NeedType.Sex].IncreaseNeed(amount);
break;
case NeedType.Comfort:
//Debug.Log("Trying to increase comfort need");
needs[(int)NeedType.Comfort].IncreaseNeed(amount);
break;
case NeedType.Cleanliness:
//Debug.Log("Trying to increase cleanliness need");
needs[(int)NeedType.Cleanliness].IncreaseNeed(amount);
break;
case NeedType.Shelter:
needs[(int)NeedType.Shelter].IncreaseNeed(amount);
break;
case NeedType.Fun:
needs[(int)NeedType.Fun].IncreaseNeed(amount);
break;
case NeedType.Intimacy:
needs[(int)NeedType.Intimacy].IncreaseNeed(amount);
break;
case NeedType.Respect:
needs[(int)NeedType.Respect].IncreaseNeed(amount);
break;
case NeedType.Safety:
needs[(int)NeedType.Safety].IncreaseNeed(amount);
break;
default:
break;
}
OnNeedStateChanged();
}
protected void OnNeedStateChanged()
{
if (NeedStateChanged != null)
{
//treating it like a function
//this is used to notify subscribers to this event
NeedEventArgs n = new NeedEventArgs();
n.need = this;
NeedStateChanged(this, n);
//Debug.Log("Physical State changed");
}
}
void OnNPCStateChanged(object source, StateEventArgs n)
{
switch (n.state.Body)
{
case BodyState.Resting:
ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
if (!needs[(int)NeedType.Rest].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Rest].MeetNeed());
}
ChangeNeedState(NeedType.Food, Time.deltaTime);
ChangeNeedState(NeedType.Hydration, Time.deltaTime);
if (!needs[(int)NeedType.Comfort].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Comfort].MeetNeed());
}
ChangeNeedState(NeedType.Sex, Time.deltaTime);
break;
case BodyState.Eating:
ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
if (!needs[(int)NeedType.Food].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Food].MeetNeed());
}
ChangeNeedState(NeedType.Rest, Time.deltaTime);
ChangeNeedState(NeedType.Hydration, Time.deltaTime);
ChangeNeedState(NeedType.Comfort, Time.deltaTime);
ChangeNeedState(NeedType.Sex, Time.deltaTime);
break;
case BodyState.Drinking:
ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
if (!needs[(int)NeedType.Hydration].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Hydration].MeetNeed());
}
ChangeNeedState(NeedType.Rest, Time.deltaTime);
ChangeNeedState(NeedType.Food, Time.deltaTime);
ChangeNeedState(NeedType.Comfort, Time.deltaTime);
ChangeNeedState(NeedType.Sex, Time.deltaTime);
break;
case BodyState.Sexing:
ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
if (!needs[(int)NeedType.Sex].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Sex].MeetNeed());
}
if (!needs[(int)NeedType.Comfort].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Comfort].MeetNeed());
}
ChangeNeedState(NeedType.Food, Time.deltaTime);
ChangeNeedState(NeedType.Hydration, Time.deltaTime);
ChangeNeedState(NeedType.Rest, Time.deltaTime);
break;
case BodyState.Bathing:
if (!needs[(int)NeedType.Cleanliness].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Cleanliness].MeetNeed());
}
if (!needs[(int)NeedType.Comfort].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Comfort].MeetNeed());
}
ChangeNeedState(NeedType.Rest, Time.deltaTime);
ChangeNeedState(NeedType.Food, Time.deltaTime);
ChangeNeedState(NeedType.Hydration, Time.deltaTime);
ChangeNeedState(NeedType.Sex, Time.deltaTime);
break;
case BodyState.Default:
ChangeNeedState(NeedType.Rest, Time.deltaTime);
ChangeNeedState(NeedType.Food, Time.deltaTime);
ChangeNeedState(NeedType.Hydration, Time.deltaTime);
ChangeNeedState(NeedType.Sex, Time.deltaTime);
ChangeNeedState(NeedType.Comfort, Time.deltaTime);
ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
break;
default:
break;
}
switch (n.state.Feeling)
{
case EmotionalStateType.Socializing:
if (!needs[(int)NeedType.Intimacy].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Intimacy].MeetNeed());
}
if (!needs[(int)NeedType.Fun].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Fun].MeetNeed());
}
ChangeNeedState(NeedType.Respect, Time.deltaTime);
break;
case EmotionalStateType.Playing:
if (!needs[(int)NeedType.Fun].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Fun].MeetNeed());
}
ChangeNeedState(NeedType.Intimacy, Time.deltaTime);
ChangeNeedState(NeedType.Respect, Time.deltaTime);
break;
case EmotionalStateType.Working:
ChangeNeedState(NeedType.Fun, Time.deltaTime);
break;
case EmotionalStateType.Arguing:
if (!needs[(int)NeedType.Intimacy].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Intimacy].MeetNeed());
}
ChangeNeedState(NeedType.Fun, Time.deltaTime);
break;
case EmotionalStateType.BeingRespected:
if (!needs[(int)NeedType.Respect].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Respect].MeetNeed());
}
if (!needs[(int)NeedType.Intimacy].IsBeingMet)
{
StartCoroutine(needs[(int)NeedType.Intimacy].MeetNeed());
}
ChangeNeedState(NeedType.Fun, Time.deltaTime);
break;
case EmotionalStateType.Default:
if (n.state.IsAlone)
{
ChangeNeedState(NeedType.Intimacy, Time.deltaTime);
ChangeNeedState(NeedType.Respect, Time.deltaTime);
}
ChangeNeedState(NeedType.Fun, Time.deltaTime);
//ChangeNeedState(NeedType.Safety, Time.deltaTime);
break;
default:
break;
}
OnNeedStateChanged();
}
// Use this for initialization
void Start()
{
SetupNeeds();
npcState = FindObjectOfType < NPCState > ();
npcState.StateChanged += OnNPCStateChanged;
}
// Update is called once per frame
void Update()
{
}
}
public class NeedEventArgs : EventArgs
{
public Needs need { get; set; }
}
No comments:
Post a Comment