Черная пятница, черная пятница… надоело. Объявляю свой личный Белый понедельник — за пару ночей написал небольшую игру и выкладываю ее код на всеобщее пользование, со скидкой 90%. Зачем мне это надо? Ну я вижу следующие плюсы — тот самый открытый код для поиска работы (да да, сейчас я нахожусь в активном поиске), почитать в комментариях о своих косяках, наконец то сменить статус на Хабре.

Суть игры


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

Познакомьтесь, это Кальцифер, ваш аватар в этой игре.


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

Ссыль на код

Пояснения насчет кода


Начнем с GameManager. Он всему голова, именно в нем меняется состоянии игры — Initialization->GameLoop->Win или Lose. Одинок и един, ибо синглтон. Так как игра не сетевая, простая, и без сложных переходов, то было принято решение использовать этот паттерн. Здесь же идет обработка попаданий по игроку (см. ниже Известные проблемы), учет хитпоинтов и проверка на выигрыш\проигрыш. Был бы GodObject, да слишком мало у нас классов, поэтому знает не всё обо всех. На этапе инициализации, создается пул объектов отображающих анимацию урона по игроку и смерти врагов. Для отслеживания состояния хитпоинтов, можно подписаться на UpdateHpWizardDelegate или UpdateHpCalciferDelegate. В нашем случае это делает GUIManager для отображения текущего хп на экране.



К этому времени SpawnManager уже составил список точек спавна врагов



a WaveManager загрузил порядок волн создания врагов. Волны можно настроить двумя способами: прописать в коде игры или загрузить с Json файла. Для редактирования этого Json написан кастомный editor:GameDataEditor



Можно прописать точный номер спавна или указать что можно создаться на любом свободном.

Создание волн сделанно с помощью хитрой корутины:

SpawnWaves
  private IEnumerator SpawnWaves()
        {
            yield return new WaitForSeconds(firstWaveDelay);
            while (currentWave < waves.Count)
            {
                if (!CheckFinishedCurrentWave())
                {
                    var step = waves[currentWave].GetCurrentWaveStep();
                    if (step != null)
                    {
                        yield return new WaitForSeconds(step.delay);
                        var spawners = step.spawners;
                        if (spawners != null)
                        {
                            foreach (var spawn in spawners)
                            {
                                SpawnPoint spawnPoint;
                                if (spawn.index == Consts.INDEX_RANDOM_SPAWN_POINT)
                                {
                                    spawnPoint = SpawnManager.S_Instance.GetRandomEmptySpawnPoint();
                                }
                                else
                                {
                                    spawnPoint = SpawnManager.S_Instance.GetSpawnPointByIndex(spawn.index);
                                }
                                if (spawnPoint != null)
                                {
                                    SpawnManager.S_Instance.SpawnEnemy(spawnPoint, spawn.name);
                                }
                                else
                                {
                                    throw new Exception("Not empty spawn point");
                                }
                            }
                        }
                        Debug.Log(step.text);
                    }
                    waves[currentWave].currentStep++;
                }
                else
                {
                    SelectNextWave();
                }
            }
        }


Вначале проходит firstWaveDelay секунд до начала запуска 1 первой волны. После этого в цикле прогоняют все волны по очереди, вставляя нужную задержку step.delay между шагами волны. Почему в корутине а не например в Update? Да собственно можно и так и эдак, просто тут более наглядно, видно где задержка ( yield return new WaitForSeconds) и не надо городить лишние циклы и проверки.

Давай те глянем что же представляют из себя SpawnPoint. Это MonoBehavior c 2 компонентами: SpawnPoint и CircleCollider2D. В первом, с помощью второго, определяется занят ли спавн каким то врагом. OnDrawGizmos отображает в редакторе Unity расположение спавнов.

OnDrawGizmos

    void OnDrawGizmos()
        {
            if (m_IsDirty)
            {
                Gizmos.color = Color.red;
            }
            else
            {
                Gizmos.color = Color.green;
            }
            Gizmos.DrawSphere(transform.position, 0.3f);
        }




Все враги происходят от базового класса BasicEnemy в котором есть несколько виртуальных методов:

  • ContactWizard() , для взаимодействия с волшебником
  • ContactCalcifer(), для взаимодействия с Кальцифером
  • ShowDeathAnimation(), для показа анимации смерти

Их можно переопределить в потомках, для различной реакции на эти события. Например PoisonEnemy ранит при прикосновении Кальцифера, в отличии от остальных врагов.

PoisonEnemy.ContactCalcifer
 
  public override void ContactCalcifer()
        {
            GameManager.S_Instance.DamageCalcifer(damage);
            base.ContactCalcifer();
        }


Кстати, врагов и многие другие объекты (много и часто создаваемых на сцене) мы не удаляем с помощью Destroy(this), а отправляем обратно в пул объектовObjectPool.Recycle(this). Таким макаром мы неплохо экономим на создании объектов, которое как известно достаточно затратное дело.

Так например анимации заканчиваются вызовом SelfDestroy(), который и возвращает объект анимации обратно в пул.



Движутся же враги с помощью силы пафоса компонента BasicEnemyMoving. В нем нас интересуют два метода: OnEnable()и Move(). OnEnable () вызывается после вытаскивания врага с пула и нужен для поворота врага (если необходимо) в сторону цели.

OnEnable
 
public void OnEnable()
        {
            if (obj!=null&&obj.NeedRotateForDirection) 
            {
                Vector3 moveDirection = transform.position - GameManager.S_Instance.wizardTransform.position;
                if (moveDirection != Vector3.zero)
                {
                    float angle =  Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg-90;
                    transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward); 
                }
            }
        }


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

Move
 
 public virtual void Move()
        {
            transform.position = Vector3.MoveTowards(transform.position, SpawnManager.S_Instance.TEMP_GOAL.position, speed * Time.deltaTime);
        }


На этом как бы всё.


Известные косяки


  • Синглтоны
  • Отсутствие классов Wizard и Calcifer. Вся логика с их взаимодействием лежит в GameManager
  • Магические числа с углами поворота спрайтов. Да понимаю что надо бы все спрайты подвести к одному углу и уже от него плясать. Но времени не хватило.
  • Отсутствует запись прогресса
  • Кривое объявление префабов врагов в SpawnManager

TODO


  • Смена дня\ночи. Днем отдыхаешь, раскидываешь скиллы и т.п. Ночью жгешь вражин.
  • Новые типы врагов: стреляющие, ранящие Кальцифера, оставляющие слизь/паутину, телепортирующиеся, размножающиеся
  • Прокачка Кальцифера: статы, tower defence, скиллы
  • Несколько концовок. Есть пара задумок насчет нескольких вариантов развития событий, в случаи проигрыша на разных этапах/разной прокачки
  • Освещение. Стены камеры освещаются Кальцифером, по мере его роста, все сильнее и сильнее.
  • Кат сцены.

Анимация огонька взята с powstudios.com
За спрайт волшебника отдельное спасибо милой Kori Tyan

