Как стать автором
Обновить

Комментарии 71

Знаете, я вас заранее плюсану и похвалю, а потом продолжу читать. Объём статьи впечатляет — при том, что состоит она не из воды. Жаль, что такие стали редкостью на хабре. Впрочем, и неудивительно: у среднестатистического читателя на неё не хватит терпения.
Спасибо за теплые слова. Знаете, слегка нервничал, не заплюют ли люди — букв поди многовато. Пока, вроде, обошлось.
Присоединяюсь однозначно в закладки, для последующего ознакомления. =)
НЛО прилетело и опубликовало эту надпись здесь
Вы совершенно правы — разрабатывать серьезный продукт на собственных велосипедах долго и бессмысленно. Однако для начала неплохо бы иметь представление о том, чем эти библиотеки в своем «нутре» занимаются. Например, я когда-то писал изометрическую игру (:-)) — и она работала жутко медленно по причине плохой оптимизации. Зато удовольствие получил просто непередаваемое. Да и теорию запомнил на всю жизнь — теперь ни один изометрический движок меня не смутит.
К сожалению, объём и качество документации библиотек зачастую таковы, что проще и быстрее оказывается написать свой велосипед, чем «въехать» в уже написанный.
В мире Java всё ещё более-менее. А вот те, кто пробует сделать кроссплатформенную игру скажем… ну, пусть будет на Rust при помощи Piston, очень быстро приходит к выводу о написании своего собственного Piston'а.
Потому я и посоветовал поначалу с этим не заморачиваться, а писать конкретно игру. При грамотном подходе нужные библиотеки можно прикрутить позже — и ничего не сломается.
Как ни крути, а без особых и очень специфических талантов можно нарисовать только плохой пиксель-арт, плохую векторную графику, плохую ASCII-графику. Последнее вообще очень узкая дисциплина, есть реальные примеры, когда топовый пиксель-художник не осилил достаточно простой ASCII. Так что это скорее варианты, к чему больше лежит душа, вдруг о чём-то не подумал, а оно — вот оно, и захочется прокачать в этом скилл.
Согласен. Зато без особо специфических талантов можно написать хорошую интересную игру (особенно если вы имеете опыт разработки неигровых приложений). О чем и статья.
Мне кажется, что если я не считаю себя художником и всё равно пытаюсь рисовать, пусть даже в ASCII, то я автоматом становлюсь художником, пусть даже очень плохим. Так почему же не прокачать немного скилл?) Не поймите неправильно, претензий к арту нет)
Ну и наркомания!
«Русского нет!.. Нет русского!.. Добавьте русский язык!.. Разрабы псы!»

Статья очень понравилась, большое спасибо:)
Как ни зайду в Steam или Google Play — везде одно и то же. Суровость русскоязычных комментариев компенсируется лишь полным отсутствием реакции со стороны разработчиков.
Крайняя суровость это да, но меня лично и другая крайность — крайний оптимизм раздражает не меньше. Когда на откровенно никчемную работу оставляются приторные отзывы.
Вы же прекрасно знаете, как это делается ;-)
Натура «любезная» — это вообще жесть.
При самом первом создании персонажа игрок задает, какой тип натуры у него будет — злой, нейтрал, добрый. В зависимости от натуры персонажу во время прокачки можно убивать только мобов противоположной натуры, чтобы не разгневать бога-покровителя, и не стать ренегатом (и не лишиться магических способностей и умений). Если персонаж — нейтрал, то ему надо убивать и добрых и злых мобов в равной пропорции.
Понятия эти достаточно расплывчаты, но для упрощения принимается такой принцип — добрые должны бить злых и наоборот. Бьешь злого — становишься добрее, бьешь доброго — становишься злым. Конечно, имеет смысл и относительная «доброта». Добропорядочность (характер) измеряется в очках от «минус» 1000 до «плюс» 1000.

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

Что это за великолепие ?

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

плохо знаю java и вообще не знаю kotlin

Пусть такая мелочь, как незнание какой-то конкретной технологии, не становится препятствием на пути к вашей мечте (ну или не конкретно вашей — я ко всем обращаюсь). Замените java на basic, а kotlin на malborge — суть от этого не поменяется.

Выбирайте те инструменты, с которыми вам удобнее.

