Showing posts with label Code. Show all posts
Showing posts with label Code. Show all posts

Thursday, December 21, 2017

Latest Update For Particularly Wavy

hey all,

It's been a while since my last post here. Part of the delay has been due to me catching a cold, and the rest is just that I've been too busy coding to actually write or talk about what I've been coding. One thing that is shown in the video is really smooth detection of hits: in the very first version of the game, I was performing raycasts every frame and based on those results, deciding if I needed to recalculate the lights, which as you might imagine leads to lots of frames where I perform the raycast and decide to do nothing.

In the latest version, I have delegates and events on every object that moves, rotates, or changes size, and when they do one of those things, the laser receives a message letting it know that something has moved, so it can then update the light positions and angles.



The problem was that when the light hit something, I was starting certain coroutines that would spam a message to the hit object once every frame, and in the code for the hit object I was running a bunch of checks inside the Update() function, which is run once every frame. Why would I do something stupid like that? Well, say you perform your calculation and you determine that light ray A is now hitting object O. You set the hit object variable of light ray A to object O and go on calculating what other results fall out from that. But what if on the previous frame light ray A was hitting object T, and object T is the target? In my game, I have a flag set inside the Target.cs script so that it knows when it is hit and what it is hit by. How do I tell the Target that it is no longer hit? That is the reason for the coroutines and Update() code: if the Target stopped receiving the message of being hit from the coroutine, which is stopped whenever I update the light anyway, then the Target can act correctly.

If you look at the code below, however, you will notice a different technique. I have a private variable called hitObject, and I have a public accessor for this called HitObject. Inside the setter, I run comparisons between the incoming value and the previous value of hitObject. Based on those, I send messages using Unity's built-in messaging system. These messages let the object know that it is no longer hit by or has just been hit by a particular light ray, as the case may be. Problem solved: no messy coroutines that need to be started or stopped, no code running every frame inside of Update() (OK, OK, no code besides the shader material updates) and using up CPU time. When something changes, the messages are sent and if nothing has changed, then nothing needs to be updated or checked.


using System.Collections.Generic;
using UnityEngine;

//[RequireComponent(typeof(CapsuleCollider))]
public class RayNode : MonoBehaviour
{
 private GameObject hitObject;
 public List < RayNode > children;
 public LineRenderer rayLR;
 public int depth;
 public bool influencedByBlackHole;
 public bool intensified;
 public GameObject metaball;

 public GameObject HitObject
 {
  get { return hitObject; }
  set
  {
   if ((value == null && hitObject != null ))
   {
    hitObject.SendMessage("OnLightExit", gameObject, 
SendMessageOptions.DontRequireReceiver);
    hitObject = null;
   }
   else if ((value != null && hitObject != value && hitObject != null ))
   {
    hitObject.SendMessage("OnLightExit", gameObject, 
SendMessageOptions.DontRequireReceiver);
    hitObject = value;
    hitObject.SendMessage("OnLightEnter", gameObject, 
SendMessageOptions.DontRequireReceiver);
   }
   else if ((hitObject == null && value != null))
   {
    hitObject = value;
    hitObject.SendMessage("OnLightEnter", gameObject,
SendMessageOptions.DontRequireReceiver);
   }
   else
   {
    hitObject = value;
   }
  }
 }

 float offset;

 private void Start()
 {
 }

 private void Update()
 {
  offset -= Time.deltaTime * 2f;
  if (offset < -.5f)
  {
   offset += .5f;
  }
  rayLR.materials[1].SetTextureOffset("_MainTex", 
new Vector2(offset / 4f, 0));
  rayLR.materials[2].SetTextureOffset("_MainTex", 
new Vector2(offset, 0));

 }


 public RayNode()
 {
  children = new List < RayNode > ();
 }

 public RayNode(GameObject RO)
 {
  hitObject = RO;
  children = new List < RayNode > ();
 }

 public void PruneSubTree(int childIndex)
 {
  //validate index
  if (childIndex > -1 && childIndex < children.Count)
  {
   RayNode toPrune = children[childIndex];
   //set the hitObject to null
   if (toPrune != null)
   {
    toPrune.HitObject = null;
   }
   

   List < RayNode > ch = new List < RayNode > ();
   for (int i = 0; i < toPrune.children.Count; i++)
   {
    ch.Add(toPrune.children[i]);
   }

   //reverse order is important in order to prevent skipping errors
   for (int i = ch.Count - 1; i > -1; i--)
   {
    toPrune.PruneSubTree(i);
   }

   if (toPrune != null)
   {
    Destroy(toPrune.gameObject, 0.1f);
   }
   
   children.Remove(toPrune);
  }
 }

