Как организовать список очереди действий персонажа, таких как движение к точке, поиск объекта, рубка дерева
Для организации списка очереди действий персонажа в Unity, вы можете использовать структуру данных, называемую "очередью" (queue) или "списком" (list) в зависимости от ваших потребностей. Вот пример, как это можно сделать:
- Создайте класс для действий персонажа:
Сначала создайте класс или скрипт, который будет представлять действие персонажа. Например, это может быть что-то подобное:
public class CharacterAction
{
public enum ActionType
{
MoveTo,
SearchForObject,
ChopTree,
// Другие действия
}
public ActionType actionType;
public Vector3 targetPosition;
public GameObject targetObject;
// Конструкторы и другие поля, если необходимо
}
- Создайте очередь действий:
Затем создайте очередь (queue) или список (list) действий вашего персонажа в вашем скрипте управления персонажем. Например:
using System.Collections.Generic;
using UnityEngine;
public class CharacterController : MonoBehaviour
{
private Queue actionQueue = new Queue();
// Метод для добавления действия в очередь
public void EnqueueAction(CharacterAction action)
{
actionQueue.Enqueue(action);
}
// Метод для выполнения следующего действия в очереди
public void PerformNextAction()
{
if (actionQueue.Count > 0)
{
CharacterAction nextAction = actionQueue.Dequeue();
switch (nextAction.actionType)
{
case CharacterAction.ActionType.MoveTo:
MoveTo(nextAction.targetPosition);
break;
case CharacterAction.ActionType.SearchForObject:
SearchForObject(nextAction.targetObject);
break;
case CharacterAction.ActionType.ChopTree:
ChopTree(nextAction.targetObject);
break;
// Обработка других действий
}
}
}
// Методы для выполнения конкретных действий, например, движение, поиск и рубка дерева
private void MoveTo(Vector3 targetPosition)
{
// Реализация движения к цели
}
private void SearchForObject(GameObject targetObject)
{
// Реализация поиска объекта
}
private void ChopTree(GameObject treeObject)
{
// Реализация рубки дерева
}
// Остальные методы и логика управления персонажем
}
- Добавление действий в очередь:
Теперь вы можете добавлять действия в очередь, вызывая метод EnqueueAction . Например:
CharacterAction moveAction = new CharacterAction();
moveAction.actionType = CharacterAction.ActionType.MoveTo;
moveAction.targetPosition = new Vector3(10f, 0f, 10f);
characterController.EnqueueAction(moveAction);
CharacterAction chopAction = new CharacterAction();
chopAction.actionType = CharacterAction.ActionType.ChopTree;
chopAction.targetObject = treeObject;
characterController.EnqueueAction(chopAction);
- Выполнение действий из очереди:
Чтобы начать выполнение действий из очереди, вызывайте метод PerformNextAction в вашем цикле обновления (например, в функции Update ).
Таким образом, вы создали механизм управления действиями персонажа через очередь или список, который позволяет вам планировать и выполнять различные действия в заданном порядке.
В данном алгоритме скажи, как реализовать, чтобы действие движения или рубки дерева происходило в update()
Если вы хотите, чтобы действия движения или рубки дерева происходили в функции Update() , вы можете изменить структуру вашего кода следующим образом:
-
Измените структуру CharacterAction:
Внесите изменения в структуру CharacterAction , чтобы она могла хранить дополнительную информацию о состоянии выполнения действия и других параметрах, необходимых для его выполнения. Например:
public class CharacterAction { public enum ActionType { MoveTo, SearchForObject, ChopTree, // Другие действия } public ActionType actionType; public Vector3 targetPosition; public GameObject targetObject; public bool isActionCompleted; // Флаг, указывающий, завершено ли действие // Другие поля и методы, если необходимо }
-
Измените метод PerformNextAction :
Метод PerformNextAction будет просто устанавливать флаг isActionCompleted для текущего действия, а фактическое выполнение действия будет происходить в Update() . Вот пример:
public void PerformNextAction() { if (actionQueue.Count > 0) { CharacterAction nextAction = actionQueue.Peek(); // Получаем следующее действие, но не удаляем его из очереди switch (nextAction.actionType) { case CharacterAction.ActionType.MoveTo: MoveTo(nextAction.targetPosition); break; case CharacterAction.ActionType.SearchForObject: SearchForObject(nextAction.targetObject); break; case CharacterAction.ActionType.ChopTree: ChopTree(nextAction.targetObject); break; // Обработка других действий } // Помечаем действие как завершенное, если оно выполнено (например, внутри методов MoveTo, SearchForObject, ChopTree) if (nextAction.isActionCompleted) { actionQueue.Dequeue(); // Удаляем завершенное действие из очереди } } }
-
Измените метод Update() :
В методе Update() вы будете вызывать PerformNextAction() для выполнения следующего действия из очереди.
void Update() { PerformNextAction(); }
-
Реализуйте выполнение действий в методах MoveTo, SearchForObject и ChopTree:
В этих методах вы будете выполнять соответствующие действия, а затем устанавливать флаг isActionCompleted в true , когда действие завершено.
Пример для метода MoveTo :
private void MoveTo(Vector3 targetPosition) { float moveSpeed = 5.0f; Vector3 moveDirection = (targetPosition - transform.position).normalized; transform.Translate(moveDirection * moveSpeed * Time.deltaTime); // Проверка, завершено ли действие (например, при достижении цели) if (Vector3.Distance(transform.position, targetPosition) < 0.1f) { isActionCompleted = true; } }
Пример для метода ChopTree :
private void ChopTree(GameObject treeObject) { // Реализация рубки дерева // Проверка, завершено ли действие (например, после завершения рубки) if (treeObject == null) { isActionCompleted = true; } }
Таким образом, действия движения и рубки дерева будут выполняться пошагово в Update() , и когда они завершатся, они будут автоматически удалены из очереди.
А как сделать чтобы часть функционала реализовывалось один раз при старте действия, а часть в update
Если вы хотите, чтобы часть функционала выполнялась один раз при старте действия, а часть выполнялась в методе Update() , вам нужно использовать концепцию состояний (state machine) для вашего персонажа. Каждое действие будет иметь свои состояния, и персонаж будет находиться в одном из них.
Вот пример, как это можно реализовать:
-
Измените структуру CharacterAction:
Добавьте состояние (state) для каждого действия и флаг, который будет указывать, выполнено ли действие. Например:
public class CharacterAction { public enum ActionType { MoveTo, SearchForObject, ChopTree, // Другие действия } public ActionType actionType; public Vector3 targetPosition; public GameObject targetObject; public enum ActionState { NotStarted, InProgress, Completed } public ActionState currentState = ActionState.NotStarted; // Другие поля и методы, если необходимо }
-
Измените метод PerformNextAction :
Теперь метод PerformNextAction будет управлять состояниями действий и их выполнением:
public void PerformNextAction() { if (actionQueue.Count > 0) { CharacterAction nextAction = actionQueue.Peek(); if (nextAction.currentState == CharacterAction.ActionState.NotStarted) { // Начать выполнение действия nextAction.currentState = CharacterAction.ActionState.InProgress; StartAction(nextAction); } else if (nextAction.currentState == CharacterAction.ActionState.InProgress) { // Продолжить выполнение действия ContinueAction(nextAction); } // Проверка, завершено ли действие if (nextAction.currentState == CharacterAction.ActionState.Completed) { actionQueue.Dequeue(); // Удаляем завершенное действие из очереди } } }
-
Добавьте методы для управления действиями:
Создайте методы StartAction и ContinueAction для каждого типа действия, чтобы начать и продолжить выполнение действия соответственно. В методе StartAction вы будете выполнять часть функционала, которая должна быть выполнена только один раз при старте действия. Затем в методе ContinueAction вы будете выполнять часть функционала, которая должна обновляться в Update() .
private void StartAction(CharacterAction action) { switch (action.actionType) { case CharacterAction.ActionType.MoveTo: // Начать движение к цели MoveTo(action.targetPosition); break; case CharacterAction.ActionType.SearchForObject: // Начать поиск объекта SearchForObject(action.targetObject); break; case CharacterAction.ActionType.ChopTree: // Начать рубку дерева ChopTree(action.targetObject); break; // Обработка других действий } } private void ContinueAction(CharacterAction action) { switch (action.actionType) { case CharacterAction.ActionType.MoveTo: // Продолжить движение (например, в методе Update()) MoveTo(action.targetPosition); break; case CharacterAction.ActionType.SearchForObject: // Продолжить поиск объекта (например, в методе Update()) SearchForObject(action.targetObject); break; case CharacterAction.ActionType.ChopTree: // Продолжить рубку дерева (например, в методе Update()) ChopTree(action.targetObject); break; // Обработка других действий } }
-
Реализуйте функционал в методах MoveTo , SearchForObject , ChopTree :
В этих методах выполните соответствующие действия в зависимости от текущего состояния действия. Например, в методе MoveTo :
private void MoveTo(Vector3 targetPosition) { if (currentState == CharacterAction.ActionState.InProgress) { // Выполните код движения (например, в методе Update()) float moveSpeed = 5.0f; Vector3 moveDirection = (targetPosition - transform.position).normalized; transform.Translate(moveDirection * moveSpeed * Time.deltaTime); // Проверка, завершено ли действие if (Vector3.Distance(transform.position, targetPosition) < 0.1f) { currentState = CharacterAction.ActionState.Completed; } } }
Теперь действия будут иметь состояния и выполняться по этим состояниям, что позволит вам контролировать, когда и как выполнять разные части функционала.
Как не допустить ошибку, которая возникает при выполнении действия из очереди с объектом, который был ранее удален. Сейчас реализовано с помощью проверки на пустой объект для действий, которые используют объект, можно ли это реализовать при добавлении в очередь или при разрушении объекта
Чтобы избежать ошибки, возникающей при выполнении действия из очереди с объектом, который был ранее удален, можно предпринять следующие шаги:
-
Проверка наличия объекта при добавлении в очередь:
При добавлении действия в очередь, проверяйте, существует ли целевой объект, и добавляйте действие только если объект существует. Например:
// Метод для добавления действия в очередь public void AddAction(CharacterAction action) { // Проверяем, что целевой объект действия существует if (action.targetObject != null) { actionQueue.Enqueue(action); } else { Debug.LogWarning("Попытка добавления действия с отсутствующим объектом в очередь."); } }
Это позволит избежать добавления действий с отсутствующими объектами в очередь.
-
Обработка удаления объекта из очереди:
Когда объект уничтожается (например, при срубке дерева), вы можете проверить, содержит ли очередь это действие. Если да, вы можете удалить это действие из очереди. Например:
// Метод для удаления действия из очереди при удалении объекта public void RemoveActionOnObjectDestroy(GameObject destroyedObject) { CharacterAction[] actionsInQueue = actionQueue.ToArray(); foreach (CharacterAction action in actionsInQueue) { if (action.targetObject == destroyedObject) { actionQueue.Dequeue(); } } }
Этот метод проходит по всем действиям в очереди и удаляет те, которые имеют удаленный объект как цель. Вы можете вызвать этот метод при удалении объекта из игры.
Эти два шага помогут предотвратить выполнение действий с удаленными объектами из очереди и убедиться, что очередь действий содержит только действия, которые можно выполнить без ошибок.
*сделал объект обязательным для всех действий, для перемещения указал объект - terrain
персонаж может брать в руки предметы, такие как бревно или мебель. И в тот момент, когда у него в руках крупный предмет, чтобы были доступны только определенные действия, такие как перемещение к точке, перемещение к объекту и выкинуть предмет, и чтобы не были доступны другие действия. А если в руках предмет и происходит клик по объекту, допустим по дереву, чтобы персонаж подходил к нему, бросал предмет и только потом начинал рубить дерево. как нужно изменить скрипт CharacterController?
Для реализации функциональности, когда персонаж может держать в руках крупные предметы и выполнять определенные действия только в этом режиме, а также для обработки случая, когда клик по объекту вызывает специфическое поведение (например, бросок предмета), вам потребуется внести несколько изменений в ваш скрипт CharacterController .
-
Добавьте переменную для хранения текущего предмета в руках персонажа:
В вашем скрипте CharacterController создайте переменную для хранения текущего предмета, который персонаж держит в руках:
public GameObject heldItem; // Текущий предмет, который персонаж держит в руках
-
Измените метод для выполнения действий:
Измените метод PerformCurrentAction так, чтобы он проверял наличие предмета в руках и в зависимости от этого выполнял различные действия. Если предмет есть в руках, то доступны только определенные действия (например, перемещение к точке, перемещение к объекту и выкинуть предмет), иначе доступны все другие действия.
Вот пример того, как может выглядеть измененный метод:
public void PerformCurrentAction() { if (actionQueue.Count > 0) { currentAction = actionQueue.Peek(); if (heldItem != null) { // Если есть предмет в руках, проверьте доступные действия для этого режима if (currentAction.actionType == CharacterAction.ActionType.MoveTo || currentAction.actionType == CharacterAction.ActionType.MoveToObject || currentAction.actionType == CharacterAction.ActionType.DropItem) { // Выполняйте только указанные действия ExecuteAction(currentAction); } else { Debug.Log("Нельзя выполнить это действие с предметом в руках."); } } else { // Если предмета нет в руках, выполните любое доступное действие ExecuteAction(currentAction); } } } private void ExecuteAction(CharacterAction action) { // Реализуйте выполнение действия здесь }
-
Измените метод для клика по объекту:
В методе, который обрабатывает клик по объекту (например, по дереву), добавьте проверку наличия предмета в руках. Если персонаж держит предмет, то сначала выполняйте действие "выкинуть предмет", а затем начинайте выполнение действия, связанного с объектом (например, рубка дерева):
public void OnObjectClick(GameObject clickedObject) { if (heldItem != null) { // Если есть предмет в руках, выкиньте его ExecuteDropItemAction(); } // Затем начните выполнение действия, связанного с объектом (например, рубка дерева) ExecuteActionRelatedToObject(clickedObject); } private void ExecuteDropItemAction() { // Реализуйте выкидывание предмета здесь } private void ExecuteActionRelatedToObject(GameObject targetObject) { // Реализуйте выполнение действия, связанного с объектом (например, рубка дерева) здесь }
Это позволит вам контролировать выполнение различных действий в зависимости от наличия предмета в руках персонажа и корректно обрабатывать клики по объектам с учетом текущего режима.