Мой комментарий был к тому, что не зная ни Java не Kotlin и не собираясь в них углубляться в ближайшм будущем из статьи можно надёргать всякого полезного.
Кстати, если кто "внезапно" изберёт целевой платформой именно malbolge то с его или её мечтой мы скорее всего не познакомимся никогда.
Более мейнстримовые brainfuck или олдскульный intercal дадут хоть какой-то шанс.

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

Что до статьи, то она нацелена больше на людей, которые имеют опыт разработки неигровых приложений, знают про модные методики (и UML им не чужд), имеют мечту, но не решаются воплощать ее в жизнь.
Пример теста в статье ужасен. Такой лютый копипаст — скорее указание как не надо делать. Осмелюсь сказать, что в тестах это даже хуже, чем в коде, т.к. код мы сможем тестом проверить и найти перевставленную опечатку, а вот в тесте — нет.
Увы, я не эксперт в написании тестов, потому спорить не буду. Тесты действительно требуют недюжинного внимания, скрупулезности и маниакальности. Ну, или можно писать тесты для тестов…

убежал патентовать идею...

Конкретно по примеру из статьи: даже не знаю, как тут можно улучшить. В руке у нас 10 активных позиций, каждая может быть или выбрана, или нет, что дает 1024 возможных варианта. В тесте рассмотрены только некоторые (как казалось, наиболее характерные). В защиту скажу, что тест очень помог при реализации метода (а процесс реализации помог найти ошибки в тесте — возможно, это обоюдный процесс).

Ну или я лось, и всё можно было сделать гораздо проще — чем не вариант?

Если писать тесты "правильно", то они будут фактически повторять реализацию проверяемой функции и сравнивать две эти реализации.


На мой взгляд такой подход вреден, т.к:


1) необходимо выполнить двойной объём работы,
2) если ошибка кроется в логике или неправильном понимании требований, обе реализации будут содержать одну и ту же ошибку.


А тесты в виде набора правил входные данные -> выходные данные:


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


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

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

Вот конкретное требование из примера в статье:
нужно взять из руки три кубика: один синий или зеленый, второй фиолетовый или желтый, третий синий или белый

Как проверить, правильно ли написано правило для такого рода требований? Кроме предложенного «обезьяньего» варианта брать кубики по очереди и говорить «окей»/«не окей» я ничего путного придумать не могу.

А вы можете?

Так я с вами и не спорю, у вас тесты правильно написаны :)

Подскажите пожалуйста, что вы использовали для рисования диаграмм и блок-схем?
Сервис draw.io. Еще у Visual Paradigm есть хороший визуализатор, но в бесплатной версии он ограничен (хотя UML там есть — возможно, если бы я наткнулся на него раньше, то использовал бы его).

Ну и ближе к теме статьи, хе-хе.
Треть (вашей) статьи (располагается) в скобках (но не уверен (может и половина)).
Мне кажется, что использовать готовый движок всё же более оптимальный подход. На базовое освоение Unity или Unreal Engine достаточно два дня. После этого можно начинать делать игру и изучать тонкости этих движков уже в процессе разработки. Скорость разработки получится точно не меньше. Зато в плюсе уже готовые библиотеки заточенные для игр, редактор уровней, магазин ассетов и поддержка всех мыслимых платформ.

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

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

