Работа со статусами персонажа. Эксперименты в Unity

Разрабатывая игру на Unity я столкнулся с интересной задачей: как сделать расширяемое время действия негативных или положительных эффектов на персонаже.

Вкратце у меня есть персонаж, к которому могут применяться некие эффекты, как ослабление, усиление, повышение скорости, снижение скорости и прочие. Чтобы оповещать игрока о действии того или иного эффекта, в игре предусмотрена статусная строка.

Первые версии этой строки содержали затемненные иконки всех статусов и при наступлении эффекта, загоралась нужная.

image

На каждом статусе была корутина, которая отменяла эффект через заданное количество времени.
В этом решении есть довольно важный минус. Если в результате неких событий в игре на персонажа накладывается одинаковый эффект через время меньшее, чем время действия предыдущего аналогичного эффекта, то здесь могут быть два варианта событий.

  1. Совсем неправильный: Запускается вторая корутина параллельно первой. Когда первая завершается, она возвращает исходные значения, то есть эффект снимается раньше, чем вторая корутина закончила работу.

    image

  2. Тоже неправильный, но в некоторых случаях приемлемый: Отменять первую корутину и запускать вторую. В этом случае время действия эффекта будет равно времени действия первого эффекта до момента отмены корутины + время действия второй корутины.

    image

Для моей задачи оба методы неприемлемы, так как мне надо, чтобы время действия эффекта продлевалось, чтобы я получил в итоге сумму по времени действия каждого наступившего эффекта вне зависимости от того, сколько раз эффект применялся.

Если персонаж наступает на шипы, у него условно повреждается нога и он не может продолжать двигаться с прежней скоростью. Допустим скорость снижается на 5 секунд. Если через 3 секунды персонаж наступает на другие шипы, то скорость должна быть снижена еще на 5 секунд. То есть 3 секунды прошло, 2 осталось + 5 секунд от новых шипов. Время действия эффекта должно продлиться еще 7 секунд с момента наступления на вторые шипы (10 в целом).

И с помощью корутин я не нашел решения задачи, так как нельзя узнать оставшееся время до завершения корутины, чтобы прибавить его к новой корутине.

Решение, которое я нашел этой задаче, состоит в использовании словаря (Dictionary). Его преимущество перед List в том, что у словаря есть ключ и значение, а значит я могу обратиться к любому значению по ключу. Плюс это решение позволяет избавиться от постоянных статусных иконок в строке и включать нужные по мере необходимости и устанавливать их в позиции в строке в порядке их наступления.

Dictionary<string, float> statusTime = new Dictionary<string, float>(); 

Словарь в данном случае выгоднее использования очереди, так как очередь работает по принципам First In First Out, но время действия эффектов разное, а значит статус, который надо снять, может стоять не первым в очереди.

Для этого я добавил три метода.

AddStatus

Добавляем нужный статус в словарь, если такой статус в словаре уже есть, то прибавляем время действия. Если статуса нет, то вычисляем время окончания и добавляем в словарь.

private void AddStatus(string status, float duration)
 {     if (statusTime.ContainsKey(status))     { 
            statusTime[status] += duration; 
    } 
    else
     { 
            float endTime = Time.timeSinceLevelLoad + duration; 
            statusTime.Add(status, endTime);
     } } 

RemoveStatus

Удаляем статус из словаря, восстанавливаем исходные значения.

private void RemoveStatus(string status)
 { 
        statusTime.Remove(status); 
        RestoreStats(status);
 } 

CheckStatus

Если в словаре есть статусы, то проверяем, не истекло ли время их действия.

Если истекло, то удаляем статус из словаря. Так как изменения словаря в цикле приводит к невозможности синхронизации значений словаря, то перекидываем тут ключи словаря в обычный List.

private void CheckStatuses()
     { 
        if (statusTime.Count > 0) 
        { 
            float currTime = Time.timeSinceLevelLoad;
             List<string> statuses = new List<string>(statusTime.Keys); 

            foreach (string stat in statuses)
             {
                 if (currTime > statusTime[stat])
                 {
                     RemoveStatus(stat);
                 }
             }
         } 
    } 

Из плюсов, очевидно, это расширяемое время действия эффектов. То есть задача решена.
Но и довольно значительный минус тут присутствует. Проверка на наличие статусов осуществляется в Update каждый фрейм. В моей игре присутствует максимум 4 игрока, а значит этот метод будет выполняться каждый фрейм 4 раза параллельно. При 4 персонажах, я думаю, это не критично, но уверен, что при большем количестве персонажей, это может вызвать проблемы производительности. Стоит так же отметить, что метод защищен проверкой на наличие в словаре элементов, и при пустом словаре нагрузка должна снижаться.

Забегая в будущее (которое пока совершенно туманно для этой игры), я так же уверен в этом решении и в онлайн режиме, так как проверка на статусы игрока будет происходить только для текущего локального игрока, а не для всех инстанциированных игроков.


ссылка на оригинал статьи https://habr.com/post/426305/

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *