Duck Game

Duck Game

Not enough ratings
Гайд по моддингу V2.1
By FaecTerr
Всем ку, скоро Duck Game исполняется 10 лет и на этот юбилей я решил вспомнить свою старую идею, о том как делать моды на Duck Game. Не знаю, не играл 4 года
   
Award
Favorite
Favorited
Unfavorite
Вступление
Зачем?
Никто под моим старым видосом в целом не жаловался, но я увидел ровно 1 комментарий вне ютуба, где кому-то что-то не понравилось, поэтому в этом гайде чутка попытаюсь устранить некоторые траблы. Хотя в этот раз это будет лишь бонусная инфа и бонусные ссылки, ибо расписывать абсолютно всё о том, как написана игра просто бессмысленно. Тот, кому интересно, найдет и поймет даже без меня, а тот кому нет - с моей помощью и без не справится.

Затем!
Для того, чтобы сделать мод, как по мне нужны следующие навыки: базовое понимание логики, умение искать и находить информацию, а также в идеале знание английского. Вы можете особо не разбираться в программировании, достаточно будет усвоить опыт тех, кто был до вас, а также осознавать свои возможности и желания. Вероятно, что вы хотите сделать – уже кто-то сделал до вас, нужно лишь понять, как применить чужой опыт на свои нужды. Конечно, знание С# не будет лишним, но это не необходимость. Свой первый мод я писал, не зная С# и часть происходящего выглядела как чёрная магия. Сейчас же это выглядит как просто спагетти код, но это уже не важно.

По программам думаю и так понятно – Visual Studio 2017 / 19 / 22, возможно есть более новые версии, но на личном опыте самая стабильная была 2017, но в виду особенностей личных дел юзаю сам 2019. Как и в прошлом гайде, в идеале установить один из примеров, переименовать его по уникальному. В целом процесс установки мода описан в воркшоповском гайде на английском.
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=484818341

Возможно нужно будет обновить ссылку на DuckGame.exe в ссылках проекта.



Задача этого гайда - не рассказать всё обо всём, а лишь дать вам возможность найти, понять и изучить всё. В каком-то смысле сборник ссылок с мелкими объяснениями.
Можете зайти на сервера по Duck Game и позадовать свои вопросы там.
Если знаете английский:
Ру сервера (я их не знаю, можете накидать в комменты)

Остальные ссылки на другие сервера найдете сами, это будет ваше первое задание от Тайлера

