using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; [DefaultExecutionOrder(10)] [RequireComponent(typeof(InstantiatePrefab))] public class WanderingAI_LeaveMarket : MonoBehaviour { [Header("Only for Debugging (do not set from here)")] public Transform[] waypoints; public List waypointsList; //public float[] wanderTimer; [Header("Scattering around the target")] public float targetScattering = 2.0f; //Streuung [Header("Leave Settings")] public float leaveTimer = 120f; public Vector3 leavePosition; // Settings Leave Market private bool leaveMarket; // Settings Humans private Vector3[][] target; private int[][] humansPrio; // Settings Waypoints and Waiting Time private int maxWPCount; private List> waitingTimer; private List> waitingTimerList; private List waitingList; private float[] currentWait; private int countFilling = 0; // Time private float[] timer; private float globalTimer; private GameObject[][] humansGO; private NavMeshAgent[][] humansNMA; private Animator[][] humansA; private const string isWalking = "isWalking"; void Start() { // Get initialized Variables from InstantiatePrefab humansGO = gameObject.GetComponent().humanGameObject; humansNMA = gameObject.GetComponent().humanNavMeshAgent; humansA = gameObject.GetComponent().humanAnimator; //wanderTimer = gameObject.GetComponent().wanderTimer; waypoints = gameObject.GetComponent().waypointsArray; humansPrio = gameObject.GetComponent().humanPriorities; // Set length of variables to humanGo.Length target = new Vector3[humansGO.Length][]; timer = new float[humansGO.Length]; // Create copies waypointsList = new List(waypoints); waitingTimer = new List>(gameObject.GetComponent().waitingTimer); //waitingTimerList = new List>(gameObject.GetComponent().waitingTimer); waitingList = new List(waitingTimer[0]); currentWait = new float[humansGO.Length]; waitingTimerList = new List>(); foreach (List timerList in waitingTimer) { waitingTimerList.Add(new List(timerList)); } maxWPCount = waypointsList.Count; // Initialize size and content for (int i = 0; i < humansGO.Length; i++) { target[i] = new Vector3[humansGO[i].Length]; timer[i] = 0; } globalTimer = 0; } // To ensure that all market stalls (WPs) are visited, all entries in the WP-list are first edited in a random order // and each edited one is appended to the end of the list so that all unedited ones can be selected by decreasing the range. // Since the order of the WP-list is the same as the first dimension of the waitingTimer (WT) matrix, // the WT matrix can be edited with the same index as for the selection of the WP. So the arrangement is always the same. // The second dimension (the times of the individual market stalls) of the WT matrix is first completely emptied each time and then refilled. // After the leaving time is reached all humans sets their destination outside the market, iff the WPs with their timers are visited completely. private void FixedUpdate() { if (waypoints.Length == 0) return; // Leave Market if time is up if(globalTimer >= leaveTimer) leaveMarket = true; for (int i = 0; i < humansGO.Length; ++i) { // Set a new destination if the waiting time is over if (timer[i] >= currentWait[i]) { // Let the class know that the first iteration is finished if (countFilling >= waitingTimer.Count) gameObject.GetComponent().startWriting = true; for (int j = 0; j < humansGO[i].Length; ++j) { if (humansNMA[i][j].isOnNavMesh) { // if all WTs are processed and leaving time is reached, then leave market if (countFilling >= waitingTimer.Count && leaveMarket) { Vector3 endTarget = leavePosition; if (humansNMA[i][j].isActiveAndEnabled && Vector3.Distance(humansNMA[i][j].destination, endTarget) > 0.01f) humansNMA[i][j].SetDestination(endTarget); humansA[i][j].SetBool(isWalking, humansNMA[i][j].velocity.magnitude > 0.01f); if (leaveMarket && (Vector3.Distance(humansNMA[i][j].transform.position, endTarget) <= humansNMA[i][j].stoppingDistance)) { humansGO[i][j].SetActive(false); } } // Set new destination iff the human reached his destination or (only in first iteration) target vector is zero else if (target[i][j] == Vector3.zero || humansNMA[i][j].velocity.magnitude <= 0.01f || humansNMA[i][j].remainingDistance <= humansNMA[i][j].stoppingDistance || humansNMA[i][j].pathStatus == NavMeshPathStatus.PathComplete || !humansNMA[i][j].hasPath) { // Reset countFilling to start a new iteration if (countFilling >= waitingTimer.Count && !leaveMarket) countFilling = 0; // Random index for waypointsList if (maxWPCount <= 0) maxWPCount = waypointsList.Count; int currentWPIndex = Random.Range(0, maxWPCount); target[i][j] = CheckTarget(waypointsList[currentWPIndex].transform.position, targetScattering); // Set destination and animation if (target[i][0].x != float.PositiveInfinity) { // Random index for waitingTimer int currentWaitingIndex = Random.Range(0, waitingTimer[currentWPIndex].Count); currentWait[i] = waitingTimer[currentWPIndex][currentWaitingIndex]; RemoveIndexFromWaitingTimer(currentWPIndex, currentWaitingIndex); // Fill waiting timer with default, if it is empty, to get a new waiting time for the next iteration if (waitingTimer[currentWPIndex].Count <= 0) { //waitingTimer[currentWPIndex] = new List(waitingList); waitingTimer[currentWPIndex] = new List(waitingTimerList[currentWPIndex]); countFilling++; // to check if ALL waitingTimer entries are processed // countFilling >= waitingTimer.Count -> All WP and WT entries are processed, the waitingTimer is filled for next iteration } if (humansNMA[i][j].isActiveAndEnabled) humansNMA[i][j].SetDestination(target[i][j]); humansA[i][j].SetBool(isWalking, humansNMA[i][j].velocity.magnitude > 0.01f); // Append the current WP and the waiting time from both lists to the end to ensure that all WPs and waiting timers are visited at the frequency indicated, // afterwords decrement the max length by 1 AppendIndexToEndWP(currentWPIndex); // Waypoints AppendIndexToEndWT(currentWPIndex); // Waiting Timer which is modified AppendIndexToEndWTDefault(currentWPIndex); // Default Waiting Timer, with all the times for each WP maxWPCount--; timer[i] = 0; } } } } } // Wait until the waiting time is over else { for (int j = 0; j < humansGO[i].Length; ++j) { // Set walking state and priority; increment timer humansA[i][j].SetBool(isWalking, humansNMA[i][j].velocity.magnitude > 0.01f); if (humansNMA[i][j].remainingDistance <= humansNMA[i][j].stoppingDistance) timer[i] += Time.deltaTime; // if humans are not walking, then set priority to 0 so that the other humans dont disturb them if (humansNMA[i][j].velocity.magnitude <= 0.01f) humansNMA[i][j].avoidancePriority = 0; else humansNMA[i][j].avoidancePriority = humansPrio[i][j]; } } } globalTimer += Time.deltaTime; } public static Vector3 CheckTarget(Vector3 target, float dist) { Vector3 modifiedTarget = Random.insideUnitSphere * dist + target; // SamplePosition also checks the y axis. However, this is not relevant for me, so valid positions (x & z) are also discarded. // 0.0f is the only valid entry for y modifiedTarget = new Vector3(modifiedTarget.x, 0f, modifiedTarget.z); NavMeshHit navHit; NavMesh.SamplePosition(modifiedTarget, out navHit, 1.0f, -1); return navHit.position; } public void AppendIndexToEndWP(int index) { waypointsList.Add(waypointsList[index]); waypointsList.RemoveAt(index); } public void AppendIndexToEndWT(int index) { waitingTimer.Add(waitingTimer[index]); waitingTimer.RemoveAt(index); } public void AppendIndexToEndWTDefault(int index) { waitingTimerList.Add(waitingTimerList[index]); waitingTimerList.RemoveAt(index); } public void RemoveIndexFromWaitingTimer(int i, int removeIndex) { waitingTimer[i].RemoveAt(removeIndex); } private void LateUpdate() { for (int i = 0; i < humansGO.Length; ++i) { for (int j = 0; j < humansGO[i].Length; ++j) { humansA[i][j].speed = 0.5f + (humansNMA[i][j].velocity.magnitude / gameObject.GetComponent().speedMinMax.y); } } } }