 public List GetChildren()
 {
  return children;
 }

 public void PruneWholeTree()
 {
  if (children.Count > 0)
  {
   List < RayNode > ch = new List < RayNode > ();
   for (int i = 0; i < children.Count; i++)
   {
    ch.Add(children[i]);
   }

   //reverse order is important in order to prevent skipping errors
   for (int i = ch.Count-1; i > -1; i--)
   {
    PruneSubTree(i);
   }
  }
 }

 public void UpdateChildren(Vector3 startPosition)
 {
  for (int i = 0; i < children.Count; i++)
  {
   children[i].gameObject.transform.position = startPosition;
   children[i].rayLR.SetPosition(0, startPosition);
  }
 }

 public void HandleMetaBall()
 {
  metaball.transform.position = (rayLR.GetPosition(0) + rayLR.GetPosition(1)) / 2f;
  Vector3 dir = rayLR.GetPosition(1) - rayLR.GetPosition(0);
  metaball.transform.rotation = Quaternion.AngleAxis(
Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg,
 Vector3.forward);
  float scaleFactor = 0;
  
  //set scaling factor in case of low distances
  if (Vector3.Distance(rayLR.GetPosition(1), rayLR.GetPosition(0)) < 5f)
  {
   scaleFactor = 0.2f;
  }

  metaball.transform.localScale = new Vector3((scaleFactor + 1.1f) * 
Vector3.Distance(rayLR.GetPosition(1), rayLR.GetPosition(0)), 2f, 1f);
 }
}


This simple fix solved several other problems as well. The targets need to keep track of what light is hitting them, and previously each light ray would send a message letting the target know this. I would have to clear the list at the beginning of a light update cycle, then add each light ray, and at the end of a frame, I would have to perform a check to see if the target's condition had been met. The exact timing of that check is important, because performing the check in-between light rays being added would lead to false positives: the target needs to be hit by red light and only red light, for example, and after one check it is being hit by red light, so the condition is marked as being satisfied. But then orange light and yellow light send their messages to the target and now the condition is not satisfied. But the message has already been sent to the game manager, which now congratulates the player on solving the puzzle even though they have not solved the puzzle. You get the idea. Since I was performing these checks every time an object was moved, that just increased the chance that one poorly timed message would screw the whole thing up. Now, I only perform these checks when a light ray first hits the target and when it stops hitting the target, greatly reducing the chance of a false positive.

One final problem that has only come to light recently is null references. Normally, these would be huge signal fires that something has broken somewhere, but these only started showing up when I changed some of my laser code to be updated just as the program stops or shuts down. When I did that, suddenly the console was getting clogged by null reference errors. Luckily, the fix was really simple: inside the PruneSubTree function, I now check if the child to delete is null and if it is not, then I delete it. That's it: no more errors.

I've been spending so much time going through and fixing these problems, updating the chargeable objects and activate-able objects, etc, that I still have not gotten around to finishing the code for my heat-able objects. But I hope to have that finished before New Years.

Thursday, June 29, 2017

Reflection

hi all,

John Dewey, the famous educator and philosopher, once said that we don't learn from experience, but by reflecting on experience.


In programming, reflection, as I in my extremely limited way, understand it, refers to the ability of code to examine itself and report its findings, and to alter itself at runtime. There are a large number situations where you might want a program to be able to do this, but in my own case it comes from working on my never finished quest system. When I design a quest, I want to be able to set up what conditions and events the quest should pay attention to, how it should change when particular events are met, etc, like in this image from Wolcen Lords of Mayhem's blog post.

One of the ways you can do this, in my opinion one of the more sensible ones, is to use reflection to generate a list of classes or objects which might contain events or conditions you are interested in, then from that list generate lists of events in those classes or objects. Why not just hard-code it? Because hard-coding is EVIL! Really, though, what if you hard code your list of classes and realize that you left out one class? In a full game project, keeping (in your head) a 100% accurate list of classes that are relevant to the task at hand is impossible. Even in my incomplete project, which has almost zero gameplay, and includes a massive TWO characters and ONE item, contains more than 40 scripts. Most of those, of course, deal with the quest system and node editor, but sorting through the other classes by hand is a recipe for disaster.