Готовые движки как раз тем и хороши, что не нужно тратить время и силы на реализацию фичей для реализации идей. Есть идея — тут же её реализовал и оценил как она в игре.
Готовый движок, это инструмент, требующий кучу времени для освоения, то есть прежде чем приступить к очередному клону марио, будешь сидеть и постигать особенности его работы, при том что 80% функционала тебе никогда не потребуется. Статья же про то, как взять, сесть, и написать что-то готовое.
Я может, конечно, тупой, но сколько не брался за UE, каждый раз бросал, суммарно провел месяц наверно. Там такое количество абстракций, что при реализации простейшей логики начинает крыша ехать, и в итоге ты ковыряешь UE, а не игру.
Вы пробовали Blueprints? С ними, говорят, проще.
Пробовал, и на плюсах пробовал, на них кстати понятнее, наверно потому что еще под ue2 мутаторы делал в свое время, в конечном итоге понимаешь, что ковыряешься в специфике движка, акторов и особенностях выполнения кода. Да и сам редактор нет-нет да и крешнется, а еще он очень тяжелый для неигрового пека, в отличие от той же идеи например.
А можно пример, который вызвал затруднения?
Сейчас уж не вспомню, но в районе 4.12 была проблема с динамическим созданием статик-мешей, в 4.19 правда то ли умнее стал, то ли упростили как-то, вроде получилось в итоге. Еще надо было разместить кучу объектов на уровне, каждый объект — актор, логика актора выполняется асинхронно и двигает актор, каждый актор тикает по своему, а мне за один тик надо взять, проверить координату и пересчитать ВСЕ акторы, я если честно не помню уже победил эту проблему или нет, но 2-3 дня на курение мануалов и закостыливание логики (с осознаванием работы акторов) потратил точно, может подход неверный избрал, но со сдвиганием акторов в глобальном event loop тоже не срослось, не помню почему уже. При том, что на обычном языке я бы просто вкрячил for в event loop и перебрал бы все акторы и проблема была бы решена, ибо в моем случае логика того что акторы сам по себе тикающий объект мне вообще ни к чему, хотя там был какой то ключик вроде bCanTick, и вот сидишь в итоге и читаешь как работает движок вместо того чтобы логику писать, начитался, выключил UE, пошел гулять :), потому что одному — тяжело. Пробовал подцепиться на готовые проекты, чтобы плавно въезжать, но чет не срослось.
просто надо изучить популярные паттерны в геймдеве.
Их акторсистем, это та же самая ECS с ерархией.
Советую взять более лайтовый движок, чисто для изучения популярных архитектур. Освоив идею граф-сцены и компонентов, переходить на такие монстры как UE4 и Unity становиться на уровень легче. Но для UE4 лучше еще изучить паттерн Акторов, который он так же использует.

Но честно скажу, в Ue4 для 2D сильно перегруженно. У UE4 2D, это как у cocos2d-x 3D.
У первого 2D не удобен для индти. У второго функционал очень скуден и документация страдает для 3D.
Я терпел, как мог, но всё ж скажу:

— Статья не про игровые движки!

Может хватит уже этих комментариев?
Немного смутило то что описание игры на английском после «Гребанных» скриншотов :) А сама игра готова? Можно в нее поиграть, с меня фидбек
Знаете, с 2003 года прошло много времени, теперь мне некоторые мысли гораздо проще выражать на английском языке (хотя и русский перевод в игре частично присутствует).

Игра не готова, она в процессе (замечания и предложения весьма приветствуются). Полезные ссылки есть в конце статьи в одноименном разделе (такая большая надпись ССЫЛКА!).
На macOs не запускается( Выходит окно с настройками, язык, музыка, что-то еще, жму на Launch и ничего не происходит. Ладно, потом на винде попробую запустить еще
Ну дык, если вы читали readme.txt, я об этом предупреждал. В качестве временного решения переключитесь на вкладку «Advanced» и нажмите кнопку «Generate script». Сохраните полученный sh-файл где-нибудь и запускайте из терминала — должно работать.

К сожалению, у меня нет мака, чтобы опробовать на нем разные решения. Открыть из командной строки терминал и передать в него команду на выполнение почему-то получилось только через osascript:

osascript -e "tell application \"Terminal\" to do script \"<COMMAND>\""

Но из кода java-приложения запустить эту команду не выходит (видимо, из-за двойных кавычек и необходимости их экранировать).

Если кто-то может оказать помощь в этом вопросе, буду несказанно благодарен.

Да, что-то я не очень внимательный в последнее время, все куда-то спешу :)


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