Другие проги и полезные ссылки
Ну, во-первых, все полезные ссылки это гайды тех или иных людей. А именно: гайд на установку (steam ссылка из прошлой главы)
Во-вторых, программы. Я лично знаю как минимум 2 проги, для того, чтобы открыть код Уток. Это DnSpy и DotPeek (JetBrains). Они позволяют открывать различные .dll и проекты на .Net (C#) и благодаря им можно скопировать реализацию нужной вам функции из игры. Насчёт установки DnSpy – где-то когда-то была инфа, что якобы официальный сайт взломан и там вирусняк, поэтому лучше качайте версию с гитхаба и перед открытием проверьте на каком-нибудь VirusTotal. Лишним не будет проверить и DotPeek, конечно, если будете качать.
(DnSpy GitHub[github.com])
(DotPeek[www.jetbrains.com])

Также полезным будет понимание Неткода игры (невозможно).
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1394086869

Интересная статья, если знаете английский
https://toqoz.fyi/duck-game-mods.html

ТАКЖЕ если вы вдруг захотите посмотреть как реализована функция в одном из модов, то вы можете это сделать, как и с обычной игрой, открыв .dll файл, или иногда, проект Visual Studio. Для этого нужно скачать мод, и открыть его папку у себя (игра > свойства > просмотреть локальные файлы), поднимаемся на два каталога вверх (< Duck Game < common < steamapps) и заходим в каталог мастерской Уток (workshop > content > 312530 > ) в ссылке на мод, смотрим на цифры и ищем папку с такими же цифрами у себя. В DnSpy либо DotPeek открываем <название мода>.dll, либо если присутствует - <название мода>.sln

Полезное видео на английском (Ссылка)

Второе задание от Тайлера - понять допустимо ли морально воровать чужой код


hippity hoppity your code is now my property
Создание мода
С основами определились, кайф, всё круто, у вас открылось, вы видите мод в списке модов, хоть он ничего и не делает? - Продолжаем.

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

Игра написана с использованием модификации MONO фреймворка XNA, который на момент релиза игры уже считался устаревшим и перестал получать поддержку ещё в 2012 году. В XNA есть 2 основных цикла вызовов – Update() и Draw(), и как следствие, разработчик Уток решил каждому объекту игры также добавить эти методы, которые игра вызывает по соответствующим правилам. Это означает, что в идеале, вы должны производить всю логику в методе Update(), а работать с графикой в методе Draw(). Это не всегда так, но это нужно, потому что у игры фиксированный апдейт-рейт, но не фрейм-рейт. Это означает, что хоть игра и пытается обновляться и отрисовываться 60 раз в секунду, на экране у вас может отображаться меньшее число кадров, чем 60. Однако число внутренних обновлений будет иметь больший приоритет. Для примера, если ввести команду fps в игровую консоль, то можно увидеть 2 числа - ups (updates per second) и fps (frames per second). Второе число будет скакать и даже может падать, но число обновлений всегда будет больше числа кадров. Надеюсь разница ясна. Если нет – первое считает, второе – рисует.

Все объекты уровня - экземпляры класса Thing. У них возможна коллизия и есть координаты. Но не обязательны. Также есть параметр graphic и если указать для него спрайт, то игра автоматически реализует отрисовку для объекта. У спрайтов тоже есть свои методы и свойства, найти вы их сможете как в Visual Studio во время написания кода, так и через ранее названные декомпиляторы.
Также, стоит назвать иерархию объектов карты в Duck Game, хоть вы опять же и можете легко её просмотреть. Holdable / Duck -> PhysicsObject -> MaterialThing -> Thing

Обычный объект Thing просто существует на карте и может даже не взаимодействовать с окружением, будучи просто летающей невидимой точкой, в то время как MaterialThing уже обладает физическими свойствами и может выполнять роль условного укрытия от пуль. (Тип Block наследует MaterialThing). PhysicsObject это уже объект, подверженный гравитации, реагирует на окружение, у него есть скорость и трение. Всё что идёт дальше более частные случаи.

Третье задание от Тайлера - найти куда пропал Landon Podbielski

Но есть ньюанс
В коде игры повторно реализованы функции дефолтного XNA, с тем исключением, что они дополнены (местами кривым) функционалом. Сейчас проясню моменты.

Функция Rando по сути возвращает случайное число и является расширением дефолтной XNA функции Random. Если вы пользовались генераторами случайных чисел в других программах, то, наверное, в курсе, что обычно результат возвращается как число с плавающей запятой, в диапазоне [0, 1). Реализация в Duck Game позволяет уточнить диапазон слева и справа, но, что примечательно, при использовании Rando.Int(a, b) возвращает значение [a, b], то есть в отличие от всех других функций этого класса в том, что эта также включает правую границу.

Также, в Утках реализована своя математическая библиотека (Maths), содержащая не мало полезных функций. Однако, среди них есть функция Maths.AngleToVec(float angle), которая как бы должна конвертировать угол в нормализованный вектор. Но эта функция возвращает результат с ошибкой и итоговый вектор отражён по вертикали. Поэтому вместе с её использованием следует помнить, что нужно будет либо умножить результат на Vec2(1.0f, -1.0f) либо задуматься о смысле своего существования
Из обычных функций в противовес указанной выше есть крайне полезная функция Math.Atan2(y, x). Она делает ровно противоположное - возвращает вам угол из вектора и является частью библиотеки System и работает корректно.

Интерфейс IAutoupdatable – крайне полезная вещь. Наследуемый метод Update() вызывается у всех объектов на карте, а также у всех экземпляров в пуле AutoUpdatables. Ну и конечно, его нужно для начала создать, удобно это делать в самом первом классе мода, к примеру MyMod.OnPostInitialize() . В отличие от объектов на уровне, интерфейс не привязан ни к чему и будет обновляться каждый игровой апдейт.


(Рис.2 Реализация интерфейса IAutoupdatable)

Также важно заметить, что если вы, например, пользовались Unity, то резонно зададите вопрос – «А где DeltaTime?» и ответ прост – не реализован. Для тех, кто не знает, параметр DeltaTime используется в игровых движках, для подсчёта времени, между обновлениями внутри игры и технически, эта функция реализована на уровне XNA вполне хорошо, однако, разработчик Уток решил этим не пользоваться. Да, если вы делаете мод, то правильно будет реализовать его самостоятельно.
Но лично я не вижу особо смысла – игра зафиксирована на 60 кадрах, и мало кто вообще захочет их анлокать. Более того, анлокнув 60 кадров, игра начнёт действовать иначе, ибо сам Разработчик делал игру с идеей, что обновляться всё будет 60 раз в секунду. Так что покадровые счётчики или значение в 1/60 ( 0.0166666666 ) в целом выполняют свою задачу как следует, пусть и технически, это не совсем корректный подход.

Уровни в игре представлены классом Level, однако некоторые функциональные уровни, расширяют этот класс и в определенных случаях требуется проверка на тип уровня:
• Редактор (Editor)
• Проверка уровня редактора (TestArea)
• Главное меню (TitleScreen)
• Лобби (TeamSelect2)
• Аркада (Arcade)
• Игровой уровень (GameLevel), уровень, на котором вы обычно играете
И конечно, вы можете создавать свои производные классы для различных нужд.

Очередной микромомент, про который я почти забыл. Если вы с помощью SFX.Play(string sound_path, float volume, float pitch, float pan, bool loop) попытаетесь воспроизвести Стерео звук, при этом указав параметр pan не равный 0, то звук не "отрендерится" и воспроизведется тишина. Если вы хотите направлять звук, потребуется сконвертировать его в Моно.

Как считывать ввод (нажатие клавиш) - зависит от того, имеете ли вы доступ к утке или вам нужен определенный ввод. В случае с уткой, у неё есть поле InputProfile, содержащий 3 нужных метода (Pressed - Само нажатие, Down - удерживание клавишы нажатой, Release - отпускание). Параметр для этих функций - строка бинда который вам нужен (SHOOT, DOWN, UP, QUACK). Чувствительно к регистру и символам, поэтому в идеале проверить название нужных биндов в декомпиляторе. Через эту же функцию можно получить спрайт соответствующей кнопки.

Также я не рассказал про местную реализацию пользовательского интерфейса, о том как используются делегаты вы думаю разбересь в гайде по C# (https://metanit.com/sharp/). Я много о чём не рассказал, но в ссылках есть почти всё, а в самой игре так и вообще - всё.

Четвертое задание от Тайлера Дердана - придите в магазин одежды и попытайтесь "поторговаться" с продавцом за свою куртку.

Классическая Ошибка Новичка (КОН)
При краше игры, она выдаст вам крашлог. Если вы или ваш друг тестер случайно закроете его, найти все крашлоги можно будет также через просмотр локальных файлов игры в файле ducklog.txt.

Как Читать Крашлог
Что касается самого крашлога, у него всегда единая структура.



Сначала указывается источник краша - ваш мод или нет. Это не всегда точно, но зачастую если игра распознает что это был мод - выключит его при перезапуске (если не указано иное в настройках модов).

Затем идёт стандартный стек[ru.wikipedia.org] вызовов в инвертированном виде на локали системы (локализован под язык вашей винды). Т.е. самый последний вызов находится выше всех. Это основной источник информации о том, когда и что пошло не так.

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

Затем идёт спецификация. Дата и время, версия игры, платформа системы, произошла ошибка в онлайне или нет (1 и 0 соответственно), Хэш сумма модов (?, не уверен), Время проведенное в игре, спец. код ошибки который знает только Лэндон, разрешение Адаптера и Графического Окна, уровень на котором произошла ошибка и аргументы запуска.

КАК ЭТО ФИКСИТЬ???!!!
Разберем классические ошибки которые происходят уже на этапе исполнения и что можно сделать для локализации / устранения проблем.

System.ArgumentOutOfRangeException
Индекс за пределами диапазона.
Самая лёгкая и очевидная к устранению ошибка и в тоже время требует больше всего контекста. Для начала желательно пробежаться глазами по стеку вызовов и найти нужную функцию, если вы ещё сами не знаете где может быть зарыта ошибка.
Основная проблема - ваш индекс оказался больше размера коллекции. Стоит пересмотреть ограничения перечисления или учесть крайние ситуации. Перед обращением к коллекции напишите в консоль размер коллекции и индекс по которому обращаетесь, возможно это поможет уточнить проблему.

System.NullReferenceException
Ссылка на объект не указывает на экземпляр объекта.
Тут всё так же просто, пробегаетесь по стеку вызовов, находите функцию с ошибкой.
Основная проблема - при обращении к указателю на объект, ссылка оказывается пустой (null). Это может случится в результате, когда вы сначала определили переменную (ссылку на объект) без значения, а затем надеялись получить ссылку откуда-то из вне (возможно другой функции), но вместо этого она не вернула ничего и вы так и остались с пустой ссылкой, а затем по этой пустой ссылке обращаетесь к несуществующему объекту.
Для исправления (избавления) ошибки достаточно проверить не обращаетесь ли вы по пустой ссылке. Часто этого достаточно, но иногда стоит также рассматривать ситуации, а почему ссылка пустая и действовать в этом случае по иному сценарию.

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

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

Возможно ваша проблема уже была решена ранее. Есть смысл проверить stackoverflow[stackoverflow.com]. Если не знаете английского, то пользуйтесь переводчиком[translate.google.com].
Заключение
Вот в целом и конец моего обновленного гайда. Вы наверняка спросите – но ты опять ничего не рассказал, на что я ничего вам не отвечу. Ладно, отвечу – рассказывать можно много и долго, но во всём этом не будет смысла, это будет просто дроп разрозненной информации. Я буквально не могу знать какие из всех бесконечных желаний и возможностей вы захотите реализовать. А реализовать можно много чего. Я попытался дать хоть крупицу какой-то информации и сказал где и как её можно находить. Если сильно хотите, то можете изучить C# (https://metanit.com/sharp/), однако игра написана на одной из старых версий, так что не все удобные функции языка будут доступны.

С десятилетием мёртвой игры вас, утята.

Последнее задание от Тайлера - Поставить гайду лайк
2 Comments
FaecTerr  [author] 26 Apr @ 11:40am 
Добавил раздел о крашлоге
ODT 20 Apr @ 11:55am 
Отличное руководство! 𝓠𝓾𝓪𝓬𝓴... :senpai: