Курсы создания компьютерных игр на C/C++ ( Часть 3 )
Слушай автор, я вот все спросить хотел, а сам то ты к программированию какое отношение имеешь? Где учился, где работаешь?
@Dansken, прости что скептически отнесся к твоим постам. Действительно очень интересные и заслуживают уважения. Судя по отзывам моих знакомых, достаточно емко как для новичков.
Единственное, что. Ты не думал перенести всю инфу на сайт и не заморачиваться с картинками? В любом случае потом готовый html можно будет перегнать в image.
В следующем посте нужно будет не только про звёздочки, а и про i++ и ++i :)
нагромождение условий в if является признаком плохого кода.
рекомендуют создавать булевые переменные, которым присваивают нужные условия, и уже их вставлять в if
PS^ с самого начала Шилдта пользовался cout << cin >> не понимая вообще ничего об ООП и объектах и не парился вообще. Потом дочитал и осознал. вообще не вижу проблем пользовать их, а не устаревший printf.
PPS^ там где то структуры предлагали использовать вместо объектов. Но Страуструп как бы наоборот говорит, что структуры - это old-fashion C, и для использования в С++ надо пользовать объекты.
хотя кто я такой, чтоб что-то говорить такому профи (без сарказма)
Еще раз спасибо автору за пост! Выполнил домашнее задание. Сама задумка с ДЗ очень понравилась, давайте в след. постах по-больше) И так к делу, при победе - выдает на экране сумму с учетом вычета ставки(естественно сам посчитал, но мне кажется это плохо, ведь если игра будет больше и сложнее то нужна формула по которой прога сама будет считать и вычитать - научите пожалуйста!) потом еще проблемка - не могу вывести надпись по серединке как у вас, из за нехватки опять же знаний и опыта, но я уже рад что без ошибок и есть нужный результат. если ставка проигрывает - то ничего на экран не выводиться. если ставка побеждает - то идет надпись +10 баксов, но при этом значки не меняются? после еще одного нажатия, надпись исчезает и значки обновляются на те "победные" что должны быть одновременно с надписью денег, тоже косяк. если подскажите как все переделать и научите как правильно буду очень признателен и все переделаю. Ждем 4-ую часть. прилагаю скриншот.
Вот что получилось у меня. Автору как обычно спасибо :-)
Не могу понять как сделать чтобы при выпадение не выигрышной комбинации пропадал прежний текст выигрыша, ну это там где Bonus и Total win.
Как-то меня напрягло различие unicode и utf (Unicode Transformation Format).
НАКОНЕЦ ТО ТРЕТЬЯ ЧАСТЬ! скачал визуал студио, именно 2013, хотя там доступна уже 2015 (2013 лучше? или проще?) автор Dansken - огромное спасибо тебе за эти посты! всегда была мечта создать свою игру, но я никогда не знал с чего начать - 3д макс, юнити 3д, везде пробовал, получалось легко довольно (никогда не было проблем с математикой и информатикой) но я все время не знал, с чего же начать? да и не умел писать коды. сейчас увидел твои посты и вспомнил про давнюю мечту! скачал учебников по С++ и следую твоим постам. Если бы мог поставил бы 1000 лайков к постам. Жду продолжения!
Я бы пошел немного другим путем. Вместо такого вывода всех символов лучше создать матрицу (карту), где в каждой ячейке объект (не символ, а объект с символом и другими характеристиками).
Еще вариант - создать список объектов, которые еще содержат поля x, y. При изменении объекта просто перерисовывать в old_x, old_y и x, y. Минусы: долго искать, какой объект рисовать на old_(x|y).
Пожалуй, твой пост спровоцировал меня написать что-то консольное, только я возьму ruby + curses
Решил изучать С#. В шарпе все с классами. Пришлось пилить с классами, вроде вышло . И да, ДЗ бы на разные уровни сложности.
Автор, надеюсь, ты увидишь здесь мой вопрос))
Дело в том, что в конце после цикла у меня появляется надпись: "You are a loser". Но проблема в том, что она появляется еще при балансе в 90$, что меньше стоимости одной игры. Почему так происходит?(
А под линухой всё сложнее.Помимо того, что некоторые библиотеки недоступны, ещё и символы не отображаются : (Ну и, что забавно, в 100% будет два из трёх одинаковые значения, иногда проскакивает три одинаковых, но такого, что игрок проиграл не было ни разу за
100 прогонов : D
Автор, почему у меня знаки вопроса за место ASCII символов?
Автор у меня постояно как туппит визуал студия то либо нормально не сохроняет то когда камплирию там выходит всё с ошибками но как только я нажму раз 5 и сохран все нормально в чём причина таких глюков
Хм, код точно такой же, но почему-то рандом генерирует всегда либо 2 одинаковых картинки, либо 3, а все 3 разные не хочет :( Да и приплюсовывает не всегда правильно, когда выпадает 3 одинаковых, он иногда прибавляет 10$, а не 50 :(
Автор, именно ты придумываешь наказания для ада?Я несколько часов пытаюсь сделать твое домашнее задание((
Ну, что тут не так?
const int nachalbabki = 1000;
const int odnaigra = 100;
const int dublebonus = 400;
const int jakpot = 2000;
int money = nachalbabki;
time_t currentTime = time(0);
//чаир-символы(звездочки,масти карт и т.д.)
//зерно генератора псевдослучайных чисел,основанные на времени.
//ду вайл - код для выполнения задачи и отображения.
/*слэш со звездочкой
для многострочного комментария*/
//все действия происходящее во время игры ( например расход денег за действие "мани=мани-прокрутка") пишется в ду вайл
system("cls");//очистка консоли. чтоб изображение не бегало бесконца. библиотека виндовс
printf("\t ooooooo \n");
printf("\t ooooo \n");
printf("\t oo BITCH oo \n");
printf("\t %c %c %c \n", panel0, panel1, panel2);
printf("\t ooooooooooooo \n");
printf("\t ooooooooooooo \n");
printf("\t +%i\n", lose);
printf("\t Money: %i$\n", money);
printf("Time: %isek\n", time);
money = money - odnaigra;
_getch();//убрать мерцание . библиотека конио
panel0 = 3 + (rand() % 4);
panel1 = 3 + (rand() % 4);
panel2 = 3 + (rand() % 4);
if ((panel0 == panel1) && (panel0 == panel2))
win = money - jakpot;
money = money + jakpot;
printf("\t vezuchiy sukin sin. \n");
else if ((panel0 == panel1) || (panel0 == panel2) || (panel1 == panel2))
win1 = money - dublebonus;
money = money + dublebonus;
else ((panel0 != panel1) || (panel0 != panel2) || (panel1 != panel2));
while (money >= nachalbabki);
Все работает чудесно, только не могу понять почему у меня за место символов вопросики =/
Разжуйте дураку для чего здесь все таки return нужен
Пост живой еще? есть вопросик О.О
Всё получилось, спасибо
А не накосячил ли ты с генерацией
Если память не изменяет, там последовательность одна и та же будет
Мог бы Автор давать больше ДЗ
и бОльшей сложности.
Т.к. хотелось бы писать разные проги(хотя я все же все это знаю(я про Си++ и объемные задания)) , а не с термометром мучиться
А с какой игры взяты спрайты персонажей?начиная со слов"Пишем игру".
Автор, спасибо тебе большое! На самом деле очень сложно в сети найти такие уроки, которые позволяют перейти от "Hello World" к практике. Так что не слушай никого, продолжай делать то, что делаешь! Хотелось бы увидеть урок, как сделать, чтобы приложение запускалось не из консоли, а как игра (фрейм или хз как это называется). И еще вопрос: почему ты используешь принтф а не cout?
Зачем вы учите людей этой лютой смеси Си и C++? Надо уж или туда, или сюда.
А я так и не понял как сделать "домашнее задание". Единственный выход который мне пришел в голов:
if ((panel1 == panel2) && (panel1 == panel3))
money = money + WinThree;
Но это не сработало..
господа програмисты, прошу у вас помощи по теме. Есть тхт файл, где прорисован уровень лабиринта("1"-стена, "2"-пустота).Как в С# вместо этих значений вывести на экран текстуры? Никак не могу понять. Начал осваивать tao framework, но до решения проблемы еще далековато. Как бы вы это сделали?
тут пару раз уже возникали темы про стилистику кода. я сам как начинающий изучать, могу порекомендовать таким же начинающим изучать хорошую статью на хабре, точнее, её перевод
@Dansken, всё-таки, два вопроса:
1. как вывести хоть какие-нибудь символы псевдографики, если подключена русская консоль:
и шрифт самой консоли lucida (т.е. русифицирована консолька)
2. как сделать нормальный выход из приложения без траты всех денег
У меня вышло что-то такое. Для того, чтобы убрать вообще сообщение об отсутствии бонуса, я просто убираю:
Вопрос такой. У меня при первом старте, сразу написано, что я лузер. Как сделать так, чтобы это сообщение появлялось только после первого раунда?
автор! Спасай! Создал второй проект (после угадывания числа), пилю в нём урок 3 попутно разбираясь. Но F5 и Ctrl+F5 все равно дают запуск проекта 1. Как это изменить? пробовал сохранить предварительно (save as. ). ПРобовал правой клавишей на проекте 2 и Rebild, rebild proect2. не помогает. http://c2n.me/3jVTcgK собственно вот скрин. Стартует первый проект ((
млин всё таки программирование походу не моё. (((
Учимся писать чистый код на Python: разбор задания к S1E1
Ну что, решений в комментах к прошлому посту скинули немало, пора их разобрать. Но сначала небольшое отступление.
Общие ошибки
Как и ожидалось, у многих встречаются одни и те же ошибки. В разном виде, в разном контексте, но суть одна и та же.
Их можно разделить на 2 группы:
- нейминг (названия объектов);
- структура/компоновка кода внутри функции.
В этом посте разберу компоновку, а в следующей серии подробнее расскажу про нейминг. Погнали :)
if/else в одну строку
@Amplicon, @kashinec, достаточно неплохие решения.
Малину немного попортило стремление впихнуть невпихуемое, а именно - записать тело `if/else` в одну строку.
Делать так категорически не рекомендуется. Против этого выступает как PEP8, так и Google Style Guide. Просто переносите тело на новую строку, даже если оно небольшое. Это выглядит гораздо красивее и читабельнее.
Почитать о том, как правильно использовать пробелы, можно по тем же ссылкам, что уже давались выше. Неверно расставленные пробелы в коде- это то же самое , что и невернорасставленныепробелывтексте. Они усложняют чтение.
Лишние слои
Следующая распространённая ошибка - это заворачивание куска кода в ненужный `if` или `else`. @DavidSarif, @Nikydzman, вы в телевизоре.
Ту же самую логику можно реализовать так:
Вместо того, чтобы использовать `if - pass` или `if - continue`, можно просто инвертировать условие, и код станет проще и линейнее.
Также стоит помнить о том, что `raise` и `return` прерывают выполнение, поэтому добавление `else` после `return` уже излишне.
P.S. Долго думал, разбирать ли код Nikydzman или нет, потому что он ЕДИНСТВЕННЫЙ НЕ ПОДПИСАЛСЯ 😈, но решил всё-таки разобрать, потому что он идеально иллюстрирует ситуацию с лишними слоями. Во второй раз такой халявы уже не будет)
Декомпозиция задачи
Отдельно отмечу решение @DarkPavlov. Дело в том, что он разбил большую задачу на отдельные функции. Это очень хороший подход, который значительно упрощает отладку и тестирование программы.
Сейчас выглядит довольно громоздко, но если убрать лишние слои `if/else`, то получается уже так:
Конечно, декомпозиция здесь выполнена не совсем верно, потому что однотипные действия выполняются несколько раз, и каждый раз перебирается вся исходная коллекция. Но такие вопросы рассмотрим уже в следующих постах, это отдельная большая тема.
UPD: Скорость выполнения кода
По совету @kashinec добавил результаты замеров скорости. Средние значения за 100 000 итераций, измеряются в микросекундах:
HaneLeiko: - (код не в виде функции)
Если кого-то забыл - прошу простить. Измерить можно самостоятельно с помощью модуля `timeit`. Абсолютная точность не гарантируется, на результаты могут повлиять разные факторы.
Вчера много думал над тем, как правильно организовать выпуск постов, и пришёл к следующему варианту.
- раз в неделю буду публиковать очередную серию сериала "Чистый код" и задание. Тему буду выбирать исходя из ошибок, которые обнаружились в прошлой серии, но если вам интересен какой-то конкретный вопрос, то обозначьте его в комментарии. Если коммент заплюсуют, то можно будет рассмотреть этот вопрос в отдельном посте;
- спустя 1-2 дня после этого буду публиковать разбор задания. Пауза в 1-2 дня нужна для того, чтобы все успели почитать и написать код;
- спустя ещё пару дней буду выкладывать "прикладной" пост, в котором буду рассматривать какие-нибудь интересные фишки или давать полноценные идеи проектов для практики.
Таким образом, каждую неделю - новая итерация цикла. От таки дела :) Надеюсь, вам зайдёт.
Учимся писать чистый код на Python: сезон 1, серия 1
Эта серия постов создаётся для тех, кто учится программировать на Python. А точнее для тех, кто хочет заниматься разработкой за деньги, а не просто для развлечения.
Первые посты будут предназначены для полных новичков, которые пока что знакомы лишь с самыми основами. Чем дальше, тем более сложные и интересные ситуации будут рассматриваться.
Есть навык, который нельзя развить с помощью книг или видеоуроков - это навык написания чистого и понятного кода. Чистым называется код, в котором:
- задачи правильно декомпозированы;
- отсутствует избыточность и дублирование;
- каждый класс и каждая функция отвечает за максимально узкий спектр задач, то есть классы и функции не являются божественными;
- для всех объектов подобраны хорошие, говорящие имена;
- соблюдаются стандарты, принятые в сообществе разработчиков в целом и в конкретной команде в частности. PEP8, Google Style Guide и прочие.
Зачем нужен чистый код?
Если вы пишете одноразовую программу по принципу "написал и забыл", то над чистотой кода можно не задумываться.
Но если вы создаёте проект, который в дальнейшем будет активно развиваться, то лучше заранее позаботиться о том, чтобы в коде было легко разобраться. В любых более-менее живых проектах код читается гораздо больше раз, чем пишется.
Можно сказать, что написание чистого кода - это инвестиция в будущее. Вы тратите чуть больше сил сейчас, зато значительно упрощаете жизнь себе и коллегам в дальнейшем.
Простейший пример
Есть задача: взять список и создать на его основе новый список, в котором все элементы будут удвоены. То есть из списка [1, 2, 3] нужно получить [2, 4, 6]. Решить эту задачу на Python можно разными способами.
Вариант №1: сделать всё "в лоб", перебрав исходный список по индексам. Для лучшего эффекта не будем использовать пустые строки и накосячим с отступами между оператором и операндами.
На C или подобных языках такой код вполне естественен. Но на Python его можно написать гораздо проще и лаконичнее.
Вариант №2: вместо ручного увеличения индекса воспользуемся встроенной функцией `range()`. Заодно поправим названия переменных и добавим пустую строку для разделения логических блоков кода.
Вариант №3: вообще не будем трогать индексы, а переберём исходный список поэлементно с помощью `for`.
Вариант №4: используем генератор списков.
Результат везде одинаковый, но читаемость кода в последнем варианте несравнимо лучше, чем в первом. И это лишь элементарный пример, простейшая операция по удвоению списка. В больших проектах возникают куда более сложные ситуации, и там разница между чистым кодом и "тяп-ляп" кодом становится ещё заметнее.
Сравниваем производительность
С помощью модуля `timeit` я замерил время выполнения кода в каждом из 4 вариантов. Исходный список `data` создал заранее, так как он используется без изменений в каждом варианте. Результаты следующие:
- Вариант №1: 573 наносекунды в среднем за 1 000 000 итераций;
- Вариант №2: 468 наносекунд в среднем за 1 000 000 итераций;
- Вариант №3: 257 наносекунд в среднем за 1 000 000 итераций;
- Вариант №4: 253 наносекунды в среднем за 1 000 000 итераций.
Самый короткий и наглядный вариант вдобавок оказался ещё и самым быстрым. Разница во времени выполнения кода между подходом "в лоб" и использованием генератора списков - в два с лишним раза.
Как научиться писать чистый код?
1. Учиться на своих ошибках. Написать хреновенький код, спустя некоторое время понять, что он непригоден для дальнейшего использования, погрустить и переделать. Повторять 100500 раз до тех пор, пока не начнёт получаться что-то нормальное;
2. Учиться на чужих ошибках. Этим я и предлагаю заняться прямо сейчас.
Ниже написана задача, которую вы можете решить и выложить своё решение в комментариях. Самые интересные решения я отдельно разберу в следующем посте.
ВАЖНО: не смотрите на чужие решения до того, как выложите своё. Суть именно в том, чтобы вы сначала попробовали сами решить задачу, и только потом сравнили свой код с тем, что получилось у остальных. Так вы сможете проанализировать свои ошибки и чему-то научиться. Если вы просто пробежитесь взглядом по чужим решениям, то в голове отложится гораздо меньше.
Создайте функцию, которая разобьёт IP-адрес на 4 октета. IP-адрес - это строка вида "127.0.0.1". Октет - это число от 0 до 255. Например, если передать в функцию строку "123.124.125.126", то она должна вернуть кортеж из 4 чисел: (123, 124, 125, 126). Если передать в неё некорректный IP-адрес, то должно возникнуть исключение ValueError.
Оформление решения
Код нужно представить в 2 форматах: изображение, сгенерированное на https://carbon.now.sh/, и текст, помещённый в цитату. Например:
Это нужно для того, чтобы по решению можно было как пробежаться глазами прямо в посте, так и скопировать его к себе в IDE для экспериментов.
Если решение будет оформлено тяп-ляп, то по вполне понятным причинам я его разбирать не буду :) Да и остальные участники, скорее всего, тоже пропустят его. Поэтому, пожалуйста, уважайте других людей и оформляйте свой код качественно и аккуратно. Ссылки на внешние ресурсы также буду пропускать, тут уж извините. Только скриншот и код прямо в ленте комментариев.
Разбор интересных решений
Как уже говорилось выше, самые интересные решения я отдельно разберу в следующем посте. Будет ли это одно решение или несколько - ещё не знаю, зависит от трудозатрат на разбор.
Приоритет, естественно, буду отдавать подписчикам. Для меня подписка - это признак того, что человек заинтересован в следующих постах и гарантированно увидит разбор. Ну и в целом приятно, когда на тебя подписываются :)
Пробуйте, учитесь, становитесь лучше. По мере возможностей постараюсь помочь. Удачи!
GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами
Сегодня у нас много матчасти и не очень много кода.
Конкретизация разработки.
Как было сказано в прошлом посте, игра, которая будет сделана в ходе данных гайдов, будет относиться к жанру стратегий, а в качестве источников для "вдохновения" у нас - "Oxygen Not Included" и "Rimworld". Теперь же конкретно поговорим с вами о том, что в игре должно быть реализовано, по пунктам.
--) Персонаж - игровой "объект", способный перемещаться по игровому миру и взаимодействовать с ним. Делятся на подконтрольных и неподконтрольных игроку. Игрок НЕ имеет прямого управления над своими подопечными, кроме специального "боевого" режима.
--) Каждый персонаж должен иметь или уметь следующее:
---) Взаимодействовать с предметами, расположенными на карте: подбирать их в свой инвентарь или выкладывать; взаимодействовать с рабочими станциями; сражаться (наносить, получать урон).
---) Каждый персонаж смертен. Каждый персонаж должен иметь ряд характеристик, которые он будет пытаться "поддерживать" на должном уровне, чтобы не умереть и продолжать быть эффективным. Эти характеристики:
---) Каждый персонаж уникален и должен отличаться от других. Черты, навыки с различными уровнями прокачки. Этот пункт может игнорироваться в случае, если это не подходит под конкретного персонажа.
--) Объекты, с которыми можно взаимодействовать через интерфейс игры и с которыми могут взаимодействовать персонажи или иные игровые сущности с какой-то целью.
---) Пример: верстак для создания вещей; исследовательский стол для открытия новых вещей и пр.
-) Интерфейс.--) Простой, понятный. Желательно, на русском языке. --) Хотя бы минимально настраиваемый.
Скажу сразу, что персонажей мы делать будем позже. Начнём мы с интерактивных объектов и размещения их в мире. Но, это будет потом.
Сейчас - продолжаем изучать матчасть.
События отрисовки.
Событие отрисовки добавляется на объект как и любое другое, через кнопку "Добавить событие (Add Event)".
Имеется два события:
--) Событие, которое отрисовывает объект в комнате.
--) Событие, которое отрисовывает объект на экране.
Иными словами, событие отрисовки GUI позволяет нам закрепить на экране наш интерфейс и то, что мы хотим игроку показывать всегда. Draw - стандартная отрисовка.
Возникает вопрос: зачем нам отдельно добавлять событие отрисовки, если объект спокойно отрисовывается и без него?
Ответ: в событии отрисовки можно написать код, который позволит нам менять параметры отрисовки, будь то: изменение размера (соотношения сторон); изменение цвета; создание дополнительных графических эффектов; etc.
Если вы добавили событие отрисовки, то вам необходимо написать в коде следующую строку:
В противном случае, объект не будет отрисован.Проделайте это сами. :)
Итак. Что мы можем делать в событиях отрисовки:-) Отрисовка спрайтов:--) draw_self() - отрисовывает спрайт текущего объекта с настройками по умолчанию.--) draw_sprite_ext( название спрайта в обозревателе, номер изображения, x, y, x-масштабирование, y-масштабирование, поворот, цвет, прозрачность) - отрисовка спрайта "расширенная" - т.е. с настройками. Можно отрисовать любой спрайт, как и везде, где его нужно отдельно указывать.
--) draw_sprite_part(название спрайта в обозревателе, номер изображения, x координата левой верхней точки спрайта, y координата левой верхней точки спрайта, ширина, высота, x, y) - отрисовка части спрайта--) draw_sprite_stretched(название спрайта в обозревателе, номер изображения, x, y, ширина, высота) - отрисовка спрайта с его "растяжением". Если у спрайта включить и настроить функцию Nine Slice (девять "срезов"), то можно создавать масштабируемые (с одинаковым разрешением, пропорциями и качеством по итогу) элементы интерфейса: окошки, кнопки, etc.
-) Отрисовка фигур:--) draw_circle(x, y, радиус, заполнение (True/False)) - рисуем круг. Аналогичное есть для прямоугольника (rectangle), стрелки (arrow), эллипса (ellipse), линии (line).--) draw_button(x, y, x2, y2, up) - Отрисовка кнопки. Где up - True или False, нажата кнопка или нет.
-) Настройки отрисовки.--) ВАЖНО. Любые настройки отрисовки нужно проводить ПЕРЕД отрисовкой. Можно делать в любом событии, но для удобства лучше тут же.--) draw_set_color(col) - устанавливаем цвет отрисовки. Базовые значения цветов начинаются с приписки "c_". Пример: c_black, c_red. ---) Для продвинутых - можно использовать HEX, как в CSS: #11CCFF как пример. Используется стандартная RGB система. Если заменить решётку на $, то система сменится на BBGGRR, т.е. наоборот.--) draw_set_alpha(alpha) - прозрачность отрисовки --) draw_set_font(font) - устанавливаем шрифт--) draw_set_halign(halign) - Расположение текста по горизонтальной оси (fa_ left/center/right)--) draw_set_valign(valign) - Расположение текста по горизонтальной оси (fa_ top/middle/bottom)
Это та часть, что касается кода и с которой мы с вами будем непосредственно работать. Можете держать как памятку у себя перед глазами, когда начнём писать код.
Коллизия - иначе говоря, столкновение объектов, происходит благодаря "маске" спрайта. Если спрайт - это картинка объекта, то маска - это "твёрдое тело", отвечающее за считывание столкновений.Чтобы её посмотреть, откройте настройки спрайта и разверните пункт "Collision Mask"
Хотя маска может иметь несколько форм, о которых будет написано ниже, устанавливается она исходя из четырёх позиций - левого верхнего и правого нижнего углов. Именно эта маска считывает столкновения, а не сам спрайт.
Mode - режим маски. Варианты: - Автоматическая- На всё изображение- Ручная
Type - тип маски. Варианты:- Прямоугольник- Прямоугольник с поворотом- Эллипс (работает медленнее)- "Алмаз" (ромб) (работает медленнее)- Предрасчет / Точный (медленнее) - ГМС постарается сам подстроить маску под форму объекта.- Предрасчет / Точный по кадрам.
Мой совет:Если игра небольшая, ставьте ручной режим и настраивайте сами те объекты, для которых это важно. Особенно те объекты, столкновение с которыми должно происходить только в определённой позиции (скажем, как в Стардью, сталкиваешься с основанием дерева, но не с его кроной).
В нашей игре данная настройка нам практически не пригодится, так как будет использоваться система поиска путей. Тем не менее, с помощью маски можно будет реализовать "выталкивание" застрявших персонажей, а также настроить косметическое "расталкивание" персонажей, если те проходят через занятую другим персонажем клетку.
Как работают скрипты.Скрипт (функция) - именованный блок кода, выполняющий определённую задачу. Все команды, которые мы использовали до этого, являются такими "скриптами". Иногда я буду называть их функциями, оба варианта правильные.
Создать скрипт просто. Для этого нажмите ПКМ по папке "Scripts" и выберете там "Script". Назовите его scrMoveУ вас откроется следующее окно:
scrMove - название нашей функции. В круглых скобках мы в неё можем передавать аргументы. Аргумент - это переменная, которая используется только в функции, передаётся в неё при вызове и может иметь любое название. В скобках напишите spd - это будет как раз наша переменная скорости.Вырежьте код движения из Step-события у игрока и вставьте в скрипт, а в Step-событии игрока напишите следующий код:scrMove(player_speed);Получится следующая картина:
Как вы заметили, "x" и "y" у нас по прежнему работают. Это потому, что мы выполняем скрипт в объекте oPlayer: данные переменные подтягиваются из объекта автоматически.
Когда нужно применять скрипты?-) Когда код, который мы пишем, нужно использовать несколько раз.-) Когда код, который мы пишем, занимает много места, чтобы его было проще отладить.-) Когда нам нужно получить какое-либо значение на основе каких-либо данных, так как скрипт может не просто выполнять код, но и возвращать значения.
Для того, чтобы скрипт вернул какое-то значение, нужно написать:
Переменную можно написать только одну. Команда return прерывает выполнение скрипта. Переменную / возвращаемое значение можно не писать, тогда скрипт просто прервётся. Конструкцию с прерыванием следует добавлять везде, где это может повлиять на игру, особенно её работоспособность.
Раньше скрипты выполнялись медленнее, чем код, написанный просто в объекте. Сейчас это не так (или, по крайней мере, не так критично).
Также стоит понимать, что создав объект "Скрипт", вы, по сути, создали просто файл для хранения кода. В одном скрипте может быть собрано сразу множество разных функций, отвечающих за выполнение одной задачи. При вызове обращаться нужно именно к имени функции.
Скрипт для отрисовки текста с обводкой.
Создаём новый скрипт и называем его scrOutlinedTextНам понадобятся следующие аргументы:-) xPos - позиция текста по X-) yPos - позиция текста по y-) col - цвет текста-) outlineCol - цвет обводки-) text - текст-) curdepth - текущая "глубина" объекта.-) Шрифт, которым будем рисовать.-) Позиционирование по X-) Позиционирование по Y
Общая схема работы скрипта:1) Назначаем максимальную "глубину". 2) Рисуем текст цветом обводки.3) Поверх рисуем текст нужным нам цветом, но с небольшим смещением.4) Возвращаем глубину к первоначальным показателям.
Код: https://pastebin.com/TahYZKc8 P.S. Изначально код не мой, а честно стырен подсмотрен с интернета, но я в него добавил больше переменных для лучшей настройки и отрисовки. Фактически, значение глубины тоже можно передавать как аргумент, чтобы разный текст выводился по разному.
Создание русского шрифта
Проверим, как работает скрипт. Сначала мы создадим русский шрифт. Для этого найдём папку "Fonts", нажмём ПКМ и создадим "Font".Выбирайте любой шрифт и размер, который хотите. Нам с вами нужна кнопка "Add", чтобы добавить диапазон с русским шрифтом.
Здесь напишем 1040 to 1105, чтобы захватить весь русский алфавит, затем нажмём "Add Range".Шрифт называйте на Ваше усмотрение. Я назову его: fontArialRusSmall
Теперь перейдём к объекту игрока. Создадим событие Draw GUI и напишем следующий код:scrOutlinedText(10, 10, c_yellow, c_red, "Скорость: " + string(player_speed), depth, fontArialRusSmall, fa_left, fa_top)Результат:
Можете заменить c_yellow и c_red на любые цвета. Помните, что можно передавать цвета в HEX с помощью # и $ (в обратном порядке).
Скрипт для поиска значения в массиве:
Так как эту тему мы не проходили, заострять внимание не буду, но вдруг кому пригодится: https://pastebin.com/1VTNg4Bp
Переходы между комнатами. Создаём меню.
Наконец, с матчастью мы закончили. Приступим к тому, что создадим меню нашей игры.Создаём новую комнату и обзываем её rmMainMenu. Затем нажмите на значок "домика", чтобы поменять стартовую комнату (ту, что отображается при запуске игры).
Сделайте так, чтобы домик был напротив созданной комнаты.Заметьте, что при создании новой комнаты у неё изменяются все настройки! Это важно.
Создадим в "Objects" папку "Меню", где создадим следующие объекты: "oMStart", "oMSettings", "oMExit", "oMMenu".OMStart - кнопка для старта нашей игры.OMSettings - кнопка для перехода в настройки игры.oMExit - кнопка для выхода из игрыoMLoad - кнопка для загрузки игрыoMMenu - управляющий объект, который будет располагать наши кнопки.
Зайдём в комнату MainMenu и поставим там объект oMMenu.Затем перейдём в код oMMenu и выберем событие создания (Create)
Там напишем следующий код:
Теперь нужно каждую из этих кнопок разместить. Но, чтобы правильно их разместить, сделаем на все кнопки один спрайт. Размер подбирайте под себя.
У каждого объекта создадим два события: Draw, которое оставим пустым, и Draw GUI, где напишем draw_self();Там же напишем:
Где "Здесь текст кнопки" - заменить на нужное: "Начать игру", "Загрузить игру", "Настройки", "Выход"
Вернёмся в OMMenu.Нажмите правой кнопкой мыши. Наведитесь на пункт "Code Snippets" и выберите пункт 5.В данном меню расположены готовые шаблоны кода, а мы создали шаблон цикла.Напишите следующий код:
for (var i = 0; i < array_length(buttons); ++i)
Теперь нужно сделать отслеживание нашей мышки. Кнопку "Настройки" мы сделаем чуть позже. Пока перейдём к кнопке для выхода из игры.
До текста отрисовки нашего текста, нам нужно сделать небольшую подсветку нашей кнопки, чтобы мы знали, что она работает. Для этого нам нужно отслеживать позицию нашей мыши относительно интерфейса, для чего есть специальная команда.
ОК. Теперь мы отслеживаем нашу мышку относительно GUI. Нужно проверить, наведены ли мы на конкретный объект и если да - нарисовать поверх него полупрозрачный белый прямоугольник. Для этого есть команда:instance_position(x, y, obj)
В x и y мы передадим переменные выше, а в качестве объекта напишем "self", чтобы проверять координаты нашего объекта.
Уже после этого пишем наш текст.Теперь при наведении на кнопку "выхода" мы увидим, что она "активна".
Вырежем наши две переменные, добавим их в событие create. Теперь мы можем использовать эти переменные в любом коде этого объекта.
Теперь перейдём в событие step. Сюда также добавим наши две переменные. Теперь они будут постоянно обновляться.Начнём настраивать логику работы.
Можете скопировать тот код, что мы писали выше, но удалить всё изнутри.Нам нужно сделать проверку:Если мы наведены на объект И нажата левая кнопка мыши - выйти из игры. То есть, немного её дополнить.
Для проверки нажатия кнопки левой мыши будем использовать команду:mouse_check_button_pressed(mb_left)Для выхода из игры: game_end()
В итоге, в коде step будет следующий код:
Итого, если мы наведёмся на кнопку выхода - она подсветится. Нажмём - игра закроется.
Аналогичный код с подсветкой и проверкой просто скопируем в oMStart. В других кнопках логику работы в step пока не настраиваем на них, работаем с кнопкой начала игры.Нужно внести одно изменение.
Вместо game_end() нам нужно написать код для смены уровня.Для этого напишем:room_goto(Room1) Где Room1 - название комнаты, в которую вы хотите переместиться. Оно должно совпадать с названием комнаты в браузере ассетов.
Итого, мы можем выйти из игры или перейти в комнату, где у нас есть игрок, которым мы можем управлять. Осталось дополнить написать код, чтобы мы могли вернуться обратно в меню, при этом так, чтобы положение игрока на экране сохранялось.
Для этого нам придётся создать новый объект и назвать его oGameManager.Разместим его в самой первой комнате и отметим у него чекбокс "Persistent"Затем напишем у него в Step код:
Если индекс текущей комнаты != 0 и нажата ESC - деактивируем все инстансы, кроме текущего и переходим в меню.
При старте комнаты, если эта комната не является меню, мы будем запускать все инстансы.
Реализация сложнее. О том, как работает Nine Slice.
Ниже будет приведена реализация меню с использованием функции Nine Slice, которая позволит нам делать кнопки других размеров.Используя Nine Slice мы не сможем проверять, наведены ли мы сейчас на нужный нам объект. Также эта функция годится только для отрисовки типовых форм с типовым шрифтом. Если у вас спрайт кнопки идёт с текстом, то данная функция не подойдёт. Чтобы отслеживать нажатия и наведение, нам необходимо проверять, находятся ли координаты мыши в нужной области. Для этого существует команда
Итак. Первый шаг - это убрать спрайт у каждой нашей кнопки.Для этого откроем объект каждой кнопки, нажмём на выбор спрайта и выберем "None".
Шаг второй.У каждой кнопки в событии Create мы пропишем:
При создании кнопок мы и так будем знать их x и y координаты, а ширину и высоту будем передавать.
Шаг третийНарисуем спрайт в виде одноцветного квадрата размером 9х9 пикселей.Затем добавим ему две обводки разных цветов, каждая шириной в 1 пиксель.Получим следующую картину:
Теперь перейдём в настройки спрайта.Назовём его sButtonSlice.Слева выберем и откроем пункт Nine Slice.
После чего поставим галочку "Activate Nine Slice".Далее - смотрим на наш спрайт и настраиваем его, как показано на скриншоте ниже:
Чтобы двигать фиолетовые полоски - достаточно на них навестись и зажать ЛКМ.В правом окошке вы можете порастягивать данный спрайт, как хотите и посмотреть на итоговый результат.Растягивающаяся рамка для кнопок готова!
Шаг четвертый.
Пропишем в Draw GUI отрисовку кнопки вместо draw_self():
Таким образом, мы отрисовываем кнопку.
Вернёмся в oMMenu, в Create, где немного допишем наш код.Мы создадим переменную, которой будем присваивать значение ID только что созданного объекта. Затем, обращаясь к этой переменной, мы будем менять у данного объекта параметры: ширину и высоту.
Меняя значения bwidth и bheight вы сможете сами настроить нужные размеры кнопок.
Обратите внимание, что создавая инстанс мы можем передать struct - то есть структуру данных.Тогда не нужно будет обращаться к инстансу через переменную, но придется удалить код
При создании наших объектов, а сам код для создания инстанса преобразится следующим образом:
Какой из вариантов удобнее - решать вам.Вариант с переменными, которые объявляются при создании объекта, я лично считаю более надёжным, а функциональных отличий между данными командами не так много. С другой стороны, такая возможность появилось недавно и её нужно тестировать.
Теперь задача - сделать так, чтобы мы могли считывать, наведены ли мы сейчас на кнопку.
Для этого мы берём координаты x и y, после чего проверяем, находится ли наша мышь в области:
Остальное - копируем из прошлого кода. Получается следующий блок:
Шаг седьмой.
Проделать тоже самое в событии Step. (По сути, просто заменяем instance_position)
Шаг восьмой.
Донастраиваем все кнопки.
Какой из вариантов использовать - решать Вам.
Можно вообще использовать следующий вариант:
Создать спрайт нужных размеров и залить его белым цветом с "прозрачностью" в 1 единицу. Совершенно незаметно. Используя bbox_left / top / right / bottom нарисовать поверх прозрачной основы кнопку и проверять уже не через "point_in_rectangle", а как в первом способе.
На этом сегодняшний гайд подходит к концу.Вкратце мы разобрали события отрисовки; как с ними работать; как работать со скриптами; как работать со шрифтами; а также вы можете смело скопировать себе и использовать несколько других скриптов в своих проектах.
Что нужно подготовить к следующему гайду:- Минимум два спрайта "породы" - на фон (темнее) и для объекта (светлее). Размер одного спрайта - 32х32.- Желательно спрайты нескольких объектов, вроде стен, дверей. Исходите из того, что размер одной клетки будет равен 32х32 пикселей.- Спрайт для неба/космоса/прочего на фон на ваше усмотрение.
Я подготовлю свои варианты и, используя их, на следующем занятии мы с вами разберёмся, как работает камера.Сделаем меню настроек с возможностью переключения в полноэкранный режим.
И небольшой спойлер на будущее.Взаимодействовать с миром можно будет двумя способами: через ЛКМ и наведением "тела" игрока на тот или иной объект.
Загрузить файл проекта и всё пощупать самому можно по ссылке:Яндекс диск
- Камера и её настройка. Разные способы реализации: от простого к сложному.
- Иерархия объектов. «Объекты-родители» и их «дети». Решение часто встречающихся проблем и немного про то, как удобно выстраивать взаимодействие с объектами. Глобальные переменные.
- Массивы и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке. Включая объяснение, в каких случаях лучше использовать встроенные функции, в каких – писать свои с нуля.
- Пути. Один большой гайд, включая скрипты поиска путей для различных сеток в т.ч. с примерами из моего личного проекта.
- Иные способы хранения информации в GMS2, когда их стоит или не стоит использовать.
- Сохранение. Встроенное VS самописное.
Есть вопрос - задавай, постараюсь ответить.Что-то не получилось, вылезла ошибка и так далее - тоже пиши, помогу или поможем.