Как получить координаты ближайшего объекта из множества других в Unity?
Нужно сделать так, чтобы каждый куб на сцене при движении рассчитывал расстояние до еды и если это расстояние меньше 10 единиц, то начинал двигаться к нему. Уже неделю пытаюсь реализовать эту затею, но сложность составляет то, что еда появляется на сцене каждые n секунд. причем произвольное перемещение работает нормально. Пробовал и присваивать в скрипте Food Script координаты объекта, и создавать отдельный GameObject, получая его координаты. Один раз получилось реализовать, но была ошибка с уничтожением объекта, т.к удалял префаб, а не сам объект. После исправления что-то изменил и теперь никак не могу вернуть... Я так понимаю, проблема в том, что мой скрипт перемещения куба не может выбрать определенные координаты, ведь еды на сцене много, но путает то, что как-то до этого работало. Помогите пожалуйста.
public class PlayerControl : MonoBehaviour
{
public GameObject prefabCube;
public Rigidbody cube;
public Vector3 moveDirection;
public Vector3 cubePosition;
public float smoothing;
private FoodScript food;
private LevelManager levelManager;
public int collectedFood;
void Start()
{
food = FindObjectOfType<FoodScript>();
moveDirection = transform.position;
}
void Update()
{
if (food != null && Vector3.Distance(transform.position, food.transform.position) < 10.0f)
{
Debug.Log("This is foodDirection");
transform.position = Vector3.MoveTowards(transform.position, food.transform.position, smoothing * Time.deltaTime);
} else
{
Movement();
}
}
private void Movement()
{
if (Vector3.Distance(moveDirection, transform.position) < 0.5f)
{
//Random.Range(-10.0f, 10.0f)
moveDirection = new Vector3(Random.Range(-40.0f, 40.0f), transform.position.y, Random.Range(-40.0f, 40.0f));
}
else
{
transform.position = Vector3.MoveTowards(transform.position, moveDirection, smoothing * Time.deltaTime);
}
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "FallDetector")
{
Destroy(prefabCube);
}
}
public class LevelManager : MonoBehaviour
{
public float timer;
public float startTimer;
public GameObject prefabFood;
private PlayerControl player;
private FoodScript food;
// Start is called before the first frame update
void Start()
{
food = FindObjectOfType<FoodScript>();
timer = startTimer;
}
// Update is called once per frame
void Update()
{
timer -= Time.deltaTime;
if (timer <= 0)
{
timer = startTimer;
SpawnFood();
}
}
public void SpawnFood()
{
Debug.Log("This is SpawnFood");
for (int i = 0; i < 5; i++)
{
Instantiate(prefabFood, new Vector3(Random.Range(-40.0f, 40.0f), transform.position.y, Random.Range(-40.0f, 40.0f)), transform.rotation);
}
}
}
public class FoodScript : MonoBehaviour
{
public Vector3 position;
private LevelManager levelManager;
private PlayerControl player;
private void Start()
{
position = transform.position;
player = FindObjectOfType<PlayerControl>();
levelManager = FindObjectOfType<LevelManager>();
}
private void Update()
{
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Cube")
{
Destroy(gameObject);
}
}
}
Ответы (1 шт):
Научитесь работать с событиями (event) (обязательно)
Научитесь работать с корутинами. Альтернатива Update, для не постоянных вещей. (желательно)
// запрет на несколько компоненов этого типа на одном GameObject
[DisallowMultipleComponent]
public class CubeManager : MonoBehaviour {
// статическая переменная к которой можно будет обращаться глобально типа как Camera.main
public static CubeManager Main;
// список всех кубов
public List<Cube> Collection;
private void Awake () {
Main = this; // Задаём данный обьект как Main
Collection = new List<Cube>();
}
}
using System;
[DisallowMultipleComponent]
public class FoodManager : MonoBehaviour {
public static FoodManager Main;
public List<Food> Collection;
// событие спавна еда
// Action<Food> - делегат некой void функции с аргуметом Food типа void Function (Food food)
public event Action<Food> FoodSpawned;
// событие удаления еды
public event Action<Food> FoodRemoved;
// префаб еды
// SerializeField - поле ведёт себя в инспекторе, как public, хотя и private,
// в public нет смысла, другим глассам незачем иметь к этому полю доступ
[SerializeField] private GameObject _foodTemplate;
[SerializeField] private float _spawnStep;
private float _spawnTimer;
private void Awake () {
Main = this;
Collection = new List<Food>();
}
private void Update () {
_spawnTimer += Time.deltaTime;
if (_spawnTimer >= _spawnStep) {
_spawnTimer -= _spawnStep;
SpawnFood();
}
}
private void SpawnFood () {
GameObject NewFoodObject = Instantiate(_foodTemplate);
Food NewFood = NewFoodObject.GetComponent<Food>();
Collection.Add(NewFood);
// передаём делегат с методом RemoveFood() для того,
// чтобы еда смогла удалить себя из листа и оповестить о событии удаления
NewFood.SetRemove(RemoveFood);
float PositionX = UnityEngine.Random.Range(-100, 100);
float PositionZ = UnityEngine.Random.Range(-100, 100);
NewFood.Transform.position = new Vector3(PositionX, 0, PositionZ);
// оповещает всех подписчиков о событии спавна с передачей ссылки на заспавниную еду
if (FoodSpawned != null)
FoodSpawned.Invoke(NewFood);
}
private void RemoveFood (Food food) {
Collection.Remove(food);
if (FoodRemoved!= null)
FoodRemoved.Invoke(food);
}
}
[DisallowMultipleComponent]
// делает Rigidbody обязательным
// автоматически добавит на GameObject, если его нет и не позволит удалить, пока есть Cube компонент
[RequireComponent(typeof(Rigidbody))]
public class Cube : MonoBehaviour {
[SerializeField] private float _followDistance = 10;
[SerializeField] private float _followSpeed = 1;
private Rigidbody _body;
private Transform _transform;
private Food _followTarget;
private IEnumerator _followCoroutine;
private void Start () {
_transform = transform; // кешируем, что-бы не запрашивать повторно (хотя в паследних версия "вроде" не надо)
// берём компонент без проверки не боясь ошибки "null reference exception", поскольку есть RequireComponent
_body = GetComponent<Rigidbody>();
// добавляется в общий список кубов
CubeManager.Main.Collection.Add(this);
// подписывается на события появления и удаления еды
FoodManager.Main.FoodSpawned += SpawnFood;
FoodManager.Main.FoodRemoved += SpawnFood;
}
private void OnDestroy () {
// при удалении куба, удаляет себя из списка и отписывается от событий
CubeManager.Main.Collection.Remove(this);
FoodManager.Main.FoodSpawned -= SpawnFood;
FoodManager.Main.FoodRemoved -= SpawnFood;
}
private void SpawnFood (Food food) {
Vector3 Vector = food.Transform.position-_transform.position;
Vector = new Vector3(Vector.x, 0, Vector.z);
// magnitude это длина Vector2/Vector3
if (Vector.magnitude < _followDistance)
StartFollow(food);
}
private void RemoveFood (Food food) {
if (food == _followTarget)
StopFollow();
}
private void CatchFood () {
Destroy(_followTarget);
}
private void StartFollow (Food food) {
_followTarget = food;
// запускаем корутину движения, если она не запущена
if (_followCoroutine == null) {
_followCoroutine = FollowCoroutine();
StartCoroutine(_followCoroutine);
}
}
private void StopFollow () {
_followTarget = null;
// корутина остановилась бы и после _followTarget = null,
// выйдя из цикла while, поскольку _followTarget в её условии,
// но вообще она останавливается так
StopCoroutine(_followCoroutine);
_followCoroutine = null;
}
private IEnumerator FollowCoroutine () {
// безконечно выполняется, пока есть цель
while (_followTarget != null) {
Vector3 FollowVector = _followTarget.Transform.position-_transform.position;
FollowVector = new Vector3(FollowVector.x, 0, FollowVector.z);
// normalized выдаёт вектор, длинной 1
Vector3 MoveVector = FollowVector.normalized*_followSpeed;
_body.velocity = MoveVector;
// ждёт до следубщего обновления физики как FixedUpdate
yield return new WaitForFixedUpdate();
}
}
private void OnTriggerEnter (Collider other) {
// столкновение с едой
if (other.GetComponent<Food>() != null)
Destroy(other.gameObject);
}
}
using System;
[DisallowMultipleComponent]
public class Food : MonoBehaviour {
public Transform Transform;
private Action<Food> _onDestroy;
public void SetRemove (Action<Food> remove) {
_onDestroy = remove;
}
private void Awake () {
Transform = transform;
}
private void OnDestroy () {
// при удалении вызывает ранее переданный метод удаляющий еду из списка и вызывающий событие
if (_onDestroy != null)
_onDestroy(this);
}
}