Telegram Web
Подробности про командную строку
#код
Запилил на днях по работе командную строку с проверкой синтаксиса и автодополнением и похвастался этим в чатике. Народ проявил интерес и поэтому рассказываю подробнее.

Нужна эта штука для двух вещей. Во-первых, сценаристы, собирающие диалоги в редакторе, должны иметь возможность проверять доступность вариантов ответа (например, реплика возможна только при заданном уровне интеллекта или после выполнения квеста), а также вызывать скрипты при ответах. Во-вторых, для разработки нужна классическая игровая консоль для ввода читов и отладки. Чтобы разом покрыть оба этих юзкейса нужно написать нехитрый интерпретатор строки. Разумеется, он не будет поддерживать все фишки C#, а только некоторый базовый сабсет, вроде функций, операторов и скобочек.

Теперь о реализации. Несмотря на кажущуюся сложность задачи, имплементация у меня до боли простая и занимает чуть более тысячи строк. Первый элемент системы — это, конечно, лексический анализатор. Который у меня в коде какого-то фига называется Parser (надо не забыть переименовать в Lexer, а то чё я, как наркоман). Лексер представляет собой одну единственную функцию-генератор, которая в цикле читает символы строки и возвращает по одной токены лексем, которыми могут быть:
Value (строки, числа, true и false),
Identifier,
Dot,
Comma,
OpenBracket,
ClosedBracket,
Operator,
AssignmentOperator,
EndOfLine

Между лексемами могут быть пробелы, они игнорируются. Помимо типа, токен также хранит начало и длину фрагмента внутри входной строки (чтобы подсветить красным место, в котором произошла ошибка) и поле типа object для дополнительной информации: для value это считанное значение, для оператора — информация о том, какой конкретно оператор и т.п. Единственный нюанс, при чтении знака минус, нужно взглянуть на то, какой токен был перед этим: если значение, идентификатор или закрывающая скобка, то это оператор минус, в противном случае начало отрицательного числа.

Едем дальше. Непосредственно сам интерпретатор совмещённый с валидатором. Он у меня однопроходный, то есть я читаю очередь лексем слева направо и сразу же выполняю. Командная строка может выполнять только выражения. Выражение — это один или несколько операндов, разделённых операторами. Например,
a + b * c + d

или просто
a


Когда мы дошли до конца выражения (конец строки, запятая или закрывающая скобка), мы выполняем операторы в порядке их приоритета («схлопываем» по два операнда, пока не останется только один).

Операндом может выступать как значение, так и другое выражение в скобках. Поэтому если наткнёмся на открывающуюся скобку, то просто запускаем процесс парсинга вложенного выражения рекурсивно. И также операндом может выступать цепочка идентификаторов, типа такой:
Foo.bar.foo(a + b, c).foo.bar


В данном случае мы тоже выполняем всё последовательно. Сначала ищем объект среди глобальных. У меня разрешены только классы скриптов (считай, синглтоны) и enum’ы. Затем на каждый доступ через точку достаём через рефлекшн соответствующий member класса, а при вызове функции запускаем сперва вложенный парсинг выражений-аргументов через запятую.

Собственно, всё. Ну ещё есть оператор присваивания, который умеет вызывать сеттер для поля или свойства, но теперь точно всё.

Валидация происходит точно также, как и выполнение, только в «холостом» режиме: вместо честных вызовов функций и операторов, мы возвращаем объект-заглушку, которая знает какого типа должен быть результат. Этой информации достаточно, чтобы проверить весь синтаксис.

Автодополнение же сделано запуском валидации строки, в которую в определённом месте вставлен символ многоточие. При разборе
GlobalVars.Pl…

лексер вернёт не идентификатор «Pl», а специальную лексему типа Autocomplete «Pl...». Ну а синтаксический анализатор, если наткнётся на эту лексему там, где предполагается идентификатор, посмотрит, какие вообще есть варианты, и если что-то подходит, то бросит AutoCompleteException, содержащий остаток строки. Заменой выделенного текста на автодополненный вариант занимается уже гуишный контрол наверху.
Курсы геймдизайна
#реклама
Знаете, чем занятия с живым человеком лучше самостоятельных? Помогает соблюдать регулярность и систематичность. Я вот перед новым годом писал, что занимаюсь английским с преподователем через вэбку и также собираюсь изучать темы по списку ключевых слов Андрея Аксёнова. И знаете что? По английскому у меня есть заметный прогресс, а ключевые слова я всё ещё собираюсь начать изучать.

И поэтому я не устану рекламировать курсы по геймдизайну от Skillfactory. Это отличный вариант для тех, кто всё собирается вкатиться в отрасль, но либо руки не доходят усесться плотно, либо не хватает систематизации. На этих курсах лид дизайнер финской BON Games объяснит и поможет вам пройти через все этапы геймдизайна: от разработки игровой мехаики до монетизации и планирования. Регулярные занятия каждую неделю в сочетании с активным участием класса и фидбеком от преподователя не дадут сбиться с ритма на протяжении всех 4 месяцев. А стартует всё это уже 11 февраля, так что записывайся сейчас: https://goo.gl/FGC6wS
SOLIDные рассуждения
#код
Месяц назад я твитнул, что, дескать, как только слышу от соискателя или работодателя в геймдеве что-нибудь про SOLID, так сразу автоматически помечаю их в своей голове как нубов, либо поехавших. С тех пор мне пришлось пару раз подискутировать на эту тему в разных уголках интернета, и я решил, что стоит уже это оформить в отдельный пост.

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

Поехавшие, в свою очередь, делятся, на две основные категории. Первые — это люди, пришедшие из энтерпрайза. Я никогда в энтерпрайзе не работал, но похоже, что там написание абстрактного кода эволюционно более привлекательная стратегия. Я ничего против этого не имею, но когда эти люди приходят в геймдев, с ними бывает тяжело сработаться, так как это зачастую сплошное ООП головного мозга. Там, где типичный программист из кровавого энтерпрайза в рубашке с длинными рукавами заведёт десять интерфейсов и фабрику фабрик; типичный геймдевелопер в перепачканной пиццой футболке вооружится принципами KISS и YAGNI и удалит это всё нахрен, написав взамен простой и понятный класс, решающий конкретную задачу. Пусть и наплевав при этом на принцип открытости/закрытости, разделения интерфейсов и другие святая святых ООП. То есть, конечно, наверняка где-то в природе существуют игры, написанные полностью по канонам SOLID. Но, во-первых, я такого не встречал, а во-вторых, боюсь представить, что там за монструозная кодобаза, и не считаю это нормальным.

Вторая важная категория «поехавших», которую хотелось бы разобрать — это люди, утверждающие, что применяют принципы SOLID, но на самом деле этого не делающие. Их аргументация обычно сводится к тому, что солид следует применять не по всему проекту, а лишь в тех местах, где он подходит. Но тут налицо непонимание разницы между паттерном и принципом. Солид это не паттерн, а принцип. Ты либо стараешься следовать ему везде, либо это уже не солид.

Поясню. Вот есть DRY. Очень простой принцип о том, что надо стараться избегать копипаста. Конечно, где-то в коде могут быть отступления от этого правила, но если человек исповедует dry, то в какой-то момент он может посмотреть на некий участок кода, решить, что он недостаточно dry, и переписать его менее влажно. Не потому, что этот участок кода работал плохо, а только лишь потому, что он не соответствует принципам, по которым мы решили писать код. И это действительно происходит в реальных проектах. То же касается KISS и YAGNI. Но не SOLID. Не бывает такого, что чувак в геймдеве смотрит на код, и решает, что дай-ка я его перепишу, потому что он какой-то недостаточно солидный. Ну не бывает такого на практике. А если ты так не делаешь, значит, ты и не следуешь этому принципу. У тебя просто в случайных местах код написан в соответствии с его правилами, но ты ими на самом деле не руководствовался.

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

Обсудить
Исходники командной строки
#код
Удивительное дело, но моего недавнего поста про командную строку в нашем проекте оказалось недостаточно, чтобы утолить к ней интерес. Народ продолжил требовать запилить ассет. С ассет стором я, конечно же, возиться не стал, но код, так уж и быть, заопенсорсил.

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

Обсудить
Паровозик вакансий
#лайт
А мы расширяемся и у нас целая пачка открытых вакансий:
C#-программист (Middle)
3D Artist (Lead)
Quest Designer (Junior)
QA (Junior)

Напомню, что мы делаем Encased (RPG в духе первых FallOut) и расположены в Санкт-Петербурге. Все вакансии предполагают работу в офисе. Удалёнка в данном случае не проканает.

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

А вот по поводу C# мидла хотелось бы рассказать подробнее. Работать предстоит непосредственно под моим руководством, фигачить геймплей и тулзы для дизайнеров всех мастей. В основном в виде расширений для нашей модульной системы. Сейчас в команде три программиста (включая меня), ищем четвёртого. Рутинную часть кода пишем не мы, а дизайнеры-скриптеры. В задачи программистов входит написание удобного API для них. Иногда с кодогенерацией. В качестве VCS у нас Plastic (считайте, что это оказуаленный git). Юнит-тестов нет, билд-машина есть. Кодстайл можете глянуть в предыдущем посте.

Резюмехи присылайте на [email protected]. Желательно сразу с примерами кода. Организационные вопросы можно обсудить заранее, и если всё ок, то перспективным кандидатам мы предложим небольшое тестовое. Без тестового на эту позицию, скорее всего, не получится.

Обсудить
Неотчёт о еврейском митапе
#лайт
Тусил я не так давно несколько недель в Израиле/Палестине, ну и забежал по случаю на местный митап-конфу в Тель Авиве. Он был посвящён прошедшему Global Game Jam, где начинающее бесполезное инди показывало свои конкурсные игры на проекторе и выбирало победителя. Ну а заодно в холле чуть более осмысленное инди устраивало шоукейс своих коммерческих проектов.

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

Несмотря на то, что это был мой первый митап за пределами постсоветского пространства, новая мысль у меня ровно одна: инди-геймдев везде одинаковый. Вот собранный из ассетов упоротый проект, совмещающий два несочетаемых жанра, и непонятно как финансируемый. Вот чуваки, делающие богомерзкие социалки с интерфейсом в стиле Angry Birds. А вот и хардкорный платформер с пикселями разного размера. Всё то же самое. Каждый типаж до боли знакомый и ты заранее знаешь, что он тебе скажет. Иногда даже прямо на русском. Израиль всё-таки.

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

А, ну ещё до этого был в Сенегале — там вообще нихрена нет.

Обсудить
Курсы как строчка в портфолио
#реклама
Последняя остановка в моей заграничной зимовке была в Южной Корее, а по пути назад я залетел в город, в котором вырос — во Владивосток. Собрались там с друзьями и коллегами с моей первой работы поболтать в ресторане, ну и зашла речь о том, как же в 2019 году устроиться геймдизайнером к этим самым бывшим коллегам. Мол, человек даже курсы проходил по геймдизайну, о чём имеет сертификат, а устроиться не может, так как вакансий открытых на геймдизайнера нет. И прозвучала фраза о том, что аргумент «я проходил какие-то курсы по геймдизайну» уже достаточно сильный, чтобы заинтересоваться как минимум поговорить. И что с таким аргументом стоит писать, даже если открытых вакансий сейчас нет. А там уже тестовое задание и дальше как пойдёт. А вы спрашиваете, зачем нужны курсы геймдизайна?

Вот, например, курсы от Skillfactory, на которых вы:
- Напишете сценарий и разработаете механику своей игры;
- сделаете прототип интерфейса и продумаете персонажей;
- посчитаете экономику и спланируете продвижение;
- разработаете дизайн-документацию.

Получите программу курса → http://bit.ly/2NCxsuU

Обсудить
GoTo the Dark Side
#код
Сегодня чатик что-то беснуется по поводу использования goto. Орден джедаев не позволяет использовать этот оператор в личных целях, а я же призываю вас перейти на Тёмную сторону Силы.

Но обо всём по порядку. Мой преподаватель программирования на первом курсе университета говорила, что goto в рамках учебного курса применять нельзя, и что она не будет принимать лабораторные работы с ним. И это очень правильно: личинкам кодера нельзя давать в руки такой инструмент, иначе они обмажут им все стены. Чтобы стать хорошим программистом всё-таки нужно сперва научиться писать код без goto.

Проблема начинается в тот момент, когда забывают добавить волшебную фразу «в рамках учебного курса». Людям преподносят goto как абсолютное зло, которое нельзя допускать ни в коем случае. Это превращается в примитивную пропаганду и, судя по количеству приверженцев идеи, так происходит достаточно часто. Да простят меня читатели за то, что я сейчас сворую блок мыслей у Андрея Коняева, но всё дело в том, что пропаганда, какие хорошие практики она бы не пыталась прививать, всё равно остаётся пропагандой. И ничего путного из этого не получится. Пропаганда даёт человеку позицию, но не объясняет её; и на выходе мы получаем кучу людей, которые не способны ответить на вопрос, почему они делают то, что делают. Максимум, что мы услышим, это лозунги вида «goto — это зло», «goto ухудшает читабельность!». Любая попытка разобраться, а действительно ли пострадала, например, читабельность исходников lua из-за того, что там обработка ошибок происходит через goto, будет встречена лишь непониманием и хейтом: «А разве не очевидно? Тут же стоит goto. А это худшая практика из всех. Или ты чё, защитник goto?»

Вам может сейчас показаться, что я утрирую, а на самом деле ненависть к goto обоснована и никакого фанатизма нет, но давайте проанализируем. Вы без труда можете представить человека (а может сами им являетесь), который ненавидит всей душой goto, но при этом любит и обильно использует макросы. Но если вдуматься, негативные эффекты от злоупотребления этими вещами крайне схожи. В обоих случаях от одного использования не случится никакой катастрофы, и даже более того, этим можно сильно облегчить себе жизнь и повысить читабельность в конкретном месте. В обоих случаях, если не локализовать применение, можно нарваться на опасные сайд-эффекты. И в обоих случаях, если пихать повсеместно, проект очень быстро превращается в неуправляемую кашу. Но в первом случае человек борется до последнего против появления в проекте «вредного сорняка», а в другом смотрит сквозь пальцы: не, ну а чё такого? Макросы же не включены в расстрельный список — значит, можно обмазываться.

Переходите на Тёмную сторону Силы и начинайте использовать не хорошие или плохоие практики, а здравый смысл.

Обсудить
Время и фантастическая тусовочка
#лайт
Несколько недель я ничего не писал сюда и на то у меня целых две причины.

Во-первых, ничего и не происходит. Как это ни прискорбно признавать, но мои личные проекты — главный по задумке источник постов в блоге — не развиваются от слова совсем. Все мои многочисленные попытки реанимировать работу над ними раз за разом проваливаются. Я связываю это с удалённой работой. Как перестал ходить в офис два года назад, так проблемы с пет-прожектами меня и не покидают.
Дело в том, что когда ты ходишь на работу по расписанию, то у тебя есть некоторое количество свободного времени, из которого ты выделяешь какую-то часть под личные проекты. Ты, конечно, при этом много прокрастинируешь, ленишься и просто неэффективно управляешь временем, но хоть сколько-то часов, да перепадает на пет-прожекты.
А когда ты работаешь дома с гибким графиком, то у тебя всё время как бы свободное, из которого ты уже самостоятельно выделяешь часть на работу. Разумеется, прокрастинация и плохой таймменеджмент никуда не деваются, а потому работаешь если не меньше положенного времени, то по крайней мере впритык точно. И это с учётом ночей и выходных, когда порой нагоняешь отставание. На личные проекты, сами понимаете, в таких условиях время выделять уже просто неоткуда.
Поэтому, боюсь, пока я работаю над Encased, ситуация не поменяется.

Вторая причина долгого молчания заключается в том, что последнее время, так совпало, я постоянно тратил все буквы на что-то другое: то документацию писал по проекту, то свои рассказы, то отзывы на чужие.
Если с документацией всё понятно, то про рассказы у меня есть пару слов. Вот уже год с небольшим, как я снова стал писать. Ну, то есть как писать? Регулярно ходить на литературные конкурсы. Принцип тот же, что на Ludum Dare, только не игры, а рассказы: задаётся тема, три дня все пишут, потом читают друг друга и голосуют (до объявления результатов все рассказы анонимные).
Так вот. Кто бы вы думали, ходит на такие контесты? Ну, во-первых, писатели профессиональны — это понятно. А кто ещё? К моему большому удивлению, я обнаружил, что каждый второй (если не больше) участник-любитель на таких конкурсах — это либо человек из геймдева (геймдизайнер, сценарист, игрожур), либо айтишник. В моём случае, так и то и другое.
Мне кажется, это довольно интересное наблюдение, поскольку как-то принято считать, что программисты люди не творческие (по крайней мере в гуманитарном смысле), а на самом деле только они в основном и пишут. Ну или айтишники просто слишком хорошо живут, так что у них есть возможность заниматься чем-то, кроме работы.

