Finding the closest target to me in Unity - c#

I have a player class in Unity which all works fine except for my ClosestTarget function. The function works how I wanted it to but now it only ever picks Cube 2 (The last element in the list) even if I'm closer to another cube.
The class is as shown:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Player : MonoBehaviour
{
public int health; //Current health
public int stamina; //Current stamina
public int maxHealth = 100; //Constant for max health
public int maxStamina = 500; //Constant for max stamina
protected CharacterController chCtrl; //Reference to the character controller
protected CharacterMotor chMotor; //Reference to the character motor
public float walkSpeed = 3; //Speed at which the player walks
public float runSpeed = 20; //Speed at which the player runs
public bool isWalking = false; //Check for whether the player is walking
public bool isRunning = false; //Check for whether the player is running
public bool isFatigued = false; //Check for whether the player is fatigued
public List<Transform> targets; //Create a list of transforms
public Transform currentTarget;
float distance = Mathf.Infinity;
// Use this for initialization
void Start ()
{
//Get the character controller assigned to the current game object
chCtrl = GetComponent<CharacterController> ();
//Get the character motor assigned to the current game object
chMotor = GetComponent<CharacterMotor> ();
//Set the stamina to the max stamina
stamina = maxStamina;
//Set the health to the max health
health = maxHealth;
//Initialise the targets list
targets = new List<Transform>();
//Call the function to retrieve all buttons in the scene
AddAllButtons();
}
// Update is called once per frame
void Update ()
{
//Call the function to set the speed of the player
SetSpeed ();
ClosestTarget();
}
public void SetSpeed ()
{
//Set the player to walking speed by default
float speed = walkSpeed;
//If the stamina is less than or equal to 0
if (stamina <= 0)
{
//Set the player as fatigued
isFatigued = true;
//Set the player to walking
speed = walkSpeed;
//Set stamina to 0
stamina = 0;
}
//If the stamina is greater than or equal to max stamina
if(stamina >= maxStamina)
{
//Set the stamina to the max stamina
stamina = maxStamina;
//Set the player as not fatigued
isFatigued = false;
}
//If the player is moving along either the x or y axis
if (Input.GetAxis("Horizontal") !=0 || Input.GetAxis("Vertical") !=0)
{
//If the player is fatigued
if(isFatigued == true)
{
//Set the player to walking speed
speed = walkSpeed;
//Player is not running
isRunning = false;
//Player is walking
isWalking = true;
//Start increasing stamina
stamina++;
}
//If the player is touching the ground and the user is either pressing left shift or right shift
else if (chCtrl.isGrounded && Input.GetKey ("left shift") || Input.GetKey ("right shift") && isFatigued == false )
{
//Set the player to running speed
speed = runSpeed;
//Player is running
isRunning = true;
//Player is not walking
isWalking = false;
//Start reducting stamina
stamina--;
}
else
{
//Set the player to walking speed
speed = walkSpeed;
//Player is not running
isRunning = false;
//Player is walking
isWalking = true;
//Start increasing stamina
stamina++;
}
}
else
{
//Player is not running
isRunning = false;
//Player is not walking
isWalking = false;
//Start increasing stamina
stamina++;
}
//Set the players speed to either walkSpeed or runSpeed
chMotor.movement.maxForwardSpeed = speed;
}
void AddAllButtons()
{
//Create an array that contains all game objects tagged with 'button'
GameObject[] buttons = GameObject.FindGameObjectsWithTag("Button");
//For each of the game objects in the array
foreach(GameObject button in buttons)
{
//Add the transform of the button
AddButton(button.transform);
}
}
void AddButton(Transform button)
{
//Add the transform of the button into the targets list
targets.Add(button);
}
void ButtonCheck(Transform button)
{
Vector3 dir = (button.position - transform.position).normalized;
float direction = Vector3.Dot(dir, transform.forward);
Debug.Log(direction);
if(Input.GetKeyDown(KeyCode.E))
if(direction > 0.7F && Vector3.Distance(currentTarget.position, transform.position) < 2.0F)
{
print("Button has been clicked");
}
}
void ClosestTarget()
{
foreach (Transform button in targets)
{
Vector3 diff = (button.position - transform.position);
float curDistance = Vector3.Distance(transform.position, button.position );
Debug.Log(curDistance);
if (curDistance < distance )
{
currentTarget = button;
distance = curDistance;
}
}
}
}
As said, the problem is with the ClosestTargets function. It's finding the distance for every cube but it only ever selects the last element in the targets list.
It still shows the distance from every cube in the console.