Комментарии (54)


  1. FeNUMe
    02.12.2017 19:30
    +1

    Бедный Хаул, вот это его жизнь потрепала:)


    1. Arkebuz Автор
      02.12.2017 19:48

      Это уже в процессе пришло, изначально в голову был образ Корвина с Амбера.


      1. Deosis
        04.12.2017 07:58

        Уж больно имя узнаваемое.
        Можно сделать так, чтобы камера переезжала иногда на новое место со сменой декораций и противников.


        1. Arkebuz Автор
          05.12.2017 09:55

          Пока по предварительным планам все дело будет происходить в этой камере. А врагов к следующему билду добавлю. Так же думаю можно будет кастомизацию приделать — вместо огонька Мортэ например, дракоша с Мулана или Джинни


  1. KpoKec
    02.12.2017 19:35

    Почему вместо кватернионов и арктангенса не использовать простр transform.up или transform.right как вектор направления?


    1. Arkebuz Автор
      02.12.2017 19:47

      Торможу, как это?


      1. KpoKec
        02.12.2017 19:49

        transform.up = wizardPosition — transform.position;


      1. KpoKec
        02.12.2017 19:50

        transform forward/up/right не только геттеры, но и сеттеры


        1. Arkebuz Автор
          02.12.2017 20:05

          Спасибо. Вот они минусы самостоятельного и одиночного изучения Unity.


  1. vasIvas
    02.12.2017 20:01
    -3

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

    С таким кодом я бы взял только забившийся Кальцифер прочищать.


    1. Arkebuz Автор
      02.12.2017 20:02

      Главное, ни слова про политику или Маска.


    1. Arkebuz Автор
      02.12.2017 20:09
      +1

      Что не так с кодом?

      почитать в комментариях о своих косяках

      Это озвучено аж до ката, так что если не сложно тыкните меня носом в косяки.
      А насчет работы… Ссылки тут нет, спама тоже, продолжение про эту игру уже составляется. Не только же зубрам писать, новички тоже хотят учиться.


      1. vasIvas
        02.12.2017 20:18
        -2

        Предложение работы и бесплатный рефакторинг хотят новички выкладывая свои наработки о кальцефере. Мне просто ипец как интересно лицезреть подобное на хабре.


        1. Arkebuz Автор
          02.12.2017 20:21
          +1

          Ясно. Ну тогда вопросов не имею к вам больше. Можете не лицезреть мои посты.


          1. vasIvas
            02.12.2017 20:36
            -2

            Меня пугает что это увидят такие же как Вы и завтра уже другие посты затеряются среди подобного контента-кальцефера.


            1. sibirier
              02.12.2017 23:59
              +1

              а на какую платформу вы предлагаете выкладывать свой код под предлогом какой-то цели, чтобы его могли увидеть работодатели в таких отраслях? на какие сайты заходят так много представителей IT компаний, чтобы на них такие статьи были настолько же эффективны (а может и больше)?
              да, соглашусь с вами, Хабр не то место, чтобы делать из него свалку мелких статей «смотрите как умею, возьмите меня куда-нибудь». а есть ли этому альтернатива? притом не просто куда можно закидывать код и пояснениями в виде статьи, но куда бы еще и заходили искать этот самый код люди, нуждающиеся в таких программистах?


            1. PapaBubaDiop
              03.12.2017 00:08
              +1

              Вы попросите @bumburum создать хаб «Ищу работу» и отпишитесь от него.


              1. LoadRunner
                04.12.2017 10:18
                +1

                1. PapaBubaDiop
                  04.12.2017 10:29

                  И его тоже.


            1. easty
              03.12.2017 12:04

              Рискую прослыть слоупоком, что такое "кальцифера"?


              1. GoldenStar
                03.12.2017 12:47

                Это такой персонаж (тлеющий уголек жизни — т.е. сердце) из известного в узких детских кругах мультфильма "Ходячий Замок" легендарного японского режиссера. Хотя его мультфильмы вполне и для взрослых. Советую посмотреть всю его фильмографию — вы точно не пожалеете.


            1. TheShock
              04.12.2017 11:36

              Меня пугает что это увидят такие же как Вы и завтра уже другие посты затеряются среди подобного контента-кальцефера.

              А мне кажется, что «ищу работу статьи» — скорее положительное явление. Автор старается максимально вложится в материал, а не просто перевести на отъебись или скопипастить новость с другого сайта.


              1. vasIvas
                04.12.2017 17:33

                Привет! Написав это сообщение Вам выпал шанс совершенно бесплатно получить от меня ответ! Я решил Вас осчастливить уникальная возможность и Вам даже не придется ждать следующую черную пятницу. Так вот мой дорогой друг! Здесь много Ёби от чувачка-новичка… Буду благодарен за подробное объяснение и наставление! И да, ведется набор в команду для написания продолжения о нападении Сифилёчка в вечернее время на Дрищьград.

                Вы реально считаете это положительным контентом? Мне Вас жаль если это так.


                1. TheShock
                  04.12.2017 17:42

                  Наверное, вы хотели донести какую-то мысль. Возможно, в голове у вас она даже казалась довольно внятной. Но сформировать в речь вы ее не смогли


        1. Daniil1979
          03.12.2017 12:38

          Ну а я новичок в разработке на C#, и МНЕ интересно. А вот многочисленные статьи про Docker-контейнеры, которыми хабр-уже-давно-не-торт засран от и до, уже реально заколебали.

          Уважаемый Arkebuz, мой Вам совет — создайте свой раздел на samlib.ru, и туда выкладывайте статьи. Потом только киньте ссылку на раздел — добавлю в друзья.


          1. Arkebuz Автор
            05.12.2017 09:49

            А почему на самлиб? Насколько я помню там же самиздат в основном.


        1. Smeilz1
          03.12.2017 15:11

          Прошу прощения, а за то, а ваш комментарий тут тоже нужно оплатить?
          Вы ведь «бесплатно» старались, тратили время на его создание.


          1. vasIvas
            03.12.2017 17:05

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


    1. BosonBeard
      03.12.2017 16:54
      +1

      То есть, я так понимаю, если бы человек убрал упоминание о поиске работы, то вы бы весь этот «хай» не подняли?
      Ну в любом случае, Ваша логика чем-то напоминает логику «лубочных» старушек у подъезда, вот «проститутка» прошла, вот «наркоман».
      Я так погляжу у Вас в принципе нет публикаций, Вы напишите свой шедевр, и посмотрите, как люди его оценят, таким образом Вы создадите качественный контент и героически разбавите так Вами называемый «контент-кальцифер».
      Я думаю если человек делает хоть что-то созидательное, что вроде бы как не разрушает особо работу существующей системы, то не надо сразу пытаться поднять его за это «на вилы».
      В конце концов объективно, автор сам по тренировался, кто-то дал дельный совет и порадовался тому что помог человеку, кто-то из любопытна прочитает, кто-то найдет эту статью через поисковую систему и заинтересуется разработкой игр на Unity, да мало ли что еще хорошего принесет эта статья.
      Я вот лично благодаря ей и комментариям к ней узнал, что такое кальцифер, не думаю, что это спасет однажды мне жизнь, но и хуже явно не сделает :)
      А Вам и вашим единомышленникам я думаю не так сложно, пролистать неинтересную статью, это вопрос долей секунд, учитывая, что превью текста статьи в ленте уложилось в один абзац.


  1. slonopotamus
    02.12.2017 23:49

    > Синглтоны

    Есть мнение что с текущей архитектурой Unity это неизбежное зло. Потому что нет никакого контекста с временем жизни «сцена» и простым доступом за O(1). В результате либо вы на каждый чих будете искать нужный объект через FindObjectOfType (что медленно) либо заводите статическую переменную и запоминаете там на него ссылку.

    А чтобы добавить жести, у вас нет никаких механизмов задания порядка инициализации этих «важных» объектов. Поэтому либо их приходится создавать программно on-demand (как делаете вы в Singleton.cs) и в результате терять возможность их настроить на сцене (поэтому вам пришлось городить огород вокруг редактирования WaveManager), либо они ставятся на сцену но тогда в процессе инициализации есть риск что с ними начнут пытаться работать до вызова Start/Awake (что у вас потенциально возможно в ObjectPool.cs). И так и так плохо, а сделать хорошо нечем.


    1. Arkebuz Автор
      03.12.2017 00:09

      Script Execution Order?


      1. slonopotamus
        03.12.2017 00:17

        В рамках одного геймобжекта — да. Но разве он вам что-то обещает про порядок обработки нескольких геймобжектов?


        1. TheShock
          03.12.2017 01:24

          А иерархия? Сначала инициируются родительские элементы, потом дочерные. Порядок инициализации дочерных между собой не важен. У меня, вроде, все сперва инициируется, а потом запускается игра, потому в порядке выполнения евейков я не уверен, но вроде так они и работают?


          1. Leopotam
            03.12.2017 13:51

            Инициализация происходит не по иерархии, а по порядку создания инстансов в сцене. Т.е. или надо обладать идеальной памятью или использовать что-то иное для инициализации.


      1. KumoKairo
        03.12.2017 17:06
        +1

        Недавно годный доклад на эту тему опубликовали на youtube. Я сам практикую немного другие вещи (ECS / Entitas), но то что сделали они мне очень понравилось www.youtube.com/watch?v=raQ3iHhE_Kk
        Очень советую к просмотру, примеры кода так же можно найти по коментам и в блоге (https://blogs.unity3d.com/2017/11/20/making-cool-stuff-with-scriptableobjects/)


    1. p4p
      03.12.2017 02:17

      Кстати есть что годное по тебе mvc в unity?


    1. WeslomPo
      03.12.2017 08:35

      Используйте Zenject или любой другой контейнер.


      1. TheShock
        03.12.2017 09:22

        Он не инджектит, если используешь GameObject.Instantiate, правильно?
        А следовательно — он предлагает послать всю компонентную систему Юнити. Или помимо компонента, который мы вешаем на объект — он еще предлагает создать компонент-контейнер? Я не совсем понимаю, как с этим работать, можете, пожалуйста, подробнее?

        Ну вот допустим есть гипотетическая логика стратегии, там содержатся ресурсы, исследования.

        У меня есть кнопка «Построить супер-здание»:
        — Она должна быть серой, если здание не исследовано или уже построено в любом месте карты
        — Она должна быть синей, если здание исследовано, но не хватает ресурсов
        — Она должны быть зеленой, если можно построить здание
        — Она должна вспыхнуть (создаться эффект с частичками), если закончилось строительство здания
        — На ней должен быть написан текст, согласно локализации.

        И таких зданий у нас пять.

        Сейчас иерархия компонентов выглядит так:

        BuildingButtonPrefab
        |--- BuildingButtonImage
        |    \--- BuildingTitleText
        \--- ConstructedParticles


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

        class BuildingButtonsList : MonoBehaviour {
            public BuildingButtonPrefab BuildingButtonPrefab;
            
            public void Start () {
                foreach (var bType in Game.instance.buildings.list) {
                    Instantiate(BuildingButtonPrefab, transform);
                }
            }
        }
        
        class BuildingButtonPrefab : MonoBehaviour {
            public Button    BuildingButtonImage;
            public Text      BuildingTitleText;
            public Particles ConstructedParticles;
        
            private BuildingTypeEnum type;
        
            public void SetBuildingType (BuildingTypeEnum type) {
                this.type = type;
                text.text = Game.instance.localization.get("Buildings." + type);
            }
        
            public void Start () {
                Game.instance.onChange += OnSomethingChanged;
                Game.instance.onConstructed += OnConstructed;
            }
        
            public void OnSomethingChanged() {
                var g = Game.instance;
        
                if (!g.tree.IsResearched(type)) {
                    Button.image = GetGreyImage();
                }
                else if (g.buildings.IsConstructed(type)) {
                    Button.image = GetGreyImage();
                }
                else if (!g.wallet.HasResourcesFor(type)) {
                    Button.image = GetBlueImage();
                }
                else {
                    Button.image = GetGreenImage();
                }
            }
        
            public void OnConstructed(BuildingTypeEnum type) {
                if (type == this.type) {
                    ConstructedParticles.Launch();
                } 
            }
        }


        Я вижу тьму проблем в таком подходе, но Юнити прям толкает на то, чтобы писать именно так. Как это правильно сделать при использовании контейнера?


        1. WeslomPo
          03.12.2017 13:18

          Стараться не использовать MonoBehaviour :).

          Вместо Object.Instantiate — нужно использовать фабрику, и тогда-то зависимости будут прокинуты.

          По примеру, больно много уже классов, чтобы быстро пояснить… я только один изменю — BuildingButtonPrefab.

          public class BuildingButton : MonoBehaviour {
          	[SerializeField] private Button _button;
          	[SerializeField] private Text _title;
          	[SerializeField] private Particles _particles;
          
          	public void Listen(Action onClick) { _button.onClick.AddListener(onClick.Invoke); }
          
          	public void Unlisten() { _button.onClick.RemoveAllListeners(); }
          
          	public void Initialize(string title) { _title.text = title; }
          
          	public void Burst() { _particles.Launch(); }
          
          	public void SetState(ButtonState state) {
          		switch (state) {
          			case ButtonState.Disabled:
          				_button.interactable = false;
          				// _button.image = _gray;
          				break;
          			case ButtonState.Inactive:
          				_button.interactable = false;
          				// _button.image = _blue;
          				break;
          			case ButtonState.Active:
          				_button.interactable = true;
          				// _button.image = _green;
          				break;
          			default:
          				throw new ArgumentOutOfRangeException("state", state, null);
          		}
          	}
          }

          Как видно, тут нет синглтонов, как и нет никакой логики изменения состояния, нет самих состояний. Вообще, я руководствуюсь правилом — если это MonoBehaviour то это Вид и он максимально тупой. Все данные которые нужны, кнопка получает через методы (или поля), сама кнопка так же ничего и не спрашивает ни у кого, у ней есть все нужные ей данные для работы. Ее внутренние состояние можно поменять, в теории, только через публичные методы. Можно выделить интерфейс — и таким образом заблокировать доступ к gameObject
          Логика изменения поведения кнопки находится в другом классе, он не-MonoBehaviour и контролирует ее состояние обращаясь к моделям или другим контроллерам.


          1. LeonThundeR
            03.12.2017 18:25

            Стараться не использовать MonoBehaviour :).
            Как бы это не было смешно, но насколько я знаю, это единственный верный путь, если нужно создать проект с более менее сложной архитектурой в Unity.


            1. TheShock
              03.12.2017 21:54

              «Не использовать» или «использовать»??


              1. LeonThundeR
                03.12.2017 22:03

                WeslomPo

                Вообще, я руководствуюсь правилом — если это MonoBehaviour то это Вид и он максимально тупой.

                Т.е. не использовать их для чего-то сложного. А выносить всю логику и т.д. в свои классы не наследуемые от MonoBehaviour и связывать с компонентами порожденным от MonoBehaviour через интерфейсы.
                Но это все актуально только для более менее сложных проектов. Совсем простые игры проще, быстрее и в целом выгоднее делать не задумываясь обо всем этом, т.к. может получиться проектирование ради проектирования.


              1. WeslomPo
                03.12.2017 22:14
                +1

                Не использовать.

                Когда изучаешь Unity, автомагически приобретаешь болезнь MonoBehaviour головного мозга xD. Вот ты захотел решить задачу со списком кнопок — и сразу создал «class BuildingButtonsList: MonoBehaviour» — а зачем? Можно же простой класс использовать, а в нужный момент вызвать метод Initialize либо другой аналог Start, из класса контроллера (вопрос целесообразности существования самого такого класса оставим за скобками).

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

                Может возникнуть опасение, что этот подход не вяжется с Unity — однако это не так, движок достаточно гибкий. Попробуй Zenject изучить, довольно неплохо помогает прокачаться.


          1. TheShock
            03.12.2017 22:06

            По примеру, больно много уже классов, чтобы быстро пояснить… я только один изменю — BuildingButtonPrefab.

            Так, ну со вьюшкой понятно. _button, _title, _particles — это все ясно. Но как там иерархией выше?

            0. У нас есть что-то вроде BuildingButtonControllerFactory, которая создает BuildingButtonController, которая передает уже все параметры этой нашей BuildingButtonPrefab?
            1. Или ее правильно назвать BuildingButtonView?

            2. А как BuildingButtonController получает ссылку на BuildingButtonPrefab? И вот смотрите, мне надо создать список кнопок. Ну допустим, это выглядит в новом подходе так:

            foreach (var bType in game.buildings.list) {
                buttons.Add( buildingButtonControllerFactory.Produce(bType) );
            }


            3. Мы делаем GameObject.Instantiate(BuildingButtonPrefab) в контроллере BuildingButtonController? (простите за каламбур)
            4. А как BuildingButtonController знает в какой трансформ необходимо добавить свой ГеймОбджект?
            5. Как вы решаете проблему, что некоторые возможности доступны только наследнику MonoBehaviour? Я сходу не вспомню, что именно, но допустим, если бы Instantiate можно было бы сделать только внутри наследника — тогда как?

            6. Какая у вас иерархия файлов? Вы отдельные компоненты храните в одной папке (их фабрику, контроллер, вьюшку и всякие сервисы). Или у вас есть директория со всеми фабриками, директория со всеми контроллерами, директория со всеми вьюшками?

            7. Вы создаете три класса вместо одного на каждую сущность — не усложняется подход на больших проектах?


            1. LeonThundeR
              03.12.2017 22:15
              +1

              Отвечу только по пункту 7. Остальное пусть лучше WeslomPo разжует ))
              Как раз таки именно в больших проектах все это и имеет смысл, т.к. относительный объем этого инфраструктурного когда будет очень малым. А преимущества от вынесения бизнес логики в отдельные классы согласно паттерна MVC будут очень значительны.


            1. WeslomPo
              03.12.2017 22:28

              0. Этому классу совсем неважно как его создадут и куда поместят, это не его проблемы.
              1. Да более подходящее имя.
              2. Не важно как :) как удобно. Например из пула объектов.
              3. Обычно контроллеры не занимаются такой ерундой (просто получают список всех нужных объектов, либо пул), но можно и так.
              4. Это одна из причин почему он этим не занимается.
              5. Все что нужно MonoBehaviour — это вид и его проблемы, там же и решаем.
              6. Под каждый кусок проекта завожу папку, внутри иерархию из папок Controllers, Views, Enums, Interfaces и т.д., папки исключаю из генерации namespace. Пока так. Но не уверен что это лучшее решение.
              7. Распространенное заблуждение :). В крупных проектах долгожителях на первый план выходит поддержка, а не написание. Создавать модули — чуточку сложнее становится, за то поддерживать в десяток раз проще. Плюс разделение логики сильно сокращает время написания. Да я, лично, не люблю прокидывать классы через контейнер (так много всего нужно учесть), но это делается один раз, а потом готовые классы просто дополняются нужным функционалом. Кстати, больше всего ошибок допускается и времени уходит на виды, а контроллеры и модели писать — одно удовольствие, чистая логика.
              В целом LeonThundeR прав, если проект мелкий и жить долго не собирается — лучше не морочить голову архитектурой особо — весь запал выжрет — это применимо только если нужно долго поддерживать проект.


  1. kraso4niy
    03.12.2017 11:32

    мне кажется или я попал на форум геймдев.ру


    1. slonopotamus
      03.12.2017 17:52

      Вы так говорите, будто это что-то плохое. Почему бы и не разбавить немного потоки статей про нейросети, блокчейны и вебдев?


  1. LeonThundeR
    03.12.2017 22:05

    Помогла ли эта статья с поиском работы?


    1. Arkebuz Автор
      03.12.2017 22:25

      Еще не знаю, выходные же:)


  1. Bookvarenko
    04.12.2017 12:11

    Мне вот тут подумалось — а что если запилить клон этой игрушки на Godot 3.0 и сравнить с оригиналом на Unity по простоте реализации?


    1. Arkebuz Автор
      05.12.2017 09:44

      Попробуй, сравним:)


      1. Bookvarenko
        05.12.2017 11:23

        OK, вот на днях текущий проект до релиза дотащу и попробую пожалуй.


        1. KumoKairo
          05.12.2017 15:28
          +1

          +, мне тоже интересно