-bash: syntax error near unexpected token `newline'
Сложно что-то сказать, не видя код, который вам сгенерировало приложение, но две фразы в вашем комментарии вызывают вопросы:

ввожу строчку в терминал
Какую конкретно? Вот эту?: /bin/bash dice.sh

Оно открывает новый терминал
Не должно. Приложение должно запускаться в текущей сессии терминала.

Пожалуйста, пришлите скрипт.

Я не программист, поэтому могу показаться несколько туповатым в этих вопросах =)


Вот эту строчку


osascript -e "tell application \"Terminal\" to do script \"<COMMAND>\""

Вот сам сгененированный скрипт


#!/bin/sh
/Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java -Dfile.encoding=UTF-8 -Duser.language=ru -cp "/Users/dmitry/Desktop/all/temp/DiceStories/engine/dice-1.0.jar:/Users/myname/Desktop/all/temp/DiceStories/stories/*" org.alexsem.dice.MainKt "/Users/dmitry/Desktop/all/temp/DiceStories/saves"
А, ну понятно. Эту-то строчку как раз выполнять не нужно — она не к вашей проблеме относилась.

Если вы сохранили скрипт, например, себе на рабочий стол (с именем dice.sh), то в терминале нужно написать следующее:

/Users/dmitry/Desktop/dice.sh
Написал так, ничего не произошло. Потом просто взял скопировал все содержимое файла в термин, и вуаля запустилось =))

Чуть-чуть поиграл, ничего не понял, тут вникать нужно, знать правила настольных игр и т.д. Напоминает старые игры в которые нужно еще разобраться как играть. А я уже слишком привык к казуалкам, где думать и вникать практически не нужно.

Спасибо за статью!


Я тайный фанат Kotlin поэтому не рассматривайте как критику мое наблюдение, но не понятнее ли будет как во втором варианте?


fun draw(): Die = dice.pollFirst()

fun draw(): Die
  = dice.pollFirst()

Или синтаксис не позволит?


А про игры — полностью согласен, это захватывающе!, с теплом вспоминаю свои первые поделки-игры на Basic на ZX Spectrum (Sinclair), и там было ascii так же.

Как тайному фанату Kotlin скажу: понятнее будет вот так:

fun draw() = dice.pollFirst()

Если честно, не понял, в чем разница. Сравните с полноценным «джавовским» аналогом:

fun draw(): Die {
    return dice.pollFirst()
}

и решите, что понятнее лично вам. Так и пишите.
Благо, синтаксис достаточно гибкий, чтобы разные варианты допускать.
Почему бы не использовать готовый игровой движок? Ответ прост: мы ничего про него не знаем, а игру хотим уже сейчас. Представьте образ мысли среднестатистического программиста: «Хочу делать игру! Там будет мясо, и взрывы, и прокачка, и можно грабить корованы, и сюжет бомбезный, и такого вообще никогда и нигде больше не было! Начну писать прямо сейчас!.. А на чем? Посмотрим, что у нас сейчас популярно… Ага, X, Y и Z. Возьмем Z, на нем сейчас все пишут...». И начинает изучать движок. А идею бросает, потому что на нее уже времени не хватает. Fin.
Т.е. по вашему написать игровой движок с нуля быстрее чем почитать документацию к тому же анрилу где примерно половина работы уже выполнена? Я нахожу это крайне сомнительным.
Нет, конечно же нет. Идея совсем не в этом.

Поясню свою позицию.


Я отнюдь не против использования готовых движков. Наоборот, они дают огромное преимущество в скорости и качестве по сравнению с собственными велосипедами (особенно если разработчик имеет хорошие навыки работы с ними). Но сам по себе движок — не панацея. Да, он поможет настроить игровой цикл, обеспечит графический вывод, ввод, взаимодействие различных объектов, физику там… Ну и всё. Основную логику работы все равно придется писать самому (и это хорошо, иначе все игры были бы похожи одна на другую).


Вот вам простой пример. Скачал как-то на мобилку интересную игру (обойдемся без конкретных названий). Весит 64Мб. Работает с тормозами и батарейку кушает как голодный африканский крокодил. А игры-то: пара статичных двухмерных спрайтиков по экрану перемещается без особых эффектов и анимации — казалось бы, чему тут тормозить?
А все потому что разработчик решил свою игру на Unity написать — ну и потянулась вся эта махина в его проект. Я с Unity крепко не работал, не знаю, может так и надо. Может, разработчик не позаботился об оптимизации и чего-то важного не сделал (читай: плохо знает инструмент), но в одном уверен: реализуй он такую же функциональность на обычном canvas'е (стандартными средствами), игра бы весила 300кб и летала быстрее, чем армейский вертолет (такой опыт у меня был, я знаю о чем говорю).


Это я все к чему? Не нужно, как мне кажется, забивать гвозди микроскопом. Если пишете клон сапера, Unreal Engine вам, скорее всего, не нужен. А даже если и нужен, то будьте людьми, не забывайте об оптимизации.


(Ну или читайте отзывы игроков на странице магазина и как-то реагируйте.)

Разработчик скорее всего затянул за движком тонну сервисов и не нужных модулей.
Обычно перед выпуском, после прототипирования, создают либо шаблон экспорта, либо пересборку нужных модулей движка. Скорее всего, данный фрукт просто подключил библиотеки System, Scurity из net core и всякие веб и аналитические сервисы.
Я не юзаю юнити. Но на годоте том же. Чистый движок без спрайтов весит 2мб, если использовать свой шаблон. А если использовать стандартный шаблон — чистый движок весит 15-20мб на андройде ( в зависимости от типа сборки и отладочной инфы)

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


А что, если я мечтаю сделать игру, которая будет хорошо продаваться? *thinking face*
Такой вариант тоже вскользь упомянут…
Эх, создать бы для начала просто игру. А то кроме как «нечто» это не назвать.
«Просто» не надо — их и так уже полно. Создайте игру, от которой вам лично будет тепло на душе. Даже если она будет неудачной, никому не понравится и прибылей не принесет.

Затог будет что вспомнить в старости у камина.
Хороший материал, большое спасибо за ваш труд и за то, что поделились! В закладки однозначно!
Не забыть тёплый ламповый генератор названий монстров по словарю.

Даёшь «Рвотный Глаз Топор» в каждый дом! :)
Иногда для стимулирования работы мозга я использовал вот этот и вот этот сервисы.
Отдельное спасибо за украинский перевод
зря конечно так категорично к движкам. Ибо когда речь идет об физике, анимации, интерполяции свойств и прочего — ты начинаешь такие лютые велосипеды изобретать. Что в итоге весь твой компайл-тайм движок превращается в рантаймого франкенштейна.
А потом ты еще свои скрипты начинаешь интегрировать. Что бы ускорить разработку. Начинаешь редакторы карт изобретать, генераторы сценариев и прочее…
А если еще и кросплатформенным его делаешь — ух… AdMob, FireBase куча всяких сервисов…

А так есть такие кросплатформенные движки как Wolf Engine, Ascid, Godot, cocos2d-x. Бинарник чистого движка весит 2-5 мб. Не такая большая плата за 100500 готовых велосипедов. (у godot надо правда свой прекомпилированный шаблон создать, для такого веса)

Все свои проекты я перевел из велосипедов на движки.
И это прекрасно, вы не находите? Особенно фраза «все свои проекты», которая доказывает, что на момент перехода эти проекты у вас были.

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

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

По поводу тому, что они решили.
Например. Физика…
В начале ты создаешь банальный перебор мешей и трасировку лучей.
Потом, когда их много — делаешь quad_tree/cube_tree с чанками. Дабы уменьшить перебор.
Потом все это дело отлаживаешь… отлаживаешь… очень долго отлаживаешь… несомненно, лучше взять хотя бы box2d.

Физика — первое решение
Интерполяция параметров. Для создания плавных анимаций и прочего. Например ни одним своим велосипедом мне не удалось превзойти гибкость того же godot, в котором ты абсолютно что угодно можешь заанимировать или размазать во времени.
Система сигналов/ивентов — часто, создавая свой движок, для создания слабой связности, реализация этой подсистемы заставляет переписать логику раза три-четыре.
Ерархия обьектов — можно пойти по классической ECS. Но часто, нужна группировка сущностей. Наследование родительских координат и т.д. Из за этого, ты опять же вынужден пересматривать по несколько раз свою архитектуру, если пишешь велосипед. Использовать GraphScene, ECS или гибридный двух этих паттернов? Или god object (антипаттерн) создать на подобии того, что есть в Unity?
Графическая подсистема. Dx, OpenGL, GL ES, Vulkan…

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

До тех пор, пока ты придерживаешься базового концепта своей игры — свой велосипед будет не плох. Но как только начнешь реализовывать фичи. Именно фичи и новые игромеханики — начнется тонная проблем и бесконечный цикл переписывания движка. И за место того, что бы делать игру — ты будешь делать движок. А когда изучишь все эти аспекты, из чего строиться движок и какие паттерны — поймешь, что все ты изобрел очередной клон какого нибудь. MonoGame + Nez, Wolf engine, Ascid, Cocos2d-x, и т.д

Опыт конечно не сомненный, но время потрачено просто чудовищно.

Даже не смотря на то, что многие говорят «В своем движке ты можешь делать все что угодно» — это отчасти лож. В голове ты все не удержишь, а связность различных систем движка, рано или поздно встанут тебе боком и ты начнешь много и част перепсывать. А если ты успеешь что-то создать на нем — то сохранение совместимости со старым проектом — обойдется тебе боком. А если документировать все и обращаться к документации постоянно — это мало чем будет отличаться, от постоянного обращения к документации того же unity.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории