H Пишем Android-игру на Xamarin+MonoGame (C#) в черновиках Tutorial
Сегодня мы будем писать простенькую игру для Android на языке C# с использованием Xamarin и MonoGame.
-
— это framework для кроссплатформенной разработки мобильных приложений (iOS, Android, Windows Phone) с использованием языка C#. — это кроссплатформенная реализация игрового движка XNA, позволяющая писать игры не только под Windows и Windows Phone (как изначально задумывалось Microsoft), но и массу других платформ, включая Android.
Сформулированный принцип: PacMan должен съесть максимальное число приведений, уворачиваясь от хаотично летающих желтых шестеренок, не задев при том границы поля (оно огорожено колючей проволокой); за каждое «съеденное» приведение начисляется одно очко, за каждое ранение шестеренкой — снимается одна жизнь (из пяти), за пересечение границы поля — сразу все жизни.
Игра будет на двух языках: русском и английском. Должно присутствовать игровое меню и дружественный для пользователя интерфейс.
Под катом много скриншотов, схем и кода) В конце урока приведена ссылка на GitHub с полными исходниками игры.
Вот такой вот «очень сердитый» (даже слишком) загрузочный экран удалось нарисовать (если что, это не хоррор, а просто игра для убивания времени): 
Работать будем в Xamarin Studion (фирменная IDE, свои впечатления об ее использовании я упоминал в статье на хабре). Так как до Xamarin 5 добраться пока не довелось, весь проект оформлен в 4.x. Впрочем, желающие могут без видимых проблем использовать VS2010/2012/2013.
Подготовка графики
Для начала подберем графику. В принципе, это шаг, на котором создается вся концепция 2d-игры. Несколько часов работы в Photoshop, создание шаблонов, и, ура, весь дизайн игры продуман и проработан! Хорошо, если в вашей команде этим занимается отдельный человек — дизайнер; мне же, выполняющему все работы сразу, всегда почему-то трудно переключаться между программированием и скрупулезной работой в графическом редакторе…
На уровне шаблона в Photoshop:

Понадобятся текстуры интерфейса — PacMan-а и его врагов (а также элементов интерфейса):


Важный момент! Чтобы в дальнейшем не было проблем с масштабированием и пропорциями, всю графику адаптируем под одно разрешение. У меня выставлено 960×540. Это не значит, что при непосредственном написании кода все будет заточено под одно разрешение, напротив, позволит равномерно масштабировать согласно выставленному на устройстве (можете использовать свое, главное, чтобы вся графика создавалась в его пределах, подробности чуть дальше).
Все фоны игры выполним в одной цветовой гамме. Я выбрал синий цвет.
Меню начала игры и паузы будет выглядеть так:


