Предыстория


Прошло довольно много времени с момента написания предыдущей статьи. Как и обещал я написал вторую часть. Хотелось бы сказать спасибо всем тем, кто давал советы в комментариях, из всех их я смог узнать что-то новое. Ну а для тех кому хочется сразу посмотреть проект вот ссылка на GitHub проекта.

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

image


Обновления


  1. Добавлен свой собственный редактор карт(как в оригинале игры).
  2. Реализован эхолот.
  3. Добавлена возможность ловить на донку и на спиннинг.
  4. Добавлено много видов рыбы.
  5. Сильно улучшена производительность проекта.
  6. Исправлено большое количество багов.
  7. Так же значительное улучшение архитектуры приложения(точнее её появление).
  8. Добавлено сохранение профиля игрока.
  9. Реализованы трофеи.
  10. Добавлена смена дня и ночи.
  11. Добавлены путешествия.
  12. Реализован продуктовый магазин.
  13. Реализован патерн MVP.
  14. Реализована система событий в игре
  15. Реализована прикормка, с возможностью миксования ингредиентов
  16. Добавлена озвучка
  17. Добавлены анимации
  18. Реализован износ удилищ, в зависимости от размера рыбы и времени вываживания

Больше изменений добавлю в README файл проекта Git.

Как чужой код ввёл в заблуждение.


image

Мы видим на скриншоте редактор карт для рыбалки, а именно сетку глубины для каждой локации (Это элементы Label с FormBorderStyle = 0, для того, чтобы показать рамку). Кстати скриншот сделан с использованием моих собственных ножниц. В чём заключалась проблема?

Исходный код
for (int x = 0; x < 51; x++){
    for (int y = 0; y < 18; y++){
    Point between = new Point(Game.CastPoint.X - LVL.Deeparr[x, y].Location.X, 
                           Game.CastPoint.Y - LVL.Deeparr[x, y].Location.Y);
    float distance = (float)Math.Sqrt(between.X * between.X + between.Y * between.Y);
        if (distance < 20){
            if (Player.getPlayer().lure != null){
                Game.gui.DeepLabel.Text = LVL.Deeparr[x, y].Tag.ToString();
                Sounder.setY(x);
                Sounder.setX(y);
            }
        }
        Game.Deep = Convert.ToInt32(Game.gui.DeepLabel.Text);
    }
        
}



Здесь мы видим простой проход по двумерному массиву(причём не правильный). Потом мы вычисляем по теореме Пифагора гипотенузу, и если она < 20, мы определяем нужную клетку. Но этот метод очень плохо работает даже с квадратом. А тут прямоугольники. Поэтому часто клетка определяется неправильно. В своё оправдание могу сказать, что этот код я взял с YouTube.

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

Код
for (var y = 0; y < CurLvl.Height; y++) {
      for (var x = 0; x < CurLvl.Widgth; x++) {
             var r = new Rectangle(CurLvl.DeepArray[x, y].Location, new 
                    System.Drawing.Size(LabelInfo.Width, LabelInfo.Height));
      if (r.IntersectsWith(new Rectangle(point, new System.Drawing.Size(1, 1)))) {
                        //SomeCode
      }
     }
}


Здесь мы берем координаты курсора, кастим их в PointToClient, и передаём в конструктор Rectangle, размеры указываем 1 и 1. Потом мы используем стандартный метод IntersectsWith проверяем, пересечение курсора, и label. Также, мы не можем просто обработать клик по label, так как форма с ними не показывается.
Также, это позволило реализовать поддержку 3-х удилищ для ловли.

Генерация рыбы


Итак, основная часть игры это генерация рыбы. Она происходит в несколько этапов:
1.При заходе на локацию мы из строки типа:
Карась Золотой:25 250-400 [Сыр, Червь, Опарыш, Кукуруза] Где содержится размер рыбы в процентах от максимального, глубина минимальная, глубина максимальная, и список наживок получаем объект рыбы(Не забыв предварительно проверив строку через регулярные выражения). Для красоты кода я определил оператор который кастит строку к рыбе.

public static explicit operator Fish(FishString fs) {
      return fs.GetFishByStr();
}


