Unity does not update NavMeshAgent destination in coroutine - c#

I'm working on an AI script in Unity 5 mostly constructed of coroutines to move around an AI object in my game. It actually works pretty well and is frame independent that way. I'm trying to avoid cluttering the Update function of the class.
However, I'm trying to create a function called wander, where the AI hangs around and randomly chooses a couple of Vector3s in the area of the waypoint and travel to each of them. After that the AI should go to the next waypoint and do the same thing into infinity basically. Collision detection and all that sort is for later parts. I want to get the navigating part fixed first.
void Start(){
agent = GetComponent<NavMeshAgent> ();
collisionRange = GetComponent<CapsuleCollider> ();
collisionRange.radius = detectionRadius;
if (waypoints.Length > 0) {
StartCoroutine (patrol ());
} else {
StartCoroutine (idle (idleTime));
}
}
IEnumerator patrol(){
agent.SetDestination(waypoints[waypointIndex].position);
Debug.Log ("Patrol started, moving to " + agent.destination);
while (agent.pathPending) {
Debug.Log ("not having path");
yield return null;
}
Debug.Log ("I have a path, distance is " + agent.remainingDistance);
while (float.Epsilon < agent.remainingDistance) {
//Debug.Log ("Moving...");
yield return null;
}
StartCoroutine (nextWaypoint ());
}
IEnumerator idle(int time){
Debug.Log ("Idleing for "+ time + " seconds");
agent.Stop ();
yield return new WaitForSeconds(time);
if(waypoints.Length > 2){
agent.Resume ();
StartCoroutine(patrol());
}
}
IEnumerator wander(){
agent.Stop ();
Debug.Log ("Going to look around here for a while.");
Vector3[] points = new Vector3[wanderPoints];
for (int i = 0; i < wanderPoints; i++) {
agent.Stop ();
Vector3 point = Random.insideUnitSphere * wanderRadius;
point.y = transform.position.y;
points [i] = point;
}
agent.ResetPath ();
agent.SetDestination(points[0]);
agent.Resume ();
Debug.Log ("point: " + points [0]);
Debug.Log ("Destination: " + agent.destination);
while (float.Epsilon < agent.remainingDistance) {
Debug.Log ("Moving...");
yield return null;
}
//StartCoroutine(patrol());
yield return null;
}
IEnumerator nextWaypoint(){
Debug.Log ("Arrived at my waypoint at " + transform.position);
if (waypointIndex < waypoints.Length -1) {
waypointIndex +=1;
} else {
waypointIndex = 0;
}
StartCoroutine(wander ());
yield return null;
}
If I swap the wander function with the idle function in nextWaypoint, everything works as expected, but this bit will never work:
agent.ResetPath ();
agent.SetDestination(points[0]);
agent.Resume ();
Debug.Log ("point: " + points [0]);
Debug.Log ("Destination: " + agent.destination);
This is a bit of test code (manually setting only 1 position to go point[0], but it never will travel to that destination. SetDestination will never get updated to the points I want to set them. I've tried calculating paths (NavMeshPath) up front and everything but the destination path will not change or reset weirdly enough. I've also had the while (float.Epsilon < agent.remainingDistance) loop in the wander function as well, but with no luck since it will remain in that loop forever since there's no path to go to.
I might be missing something here, or my logic is wrong in this case. Hopefully someone could give me a little push or maybe some extra debugging options, because I have no idea why the destination doesn't get updated in my wander function.

Consider using triggers on waypoints instead, as this will be even less taxing for the system. Below is an example that will allow you to have a variable number of waypoints easily, by getting all WayPoint type children from a parent transform. The only Coroutine that is used is for idle as you said you didn't want your Update function cluttered.
I've also exposed a couple of additional variables, including a min and max idle time, and a patrol chance, which should be set to a value less than or equal to 1, representing a percentage chance of patrolling vs idle. You can also choose a min and max number of patrol points for the agent to navigate to.
In RandomActivity you can also see that there is error handling for infinitely idle (no WayPoint children under parent). The agent will also not include the current Waypoint in it's list of points to navigate to, and will not navigate to a point already navigated to during it's current patrol (every time an element is added to m_targets, it's removed from m_availableTargets).
AgentController.cs
[RequireComponent(typeof(NavMeshAgent))]
public class AgentController : MonoBehaviour
{
[SerializeField]
private Transform m_waypointParent;
[SerializeField]
private float m_minIdle;
[SerializeField]
private float m_maxIdle;
[SerializeField]
private float m_patrolChance;
[SerializeField]
private int m_minPatrolPoints;
[SerializeField]
private int m_maxPatrolPoints;
private Waypoint[] m_waypoints;
private List<Waypoint> m_availableTargets;
private List<Waypoint> m_targets;
private Waypoint m_tempWaypoint;
private int m_currentTargetIndex;
private NavMeshAgent m_navMeshAgent;
public void Start()
{
m_waypoints = m_waypointParent.GetComponentsInChildren<Waypoint>();
m_targets = new List<Waypoint>();
m_navMeshAgent = GetComponent<NavMeshAgent>();
RandomActivity();
}
private void RandomActivity()
{
if (m_waypoints.Length == 0)
{
Debug.Log("Enemy will idle forever (no waypoints found)");
StartCoroutine(Idle(Random.Range(m_minIdle, m_maxIdle)));
return;
}
if(Random.Range(0f, 1f) <= m_patrolChance)
{
//Available waypoints
m_availableTargets = new List<Waypoint>(m_waypoints);
//Remove currentpoint
if(m_targets.Count > 0)
m_availableTargets.Remove(m_targets[m_targets.Count - 1]);
//Reset list
m_targets.Clear();
m_currentTargetIndex = -1;
//Add patrol points
for (int i = 0; i < Random.Range(m_minPatrolPoints, m_maxPatrolPoints + 1); i++)
{
m_tempWaypoint = m_availableTargets[Random.Range(0, m_availableTargets.Count)];
m_targets.Add(m_tempWaypoint);
m_availableTargets.Remove(m_tempWaypoint);
}
NextWaypoint(null);
}
else
StartCoroutine(Idle(Random.Range(m_minIdle, m_maxIdle)));
}
public void NextWaypoint(Waypoint p_waypoint)
{
//Collided with current waypoint target?
if ((m_currentTargetIndex == -1) || (p_waypoint == m_targets[m_currentTargetIndex]))
{
m_currentTargetIndex++;
if (m_currentTargetIndex == m_targets.Count)
RandomActivity();
else
{
Debug.Log("Target: " + (m_currentTargetIndex + 1) + "/" + m_targets.Count + " (" + m_targets[m_currentTargetIndex].transform.position + ")");
m_navMeshAgent.SetDestination(m_targets[m_currentTargetIndex].transform.position);
}
}
}
private IEnumerator Idle(float p_time)
{
Debug.Log("Idling for " + p_time + "s");
yield return new WaitForSeconds(p_time);
RandomActivity();
}
}
Note that for this to work, I have created a tag called Enemy so as to easily differentiate between enemies and any other tiggers in your game.
WayPoint.cs
[RequireComponent(typeof(BoxCollider))]
public class Waypoint : MonoBehaviour
{
public void OnTriggerEnter(Collider p_collider)
{
if (p_collider.tag == "Enemy")
p_collider.GetComponent<AgentController>().NextWaypoint(this);
}
}
I know this is an old post but hope this helps someone.

Related

UNITY - Crashes whenever I try to get a variable from another script

I have this simulation where the "Hunter" scans the area with the scannereyes object inside the hunter for an object called "Apple" and whenever it sees the apple its supposed to hunt after it. This is the same with other hunters from the same prefab as there are more hunters on the same platform.
The scanner eyes is supposed to send the data of the apple's position so the hunter can run for it. Ive tried putting the found apple object into static but that would resolve into every hunter hunting for the same apple so I tried instead doing this method:
Target = GetComponent<ScannerEyes>().foundobject.transform;
And instead of giving the Hunter the data it needed the whole engine just crashes whenever it tries to compile the C sharp code.
Yes everything is well updated and so is my computer. I have a strong enough computer to compile.
//In the Hunter CS file
void Update()
{
if (reproducerate < 1)
{
if (GameObject.FindGameObjectsWithTag("Food") != null ) //All apple objects have the tag "Food"
{
Target = GetComponent<ScannerEyes>().foundobject.transform;
directiontolookat = (Target.position - transform.position).normalized;
if ((transform.position - Target.position).magnitude > Distans)
{
transform.Translate(directiontolookat * Time.deltaTime * Speed);
}
}
}
}
public void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Food")
{
reproducerate += 1;
Destroy(collision.gameObject);
Debug.Log(reproducerate);
}
}
//In the ScannerEyes CS file:
public bool found = false;
public GameObject foundobject;
void Update()
{
//This is only for the scanning sphere to grow overtime cuz thats how scanners work
if (currentsize != maxsize && !found) {
transform.localScale = new Vector3(currentsize, currentsize, currentsize);
currentsize += scanningspeed;
}
if (found)
{
transform.localScale = new Vector3(0f, 0f, 0f);
}
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Food")
{
Debug.Log("WE FOUND IT");
foundobject = other.gameObject;
Debug.Log("found at" + foundobject.transform.position);
found = true;
}
}
//In the Food behaviour CS file (This is just to make it spawn in the world between -4, 4)
private GameObject foodobject;
public GameObject Foodprefab;
private float Xspawncoords;
private float Zspawncoords;
void Start()
{
Debug.Log("Food is working");
for (int i = 0; i < 2; i++){
Xspawncoords = Random.Range(-4, 4);
Zspawncoords = Random.Range(-4, 4);
Debug.Log(Xspawncoords + " and " + Zspawncoords);
foodobject = Instantiate(Foodprefab,
new Vector3(Xspawncoords, 1.4f, Zspawncoords),
Quaternion.identity);
foodobject.name = "AppleObject";
}
}
I don't understand if the engine crashes when you start the game or when you just save the scripts.
As far as I can see the Hunter checks that there is at least one GameObject with the "food" tag on it, but you are doing this in the Update() method.
This is very bad for performance since you are calling it every frame, and it's pretty expensive as it's constantly checking for every object in the scene.
You can try removing that call and move it in the Start() method, that would call the method only once!

How to execute multiple times a couroutine without stopping it?

I have an array of gameobjects that are lights, I'm trying to increase and drecrease range size of a point light over time, problem is lights some times doesn't decrease over time, they just go to range 0 instantly.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
public GameObject[] stars;
private void Start()
{
StartCoroutine(ChooseStar());
}
public void PlayGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
public void QuitGame()
{
Application.Quit();
}
IEnumerator IncreaseRadius(GameObject star, float duration)
{
Debug.Log("Increasing: "+star.name + " radius: " + star.GetComponent<Light>().range);
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
star.GetComponent<Light>().range = counter;
yield return null;
}
StartCoroutine(DecreaseRadius(star));
}
IEnumerator DecreaseRadius(GameObject star)
{
Debug.Log("Decreasing: "+star.name+" radius: "+ star.GetComponent<Light>().range);
float counter = star.GetComponent<Light>().range;
while (star.GetComponent<Light>().range >= 0f)
{
counter -= Time.deltaTime;
star.GetComponent<Light>().range = counter;
yield return null;
}
star.GetComponent<Light>().range = 0f;
}
IEnumerator ChooseStar()
{
float duration = Random.Range(3, 8);
float waitTime = 2f;
GameObject choosenStar = stars[Random.Range(0, stars.Length)];
if (choosenStar.GetComponent<Light>().range <= 0f)
{
StartCoroutine(IncreaseRadius(stars[Random.Range(0, stars.Length)], duration));
}
else
{
waitTime = 0f;
}
yield return new WaitForSeconds(waitTime);
StartCoroutine(ChooseStar());
}
}
the expected result should be this sequence:
1 - pick random star from array of gameobjects
2 - check if the star alredy is being range increased, if yes start again to search a new one if no starts to increase.
3 - light starts increasing until duration, then call decrease function
4 - star starts to decrease, when function is over it reset range to 0
To answer the question in general: You can simply put a
while (true)
{
...
yield return ...
}
around your code. As long as you yield somewhere inside it that's totally valid for Coroutines.
My guess would be that you get concurrent Coroutines because you don't wait for IncreaseRadius to finish before choosing the next random star ... which could be the same as before.
if (chosenStar.range <= 0f)
{
StartCoroutine(IncreaseRadius(stars[Random.Range(0, stars.Length)], duration));
}
else
{
waitTime = 0f;
}
also you do a Random.Range here again although you have already choosen another star before, was this intended?
First in general instead of using GetComponent<Light> all the time over and over again rather simply make
public Light[] stars;
reference the object just in the same way as before but now you are directly dealing with the Light references instead of GameObject.
Then you know that
float duration = Random.Range(3, 8);
actually returns random full int values between 3 and 7. If you rather wanted to have float values also between 3 and 8 so also including e.g. 3.253453f then you should rather use
var duration = Random.Range(3.0f, 8.0f);
Solution 1 - Only one star at a time
As simple alternative you could always animate only one star at a time. You can achieve this by yield return another IEnumerator. That make the other IEnumerator execute and at the same time waits for it to finish. Something like
public Light[] stars;
private void Start()
{
StartCoroutine(ChooseStar());
}
private IEnumerator IncreaseRadius(Light star, float duration)
{
Debug.Log("Increasing: " + star.name + " radius: " + star.range);
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
star.range = counter;
yield return null;
}
// again do the decreasing and at the same time wait for it to finish
yield return DecreaseRadius(star);
}
private static IEnumerator DecreaseRadius(Light star)
{
Debug.Log("Decreasing: " + star.name + " radius: " + star.range);
var counter = star.range;
while (star.range >= 0f)
{
counter -= Time.deltaTime;
star.range = counter;
yield return null;
}
star.range = 0f;
}
IEnumerator ChooseStar()
{
// Looks scary but is totally fine in Coroutines as long as you yield somewhere
// instead of starting a new Coroutine simple continue the one you already have
while (true)
{
var duration = Random.Range(3.0f, 8.0f);
var choosenStar = stars[Random.Range(0, stars.Length)];
// This starts the Increase routine on that star
// and at the same time waits for it to finish!
//
// since we also wait until DecreaseRadius is done this means
// at any time only exactly 1 star is animated at the same time
yield return IncreaseRadius(choosenStar, duration);
}
}
Solution 2 - Filter the random
Alternatively as it looks like you want to allow parallel animations of the stars I would simply filter out the List of available stars (ones that are not currently animated) for getting the random range. Something like
public Light[] stars;
// Use a list for dynamically adding and removing items
private List<Light> availableStars = new List<Light>();
private void Start()
{
// initialize the available list
// copy the references from stars
availableStars.AddRange(stars);
StartCoroutine(ChooseStar());
}
private IEnumerator IncreaseRadius(Light star, float duration)
{
Debug.Log("Increasing: " + star.name + " radius: " + star.range);
// As soon as you start animating this star
// remove it from the list of availables
availableStars.Remove(star);
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
star.range = counter;
yield return null;
}
// Decreasing and at the same time wait for it to finish
yield return DecreaseRadius(star);
// when finished add the star again to the availables
availableStars.Add(star);
}
private static IEnumerator DecreaseRadius(Light star)
{
Debug.Log("Decreasing: " + star.name + " radius: " + star.range);
var counter = star.range;
while (star.range >= 0f)
{
counter -= Time.deltaTime;
star.range = counter;
yield return null;
}
star.range = 0f;
}
IEnumerator ChooseStar()
{
// Looks scary but is totally fine in Coroutines as long as you yield somewhere
while (true)
{
var duration = Random.Range(3.0f, 8.0f);
// in case that currently all stars are being animated
// simply wait until the next one becomes available again
yield return new WaitUntil(() => availableStars.Count > 0);
// Pick a random star from the availables instead
var chosenStar = availableStars[Random.Range(0, availableStars.Count)];
// this check becomes then actually redundant
//if (chosenStar.range <= 0f)
//{
StartCoroutine(IncreaseRadius(chosenStar, duration));
yield return new WaitForSeconds(2f);
//}
}
}