Это статическая текстура. По середине будут появляется кнопки «Начать игру», «Выход»; либо «Продолжить», «Новая игра», «Выход»; в правом нижнем углу — ссылка на официальный сайт разработчика. Сами кнопки заготовим немного позже, сейчас главное подобрать основную графику.
Фон для меню победы: 
Фон для меню поражения: 
Фон самой игры: 
Границы экрана:
- background_game.png — фон игры
- background_lose.png — фон поражения
- background_win.png — фон победы
- border.png — границы экрана (колючая проволока)
- cross.png — значок, определяющий жизнь PacMan-а
- pacman.png — текстура PacMan-а
- pacman_back.png — отраженная по горизонтали текстура PacMan-а
- target.png — приведение
- target_bad.png — желтая шестеренка
- ru_LoadingScreen.png — начальный фон после загрузки и фон паузы на русском языке
- en_LoadingScreen.png — начальный фон после загрузки и фон паузы на английском языке
- ru_splash.png — фон загрузочного экрана на русском языке
- en_splash.png — фон загрузочного экрана на английском языке
Создание и настройка проекта
Создаем проект, указав «MonoGame for Android Application»: 
Переименовываем Game1 в Game, а Activity1 в ActivityMain. В итоге должны получить вот такой вот «чистый» проект: 
Теперь начинаем его настраивать. Щелкаем правой кнопкой по надписи PacMan во главе списка слева (голубой картинке), выбираем пункт Options. В General выставляем Target Framework «Android 4.0», в Android Build на вкладке Advanced выставляем все галки (armeabi, armeabi-v7a, x86), в Android Application ставим Minimum и Target Android Version в «4.0», а также ставим галочки Internet и WriteExternalStorage (понадобится для того, чтобы сохранять результаты игры). Также выбираем значок icon в «@drawble/icon».
Обратим внимание, что Version name установлена в 1.0 (в принципе, можете поменять на какую хотите, к примеру, 0.1b, это лишь отображаемая версия), а Version number равно 1. Значение Version number лучше не трогать, это по нему Google Play узнает, нужно ли обновлять игру, если в личный кабинет был загружен новый *.apk-файл. Таким образом, при каждом новом релизе нельзя использовать прошлые значения Version number.
В Resources/Drawble создаем папки drawable-hdpi, drawable-ldpi, drawable-mdpi, drawable-xhdpi, drawable-xxhdpi. В них нужно будет поместить значки в разных разрешениях, которые лучше всего сгенерировать используя App Icon Template. В drawble: 144×144, в drawable-hdpi: 72×72, в drawable-ldpi: 36×36, в drawble-mdpi: 72×72, в drawable-xhdpi: 96×96, в drawable-xhdpi: 144×144. Все значки должны быть названы просто icon.png.
splash.png — загрузочный экран Xamarin, который будет показываться при открытии приложения в момент подгрузки виртуальной машины. На деле (в таком маленьком приложении) он не будет показываться, поэтому можно заменить его на то же изображение, что и в ru_splash/en_splash, но без надписей. Сами ru_splash/en_splash понадобятся при загрузке текстур.
В Assets создаем папку Content, в ней — папки en и ru (для удобства — чтобы разделять языки). Помещаем туда заготовленные картинки как показано на скриншоте (в дальнейшем их еще придется пополнять): 
Также создадим в папке Resources папки values, values-ru, values-en, values-uk, values-be, values-sr, а в них файл strings.xml. Как вы уже наверняка догадались, это файлы со строками, каждая на своем языке, туда поместим название приложения. Не смотря на то, что сама игра будет только на русском и английском, сделаем так, чтобы название могло отображаться на украинском, белорусском и сербском. Трудозатрат никаких (Google Translate на славянских языках работает довольно качественно), а вот увидеть приложение в Google Play и, в дальнейшем, значок с подписью на национальном языке, все равно приятно. Само приложение достаточно сделать на русском — сербы, белорусы и те, кто выставили в настройках на телефоне украинский, все равно русский язык понимают хорошо.
На этом подготовительные работы закончены, можно приступать к непосредственной разработке.
Разработка «внутренних» классов
Поставим задачей опосредовать логику игровых объектов и логику самого движка XNA/MonoGame. Пусть «внутренние» классы — классы, используемые для обработки внутреннего игрового процесса, то есть игровых объектов «игровой процесс», «игрок», «враг»; а «внешние» классы — классы, используемые для взаимодействия внутреннего игрового процесса и игрового движка. Остановимся подробнее на «внутренних».
Ниже приведу код с комментариями. Я постарался расписать его максимально подробно, если что-то не понятно, спрашивайте. Сразу обратим внимание, что все координаты указаны в пределах 960×540, то есть на уровне логики игровых объектов масштабирования игрового поля согласно текущему разрешению экрана телефона не существует).
В Classes.GameProcess.cs используется класс ClassScoreManager, предназначенный для управления счетом (считывание из системного хранилища Android) и запись. О нем разговор пойдет в дальнейшем.
Разработка «внешних» классов
Рассмотрим Game.cs — основной класс «игра» (содержит все визуальные объекты и методы обработки игрового процесса). Тем, кто знаком с XNA Game Studio и используемой логикой, будет проще, поскольку в MonoGame ничего не изменилось. Если говорить кратко:
- public PacManGame() — инициализация класса (то есть все действия, выполняемые при создании класса «игра»)
- protected override void LoadContent() — загрузка контента (всех текстур, также понадобится загружать шрифт)
- protected override void Update(GameTime gameTime) — обновление игрового процесса, то есть все действия, выполняемые в единицу времени (кроме отрисовки)
- protected override void Draw(GameTime gameTime) — отрисовка визуальных объектов в единицу времени (также может содержать действия, т.е. Update можно не использовать, но с этим осторожнее: к примеру, обработка управления идет только через Update)
Для этого я ввел переменные const int NominalWidth = 960, const int NominalHeight = 540, float Dx, а также int CurrentWidth и int CurrentHeight. Что это значит? Что в CurrentWidth/CurrentHeight нужно считать текущую высоту и ширину экрана устройства, и посчитать соотношение с ScreenWidth и ScreenHeight, получив результат в Dx. А дальше отрисовывать всю графику с увеличением в Dx раз.
Не по стандартам Android? Определенно да. Тем не менее, без привлечения сторонних разработок, MonoGame не позволяет более гармоничным способом адаптировать приложение к разным экранам.
Таким образом, получается следующий код инициализации класса «игра»:
Обновление параметров экрана и коррекция координат X и Y (также учитывается тот факт, что разрешение экрана может быть не 16:9, а любое другое, в таком случае координаты центрируются по середине):
Схема отрисовки:
Альтернативных вариантов можно придумать много. Например, заранее подготовить всю графику в 16:10 и использовать черные рамки по вертикали если экран 16:10, либо предусмотреть обрезку и избежать рамок. В другом своем проекте, где все нужно сделать совсем без недочетов (так, чтобы прямо действительно совсем), я как раз использую вариант без рамок — он, к сожалению, более трудоемкий.
- SpriteBatch.Draw (TextureBackground, new Vector2 (AbsoluteX (0), AbsoluteY (0)), new Rectangle (0, 0, TextureBackground.Width, TextureBackground.Height), Color.White, 0, new Vector2 (0, 0), 1 * Dx, SpriteEffects.None, 0);
- TextureBackgroud — сама текстура (Texture2D)
- new Vector2(AbsoluteX (0), AbsoluteY (0)) — установка координат (обязательно прогонять через наши методы AbsoluteX и AbsoluteY!)
- new Rectangle (0, 0, TextureBackground.Width, TextureBackground.Height) — установка обрезки текстуры (без обрезки: левый верхний угол имеет координаты [0;0], а правый нижний угол координаты [ширина текстуры; высота текстуры])
- Color.White — наложение цветового слоя на текстуру
- new Vector2 (0, 0) — точка, относительно которой происходит отрисовка текстуры, в данном случае это верхний левый угол [0;0] (если бы нам был нужен центр, то можно указать [TextureBackground.Width/2; TextureBackground.Height/2])
- 1 * Dx — масштабирование текстуры (т.е. любая наша текстура должна увеличиваться в Dx раз вне зависимости от того, нужно ли дополнительное масштабирование, к примеру 2 * Dx, 3 * Dx, 0.5f * Dx . )
Суть такова: кнопка может существовать в двух режимах — нажатом и не нажатом, для каждого используется своя текстура. Пусть не нажатый режим использует текстуру TextureButton, а нажатый — TextureButtonLight (можно назвать TextureButtonPressed, просто в прошлом проекте у меня кнопки «подсвечивались» при нажатии, отсюда осталось Light). Сперва нужно обработать отрисовку кнопки через Process и обновление ее логики через Update, затем проверить, была ли она нажата.
На самом деле цепочка действий немного сложнее: кнопка «зажата», значит IsPressed становится true и вместо TextureButton отрисовывается TextureButtonLight, затем может быть так, что игрок передвинет с нее палец и отпустит (не захотел нажимать, либо случайно задел и сразу сдвинул палец), тем самым IsPressed снова станет false, а вот если игрок отпустит кнопку и в предыдущий момент времени палец будет на ней, то IsEnabled станет true и можно выполнять все действия по непосредственному назначению кнопки).
На примере кнопки паузы:
Интереснее пример с кнопкой посещения сайта:
Для всех кнопок нужно подготовить и загрузить текстуры. В процессе было выяснено, что понадобятся следующие кнопки: «Начать игру», «Новая игра», «Пауза», «Продолжить», «Сыграть заново», «Выход» (+ссылка на сайт, отображающаяся в меню).И, что не маловажно, кнопки управления со стрелками, которые будут располагаться по экрану.
Получается вот такая картина: 
В конце урока есть ссылка на GitHub, там собран весь проект, включая данные текстуры. Сначала создавались текстуры кнопок в нажатом состоянии (%name%Pressed.png), затем путем уменьшения в 0.8-0.9 раз и повышением прозрачности получалась обычная текстур. То есть изначально игрок видит чуть уменьшенные полупрозрачные надписи, при нажатии они увеличиваются и имеют четкие очертания. Помним: текстуры с префиксом «ru_» кладем в папку «ru», а текстуры с префиксом «en_» в папку «en», не забывая при том, что нужно добавлять их через контент-менеджер (панель слева).
Теперь интересный момент — загрузка текстур! Объявим в классе Game следующие поля:
По логике MonoGame/XNA есть один метод для загрузки LoadContent(), автоматически вызывающийся в самом начале. У него есть серьезный недостаток: когда текстур много, загрузка происходит довольно долго, игрок видит черный экран, что совсем нехорошо. Поэтому давайте сначала загрузим текстуру загрузочного экрана Splash, покажем ее, и лишь после этого начнем загружать остальные текстуры.
Для этого введем:
Метод LoadContent примет вид:
Что за странная переменная Language? Это строковое значение, равное «en» или «ru», про него упомянем чуть ниже. Таким образом, изначально загружается одна лишь текстура Splash. Тогда в методе Draw необходимо реализовать следующую логику:
Такой вот длинный код. Зато — избавляет от подвисания при загрузке, показываем загрузочный экран с названием игры и надписью «загрузка. », тем самым давая игроку подтверждение, что его телефон или планшет не завис.
Теперь по поводу использованной переменной Language. Как мы уже упомянули, она имеет значение «ru» или «en». В инициализации PacManGame нужно осуществить следующие действия:
То есть: считываем из системы текущий язык, и в зависимости от полученного значения присваиваем переменную Language и ряд других (такая длинная процедура с locale.Substring нужна для того, чтобы отделить от всех многочисленных en-US, en-GB и остальных просто частицу en). В принципе, то же самое можно было реализовать через strings.xml, но поскольку используемых строк немного, а игра всего лишь на двух языках, то можно обойтись и простым присваиванием прямо в коде.
Строки отрисовываются шрифтом Font.xnb. MonoGame поддерживает только скомпилированные *.xnb, а не XNA-шные *.spritefont, так что пришлось отдельно компилировать через XNA (см. оба файла на GitHub).
Полный код Game.cs, с комментариями:
Остановимся немного подробнее на методе Update — он выполняется параллельно Draw и удобен для обработки управления, в частности, кнопкой «назад» на самом телефоне или планшете (любое использование GamePad в пределах Draw приведет к зависанию).
Если нажата кнопка «назад» во время игры, то включается пауза, если во время паузы, то происходит возвращение в игру.
Важный момент, на котором тоже следует остановиться, это сохранение рекордов. Для этого я написал класс Game.Score.cs, который также использую во всех своих проектах с минимальными изменениями (на MonoGame подобные приемы очень удобны).
Для считывания и записи значения счета я использую отдельный класс и сериализацию. Конечно, чтобы хранить одно лишь значение это неэффективно и избыточно с технической точки зрения. Но что, если нужно сразу хранить весь перечень рекордов, да еще и из разных режимов игры? С этим приходилось сталкиваться неоднократно, сериализация очень выручает.
Запись в память мобильного устройства решает атрибут WriteExternalStorage, который мы выставили в самом начале.

