One-on-one. Девлог. Отделение логики от анимаций и переход на ивенты

от автора

На этой неделе не делал новые фичи, зато наконец сделал более удобную архитектуру. Это упростит разработку в дальнейшем.

Вводная инфа

Делаю коллекционную карточную PvP-игру на Unity и C#. Гитхаб.

Что сделал

Раньше у меня анимации были намертво прибиты к логике. Я получал комментарии, что это вообще-то не очень хорошо, но сам минусов не находил. Пока не столкнулся с задачей, где требовалось моментальная работа логической части, а она у меня приостанавливалась, подстраиваясь под выполнение анимаций.

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

   public IEnumerator DrawingCard(Side side, int amountOfCards = 5, float pauseTime = 0)     {         yield return new WaitForSeconds(pauseTime);         int i = side.TableCards.Count;         while (side.TableCards.Count < amountOfCards + i)         {             if (side.Cards.Count == 0)             {                 shuffledComplete = false;                 StartCoroutine(ShufflingDeck(side));                 yield return new WaitUntil(() => shuffledComplete == true);             }             GameObject card = side.Cards[0];             //card.transform.localPosition = side.StartPosition;             side.TableCards.Add(card);             side.Cards.RemoveAt(0);             yield return new WaitForSecondsRealtime(.3f);         }     }

Теперь у меня DrawCard — это метод в логической части кода и там только происходит удаление карты из одного списка и добавление в другой. Ну, и отправка соответствующего ивента:

    public void DrawCard(GameObject card)     {         TableCards.Add(card);         Cards.Remove(card);         CardAction?.Invoke(card, this, CardActionType.Draw);     }       public void DrawCards()     {         DrawCards(StartDrawCards);     }      public void DrawCards(int numberOfCards)     {         int i = TableCards.Count;         while (TableCards.Count < numberOfCards + i)         {             if (Cards.Count == 0)             {                 ShufflingDrawDeck();                 CardAction?.Invoke(null, this, CardActionType.Shuffle);             }             GameObject card = Cards[0];             DrawCard(card);         }     }

Все остальное в скрипте, который отвечает за анимации. Там создается очередь из анимаций, которые проигрываются друг за другом. Да, знаю, else if тут выглядит по-колхозному, но пока не знаю решения, как это сократить, а разбираться было лень:

    void Update()     {         if (!isProcessingQueue && eventQueue.Count > 0)         {             StartCoroutine(ProcessEventQueue());         }     }      IEnumerator ProcessEventQueue()     {         isProcessingQueue = true;         CardEvent cardEvent = eventQueue.Dequeue();                  if (cardEvent.ActionType == CardActionType.Draw)         {             PlayDrawAnimation(cardEvent.Card, cardEvent.TriggeringSide);         }         else if (cardEvent.ActionType == CardActionType.Discard)         {             PlayDiscardAnimation(cardEvent.Card, cardEvent.TriggeringSide);         }         else if (cardEvent.ActionType == CardActionType.Shuffle)         {             PlayShuffleAnimation(cardEvent.TriggeringSide);         }         else if (cardEvent.ActionType == CardActionType.Burn)         {             PlayBurnAnimation(cardEvent.Card, cardEvent.TriggeringSide);         }         yield return new WaitForSeconds(0.3f);         isProcessingQueue = false;     }      void PlayDrawAnimation(GameObject card, Side side)     {         side.DoubleTableCards.Add(card);         card.transform.localPosition = side.StartPosition;         CalculateTableCardsPosition(side);         side.DrawCounter--;     }      void OnCardAction(GameObject card, Side side, CardActionType cardActionType)     {         eventQueue.Enqueue(new CardEvent(card, cardActionType, side));     }

Долго боролся с этим багом: при перетасовке колоды карты вылетают из стопки сброса, хотя должны как бы оказаться сначала в стопке выдачи:

Решение нашел супер простое. Каждый раз, когда вызывается анимация вытаскивания карты, я сначала карту перемещаю в нужное место:

    void PlayDrawAnimation(GameObject card, Side side)     {         side.DoubleTableCards.Add(card);         card.transform.localPosition = side.StartPosition; // вот эта строчка         CalculateTableCardsPosition(side);         side.DrawCounter--;     } 

Как можете видеть, все равно у меня логика и визуал еще смешаны. В классе Side помимо логических методов я храню еще позиции карт (для стопки выдачи и стопки сброса) и дубль списка TableCards. От DoubleTableCards пока не знаю, как избавиться: он нужен, чтобы собирать в него карты с временным промежутком и красиво выстраивать на экране. Раньше у меня этот метод работал с TableCards, но при текущей логике появление всех карт на экране при выдаче будет моментальным:

    public void CalculateTableCardsPosition(Side side)     {         int offset = 105;         int width = (side.DoubleTableCards.Count - 1) * offset;         int halfWidth = width / 2;         for (int cardIndex = 0; cardIndex < side.DoubleTableCards.Count; cardIndex++)         {             GameObject Card = side.DoubleTableCards[cardIndex];             CardScript grid = Card.GetComponent<CardScript>();             sprite = Card.GetComponent<SpriteRenderer>();             sprite.sortingOrder = cardIndex;             float desiredX = -halfWidth + (offset * cardIndex);             grid.desiredPosition = new Vector2(desiredX, side.HandPosition);             grid.timestamp = Time.time + grid.timeBetweenMoves;             grid.startPosition = grid.desiredPosition;         }     }

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

Что сделаю к следующей субботе

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

    public void DescriptionTranscription()     {         if (cardSide.Strength != lastStrength)         {             lastStrength = cardSide.Strength;             finalDamage = 0;             cardDescriptionDynamic = LocalizationSettings.StringDatabase.GetLocalizedString(cardId + "_Description");             //string cardDescriptionDynamicWithoutTags;             if (cardDescriptionDynamic.Contains("["))             {                 int firstSym = cardDescriptionDynamic.IndexOf('[');                 int secondSym = cardDescriptionDynamic.IndexOf(']');                 string damage = "";                 for (int i = firstSym + 1; i < secondSym; i++)                 {                     damage += cardDescriptionDynamic[i];                 }                 cardDamage = Int32.Parse(damage);                 cardDescriptionDynamic = cardDescriptionDynamic.Replace("[", "");                 cardDescriptionDynamic = cardDescriptionDynamic.Replace("]", "");                 finalDamage = cardDamage + cardSide.Strength;                 cardDescriptionDynamic = cardDescriptionDynamic.Replace(damage, finalDamage.ToString());                   if (cardSide.Strength != 0)                 {                     string coloredDamage = "<color=" + (cardSide.Strength > 0 ? "green" : "red") + ">" + finalDamage.ToString() + "</color>";                     cardDescriptionDynamic = cardDescriptionDynamic.Replace(finalDamage.ToString(), coloredDamage);                 }              }             if (cardDescriptionDynamic.Contains(";"))             {                 int firstSym = cardDescriptionDynamic.IndexOf(';');                 int secondSym = cardDescriptionDynamic.IndexOf('?');                 string block = "";                 for (int i = firstSym + 1; i < secondSym; i++)                 {                     block += cardDescriptionDynamic[i];                 }                 cardBlock = Int32.Parse(block);                 cardDescriptionDynamic = cardDescriptionDynamic.Replace(";", "");                 cardDescriptionDynamic = cardDescriptionDynamic.Replace("?", "");                 cardDescriptionDynamic = cardDescriptionDynamic.Replace(block, cardBlock.ToString());             }             if (cardDescriptionDynamic.Contains("{"))             {                 int firstSym = cardDescriptionDynamic.IndexOf('{');                 int secondSym = cardDescriptionDynamic.IndexOf('}');                 string draw = "";                 for (int i = firstSym + 1; i < secondSym; i++)                 {                     draw += cardDescriptionDynamic[i];                 }                 cardDraw = Int32.Parse(draw);                 cardDescriptionDynamic = cardDescriptionDynamic.Replace("{", "");                 cardDescriptionDynamic = cardDescriptionDynamic.Replace("}", "");                 cardDescriptionDynamic = cardDescriptionDynamic.Replace(draw, cardDraw.ToString());             }             if (cardDescriptionDynamic.Contains("("))             {                 int firstSym = cardDescriptionDynamic.IndexOf('(');                 int secondSym = cardDescriptionDynamic.IndexOf(')');                 string strength = "";                 for (int i = firstSym + 1; i < secondSym; i++)                 {                     strength += cardDescriptionDynamic[i];                 }                 cardStrength = Int32.Parse(strength);                 cardDescriptionDynamic = cardDescriptionDynamic.Replace("(", "");                 cardDescriptionDynamic = cardDescriptionDynamic.Replace(")", "");                 cardDescriptionDynamic = cardDescriptionDynamic.Replace(strength, cardStrength.ToString());             }         }      }

Чем я делился в прошлых девлогах:


Этот пост входит в цикл постов про игру, которую я потихоньку делаю уже несколько месяцев. Я делюсь всем производственным процессом: какие решения я принимаю в разработке, геймдизайне, интерфейсе, арте и других сферах. Подписывайтесь тут или в телеграм-канале: @nigylam_blog.


ссылка на оригинал статьи https://habr.com/ru/articles/767436/