Обсудить
Силуэтики и обводочки
#кодище
Ох и затяжные каникулы сами собой получились на канале! Что ж, давайте попробуем начать что-то вроде второго сезона. Я даже второй гуглдоковский файл завёл по этому поводу.

И начнём мы с такой привычной всем штуки, как силуэты. Способов их делать существует бесчисленное множество. Мы, как водится, запилили нечто своё. Чем-то похожее на то, как везде; а чем-то, возможно, и необычное. Не претендуем на супер новизну или крутость техники, но, тем не менее, кому-нибудь может быть интересно.

Прежде всего надо отметить, что у нас в понятие силуэт входит сразу два цвета: заливка объекта (на скриншоте белый) и обводка (жёлтый). Обводка показывается всегда, а заливка только для тех областей, которые перекрыты какими-то другими объектами. В зависимости от игровой ситуации, геймплейный код помечает объекты, которые нам нужно обвести: дескать, вот этого врага надо залить розовым с красной обводкой, игрока салатовым с зелёным, а вон тот стул — только обвести белым, но не заливать. При этом все объекты могут, само собой, как угодно друг с другом пересекаться.

Мы решаем это так. Сперва берём все комбинации обводка + заливка и загоняем в StructuredBuffer, чтобы обозначать комбинацию одним байтом по индексу. Не более 256 разных вариантов в кадре получается. Нам этого не то, что на один кадр, нам этого на всю игру хватает, так что буфер меняться в процессе не будет.

Затем берём полноэкранный буфер и начинаем рисовать в него все подсвеченные объекты по следующей схеме:
// R — Always 1 (indicate that silhouette exists)
// G — Index of silhouette colors
// B — Opacity if hidden
// A — Is pixel visible? (1 — visible, 0 — hidden by other objects)


Причём достигаем мы этого аж двумя пассами. Для первого прохода выставлена опция ZTest == Equal, то есть он записывает только те пиксели, что не перекрыты другими объектами. По схеме выше выглядит это примерно так (_SilhouetteIndex — это глобальный uniform для текущего draw):
return float4(1, (float)_SilhouetteIndex / 255, 0, 1);


Второй же пасс пишет с опцией ZTest == Greater, то есть, напротив, только те пиксели, которые оказались под чем-то. R и G составляющие выглядят точно также, а для B мы вычисляем значение opacity силуэта, которое зависит от того, насколько глубоко объект перекрыт другой геометрией (сравниваем с текущим значением ZBuffer). Это нужно для того, чтобы маскировать мелкие косяки анимации, когда меш слегка погружается в пол при ходьбе по лестницам и подобным неровным местам.

А вот в альфа-канал на втором пассе мы вообще ничего не пишем! Если кто-то записал 1 в альфу, то она должна там остаться навсегда. Этот трюк необходим как раз для правильной обработки перекрытий силуэтов друг друга. Конечно, цвет испортится, но если силуэт ничем не перекрыт, то нам он и не нужен. Впрочем, при очень большом желании мы могли бы и его сохранить через блендинг вида OneMinusSrcAlpha SrcAlpha.

Но мы отвлеклись. Полноэкранный буфер с мета-инфой о силуэтах это, конечно, прекрасно, но как нарисовать силуэты? А с помощью того шейдера, что показан на скриншоте (разумеется, рисуем не полноэкранно, а только небольшие квады в тех местах, где у нас есть силуэты).

Как видно по коду, там где мы отрисовали силуэт, мы просто достаём цвет заливки и покрываем им скрытые части объекта (видимые не трогаем). А для тех мест, где силуэт не отрисован, мы делаем однопиксельное смещение во все стороны. И если находим хотя бы один пиксель силуэта, то считает это место местом обводки. Цвет обводки берётся при этом как среднее из всех найденных силуэтов вокруг этого пикселя.

Вот примерно такие дела. Надеюсь, не слишком запутанно вышло.

Обсудить
2025/07/10 18:45:20
Back to Top
HTML Embed Code: