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