My guess: distance is class member and correctly initialised to Mathf.Infinity but it's never reset. So on the first call to ClosestTarget () Cube2 might be the one closest to player as Player.Update might called later. So distance contains a value too tiny (0?) for other objects to have a chance.

I use this extension method to find the closest gameObject (warning it uses linq):
public static GameObject ReturnClosestObject(this GameObject go, float radius, LayerMask layerMask)
{
Collider[] closeObjects = Physics.OverlapSphere(go.transform.position, radius, layerMask);
closeObjects = closeObjects.OrderBy((collider) => Vector3.Distance(collider.gameObject.transform.position, go.transform.position)).ToArray();
GameObject returnedObject = null;
if(closeObjects.FirstOrDefault().Exist())
{
returnedObject = closeObjects.FirstOrDefault().gameObject;
}
return returnedObject;
}
The downside to this method is that it will only detect objects that overlapwith the projected sphere and miss objects that are inside it completly. So I will often compliment it with this recursive function that projects a finit amount of sphere at various distances.
Note while they have similier names, the above is a extension method and below is a instance method. They work together hand and hand.
public GameObject FindClosestGameObject(int startingLookUpDistance, int maxLookUpDistance, int numberOfSteps)
{
GameObject closestGameObject = gameObject.ReturnClosestObject(startingLookUpDistance, LayerMasks.gameObjects);
bool gameObjectNotFound = (closestGameObject.Equals(null));
if(gameObjectNotFound)
{
if(startingLookUpDistance <= maxLookUpDistance)
{
float stepDistance = maxLookUpDistance / numberOfSteps;
return FindClosestBuildingObject((int)(startingLookUpDistance + stepDistance), maxLookUpDistance, numberOfSteps);
}
return null;
}
else
{
return closestGameObject;
}
}
This top method works by specifying your starting distance and your max distance, you then specify how many iterations of the function you want to perform. Based on the number of iterations it will cast a n spheres equal distances apart in between your start and stop bounds.

Related

Unity Helping reading an AI Navigation Script