Unity Coroutine not working across scenes

I am calling a Coroutine shown below, which is attached to a DontDestroyOnLoad objects that persists across scenes.
IEnumerator InitMaxScore()
{
Debug.Log("will wait for some time");
yield return new WaitForSeconds(1);
GameObject[] coins = GameObject.FindGameObjectsWithTag("Coin");
Debug.Log("coins length == " + coins.Length);
if (coins.Length > 0)
{
maxScoreInitialized = true;
maxScore = score + coins.Length * 10;
foreach (GameObject healthPickup in GameObject.FindGameObjectsWithTag("Health"))
{
maxScore += healthPickup.GetComponent<Pickups>().pointsForLifePickup;
}
Debug.Log("maxScore inti == " + maxScore);
}
yield return null;
}
This Coroutine is called in the OnLevelWasLoaded event of the said gameobject which is set to DontDestroyOnLoad on awake as shown below.
private void Awake()
{
int numGameSessions = FindObjectsOfType<GameSession>().Length;
if (numGameSessions > 1)
{
Destroy(gameObject);
}
else
{
DifficultyManagement.setDifficulty(Difficulty.One); // start the game with diff one always
DontDestroyOnLoad(this.gameObject);
}
}
While the log "will wait for some time" in the Coroutine is getting printed, Debug.Log("coins length == " + coins.Length) is not getting printed all the times. I am certainly not destroying the said gameobject for the entire duration of my game that might have caused the Coroutine to behave this way. The behaviour is not consistent either, sometimes it works, sometimes it does not, and I am like why can't you make up your mind.
I have been banging my head on this for a long time and could not seem to fix this, any leads would be appreciated to lift my mental block :/
OnLevelWasLoaded is deprecated, consider using sceneLoaded event:
using UnityEngine.SceneManagement;
private void Awake()
{
int numGameSessions = FindObjectsOfType<GameSession>().Length;
if (numGameSessions > 1)
{
Destroy(gameObject);
}
else
{
DifficultyManagement.setDifficulty(Difficulty.One); // start the game with diff one always
DontDestroyOnLoad(this.gameObject);
SceneManager.sceneLoaded += (scene, mode) => StartCoroutine(InitMaxScore());
}
}

Not cycling through an array as expected

I'm trying to get the player to continue on after he jumps (see code), but what he does is return back to the jumpPosition transform without continuing on to the next point.
Here is what it is doing in Unity:
...
Here is my code for playerMovment:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class MovementController : MonoBehaviour
{
// public GameObject playerToMove; // not sure why I need this
public float moveSpeed; // move speed of the player going from player postion to current point, possible to use somewhere else
private Transform currentPoint; // used to determine where the next point the player has to move by cycling through 'points' array
public Transform jumpPoint; // used as a location trigger to tell the player when to jump -- will be attempting to make into an array
public Transform crouchPoint; // used as a location trigger to tell the player when to crounch -- will be attempting to make into an array
public Transform[] points; // an array of location for the 'currentPoint' to cycle through
public float maxPause = 100; // used to determine the length of time between when the player arrives at the 'currentPoint' and when to leave said point; default = 100
public float reducedPause = 2; // used to set 'maxPause' to a smaller number so that player won't keep jumping/crouching
public TestCharacterController2D controller; // acceses the TestCharacterController2D script (I didnt write this script but plan to modiify) used for basic move, jump, and crouch funtions
public Animator animator; // my attempt to find the player's animator
public bool isRight; // used to to determine which way the character is facing -- I think this can be accesed through the 'controller' variable (TestCharacterController2D script)
private bool jump; // to tell the 'controller' when to jump
private bool crouch; // to tell the 'controller' when to crouch
private bool pause = false; // used to determine when the player arrives at the 'currentPoint' and the 'maxPause' countdown begins
public int pointsSelection; // used to cycle the 'points' array when maxPause cycle is over and player is at current point
// public float jumpHeight = 100f; // not sure why used
void Start() // looking into 'onAwake' maybe? (or others)
{
currentPoint = points[pointsSelection]; // sets currentPoint to default location ('pointSelection' is 'publc' so can be modified in Unity
isRight = true; // player starts facing right -- as per character animations
}
void Update() // not sure if should have more in 'FixedUpdate' or others (maybe?)
{
jump = false;
if (Vector2.Distance(transform.position, currentPoint.position) < 0.05f)
// checks to see if player is at 'currentPoint'
{
pause = true; // starts the pause sequenece
Debug.Log("Pause = " + pause);
if (pause) // when the movement is pause do the the following
{
moveSpeed = 0;
animator.SetFloat("Speed", 0); // player stops moving -- works!
if (maxPause <= 100) // checks to see if still paused
{
Debug.Log("this is maxPause: " + maxPause);
if (maxPause < 0) // found 'maxPause' was going to far below zero
maxPause = 0;
maxPause--; // reduce pause amount (working way out of loop)
}
if (maxPause == 0) // when 'maxPause' timer has finished
{
pointsSelection++; // move to next point
maxPause = 100; // reset 'maxPause' timer
pause = false; // resume 'transform.position == currentPoint.position' process
}
}
if (pointsSelection == points.Length) // makes sure 'pointsSelection' doesn't go out of bounds
{
Debug.Log("at end of array");
pointsSelection = 0; // start the player's movement process over again
}
}
else // not sure if requried
{
Debug.Log("pause = false");
Debug.Log("this is the moveSpeed " + moveSpeed);
Debug.Log("pointsSelection: " + pointsSelection);
}
if (Vector2.Distance(transform.position, jumpPoint.position) < 0.05f && jumpPoint == currentPoint) // conditions for the jump action (automatic) -- I fell the whole thing needs to be more elaborate ** WORK IN PROGRESS **
{
jump = true;
}
else
jump = false;
currentPoint = points[pointsSelection]; // moved to line 130 -- not sure if better here
}
void FixedUpdate()
{
if (isRight && transform.position.x > currentPoint.position.x) // flipping the character -- I'm pretty sure I can use TestCharacterController2D to do this for me, this is comparing the player's 'transform'
{
moveSpeed = -0.25f; // tells controller to head in the left direction
isRight = false; // no longer facing right
}
if (!isRight && transform.position.x < currentPoint.position.x) // reverse of above
{
moveSpeed = 0.25f; // tells controller to head in the right direction
isRight = true; // no longer facing left
}
if (moveSpeed > 0 || moveSpeed < 0)
animator.SetFloat("Speed", 1); // player starts PlayerRun animation -- works!
// Move our character
controller.Move(moveSpeed, crouch, jump); // draws from the TestCharacterController2D script
}
public void OnLanding()
{
animator.SetBool("Jumped", false);
}
}

How do I get a component from a ever changing List of objects?

while working in a 3d endless runner game in unity I came across this issue. I have a List of platforms(segments/roads) that lay in front of the player while the player runs in z direction. I have downloaded a new asset package called Dreamteck splines. So each platform has a spline component attached to it. Once a platform is laid the player grabs the spline and runs according to the pattern of the spline.
Let's say that the player is on the first platform. When the player reaches the end of the first platform's spline, the OnEndReached() event handler is called, which basically says what you want to happen when the spline's endpoint is reached. So I want to know how to I get the next spline once the end is reached.
P = player
As seen in the image above this is what I am trying to accomplish. As a brief description of how platforms are laid is that once the player goes to the next road the one he just passed gets disabled so next time he can reuse the road in front of the player in random manner.
The code: track manager script.
public Segment[] tilePrefabs;
public static Segment newSegment;
public static List<Segment> m_Segments;
public static List<Segment> m_PastSegements;
private int m_SafeSegmentLeft;
private int m_PreSegments = -1;
private float startingSegmentDistance = 4f;
private int startingSafeSegments = 2;
private int amtSegmentsOnScreen = 10;
private float segmentRemovalDistace = -40f;
private float m_TotalWorldDistance;
private float m_CurrentSegmentDistance;
void Update ()
{
while (m_Segments.Count < amtSegmentsOnScreen)
{
SpawnNewSegment();
}
m_TotalWorldDistance += scaledSpeed;
m_CurrentSegmentDistance += scaledSpeed;
if (m_CurrentSegmentDistance > m_Segments[0].worldLength)
{
m_CurrentSegmentDistance -= m_Segments[0].worldLength;
m_PastSegements.Add(m_Segments[0]);
m_Segments.RemoveAt(0);
}
Vector3 currentPos;
Quaternion currentRot;
Transform playerTransform = playerMotor.transform;
m_Segments[0].GetPointAtInWorldUnit(m_CurrentSegmentDistance, out currentPos, out currentRot);
bool needRecenter = currentPos.sqrMagnitude > floatingOriginThreshold;
if (needRecenter)
{
int count = m_Segments.Count;
for (int i = 0; i < count; i++)
{
m_Segments[i].transform.position -= currentPos;
}
count = m_PastSegements.Count;
for (int i = 0; i < count; i++)
{
m_PastSegements[i].transform.position -= currentPos;
}
m_Segments[0].GetPointAtInWorldUnit(m_CurrentSegmentDistance, out currentPos, out currentRot);
}
playerTransform.rotation = currentRot;
playerTransform.position = currentPos;
for (int i = 0; i < m_PastSegements.Count; i++)
{
if ((m_PastSegements[i].transform.position - currentPos).z < segmentRemovalDistace)
{
m_PastSegements[i].Cleanup();
m_PastSegements.RemoveAt(i);
i--;
}
}
}
public void SpawnNewSegment()
{
int useSegment = Random.Range(0, tilePrefabs.Length);
if (useSegment == m_PreSegments)
{
useSegment = (useSegment + 1) % tilePrefabs.Length;
}
Segment segmentToUse = tilePrefabs[useSegment];
newSegment = Instantiate(segmentToUse, Vector3.zero, Quaternion.identity);
Vector3 currentExitPoint;
Quaternion currentExitRotation;
if (m_Segments.Count > 0)
m_Segments[m_Segments.Count - 1].GetPointAt(1.0f, out currentExitPoint, out currentExitRotation);
else
{
currentExitPoint = transform.position;
currentExitRotation = transform.rotation;
}
newSegment.transform.rotation = currentExitRotation;
Vector3 entryPoint;
Quaternion entryRotation;
newSegment.GetPointAt(0.0f, out entryPoint, out entryRotation);
Vector3 pos = currentExitPoint + (newSegment.transform.position - entryPoint);
newSegment.transform.position = pos;
newSegment.manager = this;
newSegment.transform.localScale = new Vector3((Random.value > 0.5f ? -1 : 1), 1, 1);
newSegment.objectRoot.localScale = new Vector3(1.0f / newSegment.transform.localScale.x, 1, 1);
if (m_SafeSegmentLeft <= 0)
SpawnObstacle(newSegment);
else
m_SafeSegmentLeft -= 1;
m_Segments.Add(newSegment);
}
The player script
//Current tile segment;
private Segment currentSegment;
//Spline Follower
private SplineFollower follower
//For Dreamteck spline -->
private Segment nextSegment;
void Start()
{
playerCollider = GetComponent<CapsuleCollider>();
anim = GetComponent<Animator>();
follower = GetComponent<SplineFollower>();
moveLane = currentLane;
follower.onEndReached += Follower_onEndReached;
}
private void Follower_onEndReached()
{
currentSegment = nextSegment;
follower.computer = currentSegment.spline;
}
void OnTriggerEnter(Collider col)
{
nextSegment = col.GetComponentInParent<Segment>();
}
The segment script : Attached to each road/ platform
public SplineComputer spline;
public static Segment next;
SplinePoint[] points;
void Start()
{
spline = GetComponentInChildren<SplineComputer>();
spline.space = SplineComputer.Space.Local;
points = spline.GetPoints();
if (points.Length == 0)
return;
}
At the moment I use colliders, each road has a box collider component. Once the player reach end of the platform it does get the next spline component. It works but sometimes it fails to recognize the next spline and use the same spline which causes the player to run the same platform that he passed again and again.
So I'm out of ideas. So came here to find a solution or advice. Help would be appreciated.
In this case I would simply store my possible segments in a List then when I reached the end get the next segment and move the current 1st segment to the end or where ever you want to move it in the list.
That is
public Segment currentSegment;
public List<Segment> segments;
void OnEndReached()
{
//Put the completed segmetn back in the list ... probably at the end or randomized anywhere but at the start
segments.Insert(segments.Count-1, currentSegment);
//Now get the next segment
currentSegment = segments[0];
segments.RemoveAt(0);
}
In this model you have a simple List which represents the order your segments will appear in, you always set the current segment to the next in the list e.g. index 0 and you put them back in the list when done... putting them at the end or if you want to randomize order slotting them in anyware except index 0 e.g.
segments.Insert(UnityEngine.Random.Range(1, segments.Count), currentSegment);
Note that I removed the segment I am on from the list ... the list just represents the upcoming order which in runner games I find it handy to know that e.g. so I can reset things, change attributes of the segments based on performance, score, etc.
Using OnTriggerEnter, or OnTriggerEnter2D if you're dealing with 2D colliders, should do the job. But as you say you already are working with colliders, I assume this is what you have tried.
You could try:
OnTriggerStay
A Raycast down to the ground object. In this link is a 2D Example: https://kylewbanks.com/blog/unity-2d-checking-if-a-character-or-object-is-on-the-ground-using-raycasts
You can also raycast with 3D objects.
What you would be using it for in this case is basically to shoot a "laser" into the ground below your player and grab an object there based on which layers you tell it to hit. So if you have a layer called "Ground" which your platform is part of, then it can only return objects from that layer.
Just remember to raycast often so it's updated to reflect the game.

Categories