Дневник разработчика ведётся главным образом в качестве компенсации за полную неопределённость с датой выхода игры. Я не хочу сказать, что до релиза пройдёт ещё много лет, но и в то же время не могу назвать дату с точностью хотя бы до месяца. Хочется поделиться некоторыми тонкими моментами, которые интересны с точки зрения разработчика. Если Вы хотите подробнее узнать, что именно и каким образом влияет на выбор той или иной механики, интерфейса, какие подводные камни встречаются по дороге к релизу, всякие технические подробности - добро пожаловать на чтение моего дневника.
Записи идут в обратном порядке по дате добавления. Нажимайте на ссылку рядом с конкретным заголовком, чтобы увидеть полный текст.
09.10.2019 > Идеальный баланс - убийца игры (часть 2).
(показать/скрыть)
Как правило, я настраиваю баланс интуитивно, без помощи всяких таблиц или текстовых файлов для считывания настроек. То есть, любые данные об NPC хранятся внутри программного кода. Вы спросите - почему? Наверное, интуитивно хочется избежать занудного баланса (сложнее процедура изменений - меньше охоты её использовать).
Сама идея применения некоего инструмента для быстрой и многократной перетасовки баланса не вызывает у меня восторга.
Ведь базы параметров NPC именно для того и применяются, чтобы вылизать, отутюжить "геймплей", довести его до тупой стенки, которую игрок уже не сможет прошибить. Вот, скажите мне: учитывая, что 99.99% (если не больше) всех игр завязаны на повторяющихся элементах - какой толк от кучи микроизменений, приводящих игру к унылой абсолютной "политкорректности" (в данном случае бранное слово) баланса? Ведь такое занятие соответствует пресловутому ненужному "бесконечному улучшению деталей", которое активно осуждается в творчестве, но только не в балансе. Или же получается нечто, подобное "мы тут сделали драконовские правила - шаг в сторону карается расстрелом, кругом тупики и стенки... а вы разве не этого хотели?!".
Обычно, куда более счастливым игрока делают случайные упущения разработчика, позволяющие неожиданно вытолкнуть игровой процесс за рамки того, на что он рассчитан. Лучше я потом сделаю обновление, корректирующее прореху в балансе, чем игроки вообще не узнают о её существовании. И потом, разве тщательное тестирование помогает от подобных ошибок на 100%? То, что их будет меньше - неправильный ответ, потому что всё равно упущенная ошибка, как правило, бывает слишком заметной. И - да, она, скорее всего, затмит впечатления от всего остального "баланса".
То есть, я тестирую игру, однако стараюсь делать это более абстрактно - в том смысле, чтобы обеспечить как можно больше случайности и просто убедиться, что в целом всё стыкуется между собой. Главное - убрать "шероховатости", чтобы общее впечатление было хорошим.
Планирование, кстати, я делаю аналогично - абстрактным. То есть, общая картина формируется несколько туманно, а детали реализации уточняются на ходу, малыми порциями.
Конечно, в крайнем (и только в крайнем) случае, я буду использовать что-нибудь вроде таблиц - если это станет критичным для хорошего результата.
С другой стороны, практически весь баланс уже просчитан Valve, а я всего лишь пытаюсь разнообразить его в стиле оригинала - играть должно быть весело и азартно, с интересом изучая что-нибудь новое. Вместо таблиц я предпочитаю эксперименты по-старинке (ну, не совсем уж дремучим образом - веду борьбу с "магическими числами" в коде).
Между прочим, способности "Нашествие" и "Десант" появились под впечатлением одного такого случая. Я тогда устал заниматься "серьёзной работой над игрой" и решил поэкспериментировать, создавая из любопытства новых NPC одного за другим в большом количестве. Выглядело забавно и по мере увеличения численности хорошо нагружало систему (это была альфа-версия, в Construct Classic).
Однако в итоге родились 2 идеи:
- сделать кучу Побегунчиков, которые за несколько секунд своей жизни дадут мощное преимущество - захватят одну-две вражеские точки... а может, и не захватят - с хорошей обороной противника они разлетятся на кусочки всей кучей;
- повысить состав команды с помощью NPC, живущих до первой смерти. А чтобы дополнительно увеличить разнообразие - появляющихся (приземляющихся на парашютах) в случайных точках в разное время.
Конечно, со всеми этими способностями в некоторые моменты равновесие может зверски перекашивать (попробуйте сыграть 2 на 2 с одним "Десант"-Налётчиком). Но ведь я стараюсь делать игру так, чтобы игрок получал больше удовольствия, а не ходил по натянутой струнке баланса под свист плёток-ограничений (увы, баланс и без того слишком явно проглядывает в игре).
Примечание: теоретически, базу параметров NPC и прочих составляющих можно использовать для создания нескольких вариантов баланса или даже наборов правил игры. Если уж очень хочется ввести рейтинговые таблицы (ради соревнований) - сделайте два отдельных режима игры: идеально сбалансированный и "для души". Но много ли кто этим озабочен?
И вообще, игра, которая не содержит "вагон и маленькую тележку всяких интересных штучек", а также честного разнообразия, просто не имеет права тщательно соблюдать баланс.
Моя игра, я считаю, из числа таких - не должна пытаться яростно блюсти равновесие. Да, и проблема игр в том, что во все времена 99.99% из них аналогично были "из той же оперы", т.е. ничуть не лучше...
[свернуть запись]
03.09.2019 > Идеальный баланс - убийца игры (часть 1).
(показать/скрыть)
Темой сегодняшней записи выступает работа над балансировкой игры - моё мнение "из нескольких слов" о том, что с этим делать и с каких сторон подступиться.
Прежде всего, слово баланс подразумевает равновесие в целом противоборствующих сил. В нашем случае речь идёт о возможности контратаки игроком или компьютером любого действия противника - на уровне классов, тактик и даже стратегий. Кажется, единственно верный путь - проверить все ситуации, соотношения сил и т.д. и т.п.
Не хотелось бы никого разочаровывать, но совершенный баланс корректно определяется только единственным образом - абсолютное равновесие, когда в среднем ничего существенного не происходит даже за короткое время. То есть, в принципе, нельзя склонить ситуацию в пользу той или иной стороны - никаким преимуществом, даже сколь угодно малым.
Например, для RPG-игры возможность побеждать врагов - уступка дисбалансу, а иначе любые противники тоже "прокачивались" бы наравне с игроком. Таков жанр: его задача - постепенно смещать баланс в пользу игрока (к сожалению, это принято компенсировать всё более затяжным и нудным "фармингом" или "гриндом").
Для 3D-шутера уступкой также является наличие более слабых врагов, по сравнению с игроком, возможность возрождения и принципиальная проходимость от начала до конца.
Для стратегии уступка существует лишь в "сюжетном режиме", поскольку другие игроки (люди) в качестве противников вполне могут не оставить ни единого шанса. Да и то, в любом случае баланс растянут во времени, зависит от разведки и мастерства противника (я ценю стратегии, в основном, за возможность неторопливо играть против компьютера в одиночной кампании, с большим разнообразием путей достижения цели, слабую зависимость от случайных ошибок).
Думаю, примеров достаточно. Без уступок, в RPG вероятность боя с каждым противником будет "50 на 50", в 3D-шутере будет то же самое плюс абсолютно тупиковые ситуации, в стратегии - на любую созданную единицу игрока сразу появлялась бы противодействующая.
С идеальным балансом не было бы значительно более мощных противников, которых так приятно побеждать, набравшись сил. Да и возможность переиграть, наверное, отсутствовала бы.
Вам и правда были бы интересны такие игры?
Вы, очевидно, хотите знать, в чём причина этих странных нападок на игровой баланс. Поясню: как правило, чем более сбалансирована игра, тем она скучнее. Возможно, я субъективен, но какой интерес играть, если в итоге ничего не выиграешь (и не проиграешь)?
В этом случае может быть интересен, конечно, сам процесс игры. Но разве мы все играем в игры, не надеясь что-нибудь выиграть (то есть, по сути, нарушить баланс)? Разве доставит удовольствие игра, о которой заранее известно, что выиграть в неё в принципе невозможно?
А ведь чем современнее игра, тем больше вероятность её тщательной балансировки, "выпиливающей" интересные отклонения от монотонности и превращающей игровой процесс в унылое занятие. Или, скорее всего, меньше вероятность нестабильных механик или нестандартных деталей (которые трудно проверить досконально).
И потом, в какой ещё такой компьютерной игре сам процесс, без учёта разных улучшений (очки за повышение опыта, новая экипировка, новые локации и пр.), был разнообразен? Обычно, это - драка со всеми врагами подряд, учитывая "ремонт и дозаправку" либо "управление базой". Или вообще - повтор ущербно ограниченного набора действий (это я об играх с головоломками). Редко-редко встречается что-нибудь уникальное. В остальных случаях, увы, интересные события лишь немного разбавляют неприятное однообразное занятие.
Если уж Вы хотите сказать, что под "балансом" в играх подразумевается некое равновесие между затраченными усилиями и полученным результатом - тем более, этого даже и близко нет в большинстве игр. А всё потому, что награды, в основном, похожи от игры к игре и представляют собой по большей части "разноцветные фантики". Которые либо ничего вообще не дают, либо дают слишком мало - эта "награда" заранее учтена в общем балансе и ничуть не решает каких-то надоевших крупных проблем.
То есть, балансировка ради интереса от игры, вообще-то говоря, должна быть индивидуальна для каждого человека. А это уже - не классический баланс, не примитивные уровни сложности, а нечто иное...
Примечание: мне уже и смешно и грустно, когда в анонсе очередной игры заявляется, будто игрока ждёт огромный (открытый) мир для исследования. Потому что исследование мира, на мой взгляд, нельзя приравнивать к изучению различных сочетаний довольно однообразных элементов конечного набора.
То же и с балансом: когда все возможные механики, прочие элементы игры сводятся в итоге к одним и тем же примитивным ограничениям - это совсем не дело.
Ещё один важный момент: меня не радует фальшивая роль первооткрывателя (об этом упоминалось в одной из предыдущих записей). Ведь чем лучше тестировалась игра, тем скорее любая ситуация уже была учтена разработчиками и проверена (Вы никогда так не думали?).
Иногда я даже недоумеваю, неужели вот эта конкретная ситуация в некой игре разрешается именно подобным дурацким способом?
Аналогичным образом, довольно глупо выглядит баланс по принципу "А побеждает Б, Б побеждает В, В побеждает А" (пресловутые "камень-ножницы-бумага" в стратегиях). Почему "замыкание круга" должно быть именно прямым, завязанным на свойствах NPC? Можно ведь сделать иначе: разные, сильно неравноценные ресурсы для получения А, Б и В (время в том числе); подавление количеством; разные условия окружения или правила, в которых соотношение А-Б-В становится иным (и не замкнутым).
Конечно, один-два раза "попасть в замкнутый круг" - нормально. Однако если это повторяется каждый раз, с одними и теми же А-Б-В, по одной и той же схеме - результат восторга не вызывает. Такой баланс выглядит слишком занудным, чтобы доставлять удовольствие от игры.
Примечание: наверное, поэтому игры со значительно выверенным балансом, скорее всего, становятся киберспортивными - в том или ином смысле. Интерес остаётся только из-за материальной награды. Ну, или обретения некоторой знаменитости.
Иными словами, такие игры волей-неволей получают дополнительные "костыли", сохраняющие интерес к ним (с точки зрения не организаторов турниров или разработчиков, а самих игроков).
Понятное дело, для относительно скоротечных турниров с частыми атаками и контратаками выгоднее (и зрелищнее) сделать классическую схему. Я тоже иногда с интересом смотрю профессиональные матчи. Однако, это соотношение сил - косвенная причина бешеного темпа "геймплея" (опасно оставлять без присмотра отряд любого типа, даже очень мощный). Видели, как игрок в StarCraft 2 "мечется" по карте и "дёргает" управление? Для меня такой стиль игры глубоко омерзителен (слишком жалкий результат на фоне усилий).
Примечание: смотря матчи по StarCraft 2, я выбираю игры ближе к финалу; перематываю ролики примерно к началу интенсивных столкновений или применению разных трюков; не люблю тупые атаки с подавлением количеством и больше всего болею за Зергов (у них, пожалуй, самое замысловатое устройство расы).
То есть, наблюдать-то ещё интересно (и не за игроками, а за общей картиной), однако играть самому категорически не хочется. Неужели в стратегиях в принципе нельзя выдумать ничего нового?
Примечание: я считаю - можно, да ещё как (на примере хотя бы The Settlers 2).
Подводя итоги первой части записи: а нужен ли вообще "классический" баланс - тот, который "как у всех"? Не слишком ли опрометчиво погружаться в упорную работу над ним?
Я всегда недоверчиво отношусь к "зажатому со всех сторон" игровому процессу, т.е. с максимально уравновешенными механиками и прочими элементами. Потому что не вижу смысла долго и нудно повторять однообразные действия ради мизерного удовольствия (это суть, к которой сводится, в конечном счёте, почти любая игра).
Правда, нужно учесть, что в моей игре компьютерному противнику ещё пока "не хватает ума". Работы в этом направлении ведутся...
[свернуть запись]
[^ следующая запись ^]
23.01.2019 > Работа над игрой: процесс медленный и хаотичный.
(показать/скрыть)
Откровенно жаль это признавать (и для себя в том числе), но, увы, именно так продвигается разработка. А причина кроется не только в том, что я работаю в одиночку, урывками, и делаю почти всё самостоятельно. Не из-за того лишь, что у меня мало опыта разработки игр вообще. Работая с хорошей командой, имея возможность обучения и практики - всё получалось бы не намного лучше.
Чтобы объяснить свою точку зрения, мне понадобится длинное вступление.
Дело вот в чём: допустим, видел я игры, которые делались "как надо" - с основательной планировкой работ, в установленные сроки (наверное, таких большинство среди крупных проектов). И что же в итоге? Моя оценка всё равно была очень близка к "плохо" или даже "отвратительно" - не по качеству чего-либо отдельно, а из-за сути: "красивая обёртка и унылая начинка".
Возможно, реализация и была тщательной, но затрагивала слишком мелкие детали, не влияющие на основной интерес. Грубо говоря, было уделено много внимания разной ерунде, вроде ненужного разветвления диалогов, детализации примитивных дополнительных заданий, неуместного юмора, "числа дырочек у пуговиц на рубашке каждого персонажа", принципиально скучных механик и прочего, и прочего.
То есть, повсюду засилье какой-то странной халтуры: с одной стороны, тонны запихиваемых в игры графики и эффектов (а "инди"-команды даже этим не заморачиваются), с другой стороны, одни и те же действия во время игры. Именно разнообразия катастрофически не хватает. Даже обидно, сколько труда художников (и не только) пропадает зря - играть-то оказывается скучно.
Складывается впечатление, что разработчики в принципе не понимают, "в чём соль" получения удовольствия от игр вообще и разных игровых деталей в частности. Вроде бы стараются делать именно то, что ожидают игроки, но выходит как-то слишком пресно и формально - что называется, без любви к искусству. Видимо, дизайнеру очередной игры уж очень хочется сделать некую основу, а дальше штамповать однотипное наполнение (content), лишь бы ему было удобно... Заметьте, что я говорю ещё о более-менее приличных "творениях".
И ведь не одна игра в итоге получилась такой унылой - их десятки и сотни, если не больше. "Унылой" здесь означает слишком рано угадываемую закономерность в повторении "геймплея". Которая откровенно слабо маскируется (или даже разработчикам хватает наглости заострять внимание на этом).
Жаль, что всё тем же грязным способом - обманом игрока, заманивая ложными обещаниями, впустую будоражащими воображение - "раскручиваются" многие "инди"-игры. А что получается в итоге, думаю, объяснять не нужно.
Но, если Вы хотите уточнений - получается унылое "растягивание резинки" - аналогичное повторение очень похожих, почти однообразных действий. В то время как подсознательно игрок ожидает каких-то интересных неожиданностей.
У меня есть довольно уверенное предположение, что разработка должна постоянно меняться (планы и направление), если, конечно, хочется сделать игру интересной и разнообразной. При этом существенное увеличение сроков - вынужденный побочный эффект, оправдывающийся усилиями разработчиков.
Ради справедливости замечу, что иногда и долгий срок, и неоднократная смена планов не спасают игру. Наверное, дело здесь в принципиальной ущербности идей. Авторы в данном случае просто хотят слишком конкретных вещей и пытаются реализовать их всеми правдами и неправдами.
Но кто говорит, что вот эти конкретные вещи настолько прекрасны, чтобы тратить на них всё время? А истинно хорошие идеи в итоге даже не пробиваются наверх, либо их безжалостно вычёркивают.
Подробнее об "инди": моё мнение таково, что хорошие "инди"-разработчики должны стремиться уложить в скудный бюджет игру, тем не менее, высокого класса. Возможно, работая в убыток (не обязательно материальный, это могут быть дополнительные траты свободного времени и сил). А иначе - "инди-игра" звучит как приговор: плохая по умолчанию.
Пока у меня не приведены в порядок накопившиеся по этому поводу заметки, могу лишь мимоходом упомянуть практически "инди-" разработчика - Frictional Games. Большинство их игр - Penumbra: Overture, Penumbra: Black Plague, Amnesia: The Dark Descent, SOMA - выполнены на редкость интересно. И даже то, что мне нравится жанр "horror", почти не меняет ситуации: у других это выходит, как правило, хуже, да и сами Frictional Games создавали неудачные, на мой взгляд, игры (Penumbra: Requiem, при участии - Amnesia: Machine for pigs).
Плохие "инди"-игры в массовом порядке наводнили, в частности, магазин Steam (практически с самого начала его работы). В них играть не хочется из-за явно проглядывающих примитивных идей и неважного содержимого (большинство я отбраковал по рекламным роликам, некоторые по просмотру "геймплея" на YouTube, некоторые - личным опытом).
Хотя, знали бы Вы, как долго я не решался купить Террарию (Terraria) и с каким интересом потом в неё играл. И неважно, что после определённой точки прогресса там просто уже нечем заняться (виртуальное строительство до сих пор ни в одной игре не реализовано на должном уровне, что меня отталкивает).
Но всё же это единственный случай, когда я был близок к ошибке, составляя мнение каким угодно способом, только не на личном опыте. Впрочем, это было отнюдь не отвращение, а скорее недоверие.
И да, я не стал бы явно критиковать подобные игры, будь они бесплатны. Но ведь это совсем не так. И смущает меня не сам факт оплаты (мне не жалко денег), а то, что такие разработчики получают финансирование, и доля плохих игр всё растёт. Как будто это нормально...
Ну, а моя игра делается медленно ещё и потому, что я, во-первых, медленно рисую новые картинки (рисовать почти не умею, но приходится); да ещё и так, что их часто надо переделывать. Во-вторых, стиль изображений, новые детали игры больше всего создаются на ходу (т.е. мало что было запланировано с самого начала). В-третьих, я хочу истинного разнообразия: чтобы многие карты содержали уникальные элементы, а не просто новые картинки; чтобы вся игра проходила не "по накатанному пути", а время от времени чем-то удивляла.
Представьте себе, насколько большего труда это требует.
Кстати, сейчас уже идёт работа над третьим этапом. И не могу сказать, что мне сильно нравятся первоначальные планы.
Вы можете подумать, конечно, что я всего-навсего плохой дизайнер, но где они, хорошие дизайнеры? Почему навскидку мне почти достаточно пальцев на обеих руках, чтобы пересчитать игры, которые выделяются из унылой разношёрстной массы? И это включая настолько древние, что запустить их сегодня проще (неизмеримо) только под эмулятором. И даже нет уверенности, что этот список останется прежним, если начать перебирать его...
Конечно, я очень требовательный игрок. Но, поверьте, мне невыносимо отвратительно тратить время на однообразные действия, да ещё с большой вероятностью не получить адекватную награду. Тем более в играх - ведь они, по определению, должны развлекать, а не мучить.
Напоследок, хочу сказать ещё несколько слов о моих заметках: я давно обратил внимание, что создание хорошей игры обычно вызывает неудобства и неприятности в коллективе разработчиков. То есть, когда процесс отлажен, все получают хорошую зарплату, не перерабатывают, не тратят личное время и силы - игра выходит "пресной" и довольно унылой. И наоборот, если путь разработки тернист, есть внутренние конфликты, переработки, срывы сроков, перенос дат и прочее - игра оказывается превосходной.
Иначе говоря, если игра создавалась больше ради прибыли - в ней мало интересного. А вот, если игра делалась с трепетным отношением к делу, ради известности или чего-то другого, где деньги далеко не на первом месте - она для многих становится шедевром.
Возможно, так получается не всегда, я не настаиваю. Но, в качестве примера, для меня во вселенной Half-Life первая часть на голову выше второй (без двух эпизодов). Хотя вроде бы и вторая часть бесподобна; она к тому же делалась в более спокойной обстановке ("на волне славы").
Второй пример - Path of Exile: пока разработчики действовали больше "ради зарплаты", игра была просто ужасной (унылой, хуже, чем её прообраз - Diablo 2). Я дважды (!) начинал играть в неё, доходил до середины последнего уровня сложности (раньше их было 3, согласно "классике") и бросал только из-за однообразия. Причём разнообразие типов предметов сводилось к разнообразию целей однообразного "фарминга" ("гринда"). Сейчас, конечно, спустя несколько переработок и добавлений, игра уже смотрится вполне себе прилично.
Между прочим, отмечу здесь, что одна довольно оригинальная вещь из последней упомянутой игры была независимо придумана мною для Smile Fortress. Конечно, есть большая разница в реализации и некоторые отличия в самой сути, но сходство довольно забавное (дождитесь только релизной версии моей игры).
Не буду скрывать: хочется стать известным в качестве разработчика игр, а заодно проверить, достойны ли мои усилия некоторой материальной награды (в качестве добровольных пожертвований); хочется быть принятым в команду, делающую хорошие игры. Но больше всего движет желание сделать такую игру, которая честно будет увлекательной, как я себе её представляю: с множеством явных и скрытых деталей разной степени важности; чтобы мне самому было интересно играть в неё. Кроме этого, мечтаю увидеть искренние восторженные отзывы - будет приятно знать, что кто-то получил море удовольствия от игры.
[свернуть запись]
[^ следующая запись ^]
18.10.2018 > Поговорим о хорошем: SFML и прочее.
(показать/скрыть)
Сегодняшняя запись напичкана технической информацией, поэтому чтение может быть скучным. Но я, по крайней мере, расскажу подробно о трудностях перехода от SDL на Windows 7 к SFML на Windows 10.
Несмотря ни на что, адаптация игры к библиотеке SFML закончена. Производительность ничуть не уменьшилась, т.к. большую часть движка удалось оставить без изменений. Всё уже работает, как и раньше, просто новая библиотека даёт больше возможностей, и некоторые вещи упрощаются из-за наличия готовых функций.
Формат карт успешно переработан - вместо одного слоя сделаны три, а также добавлена готовая карта проходимости. Из-за этого размеры карт выросли, конечно. Однако речь идёт о том, что вместо примерно 6,5 Кбайт каждая карта теперь весит примерно 25 Кбайт. Т.е. беспокойство в этом отношении совершенно лишнее.
Несмотря на то, что библиотека SDL была относительно более низкоуровневой, отдельные нестыковки со звуком она молча переваривала. А именно - звуки оружия были записаны в стереоформате (левый и правый каналы записи), хотя для воспроизведения с эффектом позиционирования в пространстве относительно слушателя обычно подходит лишь моно-формат. Как бы то ни было, SDL не вызывала никаких проблем, разве что "неправильные" звуки были громче остальных (Вы можете убедиться в этом на текущей версии игры, где пока ещё используется та самая SDL).
Я, конечно, собирался их проверить и скорректировать несоответствия. Но, в общем, всё работало.
Однако SFML "оказалась несговорчивой" и добавила мне целых полторы недели на разбирательство, что же не так. Дело осложняли применяемые ею особенности звука: отдельно буфер и отдельно объект, воспроизводящий из него звук. Плюс тот факт, что количество объектов звука не должно превышать примерно 256 для Windows (согласно документации).
Возможно, данное значение Вам ничего не скажет, но я сразу подумал на число каналов звука (это другие каналы - системные, по одному на звук; для одновременного воспроизведения) и не ошибся. То есть, если в SDL число каналов задавалось отдельно (32, 64 и 128 в моей игре), в SFML их роль стало играть число объектов-звуков. Пришлось организовывать каналы воспроизводимого звука вручную (готового решения не было).
Но главная беда была в том, что, во-первых, звуки битвы стали очень громкими, несмотря на позиционирование и специальные меры для предотвращения одновременного старта одинаковых звуков. Во-вторых, оповещения после начала раунда зазвучали очень тихо. В-третьих, все динамически позиционируемые звуки стали слышаться как будто без изменения положения в пространстве. И было непонятно, что происходит.
Изрядно помучившись, в числе последних попыток я вспомнил о некоторых звуках, записанных в несоответствующем формате, и наконец-то проблема решилась. Оказывается, "неправильные" звуки влияли каким-то образом на все остальные.
Ещё немного хлопот доставили указатели на звуки. Из-за упомянутого ограничения на число объектов-звуков поначалу были непонятные подвисания при запуске игры. Потом я сообразил, что у меня единовременно создаётся более 20000 (!) звуков из-за множеств объектов: у каждого (!) элемента простой номер канала звука для SDL был заменён целым объектом-звуком SFML. Пришлось выделить строго ограниченный запас (те самые 32, 64 или 128) звуков и связывать их с другими объектами через указатели.
Из-за того, что функция привязки получала обычные указатели на звук, которым внутри присваивались значения ссылок на активные звуки, на выходе из функции привязки благополучно пропадали. Решение нашлось относительно просто - я в кои-то веки понял, для чего можно использовать указатель на указатель.
Технические подробности C++ (показать/скрыть)
Если параметр функции выглядит как sf::Sound * snd, это передача значения указателя внутрь. Можно изменять содержимое объекта, на который указывает snd, однако перенаправлять указатель бесполезно. На выходе из функции изменение самого указателя теряется.
В случае же, когда параметр выглядит как sf::Sound * *snd (т.е. указатель на значение указателя на звук) - это передача внутрь указателя на указатель. И можно изменять не только содержимое объекта, на который указывает snd (через двойное разыменование **), но и перенаправлять указатель на другой звук (через однократное разыменование *). На выходе из функции потеряется значение указателя на указатель, но значения указателя и объекта, на который тот указывал, сохранят все изменения.
Именно это мне и было нужно - ссылку некоторого объекта "нацеливать" на конкретный звук из буфера, чтобы можно было каждый такт времени менять его координаты в двумерном пространстве. А заодно подготовить и воспроизвести этот звук.
Неожиданно выяснилось, что возникающее при запуске исключение вызывает не SDL - библиотека SFML ничуть не помогла от этого. Оказывается, дело в драйвере звуковой карты Asus (у меня теперь Asus Xonar DX). В общем, ошибку можно игнорировать, отключив при отладке, да и в пробном варианте откомпилированной Release-версии ничего плохого не случалось.
Таким образом, разрыв между двумя библиотеками сократился. И если посчитать плюсы и минусы перехода на SFML:
- + звук работает нормально;
- + не нужны отдельные низкоуровневые алгоритмы (некоторые заменены буквально на единственную строчку), что полезно в плане совместимости;
- - со звуками не всё так просто;
- - загрузка файлов (картинок, звуков) в принципе не зависит от скорости диска и выполняется заметно дольше;
- - меньше готовых функций для клавиатуры и мыши (например, двойной щелчок, режим CapsLock);
- - некоторая морока с использованием OpenGL напрямую
можно подумать, что результат не стоил усилий. Однако преимущества слишком велики по сравнению с недостатками, поэтому SFML определённо побеждает. Да и переходить обратно на SDL мне уже не хочется - тратить драгоценное время на возврат к более примитивной библиотеке почти не интересно.
Общую картину портит лишь время загрузки ресурсов, которое буквально "на ровном месте", ни с чего, увеличилось практически вдвое.
Напоследок хочу заметить, что перенос разработки игры из среды Windows 7 в Windows 10 затронул больше всего именно графику. Версии системных библиотек обновились, что частично повлияло на работу OpenGL. Из-за этого поначалу возникла самая неприятная проблема - неправильное выравнивание изображений (текстур). Речь идёт о задании специальных текстурных координат (от 0.0 до 1.0).
Если отключить сглаживание при масштабировании текстур, конечно, всё приходило в норму. Но картинка в игре становилась грубой. А при использовании сглаживания по краям каждого изображения возникали так называемые "артефакты" - горизонтальные и вертикальные линии, подхватываемые с противоположного конца картинки.
С этой проблемой удалось справиться не без помощи Интернета: оказывается, у текстурных координат нужно "отнимать половину пикселя с каждой стороны". И в результате ненавистные линии бесследно исчезли.
Графические подробности OpenGL (показать/скрыть)
Для каждого изображения (текстуры) я рассчитывал, какую долю от 0.0 до 1.0 займёт один пиксель в ширину и отдельно в высоту (т.е. размер т.н. текселя). Затем, при выводе на экран половину этих величин просто прибавлял для левой и верхней, отнимая для правой и нижней координат.
Таким образом, при сглаживании изображений перестали захватываться лишние точки.
Примечание: упоминаемое сглаживание не относится к более известному в качестве настройки во многих играх. Последнее затрагивает всю выводимую картинку и выполняется другими методами. А то, про которое здесь идёт речь, используется, чтобы получить удовлетворительный результат при перемасштабировании для текущего разрешения экрана.
[свернуть запись]
[^ следующая запись ^]
26.06.2018 > Новые планы, новая система, новые проблемы.
(показать/скрыть)
Я, конечно, говорил о том, что редактор карт будет разрабатываться после релизной версии.
Однако по ходу создания cp_madtrain выяснилось, что мне нужны дымовые трубы, стоящие на крышах зданий (Вы же видели их в игре, правда?). Пока что решение мне не нравится: труба отображается как единое целое с кусочком крыши. А если понадобится поставить её на другую крышу? И вообще, я хочу добавлять трубы переменной высоты (2-4 клетки) - как быть с их "нависанием" над свободным пространством?
С другой стороны, даже сейчас уже существует разделение по уровням, на которых отображаются разные элементы карты. Пока что оно, в основном, касается бочек, пил и прочих нескольких объектов. Так почему бы не распространить идею на все элементы?
С третьей стороны, я уже решил, что имеющийся редактор будет изменяться минимально. Ведь он сделан в Construct Classic, имея в основе логику действий, а не столь гибкий исходный код. Туда мне легче (и быстрее) всего только добавлять новые элементы, да и то количество действий превышает желаемый минимум: надо вставлять картинки, добавлять реакции, учитывать при загрузке и сохранении карты, а также при обновлении вида.
Работать в старом редакторе и так неудобно, а тут ещё необходимость в слоях...
В общем, приходится начать параллельную работу над новым редактором карт - уже на C++. Кое-что сделано, но основная часть работы впереди.
Решено было ввести 3 слоя - верхний, основной и нижний.
Основной слой, как следует из названия, будет содержать большинство объектов и все специальные элементы. А остальные два слоя можно использовать для различных "навесных" и "подстилающих" декораций, совмещая многие элементы карты. До сих пор они были, в принципе, несовмещаемые, так как имелся всего один слой.
Некоторые группы элементов будут ограничены только одним или двумя разрешёнными слоями, однако большинство (особенно статические) можно будет располагать в любом слое без ограничений.
Посмотрим, что из этого выйдет. Хотя у меня есть все основания считать введение трёх слоёв удачным ходом с целью разнообразия карт.
Несколько слов о самом редакторе.
Дополнительными возможностями, помимо нормального интерфейса, планируется сделать различные автогенерации (типа границы карты и зеркальных отражений половинок и четвертинок), проверку на ошибки (типа "дырок" или отсутствия обязательных элементов), а также генерацию специального массива проходимости.
Последнюю возможность надо пояснить: на данный момент массив проходимости создаётся в игре после загрузки карты. Используется он для поиска путей движения NPC и для проверки большинства столкновений. Дополнительно, массив облегчает реализацию некоторых реакций (гибель в воде или "впечатыванием" в стенку, всплески и т.п.), благодаря которым карта выглядит более оживлённой.
И, собственно, этот массив ничего не мешает сохранять прямо в редакторе вместе с картой... кроме обстоятельства, что алгоритм сохранения в Construct Classic этого не предусматривает. К тому же, формирование массива проходимости уже сделано в коде игры. Остаётся чуть доработать алгоритм, чтобы он мог учитывать три слоя.
Ну, а теперь - о главном.
Старая система (на основе Intel Core Quad) наконец-то "ушла на покой", и теперь я работаю на современной системе (Core i7). В кои-то веки тип основного (системного) диска поменялся с HDD на SSD, и работать, вроде бы, стало легче.
Вот основные элементы конфигурации:
- Процессор: Intel Core i7-8700
- Видеокарта: Palit GeForce 1060 GTX JetStream
- Оперативная память: 16 Гбайт
Но! Современные компоненты рассчитаны на Windows 10 (доступные драйверы), а вот как раз поработать с ходу не получилось. Я установил Visual Studio 2017, настроил проект и... меня ожидала серьёзная засада - библиотека SDL 2, на которой до сих пор базируется игра, отказалась нормально работать. Хотя версия, откомпилированная до этого (ещё в Windows 7), нормально запускается под Windows 10.
Сейчас я осваиваю библиотеку SFML и пробую адаптировать игру к ней, в случае неудачи придётся искать ещё что-нибудь. Мне нужна загрузка изображений, шрифтов, звуков и музыки, работа с мышью и клавиатурой, а также состыковка с OpenGL.
И хотя SFML позволяет абстрагироваться от прямых команд OpenGL, может оказаться, что производительность сильно просядет. Это уже было: SDL позволяла отображать примерно 1000 картинок без заметного снижения частоты кадров, а прямая работа с OpenGL - примерно 5000 картинок. Очевидная разница вынудила меня сделать "низкоуровневые" функции рисования.
Надеюсь, что решение в этот раз не займёт много времени.
[свернуть запись]
[^ следующая запись ^]
13.03.2018 > Список задач и другие вопросы.
(показать/скрыть)
В общий список задач у меня попадает всё, что только можно:
- ближайшие цели для добавления в движок игры или для рисования;
- ресурсы, которые необходимо найти или создать (например, звуки);
- ошибки, которые нужно исправить;
- изменения, которые надо внести;
- непонятные ситуации, о которых требуется найти информацию.
Кроме того, часто я никак не соберусь добавить что-нибудь в код - по той причине, что задуманная вещь представляется слишком абстрактно; в этом случае сначала пытаюсь выразить свои мысли "на бумаге" - в этом списке, и здесь же создаю примерный код - чтобы "отладить" его в уме для начала, т.е. проверить логику (если задача сформулирована неправильно или неполно, наброски кода выглядят ужасно).
Иногда, по мере накопления данных, некоторые темы переносятся в отдельные файлы. Например, это произошло для системы наград, справки по игре, а также замечаний к программному коду. Кроме того, некоторые слишком абстрактные и пока что плохо мною представляемые "штуки" также выделены в отдельный файл задач для экспериментов, большая часть из них отложена на будущее.
Таким образом, иногда размер основного списка заметно уменьшается по причине как раз очередного подобного ответвления. И его сокращение означает движение к полноценной версии игры, но к реализации далеко не всех задуманных вещей.
О том, сколько же будет карт в финальной версии, предварительно и осторожно могу сказать: примерно 6-7 штук, в дополнение к пяти уже сделанным. Нафантазировал я много чего, но ведь всё это нужно добавлять в игру и проверять. Ещё хочется доработать первые карты; с картой cp_madtrain даже необходимо это сделать, т.к. она сейчас почти в том же состоянии, что и на последнем видео - не дорисована (поезд уже появляется!).
Помимо карт есть также несколько крупных задач, которые обязательно нужно решить. В общем, работы - почти непочатый край...
Создание и изменение карт всё ещё выполняется в редакторе, созданном в Construct Classic. У меня не было времени на то, чтобы сделать аналог в C++. К счастью, пока этого достаточно, но я уже вношу заметки по новому редактору в отдельный файл. Вероятно, он будет первичной задачей, когда выйдет финальная версия игры (конечно, при отсутствии проблем во время "релиза").
Зато у меня есть, чем похвалиться: загрузка ресурсов системы игрой пока что не более 15% - и это ещё максимум, который наблюдается иногда. В основном, загрузка составляет 3..8% - притом, что я почти не злоупотребляю оптимизацией. Конечно, я проверял в игре 40 на 40 смайликов.
Иными словами, игра, скорее всего, не будет сильно требовательной к компьютеру, на котором запускается.
Примечание: здесь нужно отметить одно важное обстоятельство - через некоторое случайное время от начала раунда загрузка самопроизвольно возрастает до 25..30% и меняется уже в этих пределах. Точнее, наверное, 25% плюс "чистая" нагрузка игры (у меня 4-ядерный процессор - 100% делится на четыре ядра). По-видимому, это связано с перераспределением ресурсов операционной системой, поскольку на плавности игрового процесса никак не отражается (разве что во время этого перехода). Так что если вдруг Вы будете проверять загрузку процессора, учтите, что ОС иногда может искажать результат, начиная с некоторого момента.
Ещё нужно иметь в виду, что нагрузка будет сильно зависеть от конфигурации Вашей системы (т.е. "естественные" колебания будут уже не 3%..15%).
Для ясности, привожу основные характеристики рабочей конфигурации (на ней и выполняется проверка):
- Процессор: Intel Core Quad Q9650 (3.2 ГГц), socket 775
- Видеокарта: NVidia GeForce 560 GTX с 2 Гбайт памяти
- Оперативная память: 4 Гбайт
То есть, пусть это и относительно мощный процессор, однако принадлежащий старому поколению. Видеокарту приблизительно можно назвать средней. Само собой, оперативная память не самая быстрая и довольно старой модели.
К сожалению, в процессе доработки множеств объектов появилась неприятная проблема. Дело в том, что изначально каждое множество определялось обычным массивом на 1024 элемента. Есть указатель на первый свободный элемент - чтобы, по возможности, сократить перебор элементов.
Со временем выяснилось, что 1024 - это в половине случаев много и в некоторых случаях мало (!). Ради экономии, а также следуя рекомендациям использовать вектор-массивы, я начал адаптировать множества: теперь можно было задавать максимальный размер индивидуально.
Были исправлены ошибки, всё заработало, но как-то странно.
Во-первых, в отладочной (Debug) версии игра замедлялась наполовину и более. Во-вторых, появились странные прерывания звука и непонятные аварийные завершения сразу после запуска.
Взглянув на данные в Диспетчере задач, я был поражён: объём занимаемой памяти вырос до 800..1000 Мбайт (т.е. почти до гигабайта, или гибибайта - на современном "жаргоне"), причём сразу со 160..180 Мбайт, как было до этого!
В обычной (Release) версии просадка скорости не была заметной, однако потребление памяти оставалось ужасным (плюс все остальные неприятности).
...Через 2 дня разборок я начал догадываться о причинах этого поведения: дело в том, что множества элементов создавались динамически, т.е. выделением памяти уже во время работы (оператором new) и последующим высвобождением (оператором delete). Пока массивы были обычными, всё было в порядке. Но как только они сами стали динамическими (std::vector и другие контейнеры основаны также на new и delete), игру "стало клинить" - точнее говоря, в случае динамического выделения памяти для объекта, выделяющего память динамически ещё и внутри самого себя, компилятор или операционная система работали неудовлетворительно.
Возможно, причиной стали области памяти, выделяемые отдельными кусками и где попало. Далее они могли копироваться в сплошную область, с последующим высвобождением исходно занятой памяти. Но всё это, скорее всего, происходило уже во время игры, вызывая "рывки" звука и музыки, а также кратковременные подвисания. Другими словами, работал механизм наподобие "сбора мусора" (garbage collection), присущий обычно более медленным языкам (Java, C#).
В пользу такого вывода говорит тот факт, что при повторных запусках (без перекомпиляции) занимаемая память постепенно уменьшалась, а также почти исчезали другие проблемы.
Но мне-то как раз хотелось избежать этого безобразия! И без всяких там перезапусков!
Допустим, это была моя ошибка - сказалось желание побыстрее делать игру (в таких случаях я обычно выбираю неудачный вариант и благополучно "закрываю глаза" на очевидное). А ещё, оказывается, "весь мир" давно уже рекомендует отказываться от явного использования new и delete, и даже обычных массивов, переходя на так называемые "контейнеры".
Допустим, я исправил ситуацию: сделал множества объектов с обычной инициализацией и поменял массивы на вектор-массивы. Теперь в обычной версии, конечно, всё хорошо. И память уже редко подскакивает до 300 Мбайт.
Однако в отладочной версии иногда наблюдается всё то же замедление, когда начинается раунд или где-нибудь в разгар боя.
Допустим, причина этого - чисто в медленных отладочных версиях контейнеров. Но ведь мне и в отладке нужна нормальная скорость.
Допустим, обычные циклы надо заменить более специализированными плюс готовыми функциями библиотеки STL, чтобы в итоге скорость перебора даже выросла, по сравнению с простыми массивами.
Допустим, по мере оптимизации алгоритмов перебора элементов ситуация исправляется. Но как-то сам собою напрашивается вывод, что вектор-массивы и другие контейнеры были "придуманы" для языка C++, в первую очередь, "потому что надо". И лишь потом уже дорабатывались - ради возврата к быстроте, которую давали обычные массивы и другие примитивные структуры данных...
[свернуть запись]
[^ следующая запись ^]
08.02.2018 > Оптимизация и тестирование: многовато, но маловато.
(показать/скрыть)
Может показаться, что я помешался на оптимизации скорости выполнения такой, казалось бы, простенькой игры. Что ж, попытаюсь Вас разубедить.
Во-первых, мне хочется не побыстрее сделать минимум и успокоиться (хотя насчёт начальной бета-версии это недалеко от истины), а добавить в игру как можно больше разнообразия. Редкая игра дарит его игроку в должном объёме. А много разнообразия означает больше вычислений в единицу времени. Так что я не стал выбирать простые реализации, которые "съедают" больше ресурсов.
Во-вторых, мне нравится и кажется естественным находить незамысловатые и эффективные по скорости решения (реализация которых не такая уж и простая). Некоторые проблемы возникают лишь при их комбинировании, чтобы получить интересные эффекты.
В-третьих, я в курсе насчёт правила "80-20" (когда 80% прироста скорости даёт оптимизация всего 20% кода, грубо говоря). И хотя мне просто нравится оптимизировать код, я изо всех сил стараюсь не увлекаться. Только ведь я сам, по сути, архитектор игры и примерно вижу, где в будущем могут возникнуть "узкие места". Поэтому, например, могу тратить больше усилий на оптимизацию множеств элементов, но пока что не пытаюсь радикально ускорить поиск пути (элементов множества заведомо гораздо больше, чем NPC, которым нужен новый путь, да и число обработок в единицу времени отличается кардинально). Плюс, конечно, некоторая интуиция, которой не хватает обычным программистам-ремесленникам (прошу прощения, но упомянутые коллеги слишком назойливо "проповедуют свои устои", что иногда раздражает).
Я знаю: принято тестировать более-менее сложные функции как можно более полным образом. Но до сих пор не пытаюсь следовать этому правилу. И тому есть причины. Не могу сказать, какая из них главнее, так что начну в произвольном порядке.
Первая причина - время, его у меня как всегда не хватает. Даже самое необходимое отняло, минимум, два года на реализацию. Если я начну ещё и тщательно тестировать, скорость разработки упадёт раза в два.
Вторая причина - отвращение к примитивизму, поэтому я не люблю делать слишком простую структуру кода (наверное, если бы я работал в команде и за деньги, делая что-то "по указке", всё было бы иначе). Хорошо, конечно, организовать все алгоритмы так, чтобы каждый был разделён на самые простые кусочки. Однако такая практика, насколько я могу судить, как раз и ведёт к однообразию. Что-нибудь более-менее хитрое, вычурное, уже не укладывается в схему и требует иной организации структуры. А ведь разнообразия в игре не достигнуть одними лишь комбинациями элементов, даже если их очень много. Желательно сделать так, чтобы каждое сочетание частей приобретало новые качества (хотя бы минимально). Это требование для простого конструктора уже не "по плечу".
А что тестирование? Оно справляется хорошо только с "чёрными ящиками", т.е. с отдельными кусочками, но ведь есть ещё логика, управляющая этими кусочками. Вот её-то у меня много - больше, чем "положено" - ведь я не хочу скатиться в однообразие или чрезмерное потребление ресурсов (весьма отвратное впечатление производят игры, "насилующие" ресурсы компьютера, но при этом довольно примитивные).
Третья причина - я не хочу делать слишком предсказуемое поведение элементов игры. Вообще-то мне бывает неприятно, как игроку, если я вспоминаю, что исследуемый игровой мир заведомо был протестирован, так сказать, вдоль и поперёк разработчиками (тестерами). Это дополнительно убивает надежду встретить хоть что-нибудь оригинальное, неожиданное. А большинство игр и так не оправдали моих надежд, возложенных на них.
Поэтому я, где только можно, использую случайности (генератор случайных чисел). И тестировать стараюсь больше абстрактно, чем конкретно. То есть, добиваюсь не точного соответствия, а, скорее, тенденций (следования происходящего в игре определённой логике).
Как Вы понимаете, довольно сложно тестировать случайные сочетания случайных элементов.
Но есть всё же одна область, в которой тестирование выполняется больше всего: взаимодействие с игроком или отображение важной для него информации. Здесь я очень стараюсь реализовать всё "по-человечески", т.е. удобным в использовании. А это означает дополнительные сложности в программном коде, т.к. простая организация взаимодействия всегда приводит к неудобному интерфейсу. Неудобство может выражаться в неочевидности, назойливости, неуютном виде, а также в излишне повторяющихся нудных и рутинных действиях.
В то же время, в процессе избавления от этих проблем интерфейса, почти всё то же самое начинается в коде. Зато в результате взаимодействие с игрой становится приятным. По крайней мере, я на это надеюсь: иногда у меня хватает смелости бросить свежий взгляд на результаты своих трудов и заметить, чего именно не хватает и что нужно исправить.
Напоследок приведу один забавный факт: несмотря на примитивную систему проверки коллизий, при движении поезда летящий снаряд вполне может проскочить между его вагонами.
[свернуть запись]
[^ следующая запись ^]
28.11.2017 > "Осталась парочка секунд!"
(показать/скрыть)
- Аликс Вэнс (Half-Life 2).
Когда я решил, что бета-версия игры годится для публичного тестирования, мне тоже показалось, будто всё давно уже готово: достаточно пройтись финальным взглядом и поправить вероятные мелочи. Но стоит только начать хотя бы мысленно просматривать ключевые моменты, как вспоминается целая куча недоделанных вещей.
Я сейчас не говорю о тяжёлых задачах - нет, к счастью, большая часть оставшегося относится к графике. Просто рисование - моё слабое место.
Ну и, конечно, остались некоторые моменты, которые задумывались к бета-тестированию. Некоторые из них потянули за собой несколько больше проблем, чем было в альфа-версии.
Например, что делать с отображением значков способностей у Налётчиков, которые во время "Десанта" или "Бомбокоптера" переворачиваются на 90 градусов? Как неназойливо отобразить еще не настроенные игроком тактические слоты, но чтобы при этом он их заметил? Каким образом организовать работу сильно поменявшегося интерфейса улучшения способностей? Как найти время довести до конца настройки раунда?
Наконец, самый тяжёлый случай: как добавить больше дружелюбия, чтобы хоть чуть-чуть облегчить понимание игры новичку? Несмотря на то, что решение давно найдено, его реализация началась буквально две недели назад (сделано около 90%).
Если повезёт, к Новому Году или уже в праздничные дни бета-версия будет готова. А пока - можете взглянуть на прогресс разработки: помимо графика списка основных задач стал актуален размороженный "виток" этого самого прогресса. Правда, его значения немного скорректированы, учитывая немного изменившиеся отдельные цели и решения.
[свернуть запись]
[^ следующая запись ^]
12.10.2017 > Долгожданная встреча.
(показать/скрыть)
Сегодня я могу с радостью поделиться хорошей новостью: наконец-то бета-версия догнала альфа-версию игры, находящуюся в открытом доступе. То есть, конечно, не сделано ещё событие "Безумный поезд", а также способность "Десант". Немного не дорисовано визуальное оформление в интерфейсе выбора способностей (сам выбор функционирует исправно) и ещё некоторые детали. Но, учитывая набор добавленных (новых) мелочей, я думаю, можно приравнять достигнутый прогресс в обеих версиях.
В скором времени для загрузки станет доступна бета-версия, и в кои-то веки разморозится "виток прогресса" (на соответствующей странице). А пока что хочу рассказать о необходимых предварительных действиях, если вдруг Вы тестировали альфа-версию.
Бета-версия Smile Fortress: точки несовместима с альфа-версиями, поэтому рекомендуется предварительно удалить любые имеющиеся альфа-версии.
После автоматического удаления альфа-версии, возможно, потребуется ручное удаление папки SmileFortress в каталоге установленных программ. Кроме того, теперь дополнительный шрифт располагается в каталоге игры, так что в системном каталоге шрифтов Windows наличия шрифта tf2secondary.ttf не требуется. Если удаление альфа-версии выполнено корректно, то шрифт tf2secondary.ttf будет удалён из каталога шрифтов.
Примечание: наличие шрифта tf2secondary.ttf в системном каталоге шрифтов было необходимо только для альфа-версии Smile Fortress: точки. Игра Team Fortress 2, из которой был заимствован, в частности, этот шрифт, использует его из собственного каталога установки. Таким образом, удаление tf2secondary.ttf из каталога шрифтов не влечёт каких-либо последствий (если только Вы не используете данный шрифт для собственных целей, например, в графическом или текстовом редакторе).
Примечание: обычно каталог для установки программ имеет следующий путь: C:\Program Files или C:\Program Files (x86). Системный каталог шрифтов Windows обычно расположен по адресу C:\Windows\Fonts.
На данный момент новая карта cp_madtrain пока не работает в полной мере, и поезд появляться не будет.
Есть некоторые другие технические недоработки: в частности, возможны артефакты (искажения) в графическом оформлении, редкие зависания игры при запуске (эта проблема со звуком ещё решается).
Кроме того, карты, настройки игры и профили игроков несовместимы с предыдущими версиями. Удалять из системы альфа-версию не обязательно, однако не пытайтесь использовать какие-либо её данные в новой версии.
В завершение сегодняшней записи - немного о текущих задачах. Так уж получилось, что я не следил за развитием C++ долгое время. Из-за этого поначалу в коде игры было много отклонений от рекомендуемого стиля программирования: несколько "изобретённых велосипедов", небезопасные или плохо читаемые конструкции, неоптимальные алгоритмы. Теперь вот понемногу навёрстываю упущенное и улучшаю код.
То есть, появилась ещё одна причина, замедляющая разработку. Хотя, с другой стороны, она самым положительным образом влияет на результат.
[свернуть запись]
[^ следующая запись ^]
20.09.2017 > Борьба с ошибками: часть II.
(показать/скрыть)
Я обычно нетерпелив, особенно занимаясь сложными разработками, насыщенными деталями. То и дело перескакиваю с одного на другое, третье и так далее. Причём чаще это вызвано опасениями забыть впоследствии, что и как именно необходимо было сделать. Мне постоянно кажется, что концентрация внимания на одной детали от начала и до конца - приведёт к тому, что я забуду, так сказать, всю соль остальных деталей, для чего они создаются; не смогу увязать их в общую картину.
Для таких случаев обычно используется метод разбиения сложной детали (задачи) на более простые, причём сначала организуется верхний уровень логики, а затем уже составляющие части. И нередко мне помогает этот путь.
К сожалению, у данного способа есть очень большой недостаток: когда Вы спускаетесь на один из нижних уровней, может оказаться, что реализацию эффективнее делать способом, не вписывающимся в первоначальное разбиение задачи. То есть, придётся возвращаться на уровень выше и переделывать основную задачу с учётом новых знаний.
Вот и получается, что доверять такому подходу нежелательно, если местами используются нестандартные решения. А это, скорее всего, потребуется делать ради улучшения производительности игры.
Таким образом, приходим к тому, что при разбиении задачи на части нужно в первую очередь рассмотреть, каким образом будут сделаны эти части. И только затем уже возвращаться к верхнему уровню.
А ещё часто возникает необходимость увязывать вместе несколько сложных объектов - ради того, чтобы хоть как-то упростить и ускорить их обработку. Вот тут-то и начинается сущий ад: пересматривается каждая составляющая деталь, чтобы понять, какие корректировки нужно сделать; выполняются изменения всего и вся. И мы возвращаемся к проблеме, о которой говорится в начале записи.
Хорошо говорить о продумывании архитектуры заранее, когда Вы точно уверены, что вот это всё не будет пожирать недопустимое количество ресурсов, когда эксперименты точно не понадобятся. А когда нет? Когда разработчик (то есть я, например) очень любит экспериментировать? Когда общее впечатление от игры не тянет на "отлично", и надо проводить эксперименты? Когда элементарно бывает стыдно выпускать в мир игру с имеющимися недостатками?
А что делать, если мне принципиально нравится рассматривать каждую задачу как уникальную и непохожую на другие? Мне очень скучно иметь дело с деталями, которые легко группируются по общим чертам. Разнообразие и неповторимость гораздо интереснее, уверяю Вас.
Ладно, переходим к разбору ошибки из разряда "Побочные эффекты".
Самым первым ошибочным шагом, несомненно, было то, что многие алгоритмы я пытался брать готовыми из альфа-версии игры. Вот и в случае Мелкопакостников, ищущих вражескую технику, чтобы её заблокировать, я сделал по-новому только половину задачи - выдачу координат цели. Конкретно, "прикосновение" Мелкопакостника к технике, разрешающее установку жучка, было выполнено достаточно топорно: каждая единица техники проверялась со всеми "вредителями" на некоторое пороговое расстояние. И это ещё выполнялось каждый проход игрового цикла (т.е. с частотой кадров в секунду).
Если как следует подумать, становится очевидным, что проверять лучше не случайные касания, а именно в момент остановки, когда Мелкопакостник дойдёт до цели. Визуально это едва ли хуже, чем когда жучки "ставятся на ходу". Хотя бы потому, что новая цель может быть получена в следующем кадре, и выйдет тот же самый эффект - Мелкопакостник "как будто" поставит одного жучка и, не сбавляя хода, направится к новой цели.
Тем не менее, перейти к такому алгоритму меня побудили другие причины: пришла пора добавлять жучков, самостоятельно ищущих технику (для способностей "Кто здесь?" и "яФримен"). Вспомнив, что в альфа-версии таких жучков было немало на экране, я спохватился, как бы проверки не отхватили заметную часть времени. И решил ввести новый способ для управления их поведением.
Только потом уже я опомнился, что неплохо бы сделать так же и для самих Мелкопакостников. Но сначала надо было отладить алгоритм на жучках.
Итак, сама идея простая: завести список (массив) пар - указатель на NPC и указатель на технику, которую тот намеревается блокировать. По сути, пары означают намерения определённых жучков заблокировать определённую технику. Как только жучок получил цель - ссылка на него и на технику добавляется в список. А вот с удалением пар не всё так просто. Посмотрите, в каких случаях оно требуется:
- жучок дошёл до цели и заблокировал её;
- жучок погиб, не дойдя до цели;
- цель была заблокирована или разрушена до того, как пришёл искомый жучок;
- жучок переключился на другую цель (в т.ч. после отбрасывания, если остался жив);
Вот, в частности, эти особенности вынудили меня сделать глобальный список пар. Потому как, заведя у каждого жучка персональный указатель на цель, я был бы вынужден сообщать об изменениях с любой техникой каждому из них: ведь пока жучок идёт к цели, она может быть уничтожена, и в участок памяти с тем же адресом запишется информация о другой технике, может быть, даже цвета команды жучка. Кроме того, цель может быть к тому времени заблокирована другим жучком - ей будет ни к чему ещё один. Естественно, блокировка в любом случае обрабатывается через верхний уровень (классу жучка незачем прямо вмешиваться в класс техники). Наконец, не каждый жучок получает цель (единиц техники попросту не хватит на всех).
А это всё уравновешивает выгоду от прелестей ООП в обратную сторону (кстати, вот Вам и обещанный пример конфликта с логикой архитектуры, но далеко не единственный). Помимо того, надо учесть и моё желание включить в список Мелкопакостников. В общем, контроль был вынесен на верхний уровень - такое решение было изящнее всех остальных, пришедших в голову.
Кстати, более ресурсоёмкий способ работал тупо, но надёжно - все проверки были однотипные, выполняясь в одном месте и в одно время. Новый способ выбран интуитивно, хотя позже были выявлены строго определённые причины (названы в предпредыдущем абзаце).
Во время работы над новым алгоритмом возникла необходимость в отношении "одна техника - несколько вредителей": ссылки на технику могут повторяться в парах, а на "вредителей" - нет. Т.е. один "вредитель" может направляться только к одной единице техники, в то время как одна единица техники может быть под угрозой сразу нескольких "вредителей" (это допустимо).
Был разработан своего рода мини-контейнер (по аналогии со стандартными контейнерами C++), названный взаимно ассоциативным. В нём можно было искать как по ссылке на "вредителя", так и по ссылке на технику. Обратите внимание, что упомянутое в предыдущем абзаце отношение внутри контейнера специально не контролировалось (я решил, что это лишние хлопоты и ресурсы). Вполне достаточно было соблюдать правила извне.
Таким образом, во втором и четвёртом случае из приведённого ранее списка удаляется только одна пара по ссылке на жучка, т.к. устраняется только он, а в первом и третьем - все пары по ссылке на технику, т.к. устраняется единица техники вместе с намерениями любых жучков её заблокировать.
В принципе, я предусмотрел все ситуации на уровне жучков и техники. Алгоритм заработал адекватно сам по себе, а кое-какие нестыковки и шероховатости были оперативно сглажены корректировками. Однако последняя заставила потратить на неё аж три вечера, с учётом обдумывания в дневное время.
Хитрая ошибка заключалась в том, что жучки способности "Кто здесь?" обрели дар телепортации прямо на целевую технику. Они сразу же после появления моментально исчезали с условием "прибыл на цель" (одной из составляющих оптимальной проверки). Расследование осложнялось тем, что Мелкопакостники выпускали жучков одного за другим при виде одной и той же единицы рабочей техники (было исправлено впоследствии).
Провозившись, по сути, три дня и перерыв всё, что только могло вызвать подобный эффект, я начал было подозревать компилятор (куда же без этого?). Как вдруг, внимательно просматривая (!) код, обнаружил, что вновь созданный NPC синхронизирует текущие координаты и координаты следования, не сбрасывая текущую скорость. А она часто не равнялась нулю, оставаясь от одного из предыдущих жучков, использовавшего ранее тот же самый элемент массива, в котором хранятся данные обо всех жучках.
Разумеется, в первом же кадре скорость обнуляется, ведь текущие координаты равны конечным (после синхронизации), и NPC считается прибывшим на цель досрочно!
Только вот на какую такую цель? Извне я создавал жучка и сразу же давал координаты назначения. В первом кадре обработки они ещё не вызывали расчёт нового пути, однако NPC уже считался идущим... к своим текущим координатам.
Этот нюанс движения совсем не был заметен до того момента, пока я не добавил жучков из способности "Кто здесь?".
Любопытно, что ошибка нашлась не отладкой, а именно просмотром алгоритмов движения (это причина восклицательного знака в скобках выше). Отладка лишь закрыла неверные пути для поиска, а в конце - подтвердила "теоретические выкладки": отследив состояние движения, я моментально заметил ненулевую начальную скорость очередного только что созданного жучка.
Напоследок хочу отметить, что эта довольно хитрая ошибка появилась из-за некоторой путаницы в коде (отдельные фрагменты я создавал нетерпеливо), а немалое время, потраченное на поиск - из-за моей склонности к хитрым решениям задач. Я сначала даже не предполагал, что ошибка кроется в гораздо более старом коде, а не во вновь добавленном. Ведь, как уже упоминалось, на внешнем поведении смайликов это никак не отражалось (после исправления разница в движении незаметна).
Тем не менее, ошибка оказалась элементарной. А некоторая "хитрость" кода позволяет мне добавлять новые детали без заметного ущерба производительности - так сказать, малой кровью. Ко всему прочему, беспокоящие меня с точки зрения "плохого" кода участки постепенно исправляются, и почти каждый раз оказывается, что именно они (а не выдуманные мной алгоритмы) отнимали лишние ресурсы.
[свернуть запись]
[^ следующая запись ^]
16.08.2017 > Борьба с ошибками: часть I.
(показать/скрыть)
По правде говоря, у меня не настолько технический склад ума, как мне самому казалось в те годы, когда учёба составляла основное занятие. С одной стороны, я люблю хитрые технические решения технических же задач, испытывая удовольствие от успеха; мне нравится иметь дело с чёткими формулировками и однозначными правилами. С другой стороны, надолго в такой работе меня не хватает: не люблю слишком долго заниматься одним и тем же; очень многие задачи для меня откровенно скучны (из тех, что другие люди считают интересными); не выношу программирование просто ради программирования.
А также люблю и считаю нужным учитывать все мыслимые нюансы. В частности, знаю, что ступенчатое приближение не всегда работает - иногда следующая ступень достигается кардинальной переработкой уже имеющегося (или вовсе начинанием с нуля).
Для начала попробую объяснить мой способ восприятия информации и его справедливость.
Один пример: математика абстрагируется от реальности. Например, где в реальности встречаются математические понятия и фигуры? Точку, линию с нулевой шириной и толщиной, квадрат и прочее?
До сих пор питаю интуитивное недоверие к физическим теориям, работающим "на чистой математике" без качественного (а не количественного), более-менее наглядного объяснения сути. Насыщение физики математикой выглядит, как попытка спрятаться от расхождений теории с действительностью и перекладыванием смысла происходящего на математику.
Я совсем не против математического подхода в науке. Меня коробит, если начинают превозносить обложенные математикой теории до небес. Я понимаю, что теория должна быть в порядке, покуда её математическое описание безупречно, и не заметно расхождений с реальностью. Но не вижу особых причин удовлетворяться её объяснением устройства мира (тем более, на следствиях из математического аппарата).
Другой пример: я много раз перечитываю и переделываю любой текст, предназначенный для общественного обозрения. Причины, видимо, кроются в том, что существует много способов говорить об одном и том же. И очень часто я понимаю, что мои формулировки далеко не всем будут очевидны - много скрытого смысла и неявно подразумеваемых мелочей.
Но, хватит вступлений. Слишком далеко я отклоняюсь от темы. А всего-то хотел рассказать об основных проблемах, мешающих работать над игрой - на разборе примеров совершённых ошибок.
Ошибка из разряда "Было плохое настроение".
Яркий пример того, что любимым (и вообще - любым) делом нельзя заниматься "через силу".
Время от времени я возвращаюсь к уже реализованным алгоритмам ради интереса - освежить в памяти принципы их работы. Либо не из простого интереса, а с целью расширить их применение. И вот тут иногда бывает, что волосы на голове готовы встать дыбом от картины, которая открывается перед свежим взором.
А конкретно: поведение физического мяча используется у меня как для отлетающих от взрыва NPC, черепов и костей, так и для любых снарядов (даже если они просто уничтожаются при столкновении). Поскольку за каждую группу объектов отвечают разные классы, физический мяч "входит в состав" каждого из них.
Случайно (!), во время просмотра кода выяснилось, что внутренний флаг мяча "Отражаться от препятствий" обрабатывается, тем не менее, вовне - каждым из объемлющих классов. Иными словами, физический мяч только устанавливает или сообщает значение своего флага, обработкой его сути он и не пытается заниматься! Класс мяча даже не проверяет столкновения с препятствиями, он просто "даёт флаг в руки" вышележащему классу в иерархии. А дальше уже не его забота, хотя смысл физического мяча в том и заключается, чтобы корректно взаимодействовать с препятствиями...
В такой момент у меня вырывается обычно несколько ругательных выражений в тяжёлой форме. И зачем только было организовывать принципы объектного программирования (ООП), а потом спокойно их нарушать? В оправдание могу сказать лишь то, что иногда работаю над игрой в состоянии "голова в тумане" (не улавливаю направление или смысл), то есть, действуя наобум, без видения конечного результата. Ну, вот "результат" и не заставил себя долго ждать.
Замечание: хотя описанная ситуация оказалась не единственной, тем не менее, в некоторых других случаях вынос проверки на верхний уровень бывает оправдан лучшим распределением ролей между объектами на разных уровнях. Так что не воспринимайте лежащий выше текст как безусловный призыв соблюдать правила ООП. Логически, в данном случае корректировка была сделана правильно, но иногда логика ООП вступает в конфликт с логикой архитектуры программы. Когда-нибудь я напишу об этом подробнее.
Ошибки из разряда "Куда я смотрел?".
Начну с того, что мне захотелось сделать запуск игры максимально быстрым, не вынуждая игрока терпеть долгую загрузку всех необходимых ресурсов (по большей части звуков). Ради этого в начале работ загрузка была разделена по времени: немного на старте и всё остальное перед первым раундом. Это совсем неплохо - Вы получаете быстрый старт игры и некоторое ожидание перед началом раунда, но только первого, последующие раунды загружаются почти мгновенно.
Первая версия была не очень гладкой: загрузка ресурсов игры выполнялась в основном потоке, что вызывало неприятное подвисание на несколько секунд (нечто похожее на альфа-версию). Когда, наконец, дошла очередь до переноса загрузки в параллельный поток, чтобы сохранить отзывчивость интерфейса, выяснилось, что имеются две проблемы.
Во-первых, OpenGL не желал корректно инициализировать текстуры в фоновом потоке. Как ни странно, это оказалось почти пустяком - я успешно разделил загрузку файлов (в фоне) и окончательную "доинициализацию" (в основном потоке - быстро и практически незаметно). Особенности инициализации позволили создать стек, хранящий адреса идентификаторов текстур и выделяемых областей памяти, который заполняется при загрузке. Позже, в основном потоке, данные считываются из стека обратно, позволяя завершить создание текстур на основе получаемых пар адресов. Таким образом, идёт работа напрямую с двумя переменными конкретного объекта, которые соответствует данной паре. Благодаря указателям это очень просто реализовать.
Получилось удобно и эффективно, однако предварительно возникла вторая проблема: на завершающем этапе вылезли исключения доступа к памяти. Немного покопавшись в отладке, были установлены виновники - объекты, использующие вектор-массивы для загруженных картинок. Поясню: вектор-массив (std::vector) - очень удобное средство хранения однотипных данных, когда их количество заранее неизвестно и увеличивается динамически во время выполнения. Именно вектор применялся в наборах анимаций, которые и вызвали аварийную ситуацию.
Для начала я просто оставил загрузку проблемных ресурсов в основном потоке. Конечно, промежуточный результат не сильно радовал, но хотя бы выглядел лучше, чем ничего. Да и к тому же я не смог разобраться с ходу, в чём тут дело.
И вот, наконец, раз этак в двадцатый обдумывая проблему, меня осенило: вектор-массив ведь не знает заранее, какой размер мне нужен, а изменение его на ходу имеет критический в моей ситуации нюанс. Уж не знаю, по какой такой диагонали я минимум раз 10 (!) читал в замечательной книге Б. Страуструпа "Язык программирования C++" про особенности resize() и reserve() для векторов, если целых полгода (а то и больше) не мог догадаться, что же было упущено.
Да, я бываю ужасно невнимателен. Настолько, что упустил из виду необходимость в резервировании полного объёма памяти заранее. Ведь если выделенной памяти не хватает, вектор-массив пытается резервировать больший объём и заодно перемещает имеющиеся данные в другое место (в этот больший объём). Делается это, конечно, для того, чтобы сохранить непрерывность расположения элементов в памяти. И данное обстоятельство сыграло со мной злую шутку.
То есть, часть ссылок на текстуры к моменту завершающего этапа инициализации попросту указывала неизвестно на что, ведь прежний участок памяти давно был высвобожден (и мог быть отдан под другие данные). Уточню: это происходило наверняка, т.к. набор анимаций наземных классов составляет 50 штук, а резервируемое место по-умолчанию для вектор-массива было вообще нулевым (0 элементов).
Уф, именно это я и упустил, не резервируя объём памяти заранее. И ведь что удивительно: во всех остальных местах резервирование было тщательно продумано, и сделаны соответствующие вызовы функции reserve()!
Ради справедливости отмечу, что осталась небольшая категория объектов, оставленная для инициализации в главном потоке - это все текстовые ресурсы. Но до них просто ещё не дошла "очередь на параллелизацию" (создание текста выполняется иначе, нежели загрузка текстуры, и алгоритмы пока не адаптированы к параллельному потоку).
И ещё: оказывается, ракеты Часовых до недавнего времени не были самонаводящимися. Это даже заметно на первом видео с представленной бета-версией. Дело в том, что я забыл указать для создаваемых ракет специальную категорию - самонаведение (которое давно уже было реализовано). Притом, что регулярно и аккуратно задавал им цели.
Ошибка вскрылась случайно: стал добавлять дым (белого оттенка) и обнаружилось, что ракета инициализируется с флагом обычного снаряда, летящего по прямой. Как я этого не замечал визуально - ума не приложу.
На этом я не заканчиваю рассказ об ошибках. Окончание следует позже...
[свернуть запись]
[^ следующая запись ^]
15.06.2017 > Правильный подход к анимации.
(показать/скрыть)
Случилась ещё одна проблема, которая (по счастью) ненадолго замедлила создание игры. Она была вызвана принципами обработки анимации и была заметна только на больших скоростях проигрывания.
Дело в том, что ещё в Construct Classic Пострел-Робин Гуд ну никак "не хотел" стрелять из лука в ускоренном режиме (способность "Дикобраз!"). Постоянно были странные "заедания" выстрелов и анимации. Конкретно, выстрел должен был выполняться только во время отображения определённого кадра - с целью синхронизации. Ведь нужно показать сначала, что натягивается тетива лука, и лишь после этого производится выстрел. При обычной скорости всё работало хорошо, но стоило ускорить анимацию... в общем, на момент перехода к бета-версии (на C++) мне так и не удалось добиться идеальной состыковки без непонятных задержек. Печальнее всего, что я даже не догадывался, в чём тут дело.
Но вот, началась работа в C++, и я выбрал первую пришедшую в голову структуру анимации: данные о длительности каждого кадра анимации пересчитывались на частоту обновления экрана. То есть, вместо длительности у каждого кадра хранилось количество циклов перерисовки экрана, в которых этот кадр должен отображаться. Для кадров, длительность которых занимала менее одного цикла, принудительно выставлялась единица (округление в большую сторону). И всё было великолепно - до тех пор, пока я не дошёл до Пострела с его луком.
Нет, при обычной скорости проблем не возникало. Трудности начались при ускоренном воспроизведении - ведь ни один кадр не мог быть короче "экранного кадра". Анимация выстрела занимает 15 кадров, т.е. при частоте обновления экрана в 60 кадров в секунду минимальная продолжительность анимации составит 15 * (1000 / 60) = 250 миллисекунд. А это всего 4 выстрела в секунду (проще - 60 / 15), что очень медленно для "Дикобраза!".
Как только я упёрся в суть проблемы, стало очевидно, что используемый до сих пор формат обработки анимации не годится. И следующее решение, которое пришло в голову - отталкиваться от времени, прошедшего с начала анимации. Длительность кадров теперь не пересчитывается, а хранится в изначальном виде.
А сама обработка анимации теперь выполняется следующим образом: некоторая переменная каждый такт накапливает прошедшее время, сравнивая его с суммарным временем кадров анимации от начала до текущего кадра. Как только первая величина превысит вторую - пора сменить текущий кадр на новый. Собственно, это даже более точный метод, учитывая дополнительную корректировку из-за накапливающейся дробной части.
На самом деле, новый алгоритм хорош далеко не в этом: основное его достоинство - автоматический пропуск кадров, которые занимают менее одного цикла обновления экрана.
Но! Здесь я вовремя догадался, что Пострел не будет вести себя "прилично". Ведь с целью идеальной синхронизации выстрел происходит во время отображения строго заданного ключевого кадра, который на большой скорости зачастую будет пропущен. И - да! Я наконец-то разгадал тайну "заедания" в альфа-версии: судя по всему, Construct Classic обрабатывает анимации по тому же принципу с пропуском кадров...
К счастью, C++ - язык программирования, и он позволил скорректировать структуру данных анимации необходимым мне хитрым образом. Было введено понятие "фиксированного кадра": этот особенный кадр является приоритетным для отображения. Если в очередном цикле обновления экрана данный кадр попал в число пропущенных, анимация принудительно выставляет его текущим. Если даже вся анимация слишком коротка по сравнению с длительностью одного цикла - она будет постоянно отображать именно данный кадр.
Вы, наверное, догадываетесь, какой номер кадра был выбран фиксированным. Да, именно тот, в котором совершается выстрел из лука (т.е. появляется стрела-снаряд).
В завершение хочу сказать, что тестирование прошло успешно. Теперь "Дикобраз!" работает в полном соответствии с задуманным. А повышенная точность попадания стрел превращает Пострела-Робин Гуда на несколько секунд в оружие массового уничтожения.
И чтобы, как говорится, "поставить точки над i": некоторые детали структуры игры аналогично были организованы по принципу счётчика циклов. Поэтому их тоже пришлось корректировать, ведь иначе получилась бы рассинхронизация. К примеру, как было вначале, если делить 1000 мсек на 16 мсек (длительность одного цикла обновления экрана), выходит, что это займёт 62 кадра (все вычисления происходят с целыми числами, поэтому дробная часть отсекается). В то время как реальное число обновлений экрана в секунду будет равно 60 (в среднем), ведь один цикл обновления экрана длится не ровно 16, а 16.6666... миллисекунд (если записывать математически - 16,(6)).
Как результат - интервал, заданный равным одной секунде, займёт уже не 1000 миллисекунд, а примерно 1033 миллисекунды. Возможно, это кажется небольшой погрешностью, но ведь разность может накапливаться. А в этом случае все нестыковки будут заметны, так сказать, невооружённым глазом.
P.S. Думал, что на этом всё, а оказалось - не всё. Ведь работа нового алгоритма с миллисекундами никак не меняет того факта, что в секунде окажется 62 кадра вместо 60.
В конечном счёте, все "внутренние" вычисления времени были переведены в микросекунды. Таким образом, хорошо повышается точность замера интервалов - пройдёт почти 14 часов, прежде чем отклонение составит 1 секунду. Максимальный временной интервал, который можно отмерить, составляет около 33 минут (учитывая формат хранения времени в игре). А поскольку время раунда, единственное способное достигнуть этого предела, измеряется не напрямую, а счётчиком секунд, указанные ограничения меня вполне устраивают.
Конечно, в идеале можно было бы запрашивать время, прошедшее с предыдущего кадра, и работать уже с ним. Собственно, переход к микросекундам сделан именно по причине использования единожды вычисленной "идеальной" длительности одного цикла (16667 мксек, что, естественно, не равно в точности 1000000/60).
Возможно, к варианту с запросом точного времени и надо стремиться, хотя в таком случае при каких-либо задержках работы будут пропускаться кадры. А это, на мой взгляд, весьма неприятное решение: мне бывает некомфортно, если в самый разгар событий игра лезет за ресурсами на жёсткий диск (ждёт разрешения на продолжение от системы, занятой другими делами, или просто сбита с толку ненормальным временем отклика по сети - "пингом"), а потом "выкидывает из реальности" прошедшие секунды, за которые могло произойти что-нибудь важное. Увы, именно так происходит в современных "творениях"...
[свернуть запись]
[^ следующая запись ^]
03.06.2017 > "Торчание воткнувшихся" (стрел).
(показать/скрыть)
Задумывались ли Вы хоть раз, что любой персонаж, который стреляет видимыми снарядами, вылетающими из дула его оружия (а не его центра), нуждается в перерасчёте положения дула при любом повороте? Наверное, да, и не только задумывались, если Вам приходилось работать над игрой с наличием стрельбы "видимыми пулями".
Если бы снаряды летели точно из центра персонажа (или он не мог крутиться вокруг оси) - проблемы бы не существовало. Нужно было бы разве что учитывать направление (угол) взгляда и соответственно задавать направление полёта снаряда.
Совсем другое дело, если надо показывать, как снаряд появляется на выходе дула оружия, которое персонаж держит в "руке".
Даже принимая, что стрельба всё равно происходит в направлении взгляда, снаряд появится не строго в центре, а с некоторым смещением от него.
Далее я буду излагать собственное решение задачи. Оно достаточно простое и относительно быстрое для моих целей. Фрагменты кода оформлены в стиле C++ (в частности, символ // означает комментарий до конца строки).
Естественным образом можно заранее указать смещение так называемой "точки выстрела" для персонажа, когда он повёрнут на угол в 0 градусов (что обычно совпадает с его изображением или моделью в редакторе, поэтому легко определяется). Тем не менее, как только в игре персонаж повернётся хотя бы немного в сторону - точка выстрела переместится, но куда?
Ответ достаточно элементарен (если Вы знакомы с математикой): рассматриваем смещение точки как вектор, направленный из центра персонажа. В этом случае при повороте персонажа на угол angle нужно аналогично повернуть вектор на тот же угол - его координаты станут равны актуальному смещению.
Вот как это выглядит для двумерной игры:
// shiftX, shiftY - смещения (описание вектора) по осям X и Y точки выстрела от центра персонажа, повёрнутого на угол в 0 градусов
// angle - угол, на который повёрнут персонаж в игре (т.е. смещение угла от нуля)
angledShiftX = shiftX * cos(angle) - shiftY * sin(angle); // актуальное смещение по оси X
angledShiftY = shiftX * sin(angle) + shiftY * cos(angle); // актуальное смещение по оси Y
Такие преобразования выполняются для всех классов, стреляющих видимыми снарядами (это Костоправ, Костолом, Стерилизатор, Нервотрёп, Пострел-Робин Гуд и Налётчик). Само собой, нужно учитывать, что функции sin() и cos() библиотеки работают с углами в радианах.
А теперь, после длительного вступления, перейдём непосредственно к стрелам, втыкающимся в смайлики.
Думаю, Вы догадываетесь, что торчащие стрелы - просто визуальное "украшение" персонажа и ничего более. То есть, они никак и ни с чем не взаимодействуют, а лишь сохраняют положение и направление относительно "пострадавшего".
Значит, принцип действия таков: при попадании стрелы-снаряда в смайлик надо удалить стрелу-снаряд и создать стрелу-украшение, задав ту же позицию и угол. Дополнительно, стрела-украшение должна перемещаться и поворачиваться вслед за персонажем, её получившим. И делать это таким образом, чтобы она выглядела застрявшей в смайлике.
С углом поворота, кстати, проще: достаточно вычислить разницу направлений стрелы и персонажа на момент привязки. Зная новый угол, на который повёрнут смайлик, к нему прибавляется разница - и получается соответствующий угол поворота застрявшей стрелы.
А со смещением нас с Вами ждёт засада: ведь к моменту создания стрелы-украшения персонаж будет повёрнут, скорее всего (да наверняка), отнюдь не на нулевой угол. То есть просто так нельзя зафиксировать переданные смещения стрелы от центра вдоль осей X и Y - ведь для расчёта актуальных значений требуются смещения при нулевом угле поворота смайлика. В то время как сам угол на момент передачи смещений отнюдь не нулевой...
Есть два решения:
- зафиксировать текущий угол поворота цели (своего рода нулевое значение отсчёта) и в дальнейшем делать поправку на него;
- зная, что текущие координаты смещения стрелы можно рассматривать по сути образованными от неких значений для нулевого угла, найти эти исходные значения.
Первый вариант мне не понравился тем, что придётся каждый раз прибавлять поправку и делать нормализацию полученного угла. А вот второй способ - интереснее и красивее: один раз (во время привязки) вычисляются исходные значения, а затем всё идёт по стандартной схеме (по тем же формулам, что приведены выше для снарядов). Собственно, достаточно один раз решить обратную задачу: зная angledShiftX и angledShiftY, найти shiftX и shiftY.
Выполнив подстановки и некоторые преобразования, я получил необходимые обратные формулы:
shiftX = angledShiftY * sin(angle) + angledShiftX * cos(angle);
shiftY = angledShiftY * cos(angle) - angledShiftX * sin(angle);
Здесь все переменные - те же самые по смыслу, разве что на момент привязки нам известны актуальные смещения вдоль осей X и Y (и текущий угол поворота персонажа), по которым вычисляются смещения вдоль осей при нулевом угле поворота.
Вот теперь можно фиксировать полученные результаты, а при расчёте актуальной позиции стрелы-украшения пользоваться так называемыми "прямыми" формулами (данными в начале).
Напоследок, хочу отметить ещё одну деталь, которая не сразу бросается в глаза: угол поворота стрелы-украшения и её смещение не связаны. Угол рассчитывается от угла поворота смайлика, из которого торчит стрела, плюс зафиксированная на момент привязки разница углов. Смещение тоже рассчитывается от угла поворота смайлика, но уже по другой формуле и независимо от упомянутой разницы углов.
[свернуть запись]
[^ следующая запись ^]
25.05.2017 > Коротко о преимуществах C++ над Construct Classic.
(показать/скрыть)
Вот, ещё раз, список проблем, мешавших продолжать работу в конструкторе игр:
- однопоточность;
- неоправданно сильная загрузка доступных ресурсов;
- ограничение имитации физики (из-за предыдущего пункта);
- проблемы с полноэкранным режимом;
- добавление шрифта в системный каталог;
- невозможность простого сохранения настроек игры и данных игрока;
- абсолютная открытость сохранения;
- разбросанность структуры игры по нескольким *.exe-файлам;
- многооконность с неудовлетворительной синхронизацией (из-за предыдущего пункта);
- проверка обновления и доступ к изменяемым данным в Windows 7;
- почти отсутствующая возможность добавления в будущем новых режимов игры.
Обратите внимание: все проблемы уже решены в бета-версии. Кроме того, на сегодняшний день игра получила несколько улучшений благодаря переходу на C++:
- более чёткая логика алгоритмов действия NPC (хороший контроль без невидимых подводных камней на уровне объектов);
- возможность замены любых изображений без изменения кода игры (соответствующие ресурсы теперь хранятся отдельно в каталоге с игрой);
- возможность простой смены языка (локализация);
- использование более органичного и красивого интерфейса;
- усиление игры компьютерного противника и в большей мере качественная градация по уровням сложности.
[свернуть запись]
[^ следующая запись ^]
03.05.2017 > Работа над ошибками и прочие заботы.
(показать/скрыть)
Разработка идёт дольше, чем можно было ожидать - всё потому, что я не могу двигаться вперёд, если имеются ещё не исправленные ошибки. Неприятно думать, что их число (концентрация) будет только нарастать. Поэтому в тех случаях, когда необходимы корректировки, я отвлекаюсь от дальнейшей разработки, и прогресс волей-неволей застывает на месте.
Ошибки, в основном, появляются из-за невнимательности либо упущения некоторых обстоятельств. И некоторые из них даже трудно назвать ошибками. Например, я сталкивался дважды - в альфа-версии и бета-версии - с одной и той же забавной неприятностью. Когда нужно было проверять, как компьютер справляется с противодействием игроку, результат удручал: компьютерный оппонент проигрывал раз за разом. Через какое-то время, просматривая код, я сталкивался с введённой ранее зависимостью от сложности игры и вспоминал, что для неё в данный момент выставлено примерно среднее значение (по умолчанию для нового игрока). В общем, в обоих случаях я забывал, что проверяю не самый высокий уровень игры компьютера. Причём, заблуждение длилось несколько дней...
Немало деталей игры переделывается, поскольку в альфа-версии были черезчур неудовлетворительные решения. К тому же C++ позволяет совершать задуманное куда более точными и быстрыми способами, но, при этом, вынуждая разрабатывать алгоритмы с нуля, а не опираться на ранее созданные в Construct Classic решения.
Примером одной из таких ключевых переделок является введение специальной структуры (класса) - множества однотипных объектов. Суть его в том, что объекты создаются с желаемыми начальными параметрами и "бросаются на произвол судьбы", т.е. их отображение и обработка происходит на более низком уровне автоматически.
Не обошлось, конечно, без корректировок на высоком уровне. Зато теперь весьма и весьма удобно в нужном месте и в нужное время один раз вызвать нужную функцию, а остальные заботы возьмёт на себя ранее созданный и отлаженный код. Такой подход напоминает создание готовых объектов Construct Classic по шаблонам (что здорово ускоряет работу), но при этом отнимается гораздо меньше ресурсов.
Только не подумайте, что поведение множеств объектов упрощённое и однообразное! На самом деле, порождающие функции весьма разнообразны и почти всегда имеют много параметров. Это позволило регулировать немало нюансов: время существования, порождаемые вторичные объекты, тип и даже звук при столкновении, принцип движения, динамику размера, тени и пр. Таким образом, удалось воссоздать все детали боя из альфа-версии, сумев ещё и внести некоторую новизну. В итоге получилась этакая хитроумная система частиц, которая позволяет естественным образом объединять визуальные и звуковые эффекты с более значимыми элементами игры (NPC, снарядами и другими объектами).
В каком-то смысле принцип работы множества объектов напоминает систему графических шейдеров. Кстати сказать, до сих пор я не использовал шейдеры - по двум причинам: острая необходимость в них пока не возникла, а, кроме того, в концепции работы OpenGL произошли существенные перемены; требуется проведение экспериментов и выбор подходящего способа реализации. Но, как уже говорилось, времени на это пока не хватает.
Разработку замедляют и другие проблемы, кроме выловленных ошибок. Например, если в альфа-версии я довольствовался условно средним уровнем компьютера-противника, в бета-версии поставлена цель: научить компьютер выигрывать чаще. На данный момент получается неплохо, но, к сожалению, мне хочется видеть абсолютно эффективную игру компьютера. Идеал, как известно, недостижим, и придётся остановиться где-то на пути к нему.
С другой стороны, компьютерный оппонент действует теперь более разумно, нежели раньше. Также, я предполагаю, что однозначно выигрышная стратегия приведёт к формированию всегда чуть ли не одной и той же команды. А это будет не сильно интересно, нежели "попытки компьютера то так, то этак обыграть игрока" (среди которых преимущественно успешные).
Да и, кроме того, я стараюсь не концентрироваться на чём-то одном.
Достаточно долго существовала проблема "аномально громких звуков". Используя библиотеку SDL_mixer, я столкнулся с неписанными правилами. Пришлось немало поэкспериментировать, чтобы избавиться от неприятного и надоедливого побочного эффекта.
Непозволительно много времени отняло обдумывание и отладка интерфейса корректировки состава команды. Три способа из альфа-версии немного переделаны в визуальном плане, а также добавлен ещё один метод - с использованием только левой кнопки мыши. Новое графическое оформление и новая механика корректировки изрядно затянули рабочий процесс.
Подводя итоги, могу сказать, что будь разработка игры моим основным занятием, полагаю, я более систематично подошёл бы к делу. То есть, имея много больше времени, заранее бы расписал структуру игры, логику взаимодействия объектов, выбрал C++ сразу, а не спустя несколько лет. Но, думаю, по ходу работы всё равно бы возникали препятствия - из-за того, что многие детали на деле выглядят иначе (гораздо хуже), а также появляются новые идеи - незапланированные, но отлично вписывающиеся в общую картину. Отказаться от них было бы слишком жестоко.
Хотя и то, что произошло в итоге, не так уж страшно: выходит, альфа-версия была макетом, то есть послужила для обкатки общей структуры игры. Правда, времени на неё было затрачено гораздо больше, чем следовало.
[свернуть запись]
[^ следующая запись ^]
21.04.2017 > Что нового?
(показать/скрыть)
В самой игре произошли некоторые изменения, часть из которых навеяна тестированием альфа-версии.
Например, в отдельном тактическом слоте больше нет вероятностей тактик. Или, другими словами, может быть только одна из тактик с вероятностью 100% - такой вариант наиболее эффективен для игры команды. Эксперименты с другим распределением вероятностей, как оказалось, не имеют смысла. Да что там говорить, если уже в альфа-версии при наивысшей сложности игры противника вероятности тактик в слотах выставлялись около 100%...
Разумеется, выбор классовым NPC того или иного слота действует, как и раньше - с равной вероятностью выбирается один из доступных слотов. Просто каждому тактическому слоту теперь соответствует всего лишь одна тактика. И во время игры ни один смайлик не будет менять своей тактики до тех пор, пока не погибнет, с последующим возрождением.
Кроме того, выбор цели для лечения Костоправом или постройки Механиком теперь определяется приоритетом. Если цель с наивысшим приоритетом невыполнима (отсутствует NPC для лечения или невозможно возведение постройки), проверяется следующая цель, в порядке снижения приоритета.
Тактика Костоправа (Механика) представляет из себя определённое соотношение приоритетов. Хотя оно остаётся неизменным, пока выбран соответствующий тактический слот, указанные классы часто "перебирают" приоритеты. Таким образом, может показаться, что Костоправ или Механик то и дело меняют тактику. Но это просто следствие работы особого вида тактики, основанной на приоритетах.
На практике этот способ оказался естественным, даже очевидным. Не могу сказать, почему я не выбрал его с самого начала.
Для обычных пуль (невидимых по сути) поначалу был реализован вариант, давно подсказанный мне сообществом Construct Classic (теперь уже сообществом Construct 2): при каждом выстреле просто отнимать здоровье у цели, не прибегая к трассировке пули.
Получается очень быстро, но всё-таки я буду вынужден иногда проверять, не попала ли пуля в постороннюю цель. Это, в частности, необходимо при учёте взрывоопасных бочек: они не мешают движению и видимости, но пули и снаряды не должны пролетать их "насквозь". Таким образом, придётся вернуться к более общему случаю, выполняя приближённое (быстрое) подобие трассировки.
В игру возвращена полноценная обработка разлетающихся останков. Конечно, и Construct Classic может использовать встроенную физику для подобных эффектов, однако ресурсов на неё уходит немало. Как следствие, я был вынужден по-другому изображать движение всех этих черепов и костей. А вот C++ дал возможность сделать так, как и было задумано с самого начала.
Некоторые другие изменения предлагаю Вам отыскать самостоятельно, когда бета-версия игры будет в публичном доступе. Если Вы участвовали в альфа-тестировании, думаю, изменения произведут хорошее впечатление.
[свернуть запись]
[^ следующая запись ^]
05.04.2017 > После долгого молчания
(показать/скрыть)
Итак, бета-версия игры, начатая с нуля в C++, более-менее догнала альфа-версию. Открытый доступ к ней планируется с того момента, когда прогресс версий хотя бы примерно сравняется, а пока - можете почитать достаточно подробный отчёт о начальной стадии работы. Возможно, он будет не слишком интересным из-за обилия технических деталей.
По сути, из-за того, работа над игрой начата без опыта её организации, и происходят разного рода проблемы. Ведь я не люблю делать какие-то промежуточные, учебные, пробные наработки - это уж очень скучно, когда есть готовая идея. И, тем более, навевает уныние, когда идея уже хорошо проработана и даже частично реализована (только более простым образом).
Понятное дело, что начинать желательно с элементарных вещей. Но у меня, с одной стороны, есть некоторый опыт работы с C++ и разработки малых игр. С другой стороны, времени на долгий путь обучения катастрофически не хватает. В-третьих, на первоначальную работу затрачено много времени, хотя я предполагал, что Construct Classic является более гибким и более оптимизированным инструментом для реализации полноценной игры.
Да, именно так: даже зная, что нужна тщательная проработка игры в C++, я не хотел тратить на это много времени, поскольку моё терпение закончилось. Ведь кто же знал, что не надо доверять ни одному конструктору игр, поскольку они все безбожно пожирают системные ресурсы при попытках сделать что-нибудь приличное и без особых затрат времени? Я был довольно сильно раздражён из-за того, что приходится всё начинать с нуля...
Конечно, как мне ни хотелось быстрее идти вперёд, не уделяя много времени деталям, вышло совсем иначе. Для ясности сегодня рассказ пойдёт о несложных и немудрёных деталях. Впоследствии я добавлю записи о более хитрых задачах, которые приходится решать.
Например, сначала координаты всех объектов были целыми числами. Помимо того, что это повлекло - в случаях движения, вращения, масштабирования и отдельных эффектов - дополнительные преобразования между типами переменных (для большей точности промежуточные значения изначально были вещественными числами), картинки при этом ещё и "дёргались". К счастью, я относительно рано понял необходимость перевода к вещественному типу всех значений, используемых для изображений, и удалось благополучно вернуть плавность динамической картинки, которая была в альфа-версии (Construct Classic).
Другой проблемой стало банальное преобразование картинок в большее разрешение, что понадобилось для сглаживания изображения - ведь теперь игра легко переключается в полный экран. Мне очень хотелось сохранить стилистику изображений - в увеличенном виде по-прежнему должна быть умеренная детализация и легко узнаваемые контуры. А поскольку мои способности к рисованию невелики, решено было пойти самым простым путём. Все картинки увеличивались в редакторе изображений, и для них определялся коэффициент перемасштабирования в те размеры, которые должны быть в игре. Из-за того, что каждая анимация готовилась в виде набора-полоски кадров (т.н. spritesheet), а увеличение размеров делалось после объединения кадров, они наползали друг на друга. Пришлось вернуться назад и увеличивать каждый кадр отдельно, а затем объединять их.
По этому поводу есть и хорошие новости. Структура данных игры позволяет заменить оригинальные изображения любыми другими без какого-либо вмешательства в код игры. Необходимо только соблюдать коэффициент масштабирования (на основе оригинального) и число кадров (если это анимация). Если возникнет необходимость, я добавлю в игру возможность переключения между наборами изображений (т.н. "текстур-паками").
Третьей проблемой, которая была не то, чтобы обнаружена, а предсказана заранее - отвязка от частоты кадров. Поначалу многие динамические изменения в игре происходили у меня каждый кадр. Таким образом, на мониторе с "нестандартной" частотой кадров (а точнее, не равной 60 кадрам в секунду) ход игры исказится. Я знал это с самого начала, но при введении очередного динамического эффекта того или иного рода на первых порах забывал масштабировать его во времени.
Теперь-то независимость от частоты кадров соблюдается уже на полном автоматизме.
Четвёртая проблема, похожая на повторяющийся ночной кошмар - производительность игры. Альфа-версия (в Construct Classic) была вынужденно однопоточной и не захватывала более 25% ресурса 4-ядерного процессора (в моём случае). Собственно, это нормально для однопоточной программы, когда предел быстродействия ограничивается одним процессорным ядром. Как следствие, иногда были просто невыносимые просадки частоты кадров, и я был вынужден потратить уйму времени на оптимизацию. В результате игра стала тормозить уже приемлемо, по-прежнему отхватывая 25% большую часть времени. Большей оптимизации с упомянутым конструктором игр, пожалуй, было бы очень сложно добиться.
В бета-версии, даже зная преимущества C++, я то и дело беспокоюсь о загрузке ресурсов системы. Дело ещё и в том, что хорошую игру попросту невозможно создать без ранней оптимизации. Хотя она в таком случае больше относится к структурам данных или выбору алгоритмов, всё так же надо предсказывать заранее подводные камни и строить программный код с учётом на будущее. Иначе, столкнувшись с необходимостью оптимизации, можно обнаружить, что для этого нужно переписать много кода с нуля - уже по-другому (т.е. это недостатки общей архитектуры кода). А много ли кто возьмётся с удовольствием за это занятие?
Так вот, любые изменения, которые я делаю в бета-версии, часто подвергаются довольно тщательному обдумыванию: каким образом и где они могут "съесть" ресурсы процессора.
Хотя C++ позволил нормально задействовать процессор, запущенный стресс-тест из команд 40 на 40 смайликов по загрузке пока ещё не переваливает отметки 25% (большую часть времени - 10..15%, и то - в отладочной, более медленной версии игры), некоторые важные участки только запланированы к оптимизации, а плавность картинки и почти идеальное соответствие объектов ожидаемому поведению приятно радует, я всё равно очень осторожно добавляю каждый "лишний кусочек" в общий цикл раунда.
В итоге, мне, поневоле, обратно понравилась кропотливая работа по доведению до ума основных составляющих игры, а также различных нюансов.
[свернуть запись]
[^ следующая запись ^]
01.07.2015 > C++ и SDL против Construct Classic
(показать/скрыть)
Сегодня речь пойдёт о решении, которое мне было неприятно принимать. Начиная разработку игры, я руководствовался такими соображениями, как быстрая (без ненужной отладки) реализация необходимых концепций и взаимодействий, отсутствие большинства рутинных операций, возможность сосредоточиться на устройстве игрового процесса. Причины этому - нехватка времени, а также стремление уберечь идеи игры от погребения под ворохом программного кода, который требует написания и отладки. Вот почему изначально был выбран Construct Classic - только конструктор игр, содержащий готовые объекты, может взять на себя все возможные отвлекающие детали. Именно так и можно сосредоточиться на самой игре (ведя разработку в одиночку).
Тогда я просто не мог предположить, какие проблемы в итоге будут накапливаться в процессе работы. Конечно, отдельные проблемы удалось преодолеть, но количество "верёвочек и подпорок" начинает элементарно мешать как разработке, так и впечатлению от игры. Вот и наступил момент, когда волей-неволей пришлось направить взгляд в сторону альтернатив.
Принятое решение заключается в отказе от продолжения работы с Construct Classic, с целью переноса игры на какой-нибудь язык программирования. Конкретно был выбран C++ с использованием библиотеки SDL.
К настоящему времени появилось достаточно много причин отказаться от среды разработки Construct Classic (далее - CC). Очень существенные среди них:
- невозможность работы с Unicode и прочими расширенными кодировками
- из предыдущего следует чрезмерная сложность в установке и удалении под Windows 7 (и выше) - для игроков, не желающих выполнять дополнительные манипуляции ради установки игры или не приемлющих какой-либо нестандартный метод установки и удаления программ (а таких большинство, и я в том числе)
- ограниченная гибкость в манипулировании объектами и данными
- ограниченные возможности оптимизации производительности
- отсутствие нормальной поддержки сетевого взаимодействия
- ограниченное применение скриптовых вставок (на языке Python) и непродуманное их взаимодействие с объектами CC
Из-за этого приходится, скрепя сердце и скрипя зубами, начинать создание кода игры с нуля, используя связку C++ и SDL. Хочу отметить трудности, которые при этом сразу же возникают:
- ручное управление инициализацией и удалением любых объектов (в то время, как CC подобен C# или Java в этом отношении, и, вообще, берёт на себя заботу о большей части подобной рутины)
- отсутствие готовых концепций, поведений и анимации (в CC это были слои с управлением видимостью, поведения типа RTS с поиском пути, Turret, Bullet, Ball, XAudio2, MouseKeyboard и др.; покадровые анимации собирались очень просто)
- наглядное описание взаимодействий (как события и условия в CC) не получится. Этим можно теперь заниматься лишь на бумаге
- переработка многих алгоритмов по причине изменившихся принципов среды разработки
- стремление автора к бесконечному улучшению, которое теперь будет касаться любого алгоритма на C++
Если Вам эти пункты кажутся не сильно существенными, хочу пояснить, что они по сути обязывают к занятию рутиной, что сильно мешает творчеству. Последний же пункт касается скрытого от игрока программного кода, а не, собственно, представленной ему игры.
Так что, я думаю, начало разработки игры в CC было не таким уж и плохим решением само по себе - относительно большая часть игры теперь готова и не вызывает отвращения при прохождении.
Кроме того, наполнение игры выглядит бедным из-за малого количества готовых карт и малого разнообразия элементов, которые составляют эти карты, а эта часть работы не зависит от среды разработки. Основную роль здесь играет разработка дизайна и тестирование.
Одним словом, появляется гора дополнительной работы, означающая ещё больше затрачиваемого на разработку времени. Зато хочу надеяться, что в итоге удастся избежать всех описанных выше недостатков, присущих среде Construct Classic. А заодно, не нарваться на новые проблемы.
Напоследок могу сказать, что мне доставляет огромное удовольствие выдумывать хитрые алгоритмы и претворять их в реальность программного кода. Также очень нравится выискивать способы оптимизации того, что работает недостаточно "красиво", и "обтачивать напильником", добиваясь большего совершенства. Когда такая работа даёт положительные результаты (практически всегда), видеть их на экране монитора и слышать из динамиков акустической системы просто удивительно. Всё происходящее напоминает чистое волшебство...
Сейчас вспоминаются первые тесты, когда на экране была наспех состряпанная карта и всего два смайлика. Как только я сделал их враждебными друг другу и увидел, с каким "остервенением" они стали друг друга обстреливать, сразу же стало ясно - игра обязательно должна быть создана. Хотя бы ради того, чтобы все увидели, насколько забавно выглядит происходящее на экране. Отсюда и нарочито шутливые названия игровых классов, и "детские" голоса, и остальные детали "геймплея".
Надеюсь, теперь Вы поверите, что я могу и хочу довести работу над игрой до конца.
[свернуть запись]
[^ следующая запись ^]
03.03.2015 > Проблемы совместимости с Windows 7 и выше
(показать/скрыть)
Скоро планируется очередное обновление игры, в котором, помимо всего прочего, общие настройки перекочуют в папку profiles, а профили игроков - в папку players внутри папки profiles. Эти изменения сделаны в преддверии бета-тестирования, в котором, я почти уверен, смогу добиться корректной работы с профилями и настройками как в Windows 7, так и Windows XP - без дополнительных ручных действий после установки (назначение запуска от имени администратора).
Дело в том, что в Windows 7 и более новых версиях любые изменяемые данные необходимо размещать отдельно от рабочего каталога программы - обычно в папке "Документы" пользователя. Но среда Construct Classic не позволяет, к сожалению, оперировать путями к файлам, если в них содержатся русские (и вообще, не-ASCII) буквы.
Чтобы это преодолеть, в Windows 7, в частности, есть возможность создать символьную (или символическую) ссылку: имя ссылки будет ASCII-совместимо, но фактический путь к каталогу, на который она ссылается, может быть каким угодно. Таким образом удаётся "обмануть" игру и "подсунуть" совершенно другой каталог для файлов, которые меняются в процессе её работы (изменение настроек, обновление данных профиля игрока). Когда Вы будете устанавливать бета-версию игры, в процессе установки будет объяснено подробнее, где и зачем создаётся ссылка и каталог, а также каким образом это делается.
Сейчас же просто имейте ввиду, что после следующего обновления игра будет искать файлы профилей и настроек по новым путям. Если возникнет необходимость, чтобы игра "подхватила" имеющиеся файлы, по окончании установки обновления нужно будет переместить файл cp.cfg в каталог profiles, а файлы профилей *.sfp - в каталог profiles\players.
В заключение хочу отметить, что релизная версия игры, скорее всего, будет сделана в Construct Classic. А впоследствии, если не возникнет проблем с переходом, будет сделана версия в Construct 2. В данный момент основные причины желаемого перехода к среде Construct 2 - это однопоточность приложений и отсутствие нормальной работы с сетью (для игры между собой двух человек) в Construct Classic.
[свернуть запись]
[^ следующая запись ^]
05.08.2014 > Нюансы оптимизации
(показать/скрыть)
Поскольку альфа-версия игры стала доступна, хочется подробно рассказать про ту невидимую часть работы над движком, которая позволила выдавать нормальную частоту кадров во время жарких баталий раунда.
Возможно, это не бросается в глаза, но Вы могли заметить, что иногда NPC не видят противника. Или Часовые пропускают некоторые цели. Что касается некоторых деталей, не попавших в публичную версию игры: в первоначальном варианте то, что осталось от врага, разлеталось с учётом физики (а именно, отталкиваясь от стен), снаряды всегда попадали в цель...
Но и частота кадров (FPS) во время игры, словно маятник, моталась из крайности в крайность - от плавной картинки до почти слайд-шоу. Пришлось идти на следующие жертвы:
- размеры пуль теперь составляют половину размера NPC, а проверки на попадания в цель выполняются реже, чем было раньше при малых размерах пуль. Частота проверок снарядов уменьшена аналогично, с учётом их размера. Это дало существенное улучшение плавности картинки в 80%-90% случаев (но уменьшило точность проверок). Казалось бы, этим можно удовлетвориться, но средний FPS был ещё недостаточно высоким;
- пули сначала проверяются на столкновение с тем NPC, в которого они были выпущены, а уж потом как обычно. Не могу сказать точно, дало ли это хоть немного выигрыша;
- столкновение пули проверяется только с ближайшим NPC каждого из классов. Аналогично, вряд ли результат оказал существенное влияние на производительность;
- взамен стандартной функции проверки коллизии я полностью перешёл на проверку расстояний, благо внешний вид каждого NPC недалеко ушёл от кружочка. Небольшая неточность со снарядами (они-то явно удлинённые в большинстве своём) практически не играет роли. Вот на этом шаге явно удалось выиграть ещё 5%-10% производительности игры, да и средний FPS наконец-то достиг комфортного значения;
- вместо проверки столкновений с неподвижными объектами карты я использовал проверку координат в массиве 40х40, в который карта загружается перед раундом. Несомненно, какой-то выигрыш здесь должен быть;
- стандартная функция проверки видимости также заменена упрощённым вариантом: я просто использую тот же массив 40х40 - ведь по условиям игры только неподвижные элементы карты блокируют видимость. У Часовых используется упрощённый расчёт угла зрения. Это, к сожалению, кроме небольшого ускорения, даёт небольшие сбои в определении Часовыми видимых противников. Но в целом получилось не слишком заметно;
- выбор цели NPC производит по ближайшему из возможных четырёх представителей каждого класса. Теоретически, это даёт выигрыш до 4 раз, на практике не особо бросается в глаза. Зато NPC теперь ошибочно могут игнорировать видимого противника, так как ближайший враг данного класса оказывается ближе, находясь по другую сторону стенки (и, естественно, невидим). В целом получилось не настолько плохо, насколько можно было ожидать;
- к сожалению, пришлось отключить физику разлетающихся "останков", поскольку возникали явные просадки FPS.
Вот такой объём оптимизации пришлось невольно провести, чтобы игра, запускающаяся в одном потоке (ограничение Construct Classic), не мучила игрока "заиканием".
Возможно, кому-нибудь пригодятся эти хитрости для своих проектов. Я буду только рад.
[свернуть запись]
[^ следующая запись ^]
23.01.2014 > Игра на пути к открытому альфа-тестированию
(показать/скрыть)
Хотел "быстренько" добавить немного способностей для классов, чтобы продемонстрировать элементы второго этапа игры, но не рассчитал, что одна из них - "Дикобраз!" - потребует усилий для доводки. Делать нечего - пришлось застрять на отладке, чтобы результат выглядел соответственно названию.
Параллельно обнаружилась ошибка с Супервходом (телепортом) - если один NPC данного класса идёт к нему, то и остальные NPC этого класса делают то же самое. Вне зависимости от расстояния, которое должно быть небольшим (нет смысла идти пол-карты ради прыжка на эти же пол-карты).
К счастью, ошибка устраняется легко, что не может не радовать.
Хочу немного пояснить, зачем я обратил внимание на подобный случай: дело в том, что во многих ситуациях Construct Classic считает, что действие задаётся сразу для всех копий, созданных на основе одного объекта. Хотя условию удовлетворяет лишь одна из копий.
Приходится выяснять на практике, где происходит общее, а где индивидуальное срабатывание. Благо замена общей проверки на индивидуальную запросто решается принудительным добавлением команды For each object объекта System.
Хочу предупредить, что графическое оформление игры сейчас неудовлетворительное, однако и сильно улучшена графика вряд ли будет. Скорее всего, в релизе графика будет на уровне "хорошо". Смайликов-NPC я не планирую перерисовывать, так как они задают стиль игры; двумерные "человечки", нарисованные когда-то неизвестными авторами (от себя я добавил совсем немного), смотрятся забавно во время игры и отлично гармонируют со специально изменёнными звуками.
Спрайты, используемые для карт, на которых разворачиваются битвы, нарочно сделаны в упрощённом виде. Конечно, есть и другая причина - игре недостаёт художника, а тратить много времени на графику я не могу. Но планирую в будущем пересмотреть элементы карт с целью поменять самые неудачные варианты.
Графическое оформление интерфейса сейчас вообще на минимальном уровне и точно будет дорабатываться. Пока достаточно того, чтобы интерфейс выполнял свои функции - настройка и управление игрой.
Чтобы в релизе игра оставалась интересной, на время тестирования все способности классов ограничены. Уровневые сокращены до первого уровня, а моментальные ослаблены. Но хочу заметить, что Вы и так сможете оценить каждую способность, поскольку эффекты от них довольно заметны.
[свернуть запись]
[^ следующая запись ^]
нет даты > Начало дневника
(показать/скрыть)
Это первая запись, и она объединяет все предшествующие события и мысли. Поэтому конкретной даты не будет.
Для начала, расскажу об основной проблеме: непреодолимая пока что стенка тупости алгоритма, управляющего командой противника. Соотношение побед игрока составляет примерно 90% всех игр (вместо ожидаемого 50:50) - и это на высшей сложности! А в идеале, таким должно быть количество побед компьютера.
У меня есть несколько мыслей: ещё раз пересмотреть код подбора команды и установки тактик, улучшить поведение некоторых NPC (например, Мелкопакостник выполняет свои обязанности не более, чем удовлеторительно), проанализировать настройки, которые я выставил для своей команды. Как-будто намечается ещё одна идея по улучшению алгоритму ИИ.
Всё же, это очень неприятный фактор, который несколько замедляет разработку. Всякий раз, при очередном запуске раунда, я невольно расстраиваюсь и думаю, что же ещё можно поменять.
Обновлено: уже до момента размещения первой записи наконец-то удалось расправиться с описаной выше проблемой, хотя ещё нельзя утверждать, что задача решена. Так что вышележащий текст на всякий случай оставлен без изменений.
Всё остальное сейчас в более-менее активной стадии. Почти все интерфейсы нарисованы - не идеально, конечно. Возможно, ближе к релизу, графика игры будет приведена к лучшему виду. Но я не художник и обещать ничего не могу, к сожалению.
Рассмотрим подробнее схему запуска игры: есть основной exe-файл - главное меню игры, в котором игрок выбирает настройки и управляет профилями, переходит к игре и улучшениям способностей; есть отдельный exe-файл, в котором загружается выбранная карта и проходит раунд игры; есть exe-файлы, которые обрабатывают интерфейсы открытия тактических слотов и улучшений.
Такая система выбрана из-за особенностей среды Construct Classic, в которой создаётся игра. С другой стороны, это немного упрощает мне работу. Да и Вам понадобится запускать только основной exe-файл, все остальные вызываются из него при необходимости (автоматически).
Теперь о самом приятном: львиная доля игрового движка оптимизирована под максимальное быстродействие. Пришлось немного пожертвовать (точностью проверок, в основном) ради скорости, но зато просадки частоты кадров в секунду (FPS) сведены к минимуму.
Особенно рад я тому, что удалось придумать так называемый endgame-content (содержимое конечной стадии игры). К тому же, он легко реализуем и в теории (пока что) смотрится интересно. Но не хочу раскрывать его до поры до времени: только в релизной версии можно будет посмотреть, что же там такое.
Есть хорошие идеи новых карт, которые не кажутся мне проблемными в реализации. Постараюсь не показывать новые карты в действии, но возможно, будут "скриншоты".
Пару слов о редакторе карт: будет доступен спустя некоторое время после релиза игры. Сейчас в нём очень не хватает функций проверки карты на симметрию, отмены действий, а также нормального сохранения. Последнее - также из-за ограничений среды Construct Classic.
Много времени отнимает создание всяких "презентационных" видеороликов для игры. Но после начала открытого тестирования уже не планирую создавать ничего особенного. Разве что к релизу будет небольшая демонстрация.
И, напоследок, хочу отметить возможный перенос разработки в среду Construct 2. Это даст возможность адаптировать игру к другим платформам (кроме PC).
В настоящий момент существенное препятствие - стоимость полнофункциональной версии, которая меня, как автора бесплатной игры, сами понимаете, отнюдь не радует. А ещё я опасаюсь потенциальных проблем с обратной совместимостью кода.
[свернуть запись]
[^ следующая запись ^]
|