I've been looking at this AI Navigation script for three days (I'm a beginner) . Can someone tell me when (m_PlayerNear) is actually set to true. The only thing I can think of is that its set to true when the if condition is not met in Chasing() to set m_PlayerNear to false. I hope that made sense.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AIController : MonoBehaviour
{
public NavMeshAgent navMeshAgent; // Nav mesh agent component
public float startWaitTime = 4; // Wait time of every action
public float timeToRotate = 2; // Wait time when the enemy detect near the player without seeing
public float speedWalk = 6; // Walking speed, speed in the nav mesh agent
public float speedRun = 9; // Running speed
public float viewRadius = 15; // Radius of the enemy view
public float viewAngle = 90; // Angle of the enemy view
public LayerMask playerMask; // To detect the player with the raycast
public LayerMask obstacleMask; // To detect the obstacules with the raycast
public float meshResolution = 1.0f; // How many rays will cast per degree
public int edgeIterations = 4; // Number of iterations to get a better performance of the mesh filter when the raycast hit an obstacule
public float edgeDistance = 0.5f; // Max distance to calcule the a minumun and a maximum raycast when hits something
public Transform[] waypoints; // All the waypoints where the enemy patrols
int m_CurrentWaypointIndex; // Current waypoint where the enemy is going to
Vector3 playerLastPosition = Vector3.zero; // Last position of the player when was near the enemy
Vector3 m_PlayerPosition; // Last position of the player when the player is seen by the enemy
float m_WaitTime; // Variable of the wait time that makes the delay
float m_TimeToRotate; // Variable of the wait time to rotate when the player is near that makes the delay
bool m_playerInRange; // If the player is in range of vision, state of chasing
bool m_PlayerNear; // If the player is near, state of hearing
bool m_IsPatrol; // If the enemy is patrol, state of patroling
bool m_CaughtPlayer; // if the enemy has caught the player
void Start()
{
m_PlayerPosition = Vector3.zero;
m_IsPatrol = true;
m_CaughtPlayer = false;
m_playerInRange = false;
m_PlayerNear = false;
m_WaitTime = startWaitTime; // Set the wait time variable that will change
m_TimeToRotate = timeToRotate;
m_CurrentWaypointIndex = 0; // Set the initial waypoint
navMeshAgent = GetComponent<NavMeshAgent>();
navMeshAgent.isStopped = false;
navMeshAgent.speed = speedWalk; // Set the navemesh speed with the normal speed of the enemy
navMeshAgent.SetDestination(waypoints[m_CurrentWaypointIndex].position); // Set the destination to the first waypoint
}
private void Update()
{
EnviromentView(); // Check whether or not the player is in the enemy's field of vision
if (!m_IsPatrol)
{
Chasing();
}
else
{
Patroling();
}
}
private void Chasing()
{
// The enemy is chasing the player
m_PlayerNear = false; // Set false that hte player is near beacause the enemy already sees the player
playerLastPosition = Vector3.zero; // Reset the player near position
if (!m_CaughtPlayer)
{
Move(speedRun);
navMeshAgent.SetDestination(m_PlayerPosition); // set the destination of the enemy to the player location
}
if (navMeshAgent.remainingDistance <= navMeshAgent.stoppingDistance) // Control if the enemy arrive to the player location
{
if (m_WaitTime <= 0 && !m_CaughtPlayer && Vector3.Distance(transform.position, GameObject.FindGameObjectWithTag("Player").transform.position) >= 6f)
{
// Check if the enemy is not near to the player, returns to patrol after the wait time delay
m_IsPatrol = true;
m_PlayerNear = false;
Move(speedWalk);
m_TimeToRotate = timeToRotate;
m_WaitTime = startWaitTime;
navMeshAgent.SetDestination(waypoints[m_CurrentWaypointIndex].position);
}
else
{
if (Vector3.Distance(transform.position, GameObject.FindGameObjectWithTag("Player").transform.position) >= 2.5f) /////
// Wait if the current position is not the player position
Stop();
m_WaitTime -= Time.deltaTime;
}
}
}
private void Patroling()/////
{
if (m_PlayerNear)
{
// Check if the enemy detect near the player, so the enemy will move to that position
if (m_TimeToRotate <= 0)
{
Move(speedWalk);
LookingPlayer(playerLastPosition);
}
else
{
// The enemy wait for a moment and then go to the last player position
Stop();
m_TimeToRotate -= Time.deltaTime;
}
}
else
{
m_PlayerNear = false; // The player is no near when the enemy is platroling
playerLastPosition = Vector3.zero;
navMeshAgent.SetDestination(waypoints[m_CurrentWaypointIndex].position); // Set the enemy destination to the next waypoint
if (navMeshAgent.remainingDistance <= navMeshAgent.stoppingDistance)
{
// If the enemy arrives to the waypoint position then wait for a moment and go to the next
if (m_WaitTime <= 0)
{
NextPoint();
Move(speedWalk);
m_WaitTime = startWaitTime;
}
else
{
Stop();
m_WaitTime -= Time.deltaTime;
}
}
}
}
private void OnAnimatorMove()
{
}
public void NextPoint()
{
m_CurrentWaypointIndex = (m_CurrentWaypointIndex + 1) % waypoints.Length;
navMeshAgent.SetDestination(waypoints[m_CurrentWaypointIndex].position);
}
void Stop()
{
navMeshAgent.isStopped = true;
navMeshAgent.speed = 0;
}
void Move(float speed)
{
navMeshAgent.isStopped = false;
navMeshAgent.speed = speed;
}
void CaughtPlayer()
{
m_CaughtPlayer = true;
}
void LookingPlayer(Vector3 player)
{
navMeshAgent.SetDestination(player);
if (Vector3.Distance(transform.position, player) <= 0.3)
{
if (m_WaitTime <= 0)
{
m_PlayerNear = false;
Move(speedWalk);
navMeshAgent.SetDestination(waypoints[m_CurrentWaypointIndex].position);
m_WaitTime = startWaitTime;
m_TimeToRotate = timeToRotate;
}
else
{
Stop();
m_WaitTime -= Time.deltaTime;
}
}
}
void EnviromentView()
{
Collider[] playerInRange = Physics.OverlapSphere(transform.position, viewRadius, playerMask); // Make an overlap sphere around the enemy to detect the playermask in the view radius
for (int i = 0; i < playerInRange.Length; i++)
{
Transform player = playerInRange[i].transform;
Vector3 dirToPlayer = (player.position - transform.position).normalized;
if (Vector3.Angle(transform.forward, dirToPlayer) < viewAngle / 2)
{
float dstToPlayer = Vector3.Distance(transform.position, player.position); // Distance of the enmy and the player
if (!Physics.Raycast(transform.position, dirToPlayer, dstToPlayer, obstacleMask))
{
m_playerInRange = true; // The player has been seeing by the enemy and then the nemy starts to chasing the player
m_IsPatrol = false; // Change the state to chasing the player
}
else
{
/*
* If the player is behind a obstacle the player position will not be registered
* */
m_playerInRange = false;
}
}
if (Vector3.Distance(transform.position, player.position) > viewRadius)
{
/*
* If the player is further than the view radius, then the enemy will no longer keep the player's current position.
* Or the enemy is a safe zone, the enemy will no chase
* */
m_playerInRange = false; // Change the sate of chasing
}
if (m_playerInRange)
{
/*
* If the enemy no longer sees the player, then the enemy will go to the last position that has been registered
* */
m_PlayerPosition = player.transform.position; // Save the player's current position if the player is in range of vision
}
}
}
}