Заключение
В принципе MonoGame прост и удобен для создания двухмерных игр, недостатком является отсутствие автоматизации многих типичных задач (даже тут с ними пришлось столкнуться: подгрузка контента, кнопки, сохранение рекордов). Если вы раньше работали с XNA Game Studio, многие вещи покажутся знакомыми, простыми и понятными, у вас есть все возможности для кроссплатформенной реализации своих проектов. Если нет (и, в особенности, если хотите делать трехмерные игры) — рассмотрите возможности других движков, к примеру, Unity.
В Google Play пользователям игра нравится, во всяком случае пока 🙂
Скриншоты получившейся игры тут и тут.
Полные исходники смотрите на GitHub
Опыт разработки аркады под Android на С++ и Qt
Космос сам себя не наложит
Предпосылки
Я, как и многие программисты, выбрал эту профессию, потому что в детстве играл в компьютерные игры и мечтал их разрабатывать. Как только я научился более-менее писать код, который может компилироваться без синтаксических ошибок, я, конечно, стал делать всякие глупые игры, которые показывал всем друзьям и знакомым. Но время шло, и оно заставило заниматься совершенно другими вещами, работать над проектами, которые, мягко говоря, серьезней, чем игры. И так продолжалось последние несколько лет. А изначальные желания никуда не делись, исчезло только свободное время.
Давно хотел сделать какой-нибудь проект под Android, а, как известно, основная масса проектов разрабатывается на Android SDK и Java, а NDK рекомендуют использовать только в “критичных по скорости” местах и не делать на нем все целиком.
Но кому нужны все эти рекомендации и правила, когда есть Qt? Java я не знаю в той степени, которую считаю достаточной для качественной разработки игры, и изучать мне ее не хотелось, зато у меня имеются в запасе знания C++. После нескольких тестовых проектов на Qt под Android я понял, что на нем вполне можно разработать полноценное приложение, да еще и перенести его на другие платформы. Так же, посмотрев видео Shia LaBeouf — Just Do it, стало понятно, что я обречен это сделать.
Итак, я хочу рассказать про опыт разработки игры под Android на Qt 5.5.1 и С++.
Идея
Как ни странно, идея игры пришла довольно быстро и очень необычным образом. Мы экспериментировали с датчиками телефона в попытках сделать игрушку “стукач”(название временное), смыслом которой было — чем сильнее и чаще ударяешь телефон, тем больше очков получаешь. Для этого необходимо было отличить удар телефоном от взмаха, и, к счастью, нам это не удалось, но при выводе графиков на экран рисовались интересные картины, что и натолкнуло нас на идею игры, о которой и пойдет речь.
Внезапно, захотелось сделать игру про нечто, падающее в пещеру, контуры которой меняются со временем. Первоначально предполагалось сделать двухмерную игру с графикой в рисованном стиле. В дальнейшем все перетекло в трехмерную игру с видом сверху. Был написан генератор пещеры, который позволял создавать слои с контурами. Как рассчитывать столкновения с стенами пещеры и как превратить их в трехмерную модель? Можно было долго думать и выдумать что-нибудь невероятно классное, но я пошел обходным путем и сделал воксельную геометрию мира. Расчет столкновений и вывод на экран стали заметно проще, но ценой того, что игра стала похожа на майнкрафт. Я не растерялся и сделал её еще больше на него похожей, добавив модель персонажа из него же. Так и сформировался вид и концепция игры.
Первая версия генератора пещеры (архивное фото)
Приступая к разработке игры, я не ставил себе цель заработать на ней, как многие, а хотел просто начать свой проект и довести его до конца. Поэтому игра должна быть бесплатная (кто у меня ее купит?), но все-таки с минимальным количеством рекламы.
Графика
Средства
-
— устаревший класс, наследник QWidget. Использовался до Qt 5.0 — новый класс, выполняет те же функции, что и QGLWidget, но в своей реализации использует промежуточный фреймбуфер — предназначен для вывода графики OpenGL, не является виджетом и не тянет зависимостями модуль widget
- QGLWidget работает намного быстрее QOpenGLWidget на слабых смартфонах
- QOpenGLWidget не меняет размер внутреннего фреймбуфера при изменении размеров окна, вероятно, это баг или фича, или я просто идиот, но тем не менее, при использовании этого виджета разрешение окна не совпадало с размером вьюпорта, что приводило либо к растянутому изображению, либо к поехавшей матрице проекции. При всем при этом производительность с использованием этого виджета была крайне мала
- QOpenGLWindow — самый подходящий кандидат. Его нельзя использовать как viewport для графической сцены и нельзя добавлять дочерние виджеты, что лишает возможности создать пользовательский интерфейс средствами Qt. Но зато он самый производительный из всех трех
Для реализации игры я выбрал QOpenGLWindow, а пользовательский интерфейс пришлось велосипедить самому.
Обновление кадров было реализовано по сигналу frameSwapped(), который генерируется после swapBuffers. Использование этого сигнала позволяет достичь большей плавности смены кадров, чем при использовании таймеров.
Для расчета анимации мне требовалось посчитать время кадра. Для этого я использовал сначала QTime, что было плохой идеей, так как этот класс считает немонотонное время, а на мобильном устройстве часто происходит корректировка времени, что приводило к попаданию в прошлое или будущее, в зависимости от ухода часов на устройстве. Также разрешение изменения этого класса ограничивается миллисекундами, что недостаточно для плавной анимации при нестабильном фпс.
Поразмыслив и обратившись к документации, я решил использовать класс QElapsedTimer, который старается использовать монотонное время и имеет разрешение до наносекунд.
Текстуры и интерфейс
Для первых версий и отладки текстуры были позаимствованы временно из майнкрафта, как и какой-то скин персонажа. Первая версия интерфейса представляла собой серые квадратные кнопки и была сделана за вечер, а переработка заняла целый месяц.
В качестве дизайнера был выбрал мой друг. Плюсами такого выбора является то, что этому дизайнеру можно доверять, а минусами — то, что он не является дизайнером — он автомеханик.
В дальнейшем мы нарисовали оригинальные текстуры для нескольких уровней и сделали текстуры персонажей.
Процесс рисования “красивого” интерфейса и персонажей оказался довольно длительным. Пока дизайнер пытался выдавить из себя нового персонажа или кнопку, мне ничего не оставалось делать, кроме как продолжать оптимизировать код.
Текстура уровня представляет собой большой атлас всех блоков и вмещает в себя 32 куба со стороной 16 пикселей. При таком подходе при рисовании слоев не требуется постоянно переустанавливать текущую текстуру, а можно рисовать весь уровень, используя вершинные буферы за несколько вызовов.
Проблема возникла в крайних пикселях блоков, которые являются смежными с другими блоками. При округлении текстурных координат стали появляться артефакты в виде полосок на краях блоков. Эту проблему я решил путем добавления каждому блоку окантовки шириной в один пиксель, дублирующую пиксель края блока. Такой своеобразный CLAMP_TO_EDGE.
К сожалению, при таком подходе я не смог использовать mipmap для уровня, т.к. текстурные координаты в вершинном буфере должны учитывать это смещение на один пиксель, а при создании текстуры уменьшенной детализации это смещение становится меньше, и мне бы пришлось использовать различную геометрию уровня в зависимости от степени детализации. Я решил с этим не заморачиваться, т.к. сама текстура уровня всего 256х256 пикселей, и особого выигрыша в производительности билинейная фильтрация не принесла бы, а принесла бы лишь дополнительные сложности в реализации.
Еще хотел заметить, что хотя блоков и всего 32, но в свойствах уровня я задал возможность расставлять блоки повернутыми на 90, 180 и 270 градусов, а также анимацию переключения между текстурой блока, что позволило разнообразить визуальную составляющую игры. Хотя, анимацию я применил только на одном из уровней для создания эффекта вращения вентиляторов.
Шейдеры
В Qt имеются удобные классы для работы с шейдерами. Я использовал QOpenGLShaderProgram. Этот класс позволяет добавлять вершинные и фрагментные шейдеры, компилировать их, линковать, устанавливать uniform и attrubute. Сам по себе класс является просто оберткой над множеством вызовов OpenGL и, соответственно, не является полноценным объектом, в том понимании, что работу класса можно нарушить, используя вызовы GL напрямую между вызовами этого класса.
Удобно то, что класс автоматически добавляет define таким образом, что шейдер ES компилируется нормально и на десктопе и на мобильном устройстве. Это относится в первую очередь к спецификаторам точности, которые на десктопе превращаются в ничто.
Мне пришлось написать отдельные шейдеры для игрового мира, включающие освещение и анимацию некоторых блоков, шейдер для персонажа и шейдеры интерфейса.
- простой шейдер, выводящий текстуру
- шейдер бара хп\щит
- шейдер круга
Бар хп\щит
Этот бар я решил реализовать шейдером, сначала мне это показалось очень странной идеей, но в конце концов я ее принял как неизбежное и реализовал таким способом:
- Код шейдера компилируется без проблем на одном устройстве, а на другом возникают ошибки
- Шейдер нормально компилируется, но на некоторых устройствах неправильно работает. Например, на PowerVR, если локальная переменная называется так же, как и юниформ или атрибут — это не приводит к ошибкам, но сам шейдер перестает работать корректно
- На некоторых устройствах возникают ошибки, если переменная или функция называется так же как и встроенная функция. Например, переменная mix или clamp
- Возникали проблемы с точностью float на некоторых устройствах
Итог всего этого — если вы планируете использовать шейдеры в мобильной игре, проверяйте их на самых распространенных моделях графических ускорителей. Если шейдер компилируется и работает у вас на компьютере, это еще не означает, что он будет работать на телефоне вашего соседа. Новый Vulkan API должен решить проблему с различными компиляторами шейдеров и привнеси порядок в этот безумный мир, но это дело будущего, а сегодня имеем то, что имеем.
Звуки
Поиск
Звуки — это вообще отдельная песня. Их можно записать самому, что довольно трудоемко и требует наличия нормального микрофона и слуха (не наш случай). А можно найти желанные звуки в интернете.
Искать звуки желательно с лицензией Creative Commons 0, дабы не указывать авторство для каждого звука, коих может быть несколько десятков. Может показаться, что найти нормальный free звук довольно тяжело, и оно так и есть. Проблема не в том, что их мало, напротив, — их очень много, большинство из которых ужасны. Поиск звука — процесс, в котором надо переслушать очень-очень много звуков и выбрать из них самые подходящие.
Средства
В Qt для вывода звуков существуют классы QSound, QSoundEffect, QAudioOutput и QMediaPlayer. В первых версиях я использовал QSoundEffect для вывода эффектов и QMediaPlayer для вывода звуков. Но, как оказалось, все они не подходят.
- Фризы при запуске звука. Когда музыка играет в зацикленном состоянии, при повторении возникает ощутимая задержка всего приложения.
- При чтении медиафайла из ресурсов он создает временный файл в папке с данными приложения и не всегда удаляет его за собой, что приводит к росту размера приложения. Это было обнаружено случайно и хорошо, что вовремя.
Все эти недостатки привели к тому, что после добавления звуков в игру появились ощутимые проседания FPS при воспроизведении звуков и музыки.
Решением стал отказ от этих классов и использованием для звуков библиотеки SFML. Очень простая и легковесная библиотека, похожая на SDL. Удобные классы для работы с графикой, звуком, устройствами ввода. Эта библиотека не умеет работать с mp3 (лицензия, все дела), но зато умеет многое другое. Я использовал для эффектов и музыки формат ogg.
Вывод по звукам
Для использовании в приложении, где звуки являются чем-то необязательным, Qt-шные родные классы подходят. Для разработки игр — совсем нет. Лучше и проще использовать SFML.
Реклама в приложении
Для рекламы я использовал уже готовую реализацию AdMob под Qt — QtAdMob.
Сперва был добавлен только один маленький баннер в меню, но в дальнейшем образовалось межэкранное объявление.
Занятно, что межэкранное объявление, находясь даже в загруженном состоянии, появляется с некоторой задержкой. То есть появилась необходимость блокирования пользовательского интерфейса в момент появления рекламы и восстановление после ее закрытия. При этом, библиотека не позволяла отловить моменты, когда объявление показано и закрыто. Данный функционал библиотеки я допилил в версию под Android. Версию под ios трогать пока не стал, за неимением возможности проверить работоспособность.
Аналитика и статистика
Выкладывая первую версию на Play Market, я понадеялся на статистику в консоли разработчика. Но, как оказалось, статистика активных пользователей завязана на Google Analytics и не работает, пока в приложении не включишь трекер аналитики. А та статистика, что доступна, приходит с задержкой более суток и рассчитывается по тихоокеанскому времени. Такое положение дел не позволяет понять, как же твои действия влияют на скачивания. Поэтому я дополнил класс activity из QtAdMob, который наследуется от QtActivity функциями инициализации аналитики и функциями отправки событий игры. Примеры кода не привожу, т.к. все прекрасно описано в документации.
В события я вынес нажатие всех кнопок интерфейса, возникновение некоторых игровых ситуаций, открытия и разблокировки персонажей с уровнями.
Благодаря сбору всей этой статистики я могу сидеть за ноутбуком и наблюдать в реальном времени, как в Бразилии кто-то запустил игру, не смог пройти первую миссию, вышел и, вероятно, удалил игру.
Еще, если верить статистике,- мы сейчас чемпионы по нашей игре.
Про Google Play
Консоль разработчика сама по себе довольно удобный инструмент, но функционал статистики и рекламы завязан на другие аккаунты.
Чтобы полноценно разрабатывать приложение самому, необходимо как минимум иметь аккаунты AdWords, AdSense, AdMob, Google Analytics. При этом между ними устанавливается связь. Все эти аккаунты — отдельные продукты Google и имеют различную техническую поддержку и настройки. Также стоит заметить, что аккаунт AdMob требует наличия аккаунтов AdWords и AdSense. При этом все эти аккаунты могут быть привязаны в единственном экземпляре к основному аккаунту Gmail. Но, как показала практика, во всем этом можно запутаться с самого начала, потому что ты открываешь один сервис, он тебе предлагает создать новый аккаунт в другом, тот в третьем и так далее.
Я каким-то магическим образом так, что сотрудник техподдержки не смог объяснить произошедшее, создал 2 аккаунта AdWords и привязал их к одной почте, при этом привязав один аккаунт к консоли разработчика, а другой к AdMob (об этом я не знал).
На один аккаунт я закинул 500р, чтобы проверить рекламную кампанию. В попытках разобраться с этим и следуя советам техподдержки, я перенес один из аккаунтов на “левую” почту и закрыл сам себе доступ к нему. Все это привело сначала к неработоспособности обоих аккаунтов с моей почты, затем, при повторных отключениях и подключениях самого себя, работоспособность вернулась. Но, как выяснилось, перестал работать AdMob. Так как AdMob мне был важнее тех 500р, пришлось провести всю эту процедуру заново, попутно молясь о том, чтобы я не потерял доступ вообще ко всему, вернуть работу AdMob. И конечно, те 500р остались висеть на не подключенном аккаунте.
Так что, аккуратнее с этим.
Перевод
В игре
Игровое меню мы перевели на 2 языка — русский и английский. Выбор языка игры осуществляется по системной локали. Если в системе русский язык — то игры на русском, во всех остальных случаях — на английском.
Для осуществления переводов текстовой информации в Qt существует встроенный механизм, приводящийся в исполнения классом
Все строки, которые необходимо перевести, передаются в функцию QObject::tr(), для классов, не являющихся наследниками QObject можно использовать функцию QApplication::translate и для строк, объявленных в массивах макросы QT_TRANSLATE_NOOP, QT_TR_NOOP.
Но это только полдела. Необходимо создать сами переводы, что производится программами lupdate и lrelease. Первая собирает информацию из исходного кода, содержащего эти функции и макросы, и создает Xml файл с информацией для перевода.
Вторая собирает из xml файла бинарный файл qm, который загружается непосредственно в QTranslator.
Мы использовали в качестве переводимых строк что-то вроде тегов, которые затем переводили на английский и русский. Например, “#GameOverText” переводится в “Игра закончена”. Сделано было так, чтобы не было необходимости менять исходный код, чтобы что-то по-другому написать, а затем еще и менять все переводы, т.к. для lupdate это уже другая строчка.
На маркете
В Google Play мы пошли по самому простому пути: написали текст на русском — закинули в гугл-переводчик — перевели на английский, подкорректировали. А затем английский текст перевели на самые распространенные языки тем же самым гугл-переводчиком. Забавно, что один из вариантов описания содержал фразу “Окунись в подземелье с головой”, которая, пройдя весь этот пусть переводов на китайском, означала “Прыжки в пещере на голове”. Мы так и оставили, потому что раз они нам на АлиЭксперессе перлы выдают, так и мы не будем отставать.
Заключение и планы
В заключение хочу сказать, что процесс разработки этой игры был и является одним из самых интересных занятий, принес нам огромное удовольствие и опыт, которым я скромно делюсь в этой статье. Разработка велась в свободное время, после работы и ночью, с августа этого года. Потрачены деньги были только на аккаунт разработчика и пару тысяч на рекламу, так что я не расстроюсь, если нам эта игра принесет чуть менее, чем ничего.
Дальнейшие планы зависят от того, как люди будут реагировать на наше творение. Судя по отзывам, довольно неплохо, но судя по скачиваниям и удалениям, хочется сходить за веревкой в ближайший магазин хозтоваров. Вероятно, игра получилась довольно сложная, и у нас явный недостаток рекламной деятельности
Мы планируем перенести игру под ios, но этому мешает отсутствие яблочной техники и 100$ в год, а так же пугает перспектива общения на эльфийском Objective C.
Я надеюсь, что это не последняя наша игра, есть множество новых идей, которые я буду стараться воплотить в жизнь, учитывая полученный опыт. Эта игра — первый блин, комом он или нет — судить не мне.
Предвосхищаю комментарии по поводу того, что это можно было сделать проще и быстрее, используя готовые движки, привлекая дизайнеров и издателей. Отвечу на это, что мы желали пройти путь джедаев-разработчиков с начала и до конца, своими силами, дабы окунуться в это с головой.
Introduction
I would like to walk you through setting up a simple Android Native Activity. I will walk through the basic setup, and try to give you the tools you need to move forward.
Although my focus is on games, I will not be telling you how to write an OpenGL application or how to architect your own game engine. That is a discussion that literally fills up books.
Why C++
On Android, the OS and its supporting infrastructure are designed to support applications written in the Java or Kotlin programming languages. Applications written in these languages benefit from tooling embedded deep into the system’s underlying architecture. Many core Android system features, such as the Android UI and Intent handling, are only exposed through Java interfaces.
By choosing to use C++, you are not being “more native” to Android than using a managed language such as Kotlin or Java. Counter intuitively, you are in some ways writing a foreign application with only a subset of Android features available. For most applications, you’re better off with languages like Kotlin.
There are a few exceptions to this rule. Near and dear to my own heart is game development. Since games typically use custom rendering logic, often written in OpenGL or Vulkan, it’s expected that a game looks different from a standard Android application. When you also consider that C and C++ are near universal to every computing platform, and the relative wealth of C libraries to support games development, it may become reasonable to set off down the path of native development.
If you want to make a game from scratch or port over an existing game, Android’s Native Development Kit (or NDK) and its supporting tooling are ready and waiting. In fact, the native activity I’ll be showing you provides an easy one-stop shop in which you can set up an OpenGL canvas and start gathering user input. You may find that despite C’s cognitive overhead, some common code challenges like building vertex attribute arrays from game data become easier in C++ than higher level languages.
What I won’t Cover
I will stop short of showing you how to initialize a Vulkan or OpenGL context. I recommend reading through the samples Google provides, although I will provide some tips to make your life easier. You may opt instead to use a library like SDL or even Google’s FPLBase instead.
Setup your IDE
First we need to make sure that you have everything installed for native development. For this, we need the Android NDK. Launch Android Studio:
Пишем игру "Жизнь" под Android используя C++ и Qt за час.
Всем привет! Недавно видел на пабликах типа ТП посты мол «делаем игру жизнь на java за час», «делаем игру жизнь на python за час», вот решил сделать пост, делаем игру жизнь под андроид используя C++ и библиотеки Qt за час.
Важно отметить что игра занимает
100 строчек кода(исключая h файлы, символы переноса и т.п.), а также то, что созданная игра будет работать и под винду, и под андроид, и под линукс, и под ios и под mac os x.
И так начнем. Правила игры жизнь я думаю вы знаете. Есть поле с клетками. Клетка может быть живая или мертвая. По прошествии хода, если вокруг мертвой клетки есть 3 живых, она становится живой. Если вокруг живой клетки будет 1 или 0 живых, то она становится мертвой, также она становится мертвой если вокруг нее более 3 живых клеток. Впринципе все, подробнее вы можете прочесть на википедии.
И так что у нас должно быть:
Поле с N*M клеток, кнопка «Новая игра», кнопка «Начать эволюцию». Поле забито прямогуольниками, изначально все пустые. При нажатии на клетку она окрашивается в черный или белый цвет, в зависимости от того какой цвет был там раньше.
2. Логика. Мы должны иметь двумерный массив(используем QT’шный вектор), в котором 0 отмечена мертвая клетка а единицей живая клетка. У меня была цель создать игру примерно за час, и поэтому этот 0 и 1 соответствуют черному и белому цветам многоугольников. Соответственно у нас есть массив с указателями на наши прямоугольники. Это не хорошо, ибо логика должна быть отделена от GUI, но для уменьшения размера кода — решил сделать так.
Должны быть реализованы функции подсчета соседей для каждой из клеток, зная положение клетки по X и Y. После каждого хода, мы рассчитываем количество соседей для каждой из клеток, и в зависимости от состояния клетки и количества её соседей добавляем клетку в массив(вектор), для изменения её состояния. Если за 1 ход не было добавлено ни 1 клетки, то игра заканчивается, выводим пользователю количество ходов
Если вы не знакомы с наследованием и библиотекой STD в C++, то вы являетесь новичком в C++ и возможно код покажется вам запутанным. К сожалению не хотелось бы писать про все это в этом посте, ибо для новичков информации в интернете — тонны, а переписывать еще раз это — просто не зачем. Если вы с этим знакомы, но незнакомы с сигнально-слотовой системой Qt тоже порекомендую прочитать over9000 материалов по этой теме. Если потребуются пояснения я отвечу в комментах.
И так, как выглядит GUI:

