To finish off I've done some work on steering behaviours and generating stories for NPCs in certain circumstances.
For steering behaviours I used the Unity component Nav Mesh Agent, however this didn't allow much variation in the movement for the NPCs. The Nav Mesh Agent was simply too perfect, I wanted to add some variation. Rather than making a steering algorithm from scratch, which would involve implementing a path finding algorithm I continued to use the Nav Mesh Agent, but in a different way. I made the Nav Mesh Agent invisible, with no collisions and wrote a simple steering behaviour to follow that agent.
Unfortunately I left it too late and didn't have enough time to experiment with different variations in steering behaviours.
void Update () {
//Set the speed based on the speed of the Body nav
moveSpeed = nav.speed;
//Set the colour and scale based on the Body
rend.material.color = following.GetComponent<Renderer>().material.color;
transform.localScale = following.transform.localScale;
transform.LookAt(following.transform.position);
float dist = Vector3.Distance(transform.position,following.transform.position);
if (dist > 0.5f){
transform.position += transform.forward * moveSpeed * Time.deltaTime;
}
}
While this code utimately acts in a very similar way to before, it gives me a framework to add that variation which will make the NPCs seem more human.
For generating stories I made a few simple algorithms which searches through all the NPC people looking for specific situations to describe a story. The hardest part was distinguishing between fact and implication. Storing what each NPC might know about every other NPC is a challenge in itself, so I decided not to worry about this too much.
Here is a method for generating suspicions about affairs:
void affairSuspicion(){
PersonStats[] people = GameObject.Find("People").GetComponentsInChildren<PersonStats>();
RelationshipHelper rh = GameObject.Find("Relationships").GetComponent<RelationshipHelper>();
//For every person
foreach(PersonStats ps in people){
//If they are stressed, they have a partner and the strength of that relationship is less than 50
if (ps.stress > 50 && ps.partner != null && rh.getStrengthValue(ps.gameObject, ps.partner) < 50){
//For each of the partners friends
foreach(GameObject go in ps.partner.GetComponent<PersonStats>().contacts){
PersonStats cps = go.GetComponent<PersonStats>();
//If they have a good relationship
if (rh.getStrengthValue(go,cps.gameObject) > 0){
ps.stories.Add("Suspicious that partner is having an affair with " + cps.forename + " " + cps.surname);
Stories.Add(ps.forename + " " + ps.surname + " is suspicious that partner is having an affair with " + cps.forename + " " + cps.surname);
break;
}
}
}
}
}
If someone is stressed and the strength of the relationship with their partner is under 50, they will suspect other people who have a higher relationship strength. The limitation being these suspicions are rather unfounded. There should really be another method for getting more generating suspicions with are definitely correct. This mixture of paranoid stories which are unfounded and real suspicions would add to the variation and complexity of the simulation.
Friday, 27 November 2015
Sunday, 22 November 2015
Week 11: November 22nd 2015
For when an NPC had free time I decided to implement a finite state machine. I chose this method mainly for simplicity, I knew in the short term this would be a quick and easy way to do things. However if I add many more states in the future it might be best to switch to a behaviour tree instead.
void freeTime(){
if(dnc.hours <= 9 || asleep){
destination = ps.home.transform.FindChild("Rug").gameObject;
}
else {
if (doneFree){
doneFree = false;
freeTimeAction = Random.Range(0,5);
}
switch(freeTimeAction){
case 0:
getFood();
break;
case 1:
goToBestFriend();
break;
default:
goHome();
break;
}
}
}
As you can see there are only states at this stage. Once the getFood() state is performed the state can transition to either goHome() or goToBestFriend(). Going to best friend than then transition into going home only if it's time for bed.
This current implementation might be limited, but it can be easily adapted for more complex behaviours if required in the future.
void freeTime(){
if(dnc.hours <= 9 || asleep){
destination = ps.home.transform.FindChild("Rug").gameObject;
}
else {
if (doneFree){
doneFree = false;
freeTimeAction = Random.Range(0,5);
}
switch(freeTimeAction){
case 0:
getFood();
break;
case 1:
goToBestFriend();
break;
default:
goHome();
break;
}
}
}
As you can see there are only states at this stage. Once the getFood() state is performed the state can transition to either goHome() or goToBestFriend(). Going to best friend than then transition into going home only if it's time for bed.
This current implementation might be limited, but it can be easily adapted for more complex behaviours if required in the future.
Wednesday, 18 November 2015
Week 10: November 18th 2015
Since I got routines working I wanted to make them more advanced by making them a bit more dynamic, so you could add a new routine for the next day of an NPC. It was as simple as having a separate list of RoutineEntries called dynamicRoutine each night when an NPC goes to sleep this list is merged with the main schedule of an NPC.
//A method for populating the routine
void populateRoutine(){
ps.routine.Clear();
if (ps.occupations.Count >= 1){
foreach (GameObject go in ps.occupations){
OccupationStats os = go.GetComponent<OccupationStats>();
ps.routine.AddRange(copyRoutine(os.schedule));
}
}
ps.routine.AddRange(dynamicRoutine);
dynamicRoutine.Clear();
}
Routines can also be dynamically added to the same day by simply adding a RoutineEntry to the routine list Schedule of an NPC.
I tested this out by scheduling a funeral the next day for every NPC who knew and liked the deceased NPC.
void arrangeFuneral(PersonStats ps){
RelationshipHelper rh = GameObject.Find("Relationships").GetComponent<RelationshipHelper>();
//Foreach person who knew the deceased
foreach(GameObject go in ps.contacts){
//If the strength of the relationship was greater than -20
if(rh.getStrengthValue(ps.gameObject,go) > -20){
//Create a new Routine entry and add it to the dynamic routine of the contact
RoutineEntry re = new RoutineEntry();
re.startHour = 9;
re.startMinute = 0;
re.endHour = 16;
re.endMinute = 0;
re.location = ps.gameObject.transform.FindChild("Body").gameObject;
re.done = false;
re.priority = 10;
if (go != null){
go.transform.FindChild("Body").GetComponent<PersonMovement>().dynamicRoutine.Add(re);
}
}
}
}
While these simple systems work rather nicely, it is still very limited. Ideally events should be scheduled for weeks in advance.
However thinking about it, this could be made possible with a small adaption to the way the dynamicRoutine and routine lists merge. Items from the dynamicRoutine list should only be added to the routine list if a condition is met, for instance the day number.
Wednesday, 11 November 2015
Week 9: November 11th 2015
I didn't have many issues implementing the routines into the movement of the NPCs, although prioritizing certain activities over others if there is a time clash doesn't work as I would like to expect.
For each cycle of the void Update() method:
foreach(RoutineEntry re in ps.routine){
//If task is within start and end hour and it hasn't been done
if ((dnc.hours >= re.startHour) && (dnc.hours < re.endHour) && (!re.done)){
//They are not free
free = false;
freeChk = false;
//change destination to the location of the routine entry
destination = re.location;
p = destination.transform;
//If the type of routine is visit
if (re.type == 'V'){
if (Vector3.Distance(p.position,transform.position) < 2){
//Consider it done once a certain distance has been met
re.done = true;
}
}
}
}
This means the current routine the NPC performs is never saved, each cycle the appropriate action is applied depending on the time of day, and if the RoutineEntry is done or not. This has a few comparisons to that of a behaviour tree. The biggest advantage being that NPCs will immediately react to a change in their routine. However in it's current form RoutineEntries at the beginning of the foreach loop with have a higher priority. In the future I could set a priority to each RoutineEntry and sort the list accordingly each time a routine is added to an NPC schedule. This would solve this issue, and give the NPCs more control.
For each cycle of the void Update() method:
foreach(RoutineEntry re in ps.routine){
//If task is within start and end hour and it hasn't been done
if ((dnc.hours >= re.startHour) && (dnc.hours < re.endHour) && (!re.done)){
//They are not free
free = false;
freeChk = false;
//change destination to the location of the routine entry
destination = re.location;
p = destination.transform;
//If the type of routine is visit
if (re.type == 'V'){
if (Vector3.Distance(p.position,transform.position) < 2){
//Consider it done once a certain distance has been met
re.done = true;
}
}
}
}
This means the current routine the NPC performs is never saved, each cycle the appropriate action is applied depending on the time of day, and if the RoutineEntry is done or not. This has a few comparisons to that of a behaviour tree. The biggest advantage being that NPCs will immediately react to a change in their routine. However in it's current form RoutineEntries at the beginning of the foreach loop with have a higher priority. In the future I could set a priority to each RoutineEntry and sort the list accordingly each time a routine is added to an NPC schedule. This would solve this issue, and give the NPCs more control.
Tuesday, 3 November 2015
Week 8: Novemeber 3rd 2015
To implement the separate and individual routines of all the different occupations I needed a way to store them. So I decided to make an object called RoutineEntry it contains information on the start and end time of the routine, the location, the type of action, the priority and if that action is done or not. Using this object should be flexible enough to create some routines that seem complex. I considered saving function for an action instead of just a location, this might make it easier to encapsulate more complex behaviours. However I want to make each RoutineEntry as simple as possible, so I went for the former implementation.
For the time being I only plan to have two "types" those being visit and stay. The visit routines are considered done once the NPC has reached the location, the stay routines are never done, the NPC only stops performing them when the end time is passed. However I can see this as a future issue if an NPC needs to be interrupted from a "stay" routine they are currently performing. If prioritizing works correctly there's a chance I can add a higher priority routine to their schedule while the game is running and the NPC will switch to that instead. This needs testing though.
To briefly explain how the NPC get their routine's populated, when they go to sleep a hard coded routine from their corresponding occupations are added to the next days routine, and the routine for the previous day is cleared. It would be nice to save the history of each individuals previous routines, however I think this out of the scope for this project currently.
In hindsight clearing and populating the next day's routine when an NPC goes to sleep is a bit naive. Obviously I want the variety of some NPCs to stay up all night and not get any sleep. With the current implementation this would have the interesting side effect of the NPC taking the next day off from any form of work, if they didn't go to sleep.
For the time being I only plan to have two "types" those being visit and stay. The visit routines are considered done once the NPC has reached the location, the stay routines are never done, the NPC only stops performing them when the end time is passed. However I can see this as a future issue if an NPC needs to be interrupted from a "stay" routine they are currently performing. If prioritizing works correctly there's a chance I can add a higher priority routine to their schedule while the game is running and the NPC will switch to that instead. This needs testing though.
To briefly explain how the NPC get their routine's populated, when they go to sleep a hard coded routine from their corresponding occupations are added to the next days routine, and the routine for the previous day is cleared. It would be nice to save the history of each individuals previous routines, however I think this out of the scope for this project currently.
In hindsight clearing and populating the next day's routine when an NPC goes to sleep is a bit naive. Obviously I want the variety of some NPCs to stay up all night and not get any sleep. With the current implementation this would have the interesting side effect of the NPC taking the next day off from any form of work, if they didn't go to sleep.
Subscribe to:
Comments (Atom)