Why the speed variable have no factor when changing in the inspector the value in run time ? How to update positions and make nonstop moving?

The main goal is to move the object between the positions with a delay. This is working fine but other things are not working.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
public GameObject objectPrefab;
public LineRenderer lineRenderer;
public List<Transform> pointsToMove;
public float speed;
public bool go = false;
private List<GameObject> objectsToMove;
private List<Vector3> positions;
void Start()
{
objectsToMove = new List<GameObject>();
//Spawn objects and start movement
}
private void Update()
{
if (go && lineRenderer.positionCount > 0 && CurvedLineRenderer.linesSet)
{
positions = GetLinePointsInWorldSpace();
foreach(Transform objToMove in pointsToMove)
{
positions.Add(objToMove.position);
}
for (int i = 0; i < 3; i++)
{
objectsToMove.Add(Instantiate(objectPrefab, transform.parent));
StartCoroutine(Move(i));
}
go = false;
}
}
private IEnumerator Move(int i)
{
//Wait interval
yield return new WaitForSeconds(i * 10);
bool stillTraveling = true;
int pointIndex = 0;
float threshold = 0.1f;
while (stillTraveling)
{
//Check if close to point
if (Vector3.Distance(objectsToMove[i].transform.position, positions[pointIndex]) < threshold)
{
//Check if can move next point
if (pointIndex + 1 > positions.Count - 1)
{
stillTraveling = false;
}
//If not stop moving
else
{
pointIndex++;
}
}
//Move towards point
objectsToMove[i].transform.position = Vector3.MoveTowards(objectsToMove[i].transform.position, positions[pointIndex], speed * Time.deltaTime);
yield return null;
}
}
List<Vector3> GetLinePointsInWorldSpace()
{
var pointsToMove = new Vector3[lineRenderer.positionCount];
//Get the positions which are shown in the inspector
lineRenderer.GetPositions(pointsToMove);
//the points returned are in world space
return pointsToMove.ToList();
}
}
I'm updating once the positions in the Update.
I'm using StartCoroutine once in the Update to send the objects to move between the waypoints with a delay.
private void Update()
{
if (go && lineRenderer.positionCount > 0 && CurvedLineRenderer.linesSet)
{
positions = GetLinePointsInWorldSpace();
foreach(Transform objToMove in pointsToMove)
{
positions.Add(objToMove.position);
}
for (int i = 0; i < 3; i++)
{
objectsToMove.Add(Instantiate(objectPrefab, transform.parent));
StartCoroutine(Move(i));
}
go = false;
}
}
It's working by sending the objects to move with delay between the waypoints, but now I have some other problems.
Because I'm using StartCoroutine for the delay and because I'm setting the go flag to false then the speed value have no affect on the moving objects at run time and I'm not sure how to solve it so the speed will have a factor affecting the moving objects.
Another problem or not a problem but not sure how to do it is how to Update in run time the positions List ? Here in the Update I'm updating the List once :
positions = GetLinePointsInWorldSpace();
foreach(Transform objToMove in pointsToMove)
{
positions.Add(objToMove.position);
}
but I want that if the amount of positions in the function GetLinePointsInWorldSpace or/and the amount of pointsToMove have changed to update the positions List at rune time. I could just call this part in the update nonstop but I'm not sure of it will be too expensive ?
Last problem or how to do is how to make the object to move to move between the waypoints non stop over again from the start or moving backward when getting the last waypoint ? Again because using StartCoroutine for the delay everything is happening once.
If you want to move an object from one point to another, you should use Mathf.Sin(). The is using the sine function, and you put in the amount of cycles times Mathf.PI times 2. Here is what you would write:
float period = 2f;
Vector3 a; //the first point the object goes to
Vector3 b; //the second point that the object goes to
void Update()
{
float cycles = period * Time.time * Mathf.PI;
float amplitude = Mathf.Sin(cycles);
Vector3 location = Vector3.Lerp(a, b, amplitude);
Transform.position = location;
}
I then use Vector3.Lerp to interpolate between point a and point b. This isn’t what you were doing, but it is a much simpler way you can do it.
Vector3 a; //the first point the object goes to
Vector3 b; //the second point that the object goes to
void Update()
{
float cycles = period * Time.time;
Vector3 location = Vector3.Lerp(a, b, cycles);
Transform.position = location;
}
This one is for bringing the object just strait to point b and not back.
If you want there to be a delay between when it moves, here is the script:
float period = 2f;
float waitTime;
bool AtoB = true;
Vector3 a; //the first point the object goes to
Vector3 b; //the second point that the object goes to
void Start()
{
StartCoroutine(“Oscillator”);
}
IEnumerator Oscillator
{
float amplitude;
if (AtoB)
{
amplitude += Time.deltaTime * period;
}
else
{
amplitude -= Time.deltaTime * period;
}
amplitude = Mathf.Clamp(amplitude, 0, 1);
Vector3 location = Vector3.Lerp(a, b, amplitude);
Transform.position = location;
if (amplitude == 1)
{
AtoB = false;
yield return new WaitForSeconds(waitTime);
}
if else (amplitude == 0)
{
AtoB = true;
yield return new WaitForSeconds(waitTime);
}
else
{
yield return false;
}
}

