Почему я пишу игры на C (да, на C). Исходные тексты игр (240) Почему C - лучший выбор для меня


Я - тот ещё фрукт. Все мои личные игровые проекты, которыми я занимался в последнее время, были написаны на «vanilla» C. Больше никто так не делает, поэтому, полагаю, вам может быть интересно узнать, почему я сделал такой выбор.
Написанное дальше содержит мнение о языках программирования, которое вам может не понравиться. Я предупреждал .

Что мне нужно от языка

Некоторые требования не подлежат обсуждению или поиску компромиссов. Во-первых, язык должен быть надёжным. Я не могу позволить себе тратить своё время на поиск ошибок, которых я не совершал.

Я написал большое количество игр на Flash, и вот, Flash умер. Я не хочу тратить время на портирование старых игр на современные платформы, я хочу писать новые игры. Мне нужна платформа, в долговечности которой я буду уверен.

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

Чего я хочу от языка

Самым важным, но не обязательным требованием является простота. Изучение особенностей языка и и его чудного «умного» API я нахожу безумно скучным. Идеальный язык можно однажды изучить и никогда больше не пользоваться его документацией.

Поиск и исправление багов истощает творческий потенциал. Я хочу оставлять меньше багов, потому желаю строгую типизацию, качественные сообщения об ошибках и статический анализ кода. Хочу упростить поиск ошибок, а, следовательно, хорошие отладочные средства и динамический анализ.

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

Ещё больше меня волнует скорость компилятора. Я не какой-нибудь буддийский мастер концентрации, и ждать больше 10 секунд - расточительство. Хуже того, это выбивает из потока. Вроде только глянул Twitter, а 5 минут куда-то пропали.

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

Альтернативы

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

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

C# и Java имеют схожие проблемы. Это многословные и сложные монстры, а мне нужен маленький простой зверёк. Оба языка направляют программиста прямиком в пучину ООП, а я против этого. Как и в большинстве высокоуровневых языков, многие сложные вещи тут скрываются так, что ничего не мешает случайно выстрелить себе в ногу.

Мне очень нравится Go. Во многих аспектах это изобретённый заново C, с поправкой на то, что до представления его публике он продумывался несколько лет. Я хотел бы использовать Go, но есть один огромный подводный камень - сборка мусора. Разработка игр на Go сомнительна, ведь сборщик мусора будет приостанавливать весь игровой мир, чего разработчик не может позволить. Также тут всё не очень хорошо с игровыми библиотеками. И хотя всегда можно приспособить для этого дела библиотеку на C, причём без особых проблем, это всё равно порождает много лишней работы. Кроме того, у меня есть сомнения насчёт перспектив. Go был бы неплох для веба, но это стремительно меняющаяся среда. Особенно это почувствовалось со смертью Flash.

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

Haxe выглядит гораздо перспективнее остальных языков в этом списке. У него нет проблем с библиотеками. Если я снова начну писать под веб, то обязательно познакомлюсь с ним поближе. Несколько беспокоит относительная молодость языка, будет ли он жить? Больше мне добавить нечего, с Haxe я успел лишь немного поиграться, не углубляясь.

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

Почему C - лучший выбор для меня

Хоть C и опасен, зато надёжен. Это очень острый нож, способный отрубить пальцы так же легко, как нарезать овощи. Но он прост, и научиться правильно его использовать труда не составит.

Он быстрый. А когда дело доходит до компиляции, я и представить себе не могу, что какой-то язык способен сделать это быстрее.
Есть возможность писать код так, чтобы он работал везде. И обычно это относительно несложно. Трудно представить, что однажды это изменится.

Есть отличная поддержка библиотек и инструментов.

Хоть меня это и несколько печалит, но C до сих пор остаётся лучшим языком для меня.

Я совсем не хочу сказать что-то вроде: «Эй, вы тоже должны писать на C». Я осознаю, что мои предпочтения весьма специфичны. Кроме того, по количеству, написанного мной на разных языках, код на «vanilla» C занимает лидирующую позицию, так что это уже часть моей зоны комфорта.

Так что да, C для меня - лучший выбор.

От переводчика

Перевод местами достаточно вольный, но не в ущерб смыслу или содержанию.

Подготовил перевод специально к пятнице, в этот день он особенно уместен, на мой взгляд. Хоть автор оригинального текста и пишет, кажется, на полном серьезе… вы сами всё понимаете, если прочитали. Как истину прочитанное воспринимать не стоит.

С предложениями, пожеланиями и замечаниями, как обычно, в ЛС.

Видеоигры на протяжении уже более 30 лет активно развиваются, начиная от геймплея и графических возможностей, вплоть до продвинутых систем искусственного интеллекта и восприятия пользователями подаваемого разработчиками материала. Киберспорт, в свою очередь, является полноценным видом спорта с многомиллионными призами и миллиардными оборотами. А сама индустрия видеоигр неустанно набирает обороты, являясь одной из наиболее кассовых и массовых ответвлений сферы развлечений. А ко всему этому человечество пришло от примитивнейшей реализации «тенниса», запускавшейся на осциллографе.


Я, как первокурсник, решил в рамках изучения базовых консольных возможностей C/C++ возможно запрограммировать "классическую" ASCII-игру, требующую от игрока скорости мышления, непрерывного взаимодействия с игрой, а также обладающую несложной графической частью, которую игрок мог бы интерпретировать в некое подобие трехмерного изображения.


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

Вдохновление

На заре появления на рынке мобильных телефонов с монохромными дисплеями выделилась игра «Space Impact», прошивавшаяся в аппаратах Nokia. Она отлично вписалась в ограниченные возможности тогдашних сотовых. После введения простых игр в мобильные телефоны – продажи аппаратов резко возросли, а мобильный гейминг постепенно стал популярным, и сейчас вполне конкурирует со индустрией «взрослых» ААА-проектов.


«Space Impact» и послужила основой для моей первой игры ввиду своей легкости исполнения и популярности в начале 2000-х.

Особенности «Space Invader»

В первую очередь – легковесность программы. Исполняемый файл занимает менее 100 кб и будет работать, как задумано, практически на любом компьютере под OC Windows с пакетом Visual C++.


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


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