В итоге такой подход позволяет нам написать:

Fish fish = (Fish)new FishString("Карась Золотой: 25 250 - 400
[
Сыр,
 Червь,
 Опарыш,
 Кукуруза
]");

Код приведен для примера и не встречается в проекте в подобном виде.

2.Теперь нам надо дождаться, когда удочки будут закинуты, после этого мы запускаем таймер (свой для каждой удочки) со случайным временем поклёвки, дальше по тику таймера из нашего списка рыб размеров 1000 единиц, выбираем рыб, глубина обитания которых включает в себя глубину удочки.

3. Из этого списка случайно выбираем рыбу. Проверяем может ли съесть установленную наживку, и также проверяем, чтобы время суток активности рыбы соответствовало текущему времени в игре.

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

Благодаря процессу генерации, я стал уверенным пользователем LINQ.

Сама игра



FoodS

Скриншот продуктового магазина.

Его исходник можно посмотреть в репозитории. Там довольно интересно выполнены обработчики MouseEnter и MouseLeft для изменения изображений еды.

image

Скриншот формы для путешествий. (Все водоёмы являются тестовыми и их названия не являются подлинными.)

image

Скриншот игрового мира

Планы


  1. Сделать клиент-сервер для игры
  2. Junior FPGA(ПЛИС) Developer
  3. Распознавание лиц через Веб камеру(Ищу литературу, которая может быть полезна)
  4. Замена обычных ListView на ObjectListView

В конце предыдущей статьи, я писал, что хочу устроиться на работу. Ну что ж, в сентябре я закрыл своё первое ТЗ по SNMP, правда на языке C.

Вывод


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

Также хочется отметить, что я не вижу перспектив в карьере C# разработчика, вернее мне бы хотелось чего-то более близкого к железу, поэтому пробую изучение более низкоуровневых языков.

Всем спасибо, кто прочитал до конца, любая критика исходного кода приветствуется и будет сразу рассмотрена.

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


  1. Cykooz
    27.12.2019 09:17

    Я может чего не понимаю (очень давно использовал C# и WinForms), но зачем в обработчике евента Click для Label проверять, что координаты курсора находятся внутри этого самого Label? Неужели есть варианты, когда можно кликнуть мышкой куда угодно на экране, а событие Click сработает для рандомного Label-а?


    1. mad_danya Автор
      27.12.2019 09:21

      Нажатие по Label'у срабатывает плохо, поэтому мы обрабатываем клик по нему как клик по прямоугольнику.


      1. Cykooz
        27.12.2019 09:36
        +1

        Хм, можете немного расшифровать? Что значит «плохо срабатывает»? Не очень понятно что вы пытаетесь «починить» в плохо срабатывающем нажатии с помощью обработчика события, которое возникает при этом самом нажатии?


        1. mad_danya Автор
          27.12.2019 09:39

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


        1. mad_danya Автор
          27.12.2019 09:41

          Ну и лэйбл очень маленький, и когда нажимаешь на ячейку "таблицы" там где нет текста обработчик не срабатывает


          1. Cykooz
            27.12.2019 09:47
            +1

            Но у вас ведь обработчик Click стоит именно на Label. И если он «не срабатывает» при клике мимо текста, то значит у вас и сейчас всё это не работает.

            Наверное вы не поняли исходный вопрос. Событие Click возникает когда кликнули мышкой по Label-у. А это означает что курсор мышки находится внутри Label-а. Зачем ещё делать дополнительную проверку положения курсора?


            1. mad_danya Автор
              27.12.2019 09:56

              Нажатие иногда происходит мимо лэйбла, и этот код вставлен и туда, чтобы определить его. Ну и конечно, его можно убрать с обработчика лэйбла. Спасибо))


              1. paultwik
                28.12.2019 17:15
                +1

                Я думаю этот процесс можно было бы упростить. Вам не обязательно ставить клик на label, если важно именно отследить к какому label был ближе клик. Есть два способа — под label ставим глобальную panel, и ставим клик на неё, а после клика по координатам ищем самый близкий label. Если их генерация была в массиве и она имеет какой-то порядок, то искать можно не по всем, а только по области; способ два под каждый label ставим panel, так что бы он был прозрачен, т.е. виден он не будет и ставим на каждый из них один обработчик клик, в таком случае искать не придется, главное ставить их так, чтобы они были друг к другу вплотную. Нечто подобное применяется и в 2d-3d играх, т.к. отслеживать сложные формы дорого, поэтому простые контейнеры используются чаще всего.


                1. mad_danya Автор
                  28.12.2019 17:22

                  Сейчас применяется первый способ, только обрабатывается клик по форме(а не панели), и затем ищем самый близкий Label(это возможно, так как сами Labelы в игре не показываются). А вот второй способ заставляет меня сомневаться в производительности. Даже при размерах поля 20x15 мы получаем 300 панелей.


  1. gregor58
    27.12.2019 21:17

    А в чем смысл ковыряния автора в коде игры?.. Это игра Русская Рыбалка ранних офлайновых версий. Поначалу веселенькая, но потом довольно однообразная и нудная. Ей сто лет в обед. Ныне она благополучно переместилась в сеть, где люди за денежку могут купить себе эксклюзивные снасти, путевки в разные уголки виртуального мира игры с целью порыбачить, посмотреть успехи других игроков, продать виртуальный улов на аукционе и пр. Все уже есть и прекрасно работает. Что автор собирается преобразовывать, я не понимаю.
    rus-fishsoft.ru


  1. 1Tiger1
    28.12.2019 16:51
    +1

    Простите за банальный вопрос но почему Windows forms? Зачем над собой издеваться? Почему тот же unity не взяли?


    1. mad_danya Автор
      28.12.2019 17:13

      Просто мне интересно научиться применять различные фишки WinForms, чтобы делать красивый и удобный интерфейс, ну и возможно, пересесть в будущем на WPF, а Unity это всё таки целая область. Также, оригинал игры написан с использованием форм.


      1. 1Tiger1
        28.12.2019 23:18
        +1

        Ну дело ваше. Главное чтобы было интересно, горящие глаза это ключевой фактор развития джуниора.
        Но вы в самом начале пути, вам бы набрать опыта именно в программировании, архитектура, общие практики, мышление. Все это не особо зависит от инструмента. Специфика типа фишек winforms после этого набирается быстро. А вот если вы используете неудобные для задачи среды и инструменты вы становитесь ограничены в возможностях, и вынуждены обвешивать все это костылями или велосипедами, теряя время и фокус. Возможно я не прав, так как десктоп под винду совсем не моя область, и они в целом подходят под ваши задачи. Но если вы будете замечать что слишком много времени тратите чтобы подогнать неподходящий инструмент под задачу, что вместо удобной вам архитеруры и приёмов вы вынуждены придумывать костыли чтобы обойти недостатки инструмента — я бы советовал подумать над тем чтобы сменить инструмент. Это то что программисты делают, подбирают инструмент под задачу и используют его, а не наоборот. Благо счас достаточно инструментов чтобы извращаттся только в очень специфичных случаях и только если есть понимание что другие пути ещё хуже.


        Ну и я бы не сказал что юнити прям отдельная область. Да есть специфика, да потребуется разобраться, но это всего лишь инструмент. Главное в нем все тот же код, на все том же с#, а все эти настройки, шаблоны и прочее упрощают жизнь позволяя не отвлекаться от бизнес логики. Я слышал что его счас даже в универах используют, для обучения. Этакая альтернатива делфи или js. Ну или чистому Net. В конце концов он создан для разработки игр, то есть оптимизирован под это.


        1. mad_danya Автор
          29.12.2019 06:45

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


      1. 1Tiger1
        28.12.2019 23:21
        +1

        А зачем вам распознавание лиц?


        1. mad_danya Автор
          29.12.2019 06:39

          Распознавание лиц требует пытливый ум))


    1. gregor58
      29.12.2019 18:10

      Наверное потому, что изначально это игра была спроектирована в Windows forms, еще в далеком 2008-10 году. И как я вижу, автор даже визуальный интерфейс и картинки не поменял, а писал он вроде, что разрабатывал ее сам.