Unity2D: Enemy doesn't follow Player when in its radius

Hey Guys I've been having a problem lately that I cant seem to solve.
A sprite is supposed to roam around (as it does) while nothing is inside its radius, however if the player moves close to it the sprite should theoretically move towards it and stop roaming.
The sprite doesn't follow the player and cant even see its tag since I cant even see the contents of the "Collider2D[] hits".
using System.Collections.Generic;
using UnityEngine;
public class FireCultist : MonoBehaviour
{
public float moveTimeSeconds; //Time it will take object to move, in seconds.
private float xMax = 10.0f; // The boundaries of the spawn area
private float yMax = 10.0f;
private float xMin = -10.0f; // The boundaries of the spawn area
private float yMin = -10.0f;
public int xDistanceRange; // The max distance you can move at one time
public int yDistanceRange;
private BoxCollider2D boxCollider; //The BoxCollider2D component attached to this object.
private Rigidbody2D rb2D; //The Rigidbody2D component attached to this object.
private float inverseMoveTime; //Used to make movement more efficient.
public Vector2 start;
public Vector2 end;
public bool roam = true;
public Collider2D[] hits;
void Start()
{
boxCollider = GetComponent<BoxCollider2D>();
rb2D = GetComponent<Rigidbody2D>();
inverseMoveTime = 1f / moveTimeSeconds;
InvokeRepeating("RandomMove", 0.0f, 5.0f);
}
void Update()
{
Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, 10); // returns all colliders within radius of enemy
int i = 0;
while(hits.Length > i)
{
Debug.Log("Sees");
Debug.Log(hits[i]);
i++;
}
followPlayer();
if (roam)
{
Debug.Log("Roam");
transform.position = Vector2.MoveTowards(transform.position, end, inverseMoveTime * Time.deltaTime); //Random move to new position
}
}
public void followPlayer()
{
foreach (Collider2D hit in hits)
{
if (hit.tag == "Player") // if the player is within a radius
{
Debug.Log("Chasing Player");
transform.position = Vector2.MoveTowards(transform.position, GameObject.Find("Prefabs/Player").GetComponent<Transform>().position, inverseMoveTime * Time.deltaTime); // chase player
roam = false;
}
else
{
Debug.Log("Continues");
roam = true; // Continue RandomMove()
}
}
}
public void RandomMove() // gets random coordinates near enemy and moves there
{
float xDistance = Random.Range(-xDistanceRange, xDistanceRange); // check
float yDistance = Random.Range(-yDistanceRange, yDistanceRange);// check
if (xDistance < xMax && xDistance > xMin && yDistance < yMax && yDistance > yMin && roam == true) // If within boundaries of walking space
{
start = transform.position;
end = start + new Vector2(xDistance, yDistance);
Debug.Log("Roaming From : " + start + " To : " + end);
}
}
}
The roaming algorithm works however not too sure about the tag detection.
The script belongs to this enemy object
Player Object Properties
It looks like you are declaring a new hits variable during every update instead of using your class-level variable. This means the variable inside of followPlayer() will never be instantiated, and the information will not be passed between the two methods.
Try this:
void Update()
{
hits = Physics2D.OverlapCircleAll(transform.position, 10);
//...
}
You might also consider attaching a trigger collider to the enemy which sends a notification when the player enters/exits the trigger instead of relying on OverlapCircleAll()
void OnTriggerEnter2D(Collider2D col)
{
if(col.gameObject.tag == "Player"){
roam = false;
}
}
void OnTriggerExit2D(Collider2D col)
{
if(col.gameObject.tag == "Player"){
roam = true;
}
}