Скачиваем Qt Creator с официального сайта Qt(qt.io), выбираем бесплатный вариант для GPL приложений. Запускаемся, создаем новый проект, используя QWidget, с графической формой

Переходим к созданию. Переходим в режим дизайн и добавляем на форму «Graphics View» и 2 Push Button. Примерно вот так как на следующей картинке, и кликая правой кнопкой мыши по форме выбираем (Скомпоновать по сетке)

GUI готово! Теперь нам нужно перейти к непосредственно программированию.
QGraphicsView — это виджет, который может отображать графические элементы. QGraphicsView отображает элементы, который расположены на QScene. На QScene мы размешаем элементы в декартовой системе координат, а с помощью QGrahicsView мы её отображаем(можно поворачивать, увеличивать масштаб и т.п.).
Элементы бывают разных типов. Есть простые — QGraphicsRectItem — это многоугольник, который к слову нам и понадобиться. Есть QGraphicsPixMapItem который отобразит нам изображение. Есть QGraphicsLineItem — это линия. И другие.
И так, первая задача. Разместить на сцене N*M QGraphicsRectItem’ов — наших клеток. Мертвой клеткой будет считаться клетка, с Kletka->brush().color() == Qt::white живой Kletka-> brush().color() == Qt::black. И запихнуть все указатели на QGraphicsRectItem в отдельный двумерный вектор, чтобы мы могли с ними работать.
Однако, для этой задачи нам не подойдет «чистый» QGraphicsRectItem. Нужно будет создать класс наследник, и в нем реализовать свою обработку нажатия на клетку. Ведь в самом начале игры нам нужно будет выставить первичные мертвые и живые клетки на поле.
Т.е. по нажатию на наш прямоугольник, он должен окрашиваться в требуемый цвет. А у чистого QGraphicsRectItem обработка данного события пустая.
Создадим класс LifeRect основанный на QGraphicsRectItrem., и переопределим в нем mousePressEvent