Same goes for finding the events. If you try to hand code it, what if you make a spelling mistake for one of the events? You might not catch it and your compiler might not catch it until you're playing the game and you get a bug. So, here is some code that does just that. I create a dictionary using strings as the key and storing a list of strings as the value. The key is the class name, and the list of strings is the events found in that class.

Dictionary < string , List  < string  > > targetclasses;
Assembly assem = Assembly.GetExecutingAssembly();
   Type[] types = assem.GetTypes().Where(t => string.Equals(t.Namespace, "QuestSystem", StringComparison.Ordinal) && t.BaseType != typeof(MulticastDelegate) && t.BaseType != typeof(ScriptableObject)).ToArray();
   for (int i = 0; i < types.Length; i++)
   {
    FieldInfo[] fields = types[i].GetFields();
    EventInfo[] events = types[i].GetEvents();
    if (events.Count() > 0)
    {
     List temp = new List();
     for (int j = 0; j < events.Count(); j++)
     {
      //Debug.Log(events[j].Name);
      temp.Add(events[j].Name);
     }
     targetclasses.Add(types[i].Name, temp);
    }

   }

   }



After you select the class and event, the game system will serialize your choice as a string. When you start the system up again, it can deserialize these events (which are just stored as strings) and rehook them up to their appropriate targets using reflection again.

Thursday, June 22, 2017

Quest System and Node Editor

hey all,

Here is a short video showing some of what I've been working on for the last few weeks.



Thursday, March 30, 2017

More Digestion Game WOrk

hey all,

I'm still hard at work on the digestion game. I've completed several of the improvements that I mentioned in the previous post, but of course there remains months of work, especially since the company promises their clients 10 hours of educational content. I've made some small steps towards reaching that goal, but a lot more design, and art, and of course programming work remains to be done.

In the mean time, please enjoy the video showing my progress in updating the game.



More to come next week.

Wednesday, February 15, 2017

Node Editor Demonstration

hey all,
 Here is a brief demonstration of the node editor I've been working on. It is not perfect by any means, but I've learned quite a bit about coding GUI, serialization, and handling connections between nodes, which can get really complicated.


Cheers,

Wednesday, February 8, 2017

Quest System

hey all,
I've been working on putting together a quest system to work with my NPCs and their changing needs and moods. Following a few tutorials on how to set one up, I've decided to create an interface IObjective from which ten different objective classes will inherit.

Using these objectives, I can then create a Quest class that will contain, among other things, a list of objectives.



The quest system will have to be hooked up to the Item and Actor classes, much like it is in the Papyrus scripting used in The Elder Scrolls V: Skyrim. This way, the quests can be updated and notified using events fired off by the items and actors that are part of the quest.

As a ... I actually don't know what to properly call it, but I've been working on a custom node editor for the Unity engine at the same time. The idea is that I can have custom nodes, windows, and graphs,  which could represent conversations, behavior trees, quests, or something else. Much like Skyrim has the Creation Kit editor, after I'm finished coding, Unity will have a custom interface that will allow me or others to quickly create new content, or to design the rules that the game engine will use to generate content on its own. At least, that is the conceit now. Besides, following this tutorial will give me more experience working with GUI, interfaces, events, reflection, and serialization, which will all benefit me in the future.

Wednesday, January 25, 2017

Steering Behaviors

hey all,

I've been working on getting my steering behaviors working a bit better for most of this week, and I think I'm finally getting close.



The diagram above is based on Mat Buckland's diagrams and description of the wander algorithm that Craig Reynolds developed. Basically, you pick a random point on the red circle, then add a small random displacement to it. You then normalize this displacement to put it back on the red circle. Finally, you project this circle in front of the agent. The vector from the agent to the green circle gives your agent a goal to move towards.

Another way to do it would be to pick a random angle within a limit of your agent's current value, and move your agent towards that angle at a constant speed.

I've developed on that uses 1D Perlin noise to smoothly move from one angle to another.
Vector2 Wander()
 {
  if (currentLoc >= width)
  {
   currentLoc = 0;
   perlinNoise = GenerateNoiseMap(width, seed++, scale, octaves, persistance, lacunarity);
  }

  //get the next angle and increment currentLoc
  WanderAngle = perlinNoise[currentLoc];
  currentLoc++;

  targetRotation = new Vector3(0f, WanderAngle, 0f);
  Vector3 localwanderTarget = new Vector3(Mathf.Cos(WanderAngle * Mathf.Deg2Rad), 0f, Mathf.Sin(WanderAngle * Mathf.Deg2Rad));
  Vector3 worldwanderTarget = transform.TransformDirection(localwanderTarget);

  wanderTarget = new Vector2(worldwanderTarget.x, worldwanderTarget.z);

  return wanderTarget;
 }