Why is the camera not rotating to face the first waypoint?

When the game starts, a random waypoint is selected from an array. The camera should then rotate to face the selected random waypoint and start moving towards it.
Once the camera has reached the waypoint, it should wait 3 seconds before rotating to face and move towards the next random waypoint.
The problem I have is in Start(). The camera does not rotate to face the first waypoint before it starts moving towards it. Instead, it moves towards the first waypoint backwards. Then, when it reaches the waypoint, it waits 3 seconds to rotate and move towards the next waypoint.
It's working fine except that the camera does not rotate to face the first selected random waypoint. It's moving to it backward without first rotating to face it.
Here's my code:
The waypoints script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
public GameObject[] waypoints;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private int current = 0;
private bool rot = false;
public void Init()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
if(waypoints.Length > 0)
{
StartCoroutine(RotateFacingTarget(waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform));
}
}
void Update()
{
if (waypoints.Length > 0)
{
if (Vector3.Distance(waypoints[current].transform.position, transform.position) < WPradius)
{
current = UnityEngine.Random.Range(0, waypoints.Length);
rot = false;
StartCoroutine(RotateFacingTarget(waypoints[current].transform));
if (current >= waypoints.Length)
{
current = 0;
}
}
if (rot)
transform.position = Vector3.MoveTowards(transform.position, waypoints[current].transform.position, Time.deltaTime * speed);
}
}
IEnumerator RotateFacingTarget(Transform target)
{
yield return new WaitForSeconds(3);
lookAtCam.target = target;
rot = true;
}
}
The look at camera script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LookAtCamera : MonoBehaviour
{
//values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
//values for internal use
private Quaternion _lookRotation;
private Vector3 _direction;
// Update is called once per frame
void Update()
{
//find the vector pointing from our position to the target
if (target)
{
_direction = (target.position - transform.position).normalized;
//create the rotation we need to be in to look at the target
_lookRotation = Quaternion.LookRotation(_direction);
//rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime * RotationSpeed);
}
}
}
How can I fix this?
Let's assume that Waypoints.Init() is being called and your waypoints variable has an array of 3.
Waypoints.Init() starts a coroutine
Your coroutine waits 3 seconds
After 3 seconds, you set your camera target which Slerps to face the position
Update on its first frame says waypoints.Length > 0 == true
It's not close to its target, and rot is false, so it does not move
Now, you're waiting 3 seconds, not rotating, and not moving.
Your coroutine's 3 second wait time is up and starts the rotation toward your target
rot is now true at the start of your rotation, so your Update method starts moving toward the target as well
It would seem that your logic is off in how the order of operations works. If it needs to act as you describe, I suggest that you operate on the target differently.
I've implemented the following using an enum:
Waypoints
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
private Transform currentWaypoint;
private enum CameraState
{
StartRotating,
Rotating,
Moving,
Waiting
}
private CameraState cameraState;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private int current = 0;
private bool isCameraRotating = false;
void Start()
{
cameraState = CameraState.StartRotating;
}
void Update()
{
switch (cameraState)
{
// This state is used as a trigger to set the camera target and start rotation
case CameraState.StartRotating:
{
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
break;
// Tell the camera to start rotating
currentWaypoint = waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform;
lookAtCam.target = currentWaypoint;
cameraState = CameraState.Rotating;
break;
}
// This state only needs to detect when the camera has completed rotation to start movement
case CameraState.Rotating:
{
if (lookAtCam.IsFinishedRotating)
cameraState = CameraState.StartMoving;
break;
}
case CameraState.Moving:
{
// Move
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
// Check for the Waiting state
if (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
// Set to waiting state
cameraState = CameraState.Waiting;
// Call the coroutine to wait once and not in CameraState.Waiting
// Coroutine will set the next state
StartCoroutine(WaitForTimer(3));
}
break;
}
case CameraState.Waiting:
// Do nothing. Timer has already started
break;
}
}
IEnumerator WaitForTimer(float timer)
{
yield return new WaitForSeconds(timer);
cameraState = CameraState.StartRotating;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
LookAtCamera
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
private float timer = 0.0f;
public bool IsRotationFinished
{
get { return timer > 0.99f; }
}
// Update is called once per frame
void Update()
{
if (target != null && timer < 0.99f)
{
// Rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation((target.position - transform.position).normalized),
timer);
timer += Time.deltaTime * RotationSpeed;
}
}
}