Наипростейший интуитивно понятный интерфейс, не требующий описания или инструкций по использованию.


  • Подпункт «Новая игра» - запускает новый игровой сеанс.
  • Подпункт «Продолжить» - загружает последнюю сохраненную игру из бинарного файла в директории исполняемого файла и запускает игровой сеанс с использованием полученных данных.
  • Пункт «Помощь» - инструкции по работе с приложением и его описание. Пролистываются по нажатию стрелок на клавиатуре.
  • Пункт «Зал славы» - список лидеров игровых сессий, загружается из бинарного файла в директории исполняемого файла и форматированно выводится в консоль.
  • Пункт «Выход» - выход из приложения.
  • Сдвигающийся влево «мир» с динамической скоростью. «Мир» содержит рандомные поля сверху и снизу толщиной в 1 или 2 символа. Также между полями рандомно появляется «космический мусор», отображаемый символом «¤», являющийся препятствием для игрока.
  • Прижатый к левому краю окна «космический корабль». Корабль смещается с помощью стрелок на клавиатуре вверх и вниз, по нажатию пробела выпускает снаряд, уничтожающий «космический мусор» по касанию.
  • «Приборная панель» вверху консоли, отображающая пройденный километраж, текущую скорость и количество оставшихся попыток.
  • Как работает «Space Invader»?

    При запуске приложения возникает заставка-анимация. Она состоит из 6 заранее отформатированных символьных массивов, сменяющихся через каждые 200мс.


    Начальная заставка – итоговый слайд


    Далее идет обращение в функцию главного меню с параметром 1(целое число). Функция отображает меню с выделенным угловыми скобками пунктом меню, номер которого совпадает с входным параметром. Пунктов в меню 4, соответственно входной параметр может различаться от 1 до 4. При нажатии стрелки вниз происходит рекурсивное обращение с инкрементированным входным параметром в том случае, если входной параметр меньше 4, с параметром 1, если входной параметр равен 4. При нажатии Space или Enter происходит обращение к функции, соответствующей выделенному пункту меню.


    void StartMenu(int switcher) { system("cls"); switch (switcher) { case 1: cout << "\n\n\n << ИГРАТЬ! >> << "\n\n\n ИГРАТЬ!\n\n << ПОМОЩЬ! >> << "\n\n\n ИГРАТЬ!\n\n ПОМОЩЬ!\n\n << ЗАЛ СЛАВЫ >> << "\n\n\n ИГРАТЬ!\n\n ПОМОЩЬ!\n\n ЗАЛ СЛАВЫ\n\n << ВЫХОД >>"; break; } int choice = _getch(); if (choice == 224) choice = _getch(); if (choice == 72) if (switcher != 1) StartMenu(switcher - 1); else StartMenu(4); if (choice == 80) if (switcher != 4) StartMenu(switcher + 1); else StartMenu(1); if (choice == 13 || choice == 32) { if (switcher == 1) GameMenu(1); if (switcher == 2) Help(0); if (switcher == 3) TopChart(); if (switcher == 4) _exit(0); } }


    Главное меню (вход в функцию выполнен с параметром 1)


    При обращении к функции, соответствующей пункту «Игра» запускается функция, аналогичная по функционалу, но выбор есть только из 2-х пунктов. Соответственно, входной параметр будет 1 или 2, и при нажатии любой из стрелок (вверх или вниз) нам необходимо лишь сменить цифру на «противоположную». Наиболее оптимизированным будет вариант отнимания входного параметра от 3 (3 – 1 = 2, 3 – 2 = 1).


    void GameMenu(int switcher) { system("cls"); if (switcher == 1) cout << "\n\n\n\n\n << НОВАЯ ИГРА! >> << "\n\n\n\n\n НОВАЯ ИГРА!\n\n << ПРОДОЛЖИТЬ! >>"; int choice = _getch(); if (choice == 224) choice = _getch(); if (choice == 72 || choice == 80) GameMenu(3 - switcher); if (choice == 27) StartMenu(1); if (choice == 13 || choice == 32) Game(switcher); }


    Дополнительное меню (вход в функцию выполнен с параметром 1)


    Теперь к основному – процессу игры. При выборе подпункта «Новая игра» - запускается новый игровой сеанс. Создается двумерный массив, размерностью 14 строк на 50 столбцов. Первая строка выделяется под приборную панель. Первый прибор – количество пройденных километров, оно равно количеству обновлений консоли (изначально консоль обновляется раз в 80мс, с каждым обновлением этот параметр декрементируется, пока не достигнет значения 25).


    int odometerBuf = odometer, odometerDigitLength; for (odometerDigitLength = 0; odometerBuf != 0; odometerBuf /= 10, odometerDigitLength++);//вычисление количества цифр на одометре for (int i = odometerDigitLength, odometerBuf = odometer; i >= 0; i--, scr[i] = odometerBuf % 10 + "0", odometerBuf /= 10);//прорисовка одометра на приборную панель scr = "К"; scr = "М";//дописывание "КМ" odometer++;//наращение одометра

    Второй прибор, текущая скорость, являет собой формулу - 1000/скорость обновления консоли. Скорость измеряется в километрах в секунду. Таким образом, изначально корабль движется со скоростью 12км/с, и через некоторое время достигает отметки в 40 км/с.


    speed = 1000 / timer;//обновление спидометра int speedBuf = speed; for (int i = 42; speed != 0; i--, scr[i] = speed % 10 + "0", speed /= 10);//прорисовка спидометра на приборную панель scr = "К"; scr = "М"; scr = "/"; scr = "С";//дописывание "КМ/С"

    Третий прибор отображает количество оставшихся попыток, изначально их 3. Попытки отображаются символом «&».


    for (int i = 50; lifes > 0; i--, lifes--, scr[i] = "&");


    Приборная панель в начале игрового сеанса


    Следующие 2 строки, как и последние 2 – являются полями игры. Декорации полей выбираются случайным образом, могут состоять из 3-х символов или пробела. Крайние строки всегда полностью заполнены, а вторая и предпоследняя содержит символы, отличные от пробела лишь в тех местах, где эти символы «растут» из других.


    char borderSymbols = { "†", "‡", "¤", " " }; for (int aboveBelow = 0; aboveBelow < 50; aboveBelow++)//прорисовка верхнего и нижнего полей (2 + 2) { scr = borderSymbols; if (scr == "‡") scr = "¤"; scr = borderSymbols; if (scr == "‡") scr = "¤"; }


    Приборная панель и поля в начале игрового сеанса


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


    scr = "\\"; scr = "\\";//прорисовка корабля scr = "3"; scr = "="; scr = "="; scr = "/"; scr = "/";

    Управлять ним можно стрелками вверх и вниз.


    if (_kbhit())//если клавиша была нажата { control = _getch();//переменная примет ее значение if (control == 224) control = _getch(); } if (control == 72)//при движении корабля вверх if (scr == "\\" || scr == "\\" && scr == "¤" || scr == "\\" && scr == "¤")//если корабль врезался в верхнее поле - игра окончена if (lifes > 1) { cout << "\a"; lifes--; weaponPos = 7; GameStart(scr, lifes, &timer); Sleep(1000); } else GameOver(odometer); else { for (int i = 2; i < 13; i++)//корабль смещается на элемент выше for (int j = 0; j < 49; j++) if (scr[i][j] == "3" || scr[i][j] == "\\" || scr[i][j] == "=" || scr[i][j] == "/") { scr[j] = scr[i][j]; scr[i][j] = " "; } weaponPos--; } if (control == 80)//при движении корабля вниз if (scr == "/" || scr == "/" && scr == "¤" || scr == "/" && scr == "¤")//если корабль врезался в нижнее поле - игра окончена if (lifes > 1) { cout << "\a"; lifes--; weaponPos = 7; GameStart(scr, lifes, &timer); Sleep(1000); } else GameOver(odometer); else { for (int i = 12; i > < 49; j++) if (scr[i][j] == "3" || scr[i][j] == "\\" || scr[i][j] == "=" || scr[i][j] == "/") { scr[j] = scr[i][j]; scr[i][j] = " "; } weaponPos++; }


    Расположение корабля при двукратном нажатии стрелки вверх


    Отойдя от визуализации, стоит отметить, что вместе с кораблем мы смещаем вверх или вниз элемент массива, отвечающий за «дуло» корабля. Соответственно, из которого можно выпускать снаряды по нажатию пробела.


    Однако, ни поле, ни снаряды не двигаются, потому, чтобы «оживить» игровой процесс, по истечению таймера перерисовки экрана «мир» будет смещаться на один столбец влево, а снаряды на один столбец вправо. Корабль же остается на месте, пока игрок не захочет иного. Но после перерисовки теперь возникает один пустой столбец – заполним его случайным образом полями и «космическим мусором», который можно сбивать снарядами или боковыми отбойниками корабля.


    for (int i = 1; i < 14; i++)//все "космические" элементы смещаются на элемент влево for (int j = 0; j < 49; j++) { if (scr[i][j] == "\\" && scr[i] == "¤" || scr[i][j] == "=" && scr[i] == "¤" || scr[i][j] == "/" && scr[i] == "¤") if (lifes > 1) { cout << "\a"; lifes--; weaponPos = 7; GameStart(scr, lifes, &timer); Sleep(1000); } else GameOver(odometer); if (scr[i][j] != "3" && scr[i][j] != "\\" && scr[i][j] != "=" && scr[i][j] != "/" && scr[i][j] != "-" && scr[i] != "-") scr[i][j] = scr[i]; if (scr[i][j] == "¤") scr[i] = " "; } for (int i = 1; i < 14; i++)//все снаряды смещаются на элемент вправо for (int j = 48; j > < 12; i++)//рандомное появление космического мусора { if (rand() % 10 == 1) scr[i] = "¤"; }

    При движении корабля вверх или вниз в этот момент космический мусор будет уничтожен без урона кораблю, все благодаря отбойникам



    Если в данный момент не увернуться от мусора стрелкой вниз – корабль будет разбит


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


    Впрочем, хоть поля состоят из материалов, схожих на космический мусор, их нельзя разрушить отбойниками. Соприкосновение любой части корабля с полями приводит к неминуемому краху. Также, как и попадание космического мусора в лицевую часть корабля. Если в такие моменты на приборной панели есть обозначение еще хотя бы одной «попытки» - игра как будто начинается сначала, но сохранив набранный счет и потеряв одну «попытку». Скорость же сбрасывается до начального значения в 12км/с.


    Если же игрок «разбился», а попыток не осталось – игровой сеанс заканчивается, а игроку предлагается ввести его имя, чтобы сохранить свой результат в «зале славы».



    Предложение игроку ввести имя, чтобы сохранить свой результат в «зале славы»


    Приложение обрабатывает 2 файла:

    • «TopChart.bin» - двоичный файл для хранения таблицы лидеров. Данные хранятся в структурах (ник игрока, его счет, дата завершения игрового сеанса). Данные дозаписываются в конец файла при окончании игры. При вызове пункта «Зал славы» файл открывается для чтения с возможностью редактирования. Далее объявляется динамический массив структур, в который переписываются данные из файла, после чего массив сортируется и форматированно выводится в консоль (максимально возможное число результатов – 12, если массив содержит 13 результатов – последний отбрасывается после сортировки). Далее файл перезаписывается массивом структур результатов, после чего массив уничтожается.
    • «CurrentSave.bin» - двоичный файл для хранения сохраненной игры. Вызывается для чтения при запуске подпункта «Продолжить» пункта «Игра». Может содержать данные для восстановления одного незавершенного игрового сеанса: номер строки, в которой содержится нос корабля, количество пройденных километров, количество оставшихся попыток, расположение обьектов на экране. С помощью этих данных формируется игровой сеанс, в точности повторяющий незавершенный. Во избежание нечестной игры, при загрузке сеанса из файла – файл удаляется. При нажатии Escape во время игры данный файл создается, и в него записываются все необходимые данные для успешного дальнейшего продолжения игрового сеанса.

    Пункт главного меню «Помощь» - функция, принимающая параметр от 0 до 22, отображающая последующие 12 строк по 50 символов от входного параметра. Управление осуществляется рекурсивно с помощью стрелок вверх и вниз.


    void Help(int switcher) { system("cls"); cout << "ПРОКРУТКА: СТРЕЛКИ ВВЕРХ/ВНИЗ | ВЕРНУТЬСЯ: ESCAPE\n"; char arr = { " УПРАВЛЕНИЕ В МЕНЮ Передвигаться по пунктам – СТРЕЛКИ ВВЕРХ/ВНИЗ Выбрать пункт – ПРОБЕЛ или ENTER Вернуться в предыдущее меню – ESCAPE УПРАВЛЕНИЕ В ИГРЕ Передвигаться вверх/вниз – СТРЕЛКИ ВВЕРХ/ВНИЗ Сделать выстрел – ПРОБЕЛ Вернуться в меню, сохранив игру – ESCAPE БРИФИНГ Вы – пилот космического корабля, попавшего в космическую бурю. Вам необходимо не разбиться и пролететь как можно большее расстояние. Корабль оборудован динамическим управлением. Чем быстрее вы летите – тем острее поворачивает судно. Корабльавтоматически постепенно разгоняется до 40 км/с. Вы можете сбивать космический мусор с помощью магнитной пушки, встроенной в судно, а также боковыми отбойниками. При управлении кораблем на щитке приборов отображается пройденная дистанция, текущая скорость и количество оставшихся «ячеек отката» (отображаются символом «&»), изначально их 3. Если решите прекратить игру – просто нажмите ESCAPE. Игра сохранится, и вы сможете ее продолжить даже после перезапуска приложения с помощью пункта «ПРОДОЛЖИТЬ!». В главном меню можно посмотреть таблицу почетных пилотов. Добейтесь своего права там оказаться! АВТОРСТВО Svjatoslav Laskov – AUTHOR Igor Marchenko – COACH National Technical University «Kharkiv Polytechnic Institute» 2016" }; for (int i = 0, buf = switcher; i < 13; i++) { for (int j = buf * 50; j < buf * 50 + 50; j++) cout << arr[j]; if (i != 12) cout << endl; buf++; } int controller = _getch();//получить значение нажатой клавиши if (controller == 224)//если была нажата стрелка controller = _getch();//то определить какая именно if (controller == 72)//если стрелка вверх if (switcher > < 22) Help(switcher + 1); else Help(22); if (controller == 27)//если Escape StartMenu(2); }

    Пункт главного меню «Выход» - осуществляет выход из приложения.


    Руководство пользователя

    УПРАВЛЕНИЕ В МЕНЮ
    o Передвигаться по пунктам – СТРЕЛКИ ВВЕРХ/ВНИЗ
    o Выбрать пункт – ПРОБЕЛ или ENTER
    o Вернуться в предыдущее меню – ESCAPE
    УПРАВЛЕНИЕ В ИГРЕ
    o Передвигаться вверх/вниз – СТРЕЛКИ ВВЕРХ/ВНИЗ
    o Сделать выстрел – ПРОБЕЛ
    o Вернуться в меню, сохранив игру – ESCAPE
    БРИФИНГ
    Вы – пилот космического корабля, попавшего в космическую бурю. Вам необходимо не разбиться и пролететь как можно большее расстояние.


    Корабль оборудован динамическим управлением. Чем быстрее вы летите – тем острее поворачивает судно. Корабль автоматически постепенно разгоняется до 40 км/с.


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


    Если решите прекратить игру – просто нажмите ESCAPE. Игра сохранится, и вы сможете ее продолжить даже после перезапуска приложения с помощью пункта «ПРОДОЛЖИТЬ!».


    В главном меню можно посмотреть таблицу почетных пилотов. Добейтесь своего права там оказаться!

    Итог

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


    Хотелось еще разделить приложение на несколько потоков для более корректной реакции на нажатие клавиш пользователем, но треды я реализую уже в следующем проекте на С++.


    Исходный код

    #include "conio.h" #include "windows.h" #include "ctime" #include using namespace std; struct player//определение структуры, хранящей данные о результатах какого-либо завершенного игрового сеанса { char name; int score; int mday; int mon; int year; }; struct save//определение структуры, хранящей данные о незавершенном игровом сеансе { int weaponPos; int timer; int odometer; int lifes; char scr; }; void ScreenOutput(char scr)//функция поэлементного вывода массива в консоль { system("cls"); for (int i = 0; i < 14; i++) { for (int j = 0; j < 50; j++) cout << scr[i][j]; if (i != 13) cout << endl; } } //блок прототипов функций void StartMenu(int switcher);//функция, вызывающаяся из главного меню, содержит пункты "ИГРА" и "ПРОДОЛЖИТЬ" void GameMenu(int switcher);//функция главного меню void GameStart(char scr, int lifes, int *timer);//функция, определяющая начальный символьный массив при запуске нового игрового сеанса void Game(int var);//функция игровго сеанса void GameOver(int score);//функция, спрашивающая имя игрока, и записывающая его результат в бинарный файл void Help(int switcher);//функция помощи игроку void TopChart();//функция "ЗАЛ СЛАВЫ" - отображает список лидеров void Help(int switcher) { system("cls"); cout << "ПРОКРУТКА: СТРЕЛКИ ВВЕРХ/ВНИЗ | ВЕРНУТЬСЯ: ESCAPE\n"; char arr = { " УПРАВЛЕНИЕ В МЕНЮ Передвигаться по пунктам – СТРЕЛКИ ВВЕРХ/ВНИЗ Выбрать пункт – ПРОБЕЛ или ENTER Вернуться в предыдущее меню – ESCAPE УПРАВЛЕНИЕ В ИГРЕ Передвигаться вверх/вниз – СТРЕЛКИ ВВЕРХ/ВНИЗ Сделать выстрел – ПРОБЕЛ Вернуться в меню, сохранив игру – ESCAPE БРИФИНГ Вы – пилот космического корабля, попавшего в космическую бурю. Вам необходимо не разбиться и пролететь как можно большее расстояние. Корабль оборудован динамическим управлением. Чем быстрее вы летите – тем острее поворачивает судно. Корабльавтоматически постепенно разгоняется до 40 км/с. Вы можете сбивать космический мусор с помощью магнитной пушки, встроенной в судно, а также боковыми отбойниками. При управлении кораблем на щитке приборов отображается пройденная дистанция, текущая скорость и количество оставшихся «ячеек отката» (отображаются символом «&»), изначально их 3. Если решите прекратить игру – просто нажмите ESCAPE. Игра сохранится, и вы сможете ее продолжить даже после перезапуска приложения с помощью пункта «ПРОДОЛЖИТЬ!». В главном меню можно посмотреть таблицу почетных пилотов. Добейтесь своего права там оказаться! АВТОРСТВО Svjatoslav Laskov – AUTHOR Igor Marchenko – COACH National Technical University «Kharkiv Polytechnic Institute» 2016" }; for (int i = 0, buf = switcher; i < 13; i++) { for (int j = buf * 50; j < buf * 50 + 50; j++) cout << arr[j]; if (i != 12) cout << endl; buf++; } int controller = _getch();//получить значение надатой клавиши if (controller == 224)//если была нажата стрелка controller = _getch();//то определить какая именно if (controller == 72)//если стрелка вверх if (switcher > 0) Help(switcher - 1); else Help(0); if (controller == 80)//если стрелка вниз if (switcher < 22) Help(switcher + 1); else Help(22); if (controller == 27)//если Escape StartMenu(2); } void StartMenu(int switcher) { system("cls"); switch (switcher) { case 1: cout << "\n\n\n << ИГРАТЬ! >>\n\n ПОМОЩЬ!\n\n ЗАЛ СЛАВЫ\n\n ВЫХОД"; break; case 2: cout << "\n\n\n ИГРАТЬ!\n\n << ПОМОЩЬ! >>\n\n ЗАЛ СЛАВЫ\n\n ВЫХОД"; break; case 3: cout << "\n\n\n ИГРАТЬ!\n\n ПОМОЩЬ!\n\n << ЗАЛ СЛАВЫ >>\n\n ВЫХОД"; break; case 4: cout << "\n\n\n ИГРАТЬ!\n\n ПОМОЩЬ!\n\n ЗАЛ СЛАВЫ\n\n << ВЫХОД >>"; break; } int choice = _getch(); if (choice == 224) choice = _getch(); if (choice == 72) if (switcher != 1) StartMenu(switcher - 1); else StartMenu(4); if (choice == 80) if (switcher != 4) StartMenu(switcher + 1); else StartMenu(1); if (choice == 13 || choice == 32) { if (switcher == 1) GameMenu(1); if (switcher == 2) Help(0); if (switcher == 3) TopChart(); if (switcher == 4) _exit(0); } } void GameMenu(int switcher) { system("cls"); if (switcher == 1) cout << "\n\n\n\n\n << НОВАЯ ИГРА! >>\n\n ПРОДОЛЖИТЬ!"; else cout << "\n\n\n\n\n НОВАЯ ИГРА!\n\n << ПРОДОЛЖИТЬ! >>"; int choice = _getch(); if (choice == 224) choice = _getch(); if (choice == 72 || choice == 80) GameMenu(3 - switcher); if (choice == 27) StartMenu(1); if (choice == 13 || choice == 32) Game(switcher); } void GameStart(char scr, int lifes, int *timer) { for (int i = 0; i < 14; i++)//очищение от мусора for (int j = 0; j < 50; j++) scr[i][j] = " "; for (int i = 50; lifes > 0; i--, lifes--, scr[i] = "&"); *timer = 80; char borderSymbols = { "†", "‡", "¤", " " }; for (int aboveBelow = 0; aboveBelow < 50; aboveBelow++)//прорисовка верхнего и нижнего полей (2 + 2) { scr = borderSymbols; if (scr == "‡") scr = "¤"; scr = borderSymbols; if (scr == "‡") scr = "¤"; } scr = "\\"; scr = "\\";//прорисовка корабля scr = "3"; scr = "="; scr = "="; scr = "/"; scr = "/"; } void GameOver(int score) { system("cls"); player newPlayer;//объявляние структуры newPlayer.score = score;//инициализацие поля набранного счета cout << "Поздравляем Вас!\nВы продержались " << score << " километров.\n\n(Пожалуйста, не используйте кириллические символы)\n(Используйте не более 6 символов)\nОставьте свое имя и станьте примером\nдля подражания будущим игрокам: "; cin.getline(newPlayer.name, 7);//инициализацие поля имени time_t timeCur; time(&timeCur); struct tm * timeCurStruct = localtime(&timeCur); newPlayer.mday = timeCurStruct->tm_mday;//инициализацие даты завершения игры newPlayer.mon = timeCurStruct->tm_mon; newPlayer.year = timeCurStruct->tm_year; FILE *topChart; fopen_s(&topChart, "TopChart.bin", "ab+"); fwrite(&newPlayer, 1, sizeof(player), topChart);//дозапись результата в файл fclose(topChart); TopChart(); } void TopChart() { FILE *topChart; fopen_s(&topChart, "TopChart.bin", "rb+"); system("cls"); if (topChart == NULL)//если произошла ошибка при открытии файла { system("cls"); cout << "Нет ни единого результата."; Sleep(1000); system("cls"); cout << "Нет ни единого результата.."; Sleep(1000); system("cls"); cout << "Нет ни единого результата..."; Sleep(1000); cout << "\nНажмите любую клавишу, чтобы вернуться."; _getch(); StartMenu(3); } fseek(topChart, 0L, SEEK_END); int playerAmount = ftell(topChart) / sizeof(player); player *temp = new player; fseek(topChart, 0L, SEEK_SET); for (int i = 0; i < playerAmount; i++)//копирование содержиомого файла в структкры fread(&temp[i], 1, sizeof(player), topChart); fclose(topChart); for (int i = 1; i < playerAmount; i++)//сортировка структур по спаданию итоговых счетов if (temp[i].score > temp.score) { player tempAlone; strcpy(tempAlone.name, temp[i].name); tempAlone.score = temp[i].score; tempAlone.mday = temp[i].mday; tempAlone.mon = temp[i].mon; tempAlone.year = temp[i].year; strcpy(temp[i].name, temp.name); temp[i].score = temp.score; temp[i].mday = temp.mday; temp[i].mon = temp.mon; temp[i].year = temp.year; strcpy(temp.name, tempAlone.name); temp.score = tempAlone.score; temp.mday = tempAlone.mday; temp.mon = tempAlone.mon; temp.year = tempAlone.year; if (i > 1) i -= 2; else i = 0; } if (playerAmount > 12) playerAmount = 12; cout << "№ " << "Имя" << "\t" << "Счет" << "\t" << "Дата" << endl;//вывод таблицы лидеров в консоль for (int i = 0; i < playerAmount; i++) { cout << i + 1 << ")" << "\t" << temp[i].name << "\t" << temp[i].score << "\t"; if (temp[i].mday / 10 == 0) cout << "0" << temp[i].mday; else cout << temp[i].mday; cout << " "; switch (temp[i].mon) { case 0: cout << "января"; break; case 1: cout << "февраля"; break; case 2: cout << "марта"; break; case 3: cout << "апреля"; break; case 4: cout << "мая"; break; case 5: cout << "июня"; break; case 6: cout << "июля"; break; case 7: cout << "августа"; break; case 8: cout << "сентября"; break; case 9: cout << "октября"; break; case 10: cout << "ноября"; break; case 11: cout << "декабря"; break; } cout << " " << 1900 + temp[i].year << endl; } fopen_s(&topChart, "TopChart.bin", "wb+"); for (int i = 0; i < playerAmount; i++)//запись таблицы лидеров в бинарный файл fwrite(&temp[i], 1, sizeof(player), topChart); fclose(topChart); delete temp; _getch(); StartMenu(3); } int main() { setlocale(LC_ALL, "Rus");//задание кодировки system("mode con cols=51 lines=14");//задание размеров окна консоли system("title Space Invader");//задание описания окна консоли system("color 0A");//задание цвета консоли (0-задний фон; А-передний фон) HANDLE hCons = GetStdHandle(STD_OUTPUT_HANDLE);//получение хендла CONSOLE_CURSOR_INFO cursor = { 100, false };//число от 1 до 100 размер курсора в процентах; false\true - видимость SetConsoleCursorInfo(hCons, &cursor);//применение заданных параметров курсора int timer = 200; cout << " (____/(__) \\_/\\_/ \\___)(____)\n\n\n\n\n\n\n\n\n\n\n __ __ _ _ _ __ ____ ____ ____\n ()((\\/)(\\ / _\\ (\\(__)(_ \\";//вступительная заставка Sleep(timer); system("cls"); cout << " \\___ \\) __// \\((__) _)\n (____/(__) \\_/\\_/ \\___)(____)\n\n\n\n\n\n\n\n\n __ __ _ _ _ __ ____ ____ ____\n ()((\\/)(\\ / _\\ (\\(__)(_ \\\n)(/ /\\ \\/ // \\) D () _)) /";//вступительная заставка Sleep(timer); system("cls"); cout << " / ___)(_ \\ / _\\ / __)(__)\n \\___ \\) __// \\((__) _)\n (____/(__) \\_/\\_/ \\___)(____)\n\n\n\n\n\n\n __ __ _ _ _ __ ____ ____ ____\n ()((\\/)(\\ / _\\ (\\(__)(_ \\\n)(/ /\\ \\/ // \\) D () _)) /\n (__)\\_)__) \\__/ \\_/\\_/(____/(____)(__\\_)";//вступительная заставка Sleep(timer); system("cls"); cout << " ____ ____ __ ___ ____\n / ___)(_ \\ / _\\ / __)(__)\n \\___ \\) __// \\((__) _)\n (____/(__) \\_/\\_/ \\___)(____)\n\n\n\n\n __ __ _ _ _ __ ____ ____ ____\n ()((\\/)(\\ / _\\ (\\(__)(_ \\\n)(/ /\\ \\/ // \\) D () _)) /\n (__)\\_)__) \\__/ \\_/\\_/(____/(____)(__\\_)";//вступительная заставка Sleep(timer); system("cls"); cout << "\n ____ ____ __ ___ ____\n / ___)(_ \\ / _\\ / __)(__)\n \\___ \\) __// \\((__) _)\n (____/(__) \\_/\\_/ \\___)(____)\n\n\n __ __ _ _ _ __ ____ ____ ____\n ()((\\/)(\\ / _\\ (\\(__)(_ \\\n)(/ /\\ \\/ // \\) D () _)) /\n (__)\\_)__) \\__/ \\_/\\_/(____/(____)(__\\_)";//вступительная заставка Sleep(timer); system("cls"); cout << "\n\n ____ ____ __ ___ ____\n / ___)(_ \\ / _\\ / __)(__)\n \\___ \\) __// \\((__) _)\n (____/(__) \\_/\\_/ \\___)(____)\n __ __ _ _ _ __ ____ ____ ____\n ()((\\/)(\\ / _\\ (\\(__)(_ \\\n)(/ /\\ \\/ // \\) D () _)) /\n (__)\\_)__) \\__/ \\_/\\_/(____/(____)(__\\_)";//вступительная заставка cout << "\a"; Sleep(10 * timer);//задержка заставки StartMenu(1); return 0; } void Game(int var) { int weaponPos;//позиция строки дула в массиве int timer;//задержка между перерисовками экрана int odometer;//количество перерисовок экрана, они же итоговые очки int lifes;//количество жизней char control = "&";//переменная управления кораблем int shotPause = 4;//задержка между выстрелами (указывать на одну перерисовку больше) int speed;//скорость корабля char scr; if (var == 1) { weaponPos = 7;//позиция строки дула в массиве odometer = 1;//количество перерисовок экрана, они же итоговые очки lifes = 3;//количество жизней GameStart(scr, lifes, &timer); } else//при восстановлении игрового сеанса из сохранения { FILE *saveBin; fopen_s(&saveBin, "CurrentSave.bin", "rb"); if (!saveBin) { system("cls"); cout << "Нет сохранения."; Sleep(1000); system("cls"); cout << "Нет сохранения.."; Sleep(1000); system("cls"); cout << "Нет сохранения..."; Sleep(1000); Game(1); } fread(&weaponPos, 1, sizeof(int), saveBin); timer = 80; fread(&odometer, 1, sizeof(int), saveBin); fread(&lifes, 1, sizeof(int), saveBin); fread(&scr, 14 * 50, sizeof(char), saveBin); fclose(saveBin); remove("CurrentSave.bin"); } while (true) { int odometerBuf = odometer, odometerDigitLength; for (odometerDigitLength = 0; odometerBuf != 0; odometerBuf /= 10, odometerDigitLength++);//вычисление количества цифр на одометре for (int i = odometerDigitLength, odometerBuf = odometer; i >= 0; i--, scr[i] = odometerBuf % 10 + "0", odometerBuf /= 10);//прорисовка одометра на приборную панель scr = "К"; scr = "М";//дописывание "КМ" odometer++;//наращение одометра speed = 1000 / timer;//обновление спидометра int speedBuf = speed; for (int i = 42; speed != 0; i--, scr[i] = speed % 10 + "0", speed /= 10);//прорисовка спидометра на приборную панель scr = "К"; scr = "М"; scr = "/"; scr = "С";//дописывание "КМ/С" if (_kbhit())//если клавиша была нажата { control = _getch();//переменная примет ее значение if (control == 224) control = _getch(); } if (control == 13 && shotPause == 4 || control == 32 && shotPause == 4)//при нажатии на курок если пушка перезаряжена { scr = "-"; shotPause = 0; } if (shotPause < 4)//перезарядка shotPause++; if (control == 27)//при выходе { FILE *saveBin; fopen_s(&saveBin, "CurrentSave.bin", "wb"); fwrite(&weaponPos, 1, sizeof(int), saveBin); fwrite(&odometer, 1, sizeof(int), saveBin); fwrite(&lifes, 1, sizeof(int), saveBin); fwrite(&scr, 14 * 50, sizeof(char), saveBin); fclose(saveBin); GameMenu(2); } if (control == 72)//при движении корабля вверх if (scr == "\\" || scr == "\\" && scr == "¤" || scr == "\\" && scr == "¤")//если корабль врезался в верхнее поле - игра окончена if (lifes > 1) { cout << "\a"; lifes--; weaponPos = 7; GameStart(scr, lifes, &timer); Sleep(1000); } else GameOver(odometer); else { for (int i = 2; i < 13; i++)//корабль смещается на элемент выше for (int j = 0; j < 49; j++) if (scr[i][j] == "3" || scr[i][j] == "\\" || scr[i][j] == "=" || scr[i][j] == "/") { scr[j] = scr[i][j]; scr[i][j] = " "; } weaponPos--; } if (control == 80)//при движении корабля вниз if (scr == "/" || scr == "/" && scr == "¤" || scr == "/" && scr == "¤")//если корабль врезался в нижнее поле - игра окончена if (lifes > 1) { cout << "\a"; lifes--; weaponPos = 7; GameStart(scr, lifes, &timer); Sleep(1000); } else GameOver(odometer); else { for (int i = 12; i >= 2; i--)//корабль смещается на элемент вниз for (int j = 0; j < 49; j++) if (scr[i][j] == "3" || scr[i][j] == "\\" || scr[i][j] == "=" || scr[i][j] == "/") { scr[j] = scr[i][j]; scr[i][j] = " "; } weaponPos++; } for (int i = 1; i < 14; i++)//все "космические" элементы смещаются на элемент влево for (int j = 0; j < 49; j++) { if (scr[i][j] == "\\" && scr[i] == "¤" || scr[i][j] == "=" && scr[i] == "¤" || scr[i][j] == "/" && scr[i] == "¤") if (lifes > 1) { cout << "\a"; lifes--; weaponPos = 7; GameStart(scr, lifes, &timer); Sleep(1000); } else GameOver(odometer); if (scr[i][j] != "3" && scr[i][j] != "\\" && scr[i][j] != "=" && scr[i][j] != "/" && scr[i][j] != "-" && scr[i] != "-") scr[i][j] = scr[i]; if (scr[i][j] == "¤") scr[i] = " "; } for (int i = 1; i < 14; i++)//все снаряды смещаются на элемент вправо for (int j = 48; j >= 0; j--) if (scr[i][j] == "-") if (j != 48) { scr[i] = "-"; scr[i][j] = " "; } else scr[i][j] = " "; char borderSymbols = { "†", "‡", "¤", " " }; scr = " ";//рандомное заполнение новых элементов краев scr = borderSymbols; if (scr == "‡") scr = "¤"; scr = " "; scr = borderSymbols; if (scr == "‡") scr = "¤"; for (int i = 3; i < 12; i++)//рандомное появление космического мусора { if (rand() % 10 == 1) scr[i] = "¤"; } ScreenOutput(scr);//вывод экрана if (control != "&")//"обнуление" управляющей переменной control = "&"; if (timer > 25)//ускорение корабля timer--; Sleep(timer);//задержка перерисовки } }


    Загрузить исполняемый файл.exe можно тут.

    Создание игры для Linux с помощью Weaver Framework

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

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

    Сайт проекта находится по адресу http://weaver.nongnu.org/ , там же можно получить уже собранный пакет в формате.deb либо тарбол с исходным кодом, для не debian-based систем. Итак, приступим.

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

    weaver

    В ходе создания проекта Будет создана иерархия директорий, предназначенных для хранения графических, звуковых и исходного кода файлов. Для начала, чтобы потренироваться, создадим проект test. Он будет использоваться для изучения возможностей weaver.

    2.1. Создание пустого проекта

    weaver test

    Несколько слов о содержимом директории проекта.

    test

    |-fonts

    |-images

    |-music

    |-sounds

    | |-weaver

    LICENSE

    Makefile

    В принципе, названия говорят сами за себя. Упомяну лишь о /src , здесь содержатся как непосредственно файлы проекта ( game.c , game.h - основные), так и (в директории /weaver ) файлы самого фреймворка необходимые для удачной сборки проекта.

    Рассмотрим файл /src/game.c подробнее. Он является основным файлом проекта и именно в нем находиться главный цикл программы.

    int main(int argc, char **argv)

    Awake_the_weaver(); // Initializing Weaver API

    // Main loop

    For(;;){

    Get_input();

    If(keyboard){

    Break;

    Weaver_rest(10000000);

    May_the_weaver_sleep();

    Return 0;

    Функции

    awake_the_weaver()

    may_the_weaver_sleep()

    разрешают и запрещают использование функций из фреймворка. Между ними находится главный цикл программы.

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

    weaver_rest(n) предназначена для приостановки программы на некоторое количество наносекунд, эта же функция отвечает и за FPS, чем ниже число n, тем выше FPS.

    Для сборки проекта набираем в консоли команду make и ждем удачного завершения.

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/display.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/keyboard.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/vector2.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/vector3.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/vector4.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/weaver.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/sound.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/image.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/weaver/font.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -c src/game.c

    gcc -Wall -O2 -g $(freetype-config --cflags) -g -o test display.o keyboard.o vector2.o vector3.o vector4.o weaver.o sound.o image.o font.o game.o -lX11 -lXext -lm -lvorbisfile -lasound -lpng -lfreetype $(freetype-config --cflags)

    Удача. Запустим проект:

    ./test

    Вот и все! Нажатие любой клавиши закрывает игру.

    2.2. Работа с клавиатурой

    Состояние клавиатуры содержит глобальная переменная keyboard . Узнать нажата ли нужная нам клавиша можно таким нехитрым образом:

    if (keyboard[])

    // что-нибудь происходит

    Если нужно чтобы программа реагировала на сочетание клавиш, используем Логическое-И:

    if (keyboard[] && keyboard[])

    //что-нибудь да произойдет!

    Теперь изменим программу так, чтобы выход производился по нажатию Ctrl-Q.

    Изменим

    if(keyboard)

    Break;

    на

    if(keyboard && keyboard[Q])

    Break;

    Рискнем собрать и запустить игру. Теперь для выхода нам необходимо нажать комбинацию клавиш Ctrl-Q.

    2.3. Рисование графических примитивов

    Для рисования примитивов служат функции:

    draw_circle()

    draw_ellipse()

    draw_line()

    draw_point()

    draw_rectangle()

    Для заливки примитово используются функции

    fill_circle()

    fill_ellipse()

    fill_rectangle()

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

    Не будем стоять на месте и нарисуем желтый квадрат с координатами (x=50, y=150) и размером 125x125. В основной цикл добавим такую запись:

    draw_rectangle(50, 150, 125, 125, YELLOW);

    где YELLOW - цвет нашего квадрата.

    Собираем проект и смотрим что получилось.

    Не то, чего мы ожидали, правда?

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

    fill_rectangle(50, 150, 125, 125, YELLOW);

    Пробуем и смотрим!

    На этот раз у нас все вышло. Теперь попробуем объединить наши знания об использовании клавиатуры и рисовании примитивов. Но перед этим немного оптимизируем нашу программу, создав сам квадрат как переменную.

    rectangle является структурой, с 4 параметрами ось x (параметр x), ось y (параметр y), ширина (параметр w) и высота (параметр z).

    Создадим же переменную типа rectangle, добавив после инициализации weaver (напомню, это awake_the_weaver()):

    rectangle rect1;

    Затем инициализируем ее параметры:

    rectangle rect1;

    rect1.x=50;

    rect1.y=150;

    rect1.w=rect1.z=125;

    Зададим обработку для нажатия клавиш <стрелка-влево> и <стрелка-вправо> клавиатуры.

    If (keyboard)

    Rect1.x-=10;

    If (keyboard)

    Rect1.x+=10;

    А затем и рисование самого квадрата:

    draw_rectangle(rect1.x, rect1.y, rect1.w, rect1.z, YELLOW);

    Отмечу, что инициализация параметров переменной должна находлиться до главного цикла отрисовки, т.е. до for(;;).

    Соберем и запустим наш проект.

    И опять не то, что мы хотели бы видеть?

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

    fill_screen()

    принимающая в качестве параметра цвет заливки. Добавим ее перед рисованием квадрата и снова посмотрим, что получится.

    Удача! Перейдем к загрузке и рисованию изображений.

    Единственный возможный в weaver формат изображения - . png , его и будем использовать. Нужно помнить, что все изображения должны находиться в директории /images .

    Для начала объявим и инициализируем указатель на тип surface, представляющий изображения:

    surface *face=new_image("face.png");

    Функция new_image() принимает один аргумент - название графического файла.

    Для отображения графического файла существует функция

    draw_surface()

    принимающая в качестве аргументов указатель на изображаемый графический файл ( surface *origin ), указатель на то, где будет изображаться ( surface *destiny ) и координаты (x, y ).

    Графические файлы можнно рисовать на других графических файлах, чтобы рисовать просто на экране, в качестве параметра destiny нужно указывать window, это и есть указатель на текущее окно рисования.

    Уберем лишние упоминания о квадрате, соберем проект и запустим его.

    Итог наших стараний:

    Для более удобной манипуляции рисунком создадим структуру, состоящую из указателя на картинку и ее координат на экране.

    struct pict{

    Surface *fc;

    Int x;

    Int y;

    Заново создадим и инициализируем изображение:

    face.fc=new_image("face.png");

    face.x=100;

    face.y=100;

    Заставим его отображаться:

    draw_surface(face.fc, window,face.x, face.y);

    Теперь можно попробовать управлять местоположение изображения используя наши знания о клавиатуре. Не буду вам подсказывать, надеюсь у вас получиться.

    2.5.Вывод текста на экран.

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

    Воспользуемся им и мы. Для этого загрузим картинку с уже набранным буквами в нашу игру.

    surface *font=new_image("font2.png");

    Итак, у нас есть картинка с размещенными на ней несколькими буквами. Как же брать из нее нужные на буквы? Для вывода некотрой части изображения используется функция blit_surface(). Параметр *src принимает указатель на исходный графический файл, *dest принимает указатель на поверхность для изображения, (x_src, y_src) - координаты, откуда будет начинаться вырезанное изображение, width - ширина нового изображения, height - его высота, (x_dest, y_dest) -координаты целевого пповерхности, куда будет выводиться вырезанное изображение.

    Добавим вызов функции и посмотрим, что получиться.

    blit_surface(font, window, 0, 0, 90, 100, 500, 500);

    Не так это и страшно, но остался один вопрос - на рисунке красные буквы на темно-синем фоне, но выводятся только буквы красного цвета. Это связано с тем, что, по-умолчанию, темно-синий цвет (#00029a) считается прозрачным и не выводится. Если вас не устраивает именно этот цвет, то можно его поменять, просто задав другое значение переменной transparent_color.

    2.5.Звук

    К сожалению, на формат звуковых файлов тоже наложено ограничение - weaver проигрывает лишь файлы формата Ogg Vorbis. Для проигрывания звуков есть две функции: play_music() и play_sound(). Разница между ними в том, что play_music() будет повторять музыку до тех пор, пока не будет вызвана функция stop_music(). Функция play_sound() проигрывает музыкальный файл лишь один раз и управлять воспроизведение этой функции мы не в состоянии. Очевидно, что play_music() лучше использовать для воспроизведения звукового фона игры, а play_sound() для воспроизведения звуковых эффектов.

    Попробуем воспроизвести звук. Для play_sound() файл должен располагаться в директории sound, а для play_music() в директории music соответственно.

    Добавим перед главным циклом:

    play_sound("sound.ogg");

    Затем, как обычно, соберем и запустим игру. Et voilà, слышен звук выстрелов.

    2.6.Определение столкновений

    Для определения столкновений между объектами служит набор функций, имеющий в названии префикс collision_* . Использовать их так же легко как и все остальное. Для примера создадим два прямоугольника:

    rectangle rect1, rect2;

    rect1.x=rect1.y=50;

    rect1.w=rect1.z=25;

    rect2.x=rect2.y=150;

    rect2.w=rect2.z=50;

    Один из них будет двигаться:

    if (keyboard)

    Rect1.x-=10;

    if (keyboard)

    Rect1.x+=10;

    if (keyboard)

    Rect1.y-=10;

    if (keyboard)

    Rect1.y+=10;

    И финальная часть, проверка на столкновение и реакция на это:

    if (collision_rectangle_rectangle(&rect1, &rect2))

    Break;

    Естественно, что для наглядности следует отображать наши прямоугольники на экране.

    Теперь мы достаточно знаем, чтобы попробовать написать собственную небольшую игру. Это будет сильно упрощенный Марио, без преград, но с врагами.

    Начнем!

    3.1. Проект будет называться YAM (Yet Another Mario)

    weaver yam

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

    О интерфейсе. Его у нас не будет, по сути игра будет представлять собой один короткий уровень, пройти его нужно будет с одной попытки.

    Теперь о спрайтах. Подробнее о них вы можете почитать в Википедии. Есть большое количество сайтов, где можно найти как рипнутые из игр спрайты, так и авторские, сделанные «по мотивам». Нам нужны как минимум два типа спрайтов, для Марио и для его противника. Погуглив, я нашел несколько сайтов с базами данных спрайтов, на одном из них спрайты были размещены в едином изображении, на другом предоставлялись отдельными картинками. Выберем, где они разбиты, это нам понадобиться для более удобного создания анимации персонажей.

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

    У нас есть 3 вида спрайтов для анимации Марио.

    Принцип анимации, я думаю вам знаком, это просто последовательное изображение нескольких картинок.

    Создаем структуру, где будем хранить данные о персонаже:

    struct character{

    Surface *fc;

    Int x;

    Int y;};

    Инициализируем переменную для Марио:

    Mario.x=mario.y=100;

    Mario.fc=new_image("mw1.png");

    Mario.fc=new_image("mw2.png");

    Mario.fc=new_image("mw3.png");

    Ну и наконец начем анимацию персонажа, но так как экран обновляеться каждые 0.01 секунды, то изображения будут сменяться очень часто. Это можно исправить либо уменьшив значение FPS, передав функции weaver_rest() значение больше текущего, либо используюя счетчик кадров, который будет решать какой именно спрайт будет отображаться в данном кадре. Будем использовать метод со счетчиком.

    Перед главным циклом создадим переменную counter, которая и будет счетчиком

    int counter=0;

    А в главном цикле будет находиься проверка счетчика и в зависимости от его значения будет рисоваться тот или иной спрайт анимации:

    If (counter<=10){

    Counter++;

    If (counter>10 && counter<=20){

    Draw_surface(mario.fc, window, mario.x, mario.y);

    Counter++;

    If (counter>20 && counter<=29){

    Draw_surface(mario.fc, window, mario.x, mario.y);

    Counter++;

    If (counter>29)

    Counter=0;

    Готово, можно собирать проект и запускать.

    Точно так же анимируем и противника Марио.

    Теперь, чтобы у Марио была хоть какая-нибудь возможность победить, реализуем возможность его прыжка. С этим все просто, при нажатии на клавишу изменяется значение координат по вертикали.

    После этого можно начать обрабатывать столкновения Марио и его противника. Для этого добавим в структуры, описывающие персонажа игры, прямоугольник, за пересечением которых и будем отслеживать.

    Теперь структура выглядит так:

    struct character{

    Surface *fc;

    Int x;

    Int y;

    Rectangle col_det;};

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

    Теперь можно приступить и к реализации ответа на пересечение прямоугольников, для этого мы используем уже знакомую функцию collsion_rectangle_rectangle():

    if (collision_rectangle_rectangle(&mario.col_det, &bowser.col_det))

    Break;

    Вот и все, при столкновении персонажей игра будет завершаться. Легко и просто.

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

    Для добавления звуковых эффектов и фоновой музыки воспользуемся функциями play_music()/stop_music() и play_sound().

    Управление простое - стрелка влево - стрелка вправо - движение, стрелка вверх - прыжок

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

    Как видите, написать игру, используя лишь Си достаточно легко, гораздо труднее сделать это хорошо.

    Напоследок небольшой список полезных ссылок

    http://weaver.nongnu.org / - веб сайт фреймворка Weaver, там вы найдете как подробную документацию, так и примеры кода.

    Если вы желаете повторить примеры из статьи - вот ссылка на архив с исходными текстами нашей игры: http://narod.ru/disk/16196657001/yam.tar.gz.html .

    Вопрос: Что посоветуйте для создания игр с нуля?


    Доброго времени. Не так давно был вдохновлен игрушками вроде Papers, Please и Hotline Miami. Есть своя идея для создания игры. Но дело в том что не знаю на чем лучше писать. Игру задумал с видом сверху, не 3D, для ПК. По работе пишу на 1С, но с-ка не для игр, хотя если очень постараться то можно, также изучал самые азы Java. Я понимаю, что сначала лучше попробовать сделать что-нибудь просто, типа крестики-нолики или змейку, чтобы получить базовые знания.
    На просторах интернета предлагают использовать C++, но также предлагают использовать C# + XNA. У кого есть опыт в создании игр, посоветуйте литературу или дайте какие то советы по поводу программирования игр с нуля.

    Ответ:

    Сообщение от MonteKristo

    посоветуйте литературу

    К сожалению на русском очень мало литератуты, тем более качественной. Переводят далеко не всё, далеко не сразу, а бывает, что не качественно. Бывает, что переведут книгу через два года после выхода оригинала, а уже вышла новая версия оригинала и первая устарела. Поэтому я изучаю английский и перевожу с помощью Lingvo и translate.google.com актуальные книги

    Искать книги по созданию игр на разных языка и технологиях можно здесь:

    Но не в коем случае не начинайте с книг из категорий: Progressing и Mastering

    Что какой раздел означает можно узнать если кликнуть "i". Можно ещё бесплатно скачать примеры после регистрации. Я это показал на рисунке:

    Вопрос: Создание игры на языке Си?

    Ответ: Добавлено через 30 минут

    Сообщение от HighPredator

    Вообще на си можно написать все. Было бы желание, мозги и время.

    Желание есть, мозгов нету, время буду искать >< Благодарю!

    Сообщение от McFair

    Так же можно самому начать без движка еще изучать Opengl, создать пару тетрисов, и глядишь поймешь что к чему.

    Чувствую придется писать движок самому

    Сообщение от McFair

    7.Не совсем понятно, типо часть рисунка вырезать и наложить на куб?

    Да, отрисовать объект и оттекстурировать его, наложив изображение.

    Сообщение от cyber-satyr

    GTK, например, кросс. А так, для венды юзай winapi, там чисто сишый интерфейс.

    Благодарствую! Это я и имел в виду

    Сообщение от McFair

    8.Больше зависит от игры,целей и стиля программирования, все нужно учитывать, если брать один и тот же проект на си и шарп то конечно си намного выиграет, но в шарпе скорость разработки на 2\3 помоему повысится.

    Неплохим выбором наверное будет совмещение их. Основу писать на шарпе, а на Си докручивать фитчи?)

    Вопрос: Создание игры ММОРПГ


    Здравствуйте. Недавно создавал тут тему, где узнавал про создание игры 2д.
    Все же я решил узнать, в каком направлении мне учиться.
    Вот 2 скриншота:
    http://kape.cc/uploads/posts/2011-05...a630f08343.jpg
    http://mmohuts.com/wp-content/galler..._07.jpg?bb7a3b
    И видео:

    Мне нужно сделать игру подобного рода. В общем, локация - системой сетки. Локация поделена на некоторые кубики
    Я понимаю, то нужна графика, звуки, звуковые библиотеки, но для начала возьмем звуковые библиотеки.
    Забудьте про графика (она допустим есть), есть звуки.
    Хочу спросить, удовлетворяет ли связка с++ + OpenGL моим требованиям и созданию похожей игры, а именно: передвижение объекта по клеткам с анимацией, столкновение объектов, взятие графики со спрайтов + скелетная анимация (голый персонаж + привязка предметов - графики, к телу персонажа (одежда)).

    Ответ: 8Observer8 , никогда не любил физику, пока не понял, что она так необходима... Но сейчас, я понял, что Box2D для моей задуманной игры точно не нужен... Смотрел документацию на SFML, после, сегодня потыкал предыдущую наработку и смог сделать чтобы взаимодействовал с стенами, сколько счастья было)) Просто потратить пришло около 1-2 часа чтобы понять как это сделать, но это того стоит.
    Кстати хочу попробовать сделать хотя бы первый уровень бомбермена)

    Добавлено через 10 минут
    P.S. параллельно изучаю c++

    Вопрос: Посоветуйте литературу по созданию игр от начала до конца


    Посоветуйте литературу по созданию игр от начала до конца. С всема елементами, на с++ можна. Толька новую книгу издательства 2010-2015 года

    Ответ: А вы что для рисования будете использовать: DirectX или OpenGL? Я смотрел есть общие книги по теории игр --> . Какого плана вы будете игры писать: 2D, 3D, RPG, стрелялки, гонки, шахматы, го? У всех этих игр специфики разные

    Вопрос: создание игры


    Здравствуйте, задумал сделать простенькую игру (rpg). Делать буду просто "Для себя". Игра будет статичная (bmb картинки персонажей, объектов и т.д, возможно gif) Когда начал рыться в интернете в поисках того как это вообще делается, нашел кучу готовых компонентов для delphi (например delphix) и поэтому у меня возник вопрос, стоит ли использовать что то подобное? Или лучше все самому вырисовывать на canvas?

    Ответ:

    MrDmitry ,
    Статичная игра? Это что-то новое в программе строения.
    Если она и в правду такая то вам хватит TImage.
    А если всё таки это динамичная игра то вам потребуется TPaintBox.

    Мало кто знает но в мести с Delphi поставляются примеры программ в том числе и 3 мини-игры!

    В Windows 7, нажмите пуск | Все программы | Embarcadero RAD Studio XE5 | Samples.

    У меня эта папка
    C:\Users\Public\Documents\RAD Studio\12.0\Samples\

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

    Что касается движков. Движок ускоряет разработку.
    В движке вы найдёте кучу готового кода. Движок надо подбирать под игру.

    От движка советую посмотреть математическую и геометрическую библиотеку. Библиотеку для работы с континентом и мультимедиа. Какие форматы файлов поддерживает программа?
    Поддержка камеры. Поддержка спецэффектов.

    Вы не сказали 2D или 3D игра.
    Наличие отсутствие поддержи спрайтов, билбордов. Вывода текста.

    Добавлено через 14 минут и 34 секунды
    Думаю вам будет интересно. Есть сайт посещенный созданию игр.

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

    Вопрос: Создание своего языка в Visual Studio


    ищу понятные пошаговые примеры/уроки по созданию своего языка в Visual Studio.

    Нашёл пример языка, но мне нужно понять что, как и для чего, т.к. мой асм мало похож на оок.

    --
    Нужно вообще:
    "кросскомпилятор" из своего асма в студии, с подсказками кода, в коды для процессора Z80 (пока не разбирался со способом загрузки на "железку" или в эмулятор, это следующий этап).

    Требования:
    редактор - сама ВС (2013);
    ассемблерный Z80 код на экране, без лишних модулей;
    выбор языка при создании проекта (где список языков C#, F#, VB.NET и прочих).

    Ответ: а как "сказать" студии 2013 чтобы она подсвечивала и подсказывала?

    Вопрос: Создание игры на движке C++


    Народ, помогите с созданием игры на движке с++ . Doodle jump, Буду благодарен аналогу. Проблема в том что начал только не давно изучать. И ни как не могу написать правильную программу. Видимо я еще конкретный Нуб. Благодарен заранее!

    Здравствуй, Хабрахабр!

    На хабре не очень много уроков по созданию игр, почему бы не поддержать отечественных девелоперов?
    Представляю вам свои уроки, которые учат создавать игры на C++ с использованием SDL!

    Что нужно знать

    • Хотя бы начальные знания C++ (использовать будем Visual Studio)
    • Терпение

    О чем эта часть?

    • Мы создадим каркас для всех игр, в качестве отрисовщика будем использовать SDL. Это библиотека для графики.

    В следующих постах будет больше экшена, это лишь подготовка:)

    Почему SDL?

    Я выбрал эту библиотеку как наиболее легкую и быструю в освоении. Действительно, от первой прочитанной статьи по OpenGL или DirectX до стотысячного переиздания змейки пройдет немало времени.

    Теперь можно стартовать.

    1.1. Начало начал

    Скачиваем SDL с официального сайта.
    Создаем проект Win32 в Visual Studio, подключаем lib"ы и includ"ы SDL (если вы не умеете этого делать, то гугл вам в помощь!)

    Также необходимо использовать многобайтную кодировку символов. Для этого идем в Проект->Свойства->Свойства конфигурации->Набор символов->Использовать многобайтную кодировку.

    Создаем файл main.cpp
    #include int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { return 0; }
    Пока что он ничего не делает.

    Царь и бог каркаса - класс Game
    Game.h
    #ifndef _GAME_H_ #define _GAME_H_ class Game { private: bool run; public: Game(); int Execute(); void Exit(); }; #endif
    Game.cpp
    #include "Game.h" Game::Game() { run = true; } int Game::Execute() { while(run); return 0; } void Game::Exit() { run = false; }

    Создаем файл Project.h, он нам очень пригодится в будущем
    #ifndef _PROJECT_H_ #define _PROJECT_H_ #include #include "Game.h" #endif

    Изменяем main.cpp
    #include "Project.h" int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { Game game; return game.Execute(); }

    Уже чуточку получше, но все равно как-то не густо.

    1.2. Графика

    Создаем аж 2 класса - Graphics для отрисовки графики и Image для отрисовки картинок

    Graphics.h
    #ifndef _GRAPHICS_H_ #define _GRAPHICS_H_ #include "Project.h" #include "Image.h" class Image; class Graphics { private: SDL_Surface* Screen; public: Graphics(int width, int height); Image* NewImage(char* file); Image* NewImage(char* file, int r, int g, int b); bool DrawImage(Image* img, int x, int y); bool DrawImage(Image* img, int x, int y, int startX, int startY, int endX, int endY); void Flip(); }; #endif

    Image.h
    #ifndef _IMAGE_H #define _IMAGE_H #include "Project.h" class Image { private: SDL_Surface* surf; public: friend class Graphics; int GetWidth(); int GetHeight(); }; #endif

    Изменяем Project.h
    #ifndef _PROJECT_H_ #define _PROJECT_H_ #pragma comment(lib,"SDL.lib") #include #include #include "Game.h" #include "Graphics.h" #include "Image.h" #endif

    SDL_Surface - класс из SDL для хранения информации об картинке
    Рассмотрим Graphics
    NewImage - есть 2 варианта загрузки картинки. Первый вариант просто грузит картинку, а второй после этого еще и дает прозрачность картинке. Если у нас красный фон в картинке, то вводим r=255,g=0,b=0
    DrawImage - тоже 2 варианта отрисовки картинки. Первый рисует всю картинку целиком, второй только часть картинки. startX, startY - координаты начала части картинки. endX, endY - конечные координаты части картинки. Этот метод рисования применяется, если используются атласы картинок. Вот пример атласа:

    (изображение взято из веб-ресурса interesnoe.info)

    Рассмотрим Image
    Он просто держит свой сурфейс и дает право доступа к своим закрытым членам классу Graphics, а он изменяет сурфейс.
    По сути, это обертка над SDL_Surface. Также он дает размер картинки

    Graphics.cpp
    #include "Graphics.h" Graphics::Graphics(int width, int height) { SDL_Init(SDL_INIT_EVERYTHING); Screen = SDL_SetVideoMode(width,height,32,SDL_HWSURFACE|SDL_DOUBLEBUF); } Image* Graphics::NewImage(char* file) { Image* image = new Image(); image->surf = SDL_DisplayFormat(SDL_LoadBMP(file)); return image; } Image* Graphics::NewImage(char* file, int r, int g, int b) { Image* image = new Image(); image->surf = SDL_DisplayFormat(SDL_LoadBMP(file)); SDL_SetColorKey(image->surf, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image->surf->format, r, g, b)); return image; } bool Graphics::DrawImage(Image* img, int x, int y) { if(Screen == NULL || img->surf == NULL) return false; SDL_Rect Area; Area.x = x; Area.y = y; SDL_BlitSurface(img->surf, NULL, Screen, &Area); return true; } bool Graphics::DrawImage(Image* img, int x, int y, int startX, int startY, int endX, int endY) { if(Screen == NULL || img->surf == NULL) return false; SDL_Rect Area; Area.x = x; Area.y = y; SDL_Rect SrcArea; SrcArea.x = startX; SrcArea.y = startY; SrcArea.w = endX; SrcArea.h = endY; SDL_BlitSurface(img->surf, &SrcArea, Screen, &Area); return true; } void Graphics::Flip() { SDL_Flip(Screen); SDL_FillRect(Screen,NULL, 0x000000); }
    В конструкторе инициализируется SDL и создается экран.
    Функция Flip должна вызываться каждый раз после отрисовки картинок, она представляет получившееся на экран и чистит экран в черный цвет для дальнешней отрисовки.
    Остальные функции малоинтересны, рекомендую разобраться в них самому

    Image.cpp
    #include "Image.h" int Image::GetWidth() { return surf->w; } int Image::GetHeight() { return surf->h; }
    Нет, вы все правильно делаете, этот файл и должен быть таким:)

    Надо изменить Game.h, Game.cpp и main.cpp
    Game.h
    #ifndef _GAME_H_ #define _GAME_H_ #include "Project.h" class Graphics; class Game { private: bool run; Graphics* graphics; public: Game(); int Execute(int width, int height); void Exit(); }; #endif
    Тут мы добавляем указатель на Graphics и в Execute добавляем размер экрана

    Game.cpp
    #include "Game.h" Game::Game() { run = true; } int Game::Execute(int width, int height) { graphics = new Graphics(width,height); while(run); SDL_Quit(); return 0; } void Game::Exit() { run = false; }

    Ничего особенного, разве что не пропустите функцию SDL_Quit для очистки SDL

    Main.cpp
    #include "Project.h" int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { Game game; return game.Execute(500,350); }
    Тут мы создаем экран размером 500 на 350.

    1.3. Ввод

    Надо поработать со вводом с клавиатуры

    Создаем Input.h
    #ifndef _INPUT_H_ #define _INPUT_H_ #include "Project.h" class Input { private: SDL_Event evt; public: void Update(); bool IsMouseButtonDown(byte key); bool IsMouseButtonUp(byte key); POINT GetButtonDownCoords(); bool IsKeyDown(byte key); bool IsKeyUp(byte key); byte GetPressedKey(); bool IsExit(); }; #endif
    SDL_Event - класс какого-нибудь события, его мы держим в Input"е для того, чтобы не создавать объект этого класса каждый цикл
    Ниже расположены методы, не представляющие особого интереса. Примечание: методы с окончанием Down вызываются, когда клавиша была нажата, а с окончанием Up - когда опущена.

    Input.cpp
    #include "Input.h" void Input::Update() { while(SDL_PollEvent(&evt)); } bool Input::IsMouseButtonDown(byte key) { if(evt.type == SDL_MOUSEBUTTONDOWN) if(evt.button.button == key) return true; return false; } bool Input::IsMouseButtonUp(byte key) { if(evt.type == SDL_MOUSEBUTTONUP) if(evt.button.button == key) return true; return false; } POINT Input::GetButtonDownCoords() { POINT point; point.x = evt.button.x; point.y = evt.button.y; return point; } bool Input::IsKeyDown(byte key) { return (evt.type == SDL_KEYDOWN && evt.key.keysym.sym == key); } bool Input::IsKeyUp(byte key) { return (evt.type == SDL_KEYUP && evt.key.keysym.sym == key); } byte Input::GetPressedKey() { return evt.key.keysym.sym; } bool Input::IsExit() { return (evt.type == SDL_QUIT); }
    Здесь мы обрабатываем наш объект событий в функции Update, а остальные функции просто проверяют тип события и его значения.

    Изменяем теперь Game.h и Game.cpp
    #ifndef _GAME_H_ #define _GAME_H_ #include "Project.h" #include "Graphics.h" class Graphics; #include "Input.h" class Input; class Game { private: bool run; Graphics* graphics; Input* input; public: Game(); int Execute(int width, int height); Graphics* GetGraphics(); Input* GetInput(); void Exit(); }; #endif
    Как видно, мы добавили указатель на Input и создали методы-возвращатели Graphics и Input

    Game.cpp
    #include "Game.h" Game::Game() { run = true; } int Game::Execute(int width, int height) { graphics = new Graphics(width,height); input = new Input(); while(run) { input->Update(); } delete graphics; delete input; SDL_Quit(); return 0; } Graphics* Game::GetGraphics() { return graphics; } Input* Game::GetInput() { return input; } void Game::Exit() { run = false; }

    1.4. Итоги

    Это был первый урок. Если вы дошли до этого места, я вас поздравляю! У вас есть воля, присущая программисту:) Смотрите ссылки в начале статьи на последующие уроки для того, чтобы узнать еще много нового!

    По всем вопросам обращайтесь в ЛС, а если вам не повезло быть зарегистрированным на хабре, пишите на мейл [email protected]