Here's a short video showing it at work.



Cheers,

Saturday, January 21, 2017

On The Road to a Decision

hey all,

Jan. 20th marked the end and the beginning of an era, in more ways than one. On the world stage, Mr. Trump has entered office as the 45th POTUS, ending 8 years of Mr. Obama. Whatever you think of either man's politics, Mr. Obama presided with dignity and respect, and delivered his speeches with gravitas and eloquence. Nobody knows exactly what Mr. Trump will do, but we all know it will be done in an outrageous and probably offensive manner.

On my own small personal stage, Jan. 20th marked my last full time day as an English teacher. Starting Monday, I will be devoting my mornings to programming, research on game design and development, and job hunting for positions in the game industry.

...

I've been watching The Matrix series and blasting Rage Against the Machine's eponymous album for the last week. The themes of CHOICE and DECISION seem to be haunting me, perhaps partly because that has been what I'm struggling to implement in my The Sims-inspired needs system. I've written code that goes through all the needs, and compares how meeting each need would affect the mood of the NPC.


public float SimulateMoodChange(NeedType nt, float potency)
 {
  float curmood = CurrentMood;
  float deltamood = 0;
  //float curmood = CurrentMood- nee.MoodResponse((int)nt)[potency];
  float nttwo = 0;
  switch (nt)
  {
   case NeedType.Food:
    nttwo = nee.Hunger;
    break;
   case NeedType.Hydration:
    nttwo = nee.Thirst;
    break;
   case NeedType.Rest:
    nttwo = nee.Tiredness;
    break;
   case NeedType.Sex:
    nttwo = nee.Lust;
    break;
   case NeedType.Comfort:
    nttwo = nee.Comfort;
    break;
   case NeedType.Cleanliness:
    nttwo = nee.Cleanliness;
    break;
   case NeedType.Shelter:
    nttwo = nee.Shelter;
    break;
   case NeedType.Intimacy:
    nttwo = nee.Intimacy;
    break;
   case NeedType.Fun:
    nttwo = nee.Fun;
    break;
   case NeedType.Safety:
    nttwo = nee.Safety;
    break;
   case NeedType.Respect:
    nttwo = nee.Respect;
    break;
   case NeedType.MAX:
    break;
   default:
    break;
  }
  Debug.Log("Simulate mood change" + " need is " + nt);
  //Debug.Log("potency ")
  for (int i = 0; i < nee.NumberOfNeeds; i++)
  {
   ResponseCurveFloat mr = nee.MoodResponse(i);
   switch ((NeedType)i)
   {
    case NeedType.Food:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Hunger], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Hydration:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Thirst], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Rest:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Tiredness], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Sex:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Lust], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Comfort:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Comfort], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Cleanliness:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Cleanliness], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Shelter:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Shelter], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Intimacy:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Intimacy], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Fun:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Fun], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Safety:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Safety], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Respect:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Respect], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.MAX:
     break;
    default:
     break;
   }
  }
  //float delta = nee.MoodResponse((int)nt)[nttwo - (100f-potency)];
  float delta = deltamood - CurrentMood;

  //curmood = Mathf.Clamp(curmood + nee.MoodResponse((int)nt)[nttwo - (potency*nttwo)], -100, 100);
  return delta;
 }

Following the lecture notes to Richard Evans's GDC talk about The Sims 3, I am converting the delta value into a probability. Since his equation is a little bit mystical to me still,


I am just normalizing the value of each delta value by dividing it by the total amount of mood change, which gives a percentage between 0 (never going to happen) and 1 (an inevitability, Mr. Anderson).


void EvaluateMood()
 {
  float runningTotal = 0f;
  float needlevel = 0;

  List < NeedProb > needProbabilities = new List < NeedProb > ();
  foreach (GameObject go in adObjects)
  {
   switch (go.GetComponent < advertisingobject > ().MyNeedType)
   {
    case NeedType.Food:
     needlevel = myNeeds.Hunger;
     break;
    case NeedType.Hydration:
     needlevel = myNeeds.Thirst;
     break;
    case NeedType.Rest:
     needlevel = myNeeds.Tiredness;
     break;
    case NeedType.Sex:
     needlevel = myNeeds.Lust;
     break;
    case NeedType.Comfort:
     needlevel = myNeeds.Comfort;
     break;
    case NeedType.Cleanliness:
     needlevel = myNeeds.Cleanliness;
     break;
    case NeedType.Shelter:
     needlevel = myNeeds.Shelter;
     break;
    case NeedType.Intimacy:
     needlevel = myNeeds.Intimacy;
     break;
    case NeedType.Fun:
     needlevel = myNeeds.Fun;
     break;
    case NeedType.Safety:
     needlevel = myNeeds.Safety;
     break;
    case NeedType.Respect:
     needlevel = myNeeds.Respect;
     break;
    case NeedType.MAX:
     break;
    default:
     break;
   }
   needProbabilities.Add(new NeedProb(go.GetComponent < advertisingobject > ().MyNeedType, myMood.SimulateMoodChange(go.GetComponent < advertisingobject >().MyNeedType,
    go.GetComponent < advertisingobject > ().MyMeetNeed.potency)));
   runningTotal += myMood.SimulateMoodChange(go.GetComponent < advertisingobject > ().MyNeedType, 
    go.GetComponent < advertisingobject > ().MyMeetNeed.potency);
  }

  for (int i = 0; i < needProbabilities.Count; i++)
  {
   Debug.Log(needProbabilities[i].nt + " probability is " + needProbabilities[i].probabilty);

  }

  for (int i = 0; i < needProbabilities.Count; i++)
  {
   if (runningTotal > 0)
   {
    needProbabilities[i].probabilty = needProbabilities[i].probabilty / runningTotal;
   }
  }

  for (int i = 0; i < needProbabilities.Count; i++)
  {
   Debug.Log(needProbabilities[i].nt + " normalized probability is " + needProbabilities[i].probabilty);

  }

 }

I've also set up another function that just evaluates the needs, and ran into the following problem. If I want to implement one of the common methods of picking an option, I create a simple array of length 100, and assign a NeedType to each, I need a way of rounding the floating probabilities to integers such that they will add up to 100. For example, if Food is 45.65934% probable, Hydration is 23.763%, etc. I need a way to round those fractional values in such a way that they get assigned to appropriate needs.

Luckily for me, one of America's Founding Fathers, Alexander Hamilton, had already figured out a way to deal with this problem. Basically, you take the integer part of each percentage and sum them. You find the difference between the sum and the target number, then you take integers that had the largest remainders and add 1 to them until there no places available.

How great is it that this method was developed by a man who was the first Secretary of Treasury of the US, and, given his desire for a strong executive branch and military, and support for industrialization, would most likely have had the support of our current POTUS.


int integalPart = 0;
  List < NeedProb > remainder = new List < NeedProb > ();
  int extra = 0;
  for (int i = 0; i < needProbabilities.Count; i++)
  {
   integalPart += (int)(needProbabilities[i].probabilty * 100);
   remainder.Add(new NeedProb(needProbabilities[i].nt,(needProbabilities[i].probabilty * 100)- (int)(needProbabilities[i].probabilty * 100)));
  }

  //for (int i = 0; i < needProbabilities.Count; i++)
  //{
  // Debug.Log(needProbabilities[i].nt + " " + needProbabilities[i].probabilty);
  //}


  for (int i = 0; i < needProbabilities.Count; i++)
  {
   needProbabilities[i].probabilty = (int)(needProbabilities[i].probabilty * 100);
  }

  //needProbabilities.Sort();
  //for (int i = 0; i < needProbabilities.Count; i++)
  //{
  // Debug.Log(needProbabilities[i].nt + " at time " + Time.time + " is " + needProbabilities[i].probabilty);
  //}

  extra = 100 - integalPart;


  remainder.Sort();
  remainder.Reverse();
  for (int i = 0; i < extra; i++)
  {
   needProbabilities[(int)remainder[i].nt].probabilty++;

  }

  int cur = 0;
  int cur2 = 0;
  for (int i = 0; i < needProbabilities.Count; i++)
  {
   cur = cur2;
   for (int j = cur; j < cur + (int)(needProbabilities[i].probabilty); j++)
   {
    prob[j] = needProbabilities[i].nt;
    cur2++;
   }
  }

So anyway, using these probabilities, I can generate a random number from between 0 - 99, and access my probability array, prob, to pick a need to satisfy. The next step is to start moving towards it using my Steering Behaviors.

Cheers,

Thursday, December 22, 2016

Multiple Characters and Basic Needs

hey all,

Another busy and dramatic week at work. Three more days of work til winter vacation. Just a quick demo showing multiple characters and displaying their needs and body state. The next step is decision making.




I've setup a new channel on Youtube where you can find my video game videos.
Cheers,

Monday, December 5, 2016

Back to the Basics (Needs)

hey all,

I've been refactoring my code for needs over the last few days, and here are some of the changes I've made.


using UnityEngine;
using System.Collections;
using Gamelogic.Extensions.Algorithms;

public class Need
{
 [Range(0, 100)]
 public float severity;
 float maxTime;
 MeetNeedEvent typicalMNE;
 //public float changeRate;
 public ResponseCurveFloat curve;
 float timeSinceMet;
 public bool isBeingMet;

 //used for determining the effect during decision making
 public float multiplier;


These are the variables that my Need class contains. The severity just tracks the severity of the need, which can only be from 0 to a maximum of 100. For physical needs such as food and water, I'm planning on causing death to occur when it reaches the maximum. maxTime was the most difficult. If you recall from my previous post, I am using variations on the equation


to calculate how severe a need should be after x time has passed. The problem is that this is a logistic equation, and it has a horizontal asymptote at max_y, so you cannot just use




to try and find the x value when y = max_y, because that value does not exist. At the moment, I'm using a y value of 99.999999 and it seems to be working OK, although I do get some floating point errors every now and then.

In any case, I use the first equation to set up the response curve in the PhysicalNeeds and EmotionalNeeds classes, and also create a default MeedNeedsEvent.
 public Need(float sev, ResponseCurveFloat rc, float maxT, MeetNeedEvent mne ,float mult)
 {
  severity = sev;
  curve = rc;
  multiplier = mult;
  isBeingMet = false;
  timeSinceMet = 0f;
  typicalMNE = mne;
  maxTime = maxT;
 }

 // 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];
   
  }
 }

This is another change from before. Instead of having those ugly incremental changes in an Update function, I'm now calling a coroutine that just ticks along by itself until the MeetNeedEvent is finished. I'm actually thinking that having the IncreaseNeed function be a coroutine might be a much better idea as well, and just use body states in the PhysicalNeeds class to start and stop the appropriate coroutines.
 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;
  while (t < mne.eventLength)
  {
   t += Time.deltaTime;
   //this value needs to be clamped!!!
   severity = Mathf.Clamp(severity - (t / mne.eventLength) * mne.potency, 0f, 100f);
   yield return null;
  }
  isBeingMet = false;
  timeSinceMet = maxTime-(maxTime/100f*mne.potency);
 }
}


The point of having maxTime subtract a fraction of itself is that the potency of the event should affect the level of the need afterwards. If I have a snack, my hunger need will not be as satisfied as if I had had a hearty bowl of sweet and sour pork.

public struct MeetNeedEvent
{
 public float eventLength;
 [Range(0,100)]
 public float potency;

 public MeetNeedEvent(float el=-1f, float p =-100f)
 {
  eventLength = el;
  potency = p;
 }
}


So that's my base Needs class. PhysicalNeeds and EmotionalNeeds inherit from this class. I hope that I can start implementing how these needs affect mood.The current AI model is something like this:

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.

Tuesday, November 22, 2016

Test

hey all, This is mostly just a test to see if I can get my C# code to show up.
    [Range(0, 100)]
    float hunger;
    [Range(0, 100)]
    float tiredness;
    [Range(0, 100)]
    float thirst;
    [Range(0, 100)]
    float lust;
    [Range(0, 100)]
    float warmth;

I originally started with something like the above.But as I thought more about it, I realized that a struct is probably closer to what is needed.

PhysicalNeed[] needs = new PhysicalNeed[6];

public struct PhysicalNeed
{
    [Range(0, 100)]
    float severity;
    float changeRate;
    PhysicalNeedType type;
}

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

Upon starting, the array of physical needs needs to be initialized, and during each update loop, the NPC's physical needs are updated depending on the NPC's physical state: if the NPC is sleeping, the need for rest will decrease, while if working, all the needs will slowly increase.

I also have some callbacks setup between the PhysicalState class and the Mood class, so that whenever the PhysicalState class is updated, it will update the NPC's mood.