How can i calculate the rotation the player need to make to get to the next waypoint?

using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Waypoints : MonoBehaviour
{
public GameObject[] waypoints;
public Transform target;
public float moveSpeed = 10f;
public float slowDownSpeed = 3f;
public float reverseSlowDownSpeed = 3f;
public float rotationSpeed = 1f;
private int targetsIndex = 0;
private Vector3 originalPosition;
private GameObject[] players;
public Transform reverseTarget;
private int reverseTargetsIndex = 0;
private Vector3 reverseOriginalPosition;
public bool random = false;
public bool getNextRandom = true;
// Use this for initialization
void Start()
{
waypoints = GameObject.FindGameObjectsWithTag("Blocks");
players = GameObject.FindGameObjectsWithTag("Player");
originalPosition = players[0].transform.localPosition;
}
// Update is called once per frame
void Update()
{
if (random == true)
{
RandomWayPointsAI();
}
else
{
WayPointsAI();
}
}
private void WayPointsAI()
{
if (targetsIndex == waypoints.Length)
targetsIndex = 0;
target = waypoints[targetsIndex].transform;
if (MovePlayer())
targetsIndex++;
}
private void ReverseWayPointsAI()
{
if (reverseTargetsIndex == 0)
reverseTargetsIndex = waypoints.Length - 1;
reverseTarget = waypoints[reverseTargetsIndex].transform;
if (MovePlayer())
reverseTargetsIndex--;
}
void RandomWayPointsAI()
{
if (random == true && getNextRandom)
{
int index = UnityEngine.Random.Range(0, waypoints.Length);
target = waypoints[index].transform;
getNextRandom = false;
}
getNextRandom = MovePlayer();
}
bool MovePlayer()
{
float distance = Vector3.Distance(players[0].transform.position, target.transform.position);
players[0].transform.localRotation = Quaternion.Slerp(players[0].transform.localRotation, Quaternion.LookRotation(target.position - players[0].transform.localPosition), rotationSpeed * Time.deltaTime);
//move towards the player
if (distance < 30)
{
players[0].transform.localPosition += players[0].transform.forward * slowDownSpeed * Time.deltaTime;
}
else
{
players[0].transform.localPosition += players[0].transform.forward * moveSpeed * Time.deltaTime;
}
if (distance < target.transform.localScale.magnitude)
return true;
else
return false;
}
void DrawLinesInScene()
{
// draw lines between each checkpoint //
for (int i = 0; i < waypoints.Length - 1; i++)
{
Debug.DrawLine(waypoints[i].transform.position, waypoints[i + 1].transform.position, Color.blue);
}
// draw a line between the original transform start position
// and the current transform position //
Debug.DrawLine(originalPosition, players[0].transform.position, Color.red);
Debug.DrawLine(reverseOriginalPosition, players[1].transform.position, Color.red);
// draw a line between current transform position and the next waypoint target
// each time reached a waypoint.
if (target != null)
Debug.DrawLine(target.transform.position, players[0].transform.position, Color.green);
if (reverseTarget != null)
Debug.DrawLine(reverseTarget.transform.position, players[1].transform.position, Color.green);
}
void AddColliderToWaypoints()
{
foreach (GameObject go in waypoints)
{
SphereCollider sc = go.AddComponent<SphereCollider>() as SphereCollider;
sc.isTrigger = true;
}
}
}
There are two problems with the script.
If the Move Speed is set to 3 i need to set the Rotation Speed at least to the 10 if i will set the Rotation Speed to 3 or 4 or 5 the rotation will be too wide and it will take much time to the player to get back on track after rotating.
But if it's 10 it seems he is almost rotating on the place so it's not good either.
I want to be able to change the player rotation speed but also to keep him on the waypoints track at any time. I mean that each waypoint the player is reaching to that he will get to it's center. In this case cubes so not only to touch the waypoint but to get to it's center then moving to the next waypoint.
Another problem i see is with the RandomWayPointsAI()
When i change it to use the random method i'm not sure if it's just picking up random positions around the grid or if it's picking random blocks(Cubes).
But it's never getting to a cube it' getting close or between two or sometimes on a cube and same as before also on the random i want to make the random waypoints not just positions but blocks and to get to each random block center.
Center i mean like standing on it.
A glaring problem in the showcased code is the use of local positions and rotations. When moving and rotating characters and objects in the world you should use world space.
Localposition and localrotation are based on the position and rotation of the parent object. Using those to move your object across the world will cause weird problems.
I think the following answer has a pretty accurate explanation of that stuff https://teamtreehouse.com/community/global-space-vs-local-space

Categories

Resources