Теперь займемся реализацией. Конструктор будет использовать конструктор с такими же параметрами как у QGraphicsRectItem(позиция по x, позиция по y, широта, высота, родитель). В событии mousePressEvent будет вызываться функция changeColor.

И так, переходим дальше. Работаем с нашим виджетом(формой) — widget.c. В этом классе реализуется логика игры.
Что нам необходимо?
1. Таймер. Каждые 0.1 секунды будет происходить 1 ход.
2. Слоты нажатия на кнопку «Старт» и «Новая игра» соответственно.
3. Размеры поля — X и Y клеток. Принимаются через конструктор.
4. Переменная для хранения количества итераций(для вывода при завершении игры)
5. Двумерный вектор(используется QVector, а не std::vector) который хранит указатели на созданные нами LifeRect’ы. К его элементам мы будем обращаться, и будем представлять живое и мертвое состояние по его цвету, как я уже и писал ранее. Если среди читателей будут новички C++ в комментах могу помочь. Просто представляйте что это массив.
6. Функция для новой игры(обнуление поля, обнуления количества итераций).
7. Функция подсчета живых соседей рядом.
И так, начнем. Собственно вот заголовочный файл:

В конструкторе присвоим начальные данные(x,y из входных, 0 итераций, укажем то что сцена пока-что пустая, родительский элемент отсутствует. Проинициализируем таймер, но запускать его не будем

Создать слот on_pushButton нужно нажав правой кнопкой по кнопке на форме, выбрать перейти к слоту, и выбрать сигнал clicked()

Дальше — интереснее. И так, по нажатию на кнопку новая игра, мы должны нарисовать графическую сцену, с X*Y мертвыми многоугольниками, и наполнить двумерный вектор указателей на многоугольники ими же. Чтобы потом,когда игрок заполнит поле и нажмет на кнопку «СТАРТ» запускается таймер, по каждому тику таймера обходить этот вектор, вычислить количество живых соседей каждой клетки, и перерисовать требуемые.
Постарался максимально прокомментировать код.

К слову говоря, позиции осей X и Y в графической сцене таковы:

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

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

Все готово! Запускаем и играем на Linux, Windows или вашем Mac.
Что потребуется(если вкратце) для запуска на Android?
1. Пакеты Android SDK и Android NDK(для C++).
2. Устройство андроид или AVD.
Вы должны сконфигурировать ваш Qt Creator, в таком виде(ваши пути), и добавить в проектах сборку Android. Потребуется скачать в AVD драйвера USB, и API требуемых версий Android. Затем уже попробовать установить приложение на телефон/эмулятор. Я могу помочь, или привести ссылки где это подробно разбирается в комментах

Пробуем на стареньком LG Optimus One с Android 2.3.3. — все работает как часы и на старых андроидфонах. К слову говоря можете попробывать как все выглядет в реальности(перед созданием) по ссылке — https://play.google.com/store/apps/details?id=com.mousemove. (без рекламы и прочего, как собрал так и залил)

Серьёзные ошибки:
1. Строки 9, 32, 43, widget.cpp
Где удаление? Золотое правило — если есть new, то ОБЯЗАТЕЛЬНО должен быть delete. В Qt есть практика родителей (parent), т.е. когда родитель будет уничтожаться он удалит и своих потомков. Но Вы и её не используете. Еще рекомендую глянуть smart pointers, unique_ptr, в частности. http://ru.cppreference.com/w/cpp/memory/unique_ptr
2. Строка 46, widget.cpp
Шта эта такое? С тем же успехом Вы могли просто скопировать указатель, а не кастовать его, да еще и RTTI`ем. К ознакомлению обязательно: http://alenacpp.blogspot.com/2005/08/c.html https://en.wikipedia.org/wiki/Run-time_type_information
3. Строки 33, 38, widget.cpp
Похоже на какое-то ярое костылирование. Метод ресайз у вектора, в принципе, обычным программистам трогать не стоит, потому как они просадят производительность контейнера. Политика ресайза у вектора отлично работает и без ручного управления. Просто попробуйте добавлять елементы через push_back; а в std::vector рекомендую глянуть emplace_back, если знакомы с С++11. Так же вместо вызова ресайза можно вызвать конструктор, который принимает количество елементов. Я крайне рекомендую переписать этот метод.
1. Строки 6, 9, widget.cpp
2. widget.h, private section:
Как правило, приватные переменные помечаются в именах. ЕМНИП, Qt помечает их как m_variableName, Boost — variableName_
3. Строки 81-83, 121-122, widget.cpp
Нужно сохранять общую стилистику при написании кода.
4. Строки 131-133, widget.cpp. Попробуйте использовать шорткаты Qt creatora ctrl + a -> ctrl + i.
5. Строки 106-109, widget.cpp. Имена переменных должны быть понятны не только для писателя, но и для читателя. Немаловажно, что бы Вы могли прочитать написанный Вами код, скажем, через год быстро и могли его поправить\объяснить коллегам.
Я писал Жизнь на делфи, когда только начал изучать программирование. Ибо в реале играть было в нее сложно и косячно )
Можно ссылку на Java реализацию?
Отлично! Qt в массы!
Исходники где-нть посмотреть можно будет?
А если вынести всю графику в QML, а данные пробросить через свойства, то можно вполне быстро и красиво получить раздение модели и вида. Для пущей радости можно все это залить на гитхаб.

Топ 10 бесплатных сайтов по программированию
Программирование становится все более востребованным навыком в современном мире, и все больше людей стремятся научиться кодированию. Однако, не все готовы потратить деньги на покупку дорогостоящих учебных материалов или курсов. Счастливо, существует множество бесплатных ресурсов, которые помогут вам освоить программирование без лишних затрат. В этой статье мы рассмотрим топ 10 бесплатных сайтов по программированию, которые помогут вам начать свой путь в мире кодирования.
Codecademy (https://www.codecademy.com/)
Codecademy — один из наиболее популярных сайтов по программированию. Он предлагает широкий выбор курсов, которые охватывают различные языки программирования, включая Python, JavaScript, Ruby, HTML, CSS и многие другие. Все курсы Codecademy бесплатны, но для получения доступа к некоторым дополнительным возможностям сайта требуется платная подписка.
Coursera (https://www.coursera.org/)
Coursera — это образовательная платформа, которая предлагает бесплатные курсы по программированию, разработке веб-сайтов, анализу данных и многому другому. Среди доступных языков программирования на Coursera можно найти Python, Java, C++, JavaScript и многие другие. Курсы на Coursera разработаны университетами и компаниями по всему миру.
edX (https://www.edx.org/)
edX — это еще одна образовательная платформа, которая предлагает бесплатные онлайн-курсы по программированию и другим темам. Курсы на edX предоставляются ведущими университетами, включая MIT, Harvard и Berkeley. Среди языков программирования, которые можно изучить на edX, находятся Python, Java, C#, JavaScript и многие другие.
FreeCodeCamp (https://www.freecodecamp.org/)
FreeCodeCamp — это некоммерческая организация, которая предоставляет бесплатные курсы по программированию и разработке веб-сайтов. Она известна своим курсом по полному стеку веб-разработки, который включает HTML, CSS, JavaScript, Node.js и другие технологии.
W3Schools (https://www.w3schools.com/)
W3Schools — это сайт, который предоставляет обучение веб-технологиям, таким как HTML, CSS, JavaScript, SQL и многие другие. Этот сайт является очень популярным среди начинающих веб-разработчиков, так как он предлагает краткие и понятные уроки, а также многочисленные примеры кода.
Khan Academy (https://www.khanacademy.org/)
Khan Academy — это некоммерческая организация, которая предоставляет бесплатные образовательные ресурсы для учащихся всех возрастов. Она предлагает бесплатные курсы по программированию, включая JavaScript, HTML/CSS, SQL и многие другие. Khan Academy известна своими понятными и доступными уроками, которые могут быть использованы как начинающими, так и продвинутыми учащимися.
Mozilla Developer Network (https://developer.mozilla.org/)
Mozilla Developer Network (MDN) — это сайт, который предоставляет бесплатные ресурсы для разработчиков веб-сайтов. MDN предлагает обширную документацию по HTML, CSS, JavaScript и другим веб-технологиям, а также множество уроков и примеров кода.
Udacity (https://www.udacity.com/)
Udacity — это образовательная платформа, которая предоставляет бесплатные и платные курсы по программированию и другим техническим темам. Udacity сотрудничает с компаниями, такими как Google, Facebook и Amazon, чтобы создавать курсы, которые соответствуют современным требованиям рынка. Среди языков программирования, которые можно изучить на Udacity, находятся Python, Java, JavaScript и многие другие.
GitHub (https://github.com/)
GitHub — это онлайн-хранилище, которое используется разработчиками для хранения и совместной работы над проектами. На GitHub вы можете найти множество бесплатных проектов с открытым исходным кодом, а также участвовать в совместных проектах с другими разработчиками.
Stack Overflow (https://stackoverflow.com/)
Stack Overflow — это сайт, который предоставляет ответы на технические вопросы, связанные с программированием и другими техническими темами. Стек переполнение является очень полезным ресурсом для начинающих программистов, так как он содержит ответы на множество вопросов, которые могут возникнуть в процессе изучения программирования.
Большая подборка книг по программированию у нас в телеграме, подписывайся!

Ответ на пост «Как полезно и важно порой чувствовать себя идиотом»
На мой взгляд, проблема обучения программированию, что дают знания не обучая отладке, особенно в универе. Редко кто говорит о типичных ошибках начинающих программистов. Редко задачу дробят, чтобы объяснить (Особенно при вложенных циклах for). Никогда не забуду про операции с матрицами в C. Редко делают разбор нескольких сложных задач. На обычных курсах дают пару тривиальных примеров , а в домашнем задании примеры не соответствуют уровню пройденного занятия и сложность задач растет в геометрической прогрессии. Частая проблема новичков в невнимательности и преподавателей это сильно раздражает. Программирование на С/С++ в начале 00-ых мне далось очень тяжело, сказалось отсутствие ПК дома. Преподаватель по С (по совместительству бывший завкафедры и к.т.н) при группе сказал , что такие как я не доучиваются (ладно бы если сам про себя сказал,а тут публично считай назвали идиотом), но так получилось я всё-таки доучился , а те кто знал лучше меня C далеко не все доучились. Я тогда взял себе за правило, если буду когда-либо помогать в обучении, то всегда буду относиться обучающемуся с уважением (без снобизма, подколок), если возникает непонимание, переформулировать иначе и кроме того я обучал на реальных задачах. Если студент не понял и/или ошибается, это не только проблема студента, но и того кто обучает. Сейчас немного программирую на Python, занимаюсь парсингом при помощи xpath и regex. Иногда помогаю в изучении питона знакомому школьнику. По его словам обучение со мной оказалось гораздо интереснее и полезнее чем с преподавателем. К счастью или к сожалению, программирование начинается сколько не с программирования , а прежде всего с умения анализировать (в точнее умении декомпозировать задачу), умении сконцентрироваться и не отвлекаться (по началу списать код с учебника без ошибок сложно), многократном повторении пройденного материала, в самостоятельном поиске информации, умении задавать правильные вопросы и запросы в поисковой системе.

Ребята в недосягаемости

Первая встреча с ужасных кодом


Ответ на пост «Как программисты пишут код?»
Есть два типа людей: одни могут писать код, другие нет. Те, которые могут, делятся на еще на два: те, кто сразу видят решение, и те, кто итеративно работает.
Я в разработке с 2007 и всякого дерьма повидал. За сим есть, кой-чего сказать. Те, кто сразу видит решение, их меньшинство. По моему опыту, не более 10-15%. Остальные — только через итерации, либо фрагментарный подход.
Комплексный подход. Программист сразу пишет примерно 80% кода, можно сказать, на одном дыхании. Далее — косметика, марафет, отладка. В 99% первоначальный код не меняется. Это, имхо, — либо врожденная способность, либо нечто приобретенное в сильно раннем детстве.
Итеративный подход. Программист пытается хоть как-то решить задачу. Криво, косо, но решить. Далее начинаются итерации, которых может быть очень много в засимости от сложности и погружения разработчика в предметную область. Это обычный подход среднестатистического работника, коих на рынке большинство. Имхо, таких 75-80%.
Фрагментарный подход. Программист пытается декомпозировать код, ибо сразу он его обработать не в силах. Далее идет пошаговая реализация различных его кусков, после чего попытка связать все воедино, что, как правило в 99.99%, приводит к значительным переработкам ранее готовых кусков кода. Это следствие неприспособленности мозга. Т.е. человек может писать код, но ему это очень сложно дается.
От себя еще добавлю, что современная реальность требует всех видов людей: каждому программисту найдется место, если он все-таки по итогу выдает рабочий код.

Ответ на пост «Как программисты пишут код?»
Вопрос очень интересный.
Думаю, программирование тут вообще не причем. Это просто один частный пример применения определенных способностей. А в общем это умение создавать что то «своими руками». Лично я мыслю абсолютно одинаково и когда занимаюсь программированием и когда планирую ремонт дома. Честно говоря, это происходит полностью интуитивно. Я практически всегда знаю какие общие и конкретные шаги надо предпринимать чтобы достигнуть нужных целей и сразу же представляю где могут быть проблемы и уже отдельно обдумываю варианты их решения. Понятное дело тут не последнюю роль играет опыт, но когда 25 лет назад начинал на бейсике и паскале, то мыслил точно также.
С другой стороны, тут на пикабу, узнал что люди покупают наборы лего, собирают их точно по инструкции и ставят на полку(понятное дело что все люди разные и я не в негативном ключе пишу). Для меня в детстве это было бы немыслимо. Лего у меня не было, но был похожий отличный советский конструктор. Даже если бы у меня была к нему инструкция, то я бы все равно строил что то свое. Кстати мыслительный процесс при этом очень похож на программирование.
Ну и самое главное — меня прет от процесса. И думаю всех, кто считает себя серьезным профессионалом в своей сфере. И началось это с самого первого раза, когда я на спектрумовском бейсике часики рисовал. Это дает возможность не останавливаться, если что то не получается. Любой баг, причин которого ты не понимаешь, становится персональным врагом. И когда ты наконец решаешь проблему, то организм радует тебя дофамином.
Я очень сомневаюсь, что этим конкретным навыкам и способностям можно научить, как ни крути. Можно толь дать базу тем, у кого они уже есть — думаю на всяких инфоцыганских курсах «войти в ИТ» каких либо положительных результатах добиваются только граждане которые сразу программировать умеют, только еще не знают об этом)
А еще хороший программист всегда ленив. И это тоже напрямую влияет на умение «писать код». Основная и, не побоюсь этого слова, основополагающая проблема в любом серьезном проекте не как что то сделать, а каким образом это сделать в контексте уже написанного и так, чтобы в будущем тоже можно было что то изменить. Лень позволяет сразу планировать все так, чтобы минимизировать труд потом.
Резюмируя, думаю что есть некий набор врожденных качеств и способностей и позволяет некоторым успешно работать программистами. Собственно это верно и в любой другой сфере )))

Python — высокоуровневый язык, потому что у него высокий уровень заботы



Разработчикам на «плюсах» посвящается:

Ответ на пост «Чудо-курсы по ИТ»
Герой истории https://journal.tinkoff.ru/diary-razrabotchik-erevan/ похоже что реальный (судя по истории постов и комментов на TJ, которая явно нарабатывалась реальным человеком в течение нескольких лет), но история скорее всего приукрашена и довольно нереалистична.
Я тот самый типаж душного разработчика старой школы, который больше всего против подобных вкатываний. C++ программист (пришёл из Unix-админов), математическое образование, далее годами развивал джентльменский набор околоакадемического программиста того времени — POSIX и стандарты юниксов, *sh, Perl, ассемблер, C, C++, Lisp, экосистема GNU, на сдачу радио- и микроэлектроника как хобби, ну и естественно алгоритмы и прочий матан.
Так вот, чуть не 15 лет уже прошло, а я всё ещё закрываю зияющие дыры в знаниях во всём вышеперечисленном, и то по остаточному принципу, тк всё время съедают рабочая рутина и дети. Скиллы разработчика и смежные скиллы можно нарабатывать годами и всё равно не достичь совершенства или даже приемлемого уровня (в сложных областях).
И тут заявляется такой вкатывальщик, который учился 9 месяцев, потом сидел делал какие-то пет-проекты, и который после этого якобы сразу может вести коммерческий проект (за весьма хорошие деньги) и вообще приравнивается к профильному специалисту.
Нет. Просто нет. Даже с нагрузкой по учёбе 40 часов в неделю. Даже с мотивацией. Выучить типовые сценарии CI, git, ит.д., и на начальном уровне JS / TypeScript / . — может быть. Но НИ ОДИН человек с настолько мизерным опытом не может без посторонней помощи уверенно сопровождать коммерческий проект. Боюсь представить, какой адище творится внутри его части проекта. Я уж не говорю, что будет, если попросить его например выяснить, почему тормозит IDE на его машине, или спроектировать например версионирование компонента.
И безотносительно хард-скиллов (ок, предположим что он компьютерный гений, который стал фулстек-разработчиком за 9 месяцев + сколько-то месяцев работы над пет-проектами) — его карьерьные успехи вызывают очень много вопросов и больше напоминают ошибку выжившего. Вот про что умалчивается в посте:
— Нет диплома? До свидания. Крайне маловероятное совпадение, что герой получил работу в США не имея диплома и имея лишь завершённые курсы в активе. На хорошие вакансии и денежные проекты на удалёнке — очередь из исполнителей с профильным IT образованием (ну и десятки индусов и китайцев), а скиллы выпускников курсов стали притчей во языцех, работодатели уже в большинстве своём просекли ситуацию с курсами. Ну и вообще, диплом — простейший фильтр, довольно бесполезный для оценки хард-скиллс, но показывающий, что человек хотя бы умеет завершать долговременные дела не соскакивая. Работодателю проще не гадать, что там за специалист без диплома, а просто кинуть CV в мусорку и взять из стопки следующее, с дипломом. Исключения (самоучки) есть, но обычно им приходится долго и трудно доказывать профпригодность. Кстати при том, что герой рассказа не осилил универ и имел психологические проблемы и проблемы с мотивацией, мне очень, очень, очень слабо верится в то, что он магически излечился и фигачил на курсах 9 месяцев с большой нагрузкой.
— Нубы после выпуска становятся сами преподавателями тех же курсов? Уровень преподавания должен быть просто ниже плинтуса.
— Очень розовый пост. Расслабленная работа над проектом, вперемежку с поцелуйчиками, обнимашками и ресторанами. В жизни так не бывает. Нерешённая техническая проблема или близость просирания дедлайна будут залезать в свободное время и лишать психологического комфорта. Можно конечно работать строго от забора до обеда, но практика показывает, что хороших хард-скиллов при таком подходе ждать не стоит, да и возможно это только в крупных компаниях с размеренным графиком. Кстати лично мне странно, что в такой небольшой компании, как у героя, где разрабы судя по всему и сами себе девопсы и пмы, в посте ни разу не возникла ситуация дедлайна. .
— Заводить троих детей в чужой стране, где не живут родители и родственники? Ооочень наивно. Я могу по себе сказать, что такое *всего двое* мелких детей при релокации в страну Европы. Нет НИХРЕНА свободного времени. Пока они в садике/школе несколько часов, судорожно фигачишь работу, не успеваешь, затем под вечер кое-как доделываешь, пытаясь одновременно развлечь детей. Какие там сериалы или аналитические каналы, омг, сил потом хватает только на прогулку с плеером, или на потупить в соцсети, ну иногда на матч в третьих героев. Бонусом языковой барьер у детей.
— Жена в чужой стране без работы звереет. 🙂 Курсики по телеграм-каналам — это не работа. Если жена не в айти или там не в фарме и не может устроиться на работу — имеем очень неудачный расклад с отсутствием самореализации и зависимостью, который только усугубится с детьми. Отдельный печальный сценарий — если жена от безысходности идёт в это ваше IT каким-нибудь QA, что возможно даром ей бы не сдалось в более мягких условиях и редко когда коррелирует с её настоящими жизненными целями.
— Отдельным пунктом — разглагольствования про стартапы и гугл в посте ничего кроме раздражения лично у меня не вызывают.