Как написать игру платформер

Главная / Быстрый старт / Как сделать игру платформер за 5 минут, создаем игру сами


Данная статья написана очень подробно, и предназначается в первую очередь для новичков. Предполагается, что читатель не обладает знаниями интерфейса, объектов, поведений и ранее не создавал ни одной игры в программе Scirra Construct.

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

Содержание

  1. Подготовка
  2. Создание каркаса и декораций
  3. Добавление анимации для главного героя
  4. События для главного героя
  5. Создание врага
  6. Переход на новый уровень
  7. Звуки и музыка
  8. Постскриптум

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

Подготовка

Итак, давайте приступим. Откройте программу, на главной панели команд нажмите Файл, затем New (Новый) и далее New Direct-X game (Новая Direct-X игра).
Перед вами новый проект.

Прежде чем создавать свою первую игру, нам нужно произвести некоторые настройки. По умолчанию программа создаёт проект с шириной и высотой уровня 640х480, но наш первый уровень будет иметь размер немного больше. Поэтому, первым делом нам нужно настроить уровень: на панели Properties (Свойства), которая располагается слева (смотрите рис. 1), во вкладке Layout Properties (Свойства Уровня) напротив Wight (Ширина) установите значение 2000, а напротив Height (Высота)600. Теперь наш уровень будет иметь длину в 2000, а высоту в 600 пикселей.

Рис. 1 — Layout Properties (Свойства Уровня).

Далее нам нужно настроить размер окна, через которое мы, подобно видеокамере, будем наблюдать за персонажем и окружающими его объектами в игре. Для этого во вкладке Layout Properties (Свойства Уровня) напротив Application (Приложение) кликните по ссылке Properties (Свойства). Откроется ряд новых опций, среди которых нам нужно найти Window Properties (Свойства Окна) — смотрите рис. 2. Напротив Window Width (Ширина Окна) установите значение 800, а напротив Window Height (Высота Окна) установите 600. Теперь при запуске приложения, размер игрового окна будет 800 на 600 пикселей.

Рис. 2 — Window Properties (Свойства Окна).

Кстати, на панели Properties (Свойства), чуть выше есть вкладка Information Properties (Информация), где напротив Name (Имя) вы можете написать название вашей игры, а напротив Creator (Создатель) вписать своё имя — смотрите рис. 3.

Рис. 3 — Information Properties (Информация).

Также я рекомендую включить опцию FPS in caption (FPS в заголовке), которая располагается чуть ниже, во вкладке Runtime Properties (Опции Запуска) — смотрите рис. 4 Эта опция помогает отслеживать количество кадров в секунду и загруженность видеопамяти при запуске приложения.

Рис. 4 — Runtime Properties (Опции Запуска)

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

Примечание: если вы включили опцию FPS in caption, то информация о количестве кадров и видеопамяти будет отображаться только в режиме окна (т.е. опция Fullscreen должна быть выключена).

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

Создание каркаса и декораций

Объекты в сцене можно условно разделить на два типа — каркас и декорации. Каркас является основой взаимойдействия объекта с другими телами, именно к нему применяются все поведения, но он не виден в сцене. Декорации — это то, что мы видим в игре на экране, например, анимация персонажа. Обычно они привязаны к каркасу и наследуют его поведение. Такое разделение создано по одной простой причине — чтобы столкновение объектов всегда происходило одинаково. Анимированый персонаж, или спрайт, постоянно меняющий размер, может пересекаться с объектами абсолютно неожиданным образом, что обычно приводит к его телепортации. Но если персонаж будет привязан к невидимому боксу, который имеет всего 1 кадр анимации и 1 направление, и именно этот бокс будет сталкиваться со всеми объектами — столкновение всегда будет работать одинаково корректно.

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

В центре экрана располагается белое поле, это Layout Editor (Редактор Уровня), тут мы и будем собирать весь уровень в игре. Чтобы добавить спрайт, кликните на белом поле правой кнопкой мыши (далее сокращенно ПКМ), откроется всплывающее меню, в котором нужно выбрать команду Insert an object (Вставить объект), кликнув на ней левой кнопкой мыши (далее сокращенно ЛКМ). Смотрите рис. 5.

Рис. 5 — Всплывающее меню.

Откроется окно с заголовком Insert new object (Вставить новый объект), где содержатся все объекты программы. Среди них найдите объект под названием Sprite (Спрайт) — смотрите рис. 6. Кликните по нему 2 раза ЛКМ, и вместо курсора на поле появится крестик. Кликните ещё 1 раз ЛКМ в том месте, где хотите разместить объект на поле.

Рис. 6 — Insert new object (Вставить новый объект).

Откроется окно редактора Picture Editor (Редактор Изображений) — смотрите рис. 7. В самом верху есть иконка с изображением открытой папки — это кнопка Open (Открыть), кликните на неё и, указав нужный путь к файлу platforma01.png, нажмите Открыть. В редакторе появится спрайт. Закройте окно редактора, кликнув на крестик (справа вверху), вас спросят Do you want to save changes to this images? (Вы хотите применить изменения к изображениям?) — нажмите кнопку Да.

Рис. 7 — Picture Editor (Редактор Изображений).

На белом поле появится спрайт. Давайте зададим ему имя.

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

Чтобы назначить имя спрайту, перейдите на панель Properties (Свойства), и там во вкладке Common (Общие), напротив опции Name (Имя) впишите имя спрайта — назовите его platforma01. Смотрите рис. 8.

Рис. 8 — Common (Общие)

Теперь нужно задать спрайту дополнительный атрибут, чтобы наш персонаж не смог проваливаться сквозь объект. Найдите чуть ниже вкладку Groups (Группы), а в ней ещё одну под-вкладку Attributes (Атрибуты) и установите галочку напротив опции Platform (Платформа) — смотрите рис. 9. Данный параметр создает одностороннее столкновение персонажа и платформы, т. е. персонаж не сможет провалится сквозь платформу, но он всегда сможет запрыгнуть на нее снизу.

Рис. 9 — Атрибут Platform (Платформа).

Так как этот спрайт является каркасом, по которому будет ходить наш персонаж, нужно сделать его невидимым. Для этого спуститесь ещё ниже по списку, найдите вкладку Appearance (Появление) и напротив опции Invisible on start (Невидимый при старте) поставьте галочку — смотрите рис. 10. Теперь при запуске игры спрайт platforma01 будет невидимым.

Рис. 10 — Appearance (Появление)

Пришло время заняться расстановкой спрайта. Но перед этим нам нужно включить одну очень полезную опцию, которая позволяет точно располагать объекты в сцене. В самом верху программы на главной панели команд выберите вкладку Layout (Уровень), среди открывшихся опций найдите раздел Grid (Сетка), включите сетку, нажав на большой кнопке Toggle Grid (Переключить Сетку), и рядом установите галочку Snap-to moving (Движение с привязкой к сетке). Смотрите рис. 11.

Рис. 11 — Grid (Сетка).

В этой же группе нажмите на кнопку Edit grid (Править сетку), откроется окно Grid settings (Настройки сетки)смотрите рис. 12. Установите Horizontal width (Горизонтальная ширина) и Vertical height (Вертикальная высота) по 64 пикселя, нажмите ОК.

Рис. 12 — Grid settings (Настройки сетки).

Теперь расположите спрайт platforma01 в левом нижнем углу сцены Layout Editor (Редактора уровня), он самостоятельно примагнитится к точке и слегка выйдет за границу сцены. Чтобы скопировать спрайт, удерживайте клавишу Control + ЛКМ перетащите его курсором мыши. Спрайт автоматически примагнитится к сетке. Раскопируйте спрайт по всему уровню, чтобы у вас получилось, как на рис. 13 (масштаб уровня уменьшен на 75%).

Рис. 13 — Расположение спрайта platforma01

Примечание: в Construct есть два типа копирования спрайта.

Первый тип — Instance (Образец). В этом типе все события и условия одинаково распространяются на все копии объекта. Они имеют одинаковые поведения и некоторые общие свойства (не все). Чтобы создать копию Instance (Образец), можно скопировать объект с помощью клавиши Control или нажать ПКМ на объекте, и в открывшемся списке выбрать Copy (Копировать), а затем Paste (Вставить). Также есть еще один способ — нажать и удерживать на объекте ЛКМ и, перемещая его по экрану, нажимать клавишу Enter (это особенно полезно при создании уровня). Копии объекта, скопированные таким образом, не появляются в списке объектов и не занимают дополнительных ресурсов, но! если все же провести эксперимент и попытаться создать многочисленные копии Instance (Образец) достаточно крупных анимированных спрайтов, размером, например, 128×128 и раскопировать их больше 1000 копий — возможно значительное падение FPS.

Второй тип — Clone (Клон). В этом типе все объекты уникальны, и для каждого из них по отдельности нужно создавать события. У них могут быть совершенно независимые поведения и свойства. Чтобы создать копию Clone (Клон), нажмите ПКМ на объекте и в открывшемся списке выберите Copy (Копировать), а затем Paste Clone (Вставить Клон). Копии объекта, скопированные таким образом, будут появляться в списке объектов сцены и, как совершенно новые объекты, будут расходовать ресурсы системы.

Теперь создадим спрайт, который будет основой нашего персонажа. Скачать изображение спрайта можно по этой ссылке — basis.png. В Layout Editor (Редактор Уровня) на белом поле кликните ПКМ и добавьте новый объект спрайт. Откроется окно Picture Editor (Редактор изображения), укажите путь к изображению и, загрузив его, закройте редактор. Поместите спрайт чуть выше платформ, которые мы создали до этого. Смотрите рис. 14. (масштаб уровня уменьшен на 50%).

Рис. 14 — Расположение спрайта basis

Затем в свойствах спрайта, во вкладке Common (Общие) задайте имя basis. Далее ниже, во вкладке Behaviors (Поведения), напротив опции New behaviors (Новое поведение), нажмите Add (Добавить). Смотрите рис. 15.

Рис. 15 — Behaviors (Поведения).

Откроется список поведений, среди которых нам нужно выбрать поведение Platform (Платформер). Смотрите рис. 16.

Рис. 16 — Список поведений.

Теперь нам нужно сделать так, чтобы камера в игре всегда следила за нашем персонажем. Для этого ниже ищем вкладку Groups (Группы), открываем под-вкладку Attributes (Атрибуты) и напротив опции Center view on me (Центрировать взгляд на мне) ставим галочку. Смотрите рис. 17.

Рис. 17 — Опция Center view on me (Центрировать взгляд на мне).

Так как спрайт basis тоже является лишь каркасом для основного персонажа, нужно сделать его невидимым также как и платформу. Для этого ниже в свойствах, во вкладке Appearance (Появление) поставьте галочку напротив опции Invisible on start (Невидимый при старте).

Итак, основной каркас игры мы построили. Теперь нам нужно создать второй слой уровня, куда мы будем загружать спрайты декораций. В правой части экрана есть панель, где располагаются три вкладки: Project (Проект), Animator (Анимация) и Layers (Слои). Смотрите рис. 18.

Рис. 18 — Вкладки на правой панели.

Нажмите на третью вкладку Layers (Слои), и на панели отобразится список всех слоев в сцене. Смотрите рис. 19.

Рис. 19 — Вкладка Layers (Слои).

Кликните ЛКМ на названии слоя (по надписи Layer 1), слева, на панели Properties (Свойства) появятся свойства слоя. Найдите вкладку Layer Properties (Свойства Слоя) и напротив Name (Имя)впишите новое имя слоя carcass. Смотрите рис. 20.

Рис. 20 — Layer Properties (Свойства Слоя).

Возвращаемся в правую часть экрана к вкладке Layers (Слои) и создаем новый слой, нажав на зеленую стрелку с направлением вверх (рядом с корзиной). Появится новый слой с названием Layer 2, кликните по нему ЛКМ и переименуйте в свойствах на scenery. У вас должно получится в точности, как на рис. 21.

Рис. 21 — Слои.

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

Пришло время загрузить платформу, по которой будет бежать наш главный герой. Скачать изображение платформы можно по этой ссылке — platforma02.png.

Итак, убедитесь, что слой 2 выделен (если нет, то кликните ЛКМ по названию слоя — scenery), перейдите в Layout Editor (Редактор Уровня), кликните на белом поле ПКМ и создайте новый объект спрайт. Загрузите изображение платформы и переименуйте его в platforma02. Далее раскопируйте и расставьте спрайт таким образом, чтобы копии находились на тех же местах, что и спрайты platforma01 (т.е. перекрывали их). Смотрите рис. 22 (масштаб уровня уменьшен на 75%).

Рис. 22 — Расположение спрайта platforma02.

Давайте добавим задний фон. Для этого скачайте его по ссылке — background.png, затем выделите слой carcass (кликнув по названию) и нажмите зеленую стрелку с направлением вниз. У вас появится новый слой. Переименуйте его в background, затем добавьте на этот слой новый объект спрайт и, загрузив в него изображение заднего фона, переименуйте сам спрайт на background. Там же, в свойствах, во вкладке Common (Общие), установите координаты расположения заднего фона в пикселях — значение 1000 напротив надписи X, 300 — напротив Y. Также давайте зададим размер заднего фона, установив параметр Width (Ширина) — 2000, а Height (Высота) — 600. Если ничего не напутали, у вас должно получится как на рис. 23.

Рис. 23 — Параметры спрайта background.

Теперь во вкладке Layers (Слои), на слое с названием background, кликните на изображение замка. Смотрите рис. 24. Из зеленого он перекрасится в серый цвет — это означает, что теперь вы не сможете выделить спрайты, находящиеся на этом слое, пока не выключите блокировку. Эта очень полезная опция, которая предотвращает случайное выделение объектов, стоящих на заднем плане.

Рис. 24 — Включение режима блокировки слоя.

Добавление анимации для главного героя

Пришло время добавить в игру главного героя. Скачайте архив с анимацией персонажа по этой ссылке — character.zip. Распаковав архив, внутри вы обнаружите 4 папки с анимацией.

  1. Папка standing — стойка.
  2. Папка moving — бег.
  3. Папка jumping — прыжок.
  4. Папка falling — падение.

Для начала загрузим анимацию стойки. Для этого вернитесь к слою scenery (выделив его) и создайте в Layout Editor (Редактор Уровня) новый объект спрайт. В Picture Editor (Редактор изображений) загрузите кадр анимации с именем frame01 из папки standing. Переименуйте спрайт в character и переместите его поближе к спрайту basis.
Далее переходим на панель в правой части экрана и внизу выбираем вкладку Animator (Анимация). На панели отобразится список анимаций. Смотрите рис. 25.

Рис. 25 — Анимации.

По умолчанию там только одна анимация default. Выделите ее, кликнув по названию, и на панели слева в свойствах анимации, напротив Animation name (Имя анимации) впишите standing. Смотрите рис. 26.

Рис. 26 — Animation name (Имя анимации).

Теперь давайте добавим анимацию бега. Возвращаемся на панель анимаций и кликаем ПКМ по названию анимации standing, откроется список команд, среди которых нам нужно выбрать Add new animation (Добавить новую анимацию). Смотрите рис. 27.

Рис. 27 — Add new animation (Добавить новую анимацию).

В списке анимаций добавится новая анимация с названием Animation 1, в свойствах переименовываем ее в moving. Далее на панели анимации кликаем под ней ЛКМ на закрученную стрелку с надписью Angle: 0* (Right) (Угол: 0* (Право)). Мы увидим, что в нижнем окне появился один пустой кадр. Смотрите рис. 28.

Рис. 28 — Анимация moving.

Рядом с пустым кадром нажимаем ПКМ и в открывшемся списке выбираем команду Import frames (Импортировать кадры). Смотрите рис. 29.

Рис. 29 — Import frames (Импортировать кадры).

Через открывшийся проводник находим скачанную папку character и в ней папку moving. Выделяем все кадры (все 16 файлов) и нажимаем Открыть. Появится окно с названием Import frames (Импортировать кадры), в котором должны быть загружены все 16 кадров анимации. Смотрите рис. 30.

Рис. 30 — Import frames (Импортировать кадры).

Нажимаем внизу Import (Импортировать), и в нижнем окне добавятся кадры анимации бега, среди которых остался один пустой кадр — его обязательно нужно удалить! Выделите пустой кадр и нажмите Delete на клавиатуре. Смотрите рис. 31.

Рис. 31 — Удаление пустого кадра.

Тем же путем добавьте еще 2 анимации — jumping и falling. Изображения для них лежат в соответствующих папках. Будьте внимательны: у анимации прыжка всего 1 кадр, а у падения — 3. Название анимаций должно соответствовать названиям папок. Также не забывайте удалять пустой кадр. Если его не удалить, то анимация в игре будет отображаться некорректно.

Если вы ничего не напутали, в итоге у вас должно получится в точности как на рис. 32.

Рис. 32 — Добавлено две новых анимации jumping и falling.

После этого переходим к главной части — создание событий.

События для главного героя

Для создания событий, в самом низу программы, кликните ЛКМ на вкладке Event Sheet Editor (Редактор событий). Смотрите рис. 33.

Рис. 33 — Event Sheet Editor (Редактор событий).

Поле со спрайтами исчезнет, и вместо него появится пустая страница. В центре этой страницы кликните ПКМ и в открывшемся списке команд выберите Insert group (Вставить группу). Смотрите рис. 34.

Рис. 34 — Insert group (Вставить группу).

Появится окно Modify Event Group (Редактировать Группу Событий). Напротив Title (Название) укажите название группы — Character, а напротив Description (Описание) вы можете написать небольшое описание для группы, например, События для главного героя. Смотрите рис. 35.

Рис. 35 — Modify Event Group (Редактировать Группу Событий).

Кликните по кнопке OK, и на странице появится новая группа с названием Character. Именно в ней мы и будем создавать все события, связанные с персонажем.

Но сначала, давайте поговорим о том, что же такое события и какие виды событий бывают?

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

Каждое событие состоит из двух частей: Condition (Условие) и Action (Действие). Условие всегда расположено в левой колонке, оно определяет задачу, условие, при котором должно выполниться действие (например, Всегда, Только один раз, Когда спрайт столкнулся с врагом и т.д.). Действие всегда располагается в правой колонке и определяет, что конкретно должно произойти при определенном условии (например, Объект уничтожится, Переместится и т.д.). В одном событии могут быть несколько смежных условий. Смотрите рис. 36.

Sub-events (Под-события) используются как для организации событий, так и для того, чтобы логически запускать события только после того, как запустились другие события.

Рис. 36 — Схема вариантов Событий.

Следует учитывать, что в одном событии может быть множество условий, действий и под-событий. Все зависит от конкретной задачи.

Прежде чем создать событие, нужно определить задачу, которую мы перед собой ставим, а именно — у нас есть спрайт главного героя с именем character и спрайт basis, к которому применено поведение Platform (Платформер). Нам нужно, чтобы спрайт персонажа всегда был привязан к спрайту basis, для этого создадим следующие событие: в группе Character кликните ЛКМ на красной надписи New event to: Character (Новое событие для: Персонажа). Смотрите рис. 37.

Рис. 37 — Создание нового события.

Откроется окно New condition (Новое условие), где изображены все доступные объекты. Выберите объект System (Система) и нажмите Next (Далее). Смотрите рис. 38.

Рис. 38 — Окно New condition (Новое условие).

Откроется список с условиями для объекта системы, среди них выберите условие Always (Всегда) и нажмите кнопку Finish. Смотрите рис. 39.

Рис. 39 — Conditions for ‘System’ (Условия для объекта ‘System’).

После этого в Event Sheet Editor (Редакторе событий), в группе Character появится созданное нами условие. Смотрите рис. 40.

Рис. 40 — Условие.

Далее для этого условия нам нужно создать действие. Напротив условия Always (Всегда) кликните на красной надписи New action (Новое действие), откроется окно New action (Новое действие), где изображены доступные объекты. Выберите спрайт главного героя character и жмите Next (Далее). Смотрите рис. 41.

Рис. 41 — Окно New action.

Откроется список доступных для этого объекта действий, в котором вам нужно выбрать действие Set position to another object (Установить позицию относительно другого объекта) и нажать Next (Далее). Смотрите рис. 42.

Рис. 42 — Actions for ‘character’ (Действия для объекта ‘character’).

Далее в появившемся окне нажмите на большую кнопку с надписью Pick an Object (Указать объект). Смотрите рис. 43.

Рис. 43 — Object to set position to (Объект, относительно которого установить позицию).

Откроется новое окно Select object (Выбрать объект), где вам нужно указать к какому конкретно объекту будет привязан спрайт главного героя. Следовательно выбираем спрайт basis. Смотрите рис. 44.

Рис. 44 — Select object (Выберите объект).

Жмем ОК и затем Finish. Если вы все сделали правильно, то у вас должно получится точно такое же событие, как на рис. 45.

Рис. 45 — События группы Character.

Итак, вы создали первое событие, в котором персонаж всегда привязан к спрайту basis. Проверить, работает ли оно, можно запустив игру — в самом верху на главной панели команд во вкладке Project (Проект) нажмите на большую кнопку Run layout (Запустить уровень). Смотрите рис. 46.

Рис. 46 — Run layout (Запустить уровень).

Если вы не включали в свойствах параметр Fullscreen (Полный экран), то игра запустится в оконном режиме, и вы сможете наблюдать за количеством FPS и загруженностью видеопамяти VRAM. Смотрите рис. 47 (масштаб изображения уменьшен на 50%).

Рис. 47 — Игра в оконном режиме.

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

Закройте игру (если вы находитесь в полноэкранном режиме, то для выхода из игры нажмите Alt+F4, перейдите в Layout Editor (Редактор уровня) и добавьте новый объект Mouse & Keyboard (Мышь и Клавиатура). Объект должен появится на панели Objects (Объекты) в списке рядом с другими объектами. Смотрите рис. 48.

Рис. 48 — Список объектов.

Теперь создадим событие, при котором наш персонаж сможет поворачиваться строго в ту сторону, в которую мы движемся. Для этого возвращаемся обратно в Event Sheet Editor (Редактор событий) и создаем новое условие — кликаем ЛКМ по надписи New event to: Character (Добавить новое событие) и в открывшемся окне выбираем объект Mouse & Keyboard (Мышь и Клавиатура). Далее ищем строку Key is down? (Клавиша зажата), кликаем на нее и в списке выбираем клавишу Left arrow (Стрелка влево). Смотрите рис. 49.

Рис. 49 — Key to check if down (Зажата ли клавиша).

Жмем Finish (Завершить), и у нас появляется новое условие. Напротив него создаем действие кликнув ЛКМ по надписи New action (Новое действие). В открывшемся окне выбираем спрайт персонажа, далее ищем строку Set angle (Задать угол), кликаем на ней и переходим дальше. В следующем окне мы видим поле для ввода текста со значением 0 — это значение угла. Вверху есть подсказка, в которой написано New angle, in degrees 0-360 (Новый угол, в градусах 0-360). Смотрите рис. 50.

Рис. 50 — Set angle (Задать угол).

Нашей задачей сейчас является создание события, в котором при нажатии на клавишу стрелки влево (на клавиатуре) наш персонаж поворачивался бы в этом же направлении. Это можно сделать, поняв один простой принцип — дело в том, что в программе Construct каждый спрайт по умолчанию имеет значение угла равное 0, при этом само направление угла смотрит вправо. Смотрите рис. 51.

Рис. 51 — Схема направлений угла (0-360 градусов).

Поэтому для того, чтобы при движении влево персонаж смотрел в этом направлении, нам нужно вместо 0 вписать значение 180. Нажимаем Finish, и у вас должно получится так же, как на рисунке 52.

Рис. 52 — События группы Character.

Теперь создайте еще одно точно такое же событие, только в условии вместо клавиши Left arrow (Стрелка влево), выберите Right arrow (Стрелка вправо), а в действии вместо 180 градусов, укажите 0. В итоге у вас должно получится как на рис. 53.

Рис. 53 — События группы Character.

Примечание: вам необязательно создавать событие заново, достаточно скопировать его, перетащив ЛКМ и удерживая клавишу Control. А затем просто изменить в нем необходимые параметры (кликая 2 раза ЛКМ по надписям в событии).

Теперь, когда мы будем двигать героя вправо, его взгляд тоже будет направлен в правую сторону, но, если вы сейчас запустите игру и нажмете стрелку влево, то увидите ошибку! Персонаж будет менять направление, но при этом сам будет переворачиваться вверх тормашками, потому что мы создали событие, в котором мы переворачиваем нашего персонажа на 180 градусов. Для того, чтобы спрайт отображался корректно, перейдите в Layout Editor (Редактор уровня), выделите персонажа и в его свойствах найдите вкладку Angle (Угол) и установите галочку напротив опции Auto mirror (Автоматическое отражение). Смотрите рис. 54.

Рис. 54 — Auto mirror (Автоматическое отражение).

Теперь запустите игру и нажмите стрелку влево — спрайт персонажа отразится в противоположном направлении. Смотрите рис. 55.

Рис. 55 — Движение влево.

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

Далее нам нужно оживить персонажа, чтобы при движении у него воспроизводилась анимация. Для этого перейдите в Event Sheet Editor (Редактор событий) и в группе Character создайте новое событие, в окне New condition (Новое условие) выберите спрайт basis, далее перейдите во вкладку Platform и в списке условий выберите Is on ground (На земле) и Finish (Завершить). Смотрите рис. 56.

Рис. 56 — Вкладка Platform.

Теперь добавим под-событие — на созданном вами событии кликните ПКМ на надписи Is on ground и в открывшемся списке выберите команду Insert sub-event (Вставить под-событие). Смотрите рис. 57.

Рис. 57 — Insert sub-event (Вставить под-событие).

В списке объектов снова выберите спрайт basis и, зайдя во вкладку Platform, выберите команду Is moving (Движется). В итоге у вас должны получиться событие и под-событие. Смотрите рис. 58.

Рис. 58 — Событие и под-событие.

Теперь напротив под-события создайте действие — выберите персонажа, затем найдите действие Set animation (Установить анимацию), в поле ввода с заголовком Name (Имя) впишите в кавычках (‘ ‘) имя анимации, которая должна воспроизводиться при движении персонажа (в данном случае анимация ‘moving’). Смотрите рис. 59.

Рис. 59 — Set animation (Установить анимацию).

Теперь удерживая Control, скопируйте под-событие еще раз. Затем на условии Is moving (Движется) нажмите ПКМ и в открывшемся списке выберите команду Invert condition (Инвертировать условие). Смотрите рис. 60.

Рис. 60 — Invert condition (Инвертировать условие).

Напротив инвертированного условия нам нужно немного подкорректировать действие. В строке Set animation to (Установить анимацию) кликаем ЛКМ по надписи ‘moving’ и вместо нее вписываем другую анимацию с названием ‘standing’, нажимаем Enter. Смотрите рис. 61.

Рис. 61 — Редактирование действия Set animation (Установить анимацию).

Итак, мы создали событие, в котором есть еще два параллельных под-события. В первом мы задали условие, что когда персонаж движется, то воспроизводится анимация бега. Во втором под-событии мы инвертировали условие и получилось, что когда главный герой стоит на месте (не движется) — воспроизводится анимация стойки.
Запустите игру и, нажимая на клавиатуре стрелки влево и вправо, побегайте персонажем. При движении будет воспроизводиться анимация бега (смотрите рис. 62), а при отсутствии движения персонаж будет становится в стойку.

Рис. 62 — Анимация бега.

Управляя героем, вы наверняка заметили, что анимация бега воспроизводится чересчур быстро. Дело в том, что по умолчанию скорость воспроизведения каждой анимации составляет 50 кадров в секунду. А анимация бега, которую мы загрузили была нарисована для 24-х кадров.
Чтобы изменить скорость воспроизведения анимации перейдите в Layout Editor (Редактор Уровня), выберите персонажа и справа на панели управления анимацией, под строкой moving, кликните ЛКМ мыши на закрученную стрелку с надписью Angle: 0* (Right), чтобы выделить анимацию. Смотрите рис. 63.

Рис. 63 — Анимация moving.

Дальше слева на панели свойств напротив Animation speed (Скорость анимации) установите значение 24. Смотрите рис. 64.

Рис. 64 — Animation speed (Скорость анимации).

Запустите игру и проверьте анимацию. Сейчас скорость воспроизведения анимации соответствует скорости движения персонажа. Нам осталось включить еще 2 анимации — прыжок и падение.

Для включение прыжка перейдите в редактор событий и в группе Character создайте новое событие. В списке объектов выберите basis, далее вкладка Platform и выберите там условие is jumping (Прыгает). В действии назначьте анимацию jumping.

Потом скопируйте созданное вами событие и, щелкнув 2 раза ЛКМ по условию is jumping (Прыгает), поменяйте его на команду is falling (Падает), а в действии замените название анимации ‘jumping’ на ‘falling’. В итоге у вас должно получится как на рис. 65.

Рис. 65 — События персонажа.

Запустите игру и нажмите клавишу Shift на клавиатуре. Если вы все сделали правильно, то во время прыжка вы увидите, как включится анимация jumping, а при падении она сменится анимацией falling. При падении видно, что персонаж начнет дергаться — чтобы исправить эту ошибку перейдите в редактор уровня и измените скорость воспроизведения анимации falling на 24 кадра в секунду. Также в свойствах анимации следует снять галочку с опции Loop (Зацикленный). Эта опция нужна, чтобы анимация воспроизводилась по кругу, но в нашем случае при падении анимация должна проигрываться только один раз. Смотрите рис. 66.

Рис. 66 — Свойства анимации falling.

Снова запустите игру и попробуйте попрыгать. При падении анимация должна отображаться корректно (без каких-либо дерганий). Смотрите рис. 67.

Рис. 67 — Анимация jumping и falling.

Внимание! Если при запуске игры у вас некорректно работает анимация — проверьте, совпадает ли имя анимации в свойствах и событиях.

Примечание: если стоя на платформе, зажать стрелку вниз и не отпуская ее, нажать клавишу Shift, то персонаж провалится вниз. Это не ошибка! а особенность включенного вами атрибута Platform в свойствах объекта platforma01. Данная опция позволяет вашему герою спрыгнуть на платформу ниже, как это было в классических платформенных играх. Если в вашей игре персонаж не должен иметь способности прыгать сквозь платформы — тогда в свойствах, во вкладке Attributes (Атрибуты) вам следует снять галочку Platform, а вместо нее включить опцию Solid (Твердый).

Итак, у нас есть действующий персонаж со всеми включенными анимациями. Теперь давайте добавим в игру монеты и сделаем интерфейс с подсчетом очков и жизней. Скачайте архив с изображениями монеты — money.zip, затем перейдите в Layout Editor (Редактор уровня) и на слое с названием scenery создайте новый спрайт. Загрузите в него все 15 кадров анимации монеты из скачанного архива и переименуйте спрайт в money. Затем в свойствах анимации установите скорость воспроизведения 24 кадра. Переместите монету вправо, над второй платформой, и раскопируйте ее в виде пирамиды как на рис. 68 (масштаб изображения уменьшен на 50%).

Рис. 68 — Монеты.

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

Примечание: в программе Construct существует 2 типа переменных:

1. Приватная переменная — создается в каждом конкретном объекте и распространяется на его копии. Работает эта переменная локально, только на одном уровне, т.е. при переходе на новый уровень значение не используется.

2. Глобальная переменная — создается в проекте и работает на всех уровнях в игре.

Для нашей задачи мы будем использовать глобальную переменную. Чтобы ее создать, кликните на вкладке Project (Проект), которая расположена в правом нижнем углу программы. Смотрите рис. 69.

Рис. 69 — Вкладка Project (Проект).

Далее в списке ресурсов найдите папку Global variables (Глобальные переменные), кликните на ней ПКМ и нажмите на Add global variable (Добавить глобальную переменную). Смотрите рис. 70.

Рис. 70 — Папка Global variables (Глобальные переменные).

На экране появится новое окно с названием Add global variable (Добавить глобальную переменную). В поле Name (Имя) впишите score и нажмите ОК. Смотрите рис. 71.

Рис. 71 — Add global variable (Добавить глобальную переменную).

Под папкой Global variables (Глобальные переменные) появится строка с названием вашей глобальной переменной. Смотрите рис. 72. При взятии монет ее значение будет увеличиваться и сохраняться на протяжении всей игры.

Рис. 72 — Переменная ‘score’.

Перейдите в редактор событий и в группе Character создайте новое событие. В открывшемся окне выберите спрайт basis, затем в списке условий кликните на On collision with another object (При столкновении с другим объектом), далее нажмите на кнопку Pick object (Указать объект) и выберите спрайт money.
Не создавая действия на это событие, добавьте под-событие, выберите объект System (Система) и в списке команд нажмите For Each Object (ordered) (Для каждого объекта (упорядоченно)), затем кликните на кнопке Pick object (Указать объект) и выберите спрайт money. В созданном вами под-событии добавим действие — выбираем спрайт money, и выбираем действие Destroy (Уничтожить). Далее в этом же под-событии создаем еще одно действие — выберите объект System (Система) и в списке найдите команду Add to value (Добавить к значению), откроется окно, где в поле Variable (Переменная) нужно выбрать имя глобальной переменной score, а в поле Value (Значение) указать количество прибавляемых очков (например, 100). Смотрите рис. 73.

Рис. 73 — Value to add (Добавить значение).

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

Рис. 74 — Событие для спрайта money.

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

Теперь давайте сделаем интерфейс, на котором будут отображаться жизни персонажа и количество заработанных им очков. Перейдите в Layout Editor (Редактор уровня) и на панели Layers (Слои) выделите слой с названием scenery, нажав на зеленую стрелку с направлением вверх, создайте новый слой. Далее в свойствах впишите имя слоя interface, а ниже во вкладке Display (Отображение) напротив опций Scroll X Rate (Скорость прокрутки по X) и Scroll Y Rate (Скорость прокрутки по Y) вместо 100% впишите значение 0.
Значение 0 нужно для того, чтобы слой, на котором расположен интерфейс, не прокручивался за уровнем и всегда стоял на месте.

Примечание: с помощью Scroll X Rate (Скорость прокрутки по X) и Scroll Y Rate (Скорость прокрутки по X) можно создать эффект плывущего фона, так называемый параллакс. Если к примеру поместить на фон дополнительный бэкграунд и настроить Scroll X Rate (Скорость прокрутки по X) и Scroll Y Rate (Скорость прокрутки по Y) по 50%, то при движении персонажа фон будет как бы плыть за ним.

Очень важно, чтобы слой интерфейса всегда находился в самом верху в списке слоев, так как объекты находящиеся на нем (жизни, количество очков и т.д.) должны идти поверх других объектов в игре. Смотрите рис. 75.

Рис. 75 — Список слоев.

Далее загрузите изображение lives.png, и на созданном вами слое добавьте новый спрайт. Переименуйте его в lives и поместите в левый верхний угол. Смотрите рис. 76.

Рис. 76 — Спрайт lives.

Теперь, находясь на этом же слое, кликните ПКМ на уровне и выберите Insert an object (Вставить объект), а в списке объектов найдите объект Text (Текст). Поставьте его рядом со спрайтом lives, затем зайдите в свойства и переименуйте объект в text_lives. Задайте точные координаты расположения объекта, указав напротив строки X значение 85, напротив Y значение 56. Теперь измените размер контейнера для текста указав напротив Width (Ширина) и Height (Высота) значение 30. Смотрите рис. 77.

Рис. 77 — Свойства объекта ‘text_lives’.

Cпуститесь ниже и найдите вкладку Properties (Свойства), и здесь напротив Size (Размер) установите размер шрифта 34 пикселя. Затем отметьте галочки напротив Bold (Жирный) и Italics (Курсив), чтобы сделать шрифт жирным и наклонным. Далее установите позицию текста внутри самого контейнера выбрав Left (Слева) в опции Horizontal alignment (Горизонтальное выравнивание) и Top (Верх) в Vertical alignment (Вертикальное выравнивание). Осталось задать цвет текста: напротив опции Color (Цвет) щелкните на цветной прямоугольник и вам предложат выбрать цвет — в самых верхних строчка впишите параметры 255; 190; 0. Смотрите рис. 78.

Рис. 78 — Свойства текста.

Чтобы при запуске игры объект text_lives показывал нам количество жизней персонажа, нужно создать приватную переменную. Выделите спрайт главного героя и в свойствах найдите вкладку Private Variables (Приватные Переменные). Смотрите рис. 79.

Рис. 79 — Private Variables (Приватные Переменные).

Нажмите Add/Edit (Добавить/Редактировать), откроется окно Manage Private Variable (Управление приватными перемеными). Смотрите рис. 80.

Рис. 80 — Управление приватными перемеными.

В нижней части окна кликните на зеленый плюс. Откроется новое окно Add Private Variable (Добавить приватную переменную), в поле Name (Имя) впишите название lives, а в Initial value (Первоначальное значение) укажите количество жизней по умолчанию, вписав значение 3. Смотрите рис. 81.

Рис. 81 — Add Private Variable (Добавить приватную переменную).

Далее нажимаем кнопку OK, а затем Done. Приватная переменная появится в свойствах персонажа. Теперь переходим в редактор событий и в группе Character ищем событие, в котором есть условие Always (Всегда) (это было самое первое событие, которое мы создали). В нем уже есть действие, поэтому под ним кликаем ЛКМ на New action (Новое действие) и создаем еще одно действие. В списке объектов выбираем text_lives, затем действие Set text (Установить текст). В окне появится строка для ввода текста, а ниже список объектов, среди которых нам нужно выбрать спрайт Character. Щелкните по спрайту 2 раза ЛКМ, на экране появится окно New expression (Новое выражение), где вам нужно найти команду Get private variable (Получить приватную переменную). Смотрите рис. 82.

Рис. 82 — Окно New expression (Новое выражение).

Нажав Finish вы увидите, что в поле для ввода текста появилась строчка character.Value(‘Variable name’). Вместо Variable name (Имя значения) вам нужно вписать имя приватного значения жизней персонажа (то есть lives). Смотрите рис. 83.

Рис. 83 — Окно Set text (Установить текст).

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

Рис. 84.

Теперь запустите игру и проверьте — вместо буквы Т будет отображаться количество жизней персонажа. Смотрите рис. 85.

Рис. 85 — Отображение количества жизней персонажа.

На этот же слой добавьте еще один объект Text и переименуйте его в text_score, ниже задайте координаты — для X значение 600, для Y значение 56. Размер контейнера Width (Ширина) — 200, Height (Высота) — 30. Спускаемся ниже во вкладку Properties (Свойства) и настраиваем объект следующим образом: размер шрифта ставим такой же, как и у первого объекта, 34 пикселя. Также отмечаем галочки напротив Bold (Жирный) и Italics (Курсив). Ставим точно такую же позицию внутри самого контейнера выбрав Left (Слева) в опции Horizontal alignment (Горизонтальное выравнивание) и Top (Верх) в Vertical alignment (Вертикальное выравнивание). Задаем тот же цвет, установив параметр цвета на 255; 190; 0. Смотрите рис. 86.

Рис. 86 — Настройки объекта text_score.

Возвращаемся в редактор событий и под действием для объекта text_lives создаем еще одно действие. Выбираем объект text_score, далее команду Set text (Установить текст), внизу из списка объектов выбираем объект System (Система), ищем команду Get global variable (Получить глобальную переменную) и жмем Finish (Завершить). Теперь внимание! В поле для ввода текста вы увидите строку global(‘Variable name’), если вы сейчас вместо Variable name (Имя переменной) просто впишите имя глобального значения score и нажмете Finish (Завершить), то в игре будут отображаться лишь цифры! Но нам нужно, чтобы перед количеством очков была надпись, обозначающая, что это за цифры. Для этого перед строкой global(‘Variable name’), в кавычках нужно написать текст, который вы хотите увидеть перед количеством набранных вами очков (например »Score: » или »Очков: »), затем поставить амперсанд &, после которого уже идет строка global(‘score’). Смотрите рис. 87.

Произвольный текст может идти в любом месте, например, чтобы поставить его после количества очков, достаточно перенести текст в конец строчки, после глобальной переменной, и связать их знаком амперсанда. Получится строчка global(‘score’) & »очков».

Рис. 87.

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

Рис. 88.

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

Рис. 89.

Создание врага

Пришло время добавить в игру врага. Скачайте архив enemy_croc.zip, разархивировав его, вы обнаружите 2 папки с анимацией врага — moving и destroyed. Перейдите в Level Editor (Редактор уровня) и на слое scenery создайте новый спрайт. Переименуйте его в enemy_croc, добавьте 2 анимации с именами moving и destroyed и загрузите в них кадры из соответствующих папок. Смотрите рис. 90.

Рис. 90 — Анимации врага.

У обоих анимаций установите скорость воспроизведения 24 кадра в секунду. У анимации destroyed в настройках снимите галочку Loop (Зацикленный), чтобы после уничтожения врага, он оставался без изменений.

Давайте оживим нашего врага.

Существует множество способов, как заставить врага двигаться вперед-назад. Например, для подобной задачи часто используются поведения Grid Movement (Движение по сетке) или Custom Movement (Пользовательское движение), но такой метод требует создание множества событий, а в некоторых случаях даже дополнительных объектов. Мы же сделаем хождение по платформе с помощью всего одного события.

Для начала поставьте спрайт врага на третью платформу на самый правый край. Смотрите рис. 91.

Рис. 91 — Расположение врага.

Потом перейдите в свойства спрайта и найдите вкладку Behaviors (Поведения), кликните ЛКМ на надписи Add (Добавить) напротив New behavior (Новое поведение). В списке поведений выберите поведение Bullet (Пуля). В свойствах, во вкладке Bullet Behavior (Поведение Пули), напротив опции Speed (Скорость) установите значение 90. Смотрите рис. 92.

Рис. 92 — Bullet Behavior (Поведение пули).

Затем ниже, во вкладке Angle (Угол), установите галочку напротив опции Auto mirror (Автоматическое отражение). Переходим к созданию событий.

Для начала создайте новую группу и назовите ее Enemy_Croc. По желанию можете написать небольшое описание для созданной группы (например, события для врага). Так как взаимодействие с врагом будет происходить лишь тогда, когда он ходит по платформе, нужно создать основное событие, которое будет включать в себя все остальные под-события, связанные с движением и взаимодействием героя с противником. В группе Enemy_Croc создаем новое событие, в списке объектов выбираем спрайт enemy_croc, далее в списке условий кликаем на An animation is playing (Проигрывается анимация), в поле ввода текста в кавычках вписываем название анимации ‘moving’ и жмем Finish (Завершить). У нас должно получится событие как на рисунке 93.

Рис. 93.

Теперь создаем под-событие: в списке объектов выбираем объект System (Система), кликаем на команду Every X Milliseconds (Каждые X милисекуд), в поле ввода текста вписываем значение 3500 и жмем Finish (Завершить). Далее напротив этого условия создаем действие: выбираем спрайт enemy_croc, кликаем на действие Set angle (Установить угол) и затем внизу, в списке объектов, еще раз выбираем спрайт enemy_croc. Откроется окно, где нужно выбрать команду Get angle (Получить угол), и в поле ввода текста появится строка enemy_croc.Angle (т.е. угол врага), к ней нужно добавить +180 (плюс сто восемьдесят), чтобы получилось как на рисунке 94.

Рис. 94.

Жмем Finish, и в итоге должно получиться событие как на рисунке 95.

Рис. 95.

Итак, если помните, мы назначили врагу поведение пули, которая летит всегда прямо в направлении угла 0. То есть, если перевернуть угол, то и направление врага тоже изменится. Поэтому мы создали событие, где задали, что каждые 3500 миллисекунд (1000 миллисекунд = 1 секунда), к углу персонажа прибавляется +180 градусов, а так как по умолчанию его угол равен 0 — получается, что каждые 3,5 секунды он будет отражаться и идти в обратном направлении. Запустите игру и убедитесь, что враг ходит по платформе от одного края к другому и обратно.

Осталось лишь создать события для взаимодействия нашего героя с врагом.

Существует множество различных вариантов взаимодействия героя с врагом. Мы рассмотрим классический пример, когда персонаж уничтожает врага прыгая на него сверху, а при подходе и касании врага с любой стороны — герой теряет жизнь и его отбрасывает чуть в сторону. Помните, что все последующие события взаимодействия персонажа с врагом будут создаваться под основным событием, которое мы добавили выше. Приступим: создайте новое под-событие, в списке объектов выберите объект basis, далее выберите условие On collision with another object (При столкновении с другим объектом), кликните на большой кнопке Pick an object (Указать объект) и укажите спрайт enemy_croc. Убедитесь, что созданное вами событие совпадает с рис. 96.

Рис. 96.

Затем для этого под-события создаем еще под-событие: в списке объектов выбираем объект basis, далее вкладку Platform и команду Is on ground (На земле). Для этого под-события создаем еще одно под-событие — выбираем спрайт character, команду Compare angle (Сравнить угол) и, ничего не меняя, жмем Finish (Завершить). Для только что созданного под-события нужно создать еще под-событие — в списке объектов выбираем объект System (Система), далее команду Trigger once while true (Выполнить один раз, если правда). А теперь напротив этого под-события создаем 2 действия.

Первое действие: в списке объектов выберите объект basis, затем вкладку Platform и далее команду Set X component of motion (Задать X компонент движения). В поле ввода текста введите значение –1000 (минус тысяча) и жмите Finish.

Второе действие: в списке объектов выберите спрайт character, затем команду Subtract from value (Вычесть из значения), и в поле ввода текста для приватного значения lives впишите значение 1.

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

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

Рис. 97.

Теперь нужно создать точно такие же действия, но для другого угла и, соответственно, отлетать от врага персонаж должен в другую сторону. Для этого, используя Control, скопируйте под-событие character: Angle Equal to (на рис. 97 это событие номер 15) и измените в условии угол на 180 градусов, кликнув 2 раза ЛКМ по цифре 0. Далее внесите изменения в действие basis: Set horizontal speed to, кликнув 2 раза ЛКМ по значению –1000, и замените его на положительное значение 1000 (без минуса). Смотрите рис. 98.

Рис. 98.

Пришло время реализовать механизм уничтожения врага. Для этого кликните ПКМ на под-событии с условием On collision between basis and enemy_croc (на рис. 98 это событие номер 13) и, используя команду Insert sub-event (Добавить под-событие), создайте новое под-событие. В списке объектов выберите спрайт basis, во вкладке Platform выберите команду Is falling (Падает). Далее для этого же под-события создайте еще одно под-событие. Выберите объект System (Система), нажмите команду Trigger once while true (Выполнить один раз, если правда). Переходим к созданию действий.

Первое действие: в списке объектов выберите спрайт enemy_croc, далее найдите вверху вкладку Bullet (Пуля) и в ней нажмите на действие Set activated (Активировать). Появится окно, где под надписью Activation (Активация) в списке выберите Deactivate (Деактивировать) и нажмите Finish (Завершить).

Второе действие: в списке объектов выберите спрайт enemy_croc, далее нажмите на команду Set animation (Установить анимацию) и в поле ввода в кавычках впишите имя анимации ‘destroyed’.

Третье действие: в списке объектов выберите спрайт basis, во вкладке Platform нажмите команду Jump (Прыгать).

Четвертое действие: в списке объектов выберите объект System (Система), далее команду Add to value (Добавить к значению), в открывающемся списке под надписью Variable (Переменная) выберите созданное нами ранее глобальное значение score, и в поле ввода текста впишите значение 1000.

Итак. Мы создали под-событие, в котором мы есть условие, что при падении один раз совершаются 4 действия — происходит полная остановка врага, включается его анимация разрушения, в это же время наш персонаж отпрыгивает от врага и получает дополнительные 1000 очков. Если вы ничего не перепутали, в итоге должно получиться вот такое сложное событие как на рис. 99.

Рис. 99.

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

Примечания:

1. Если вы хотите, чтобы после уничтожения врага он полностью исчезал с уровня, тогда удалите действия 1 и 2, а вместо них для спрайта врага выберите команду Destroy (Уничтожить). Смотрите рис. 100.

Рис. 100.

2. Если вы размножите спрайт врага, то при попытке уничтожить его — уничтожаться все копии этого спрайта. Чтобы этого не происходило, нужно создать под-событие For Each Object (Для каждого объекта) и поставить под него под-событие On collision between basis and enemy_croc со всей цепочной под-событий с условиями и действиями. Смотрите рис. 101.

Рис. 101.

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

Переход на новый уровень

После того, как игрок собрал все монеты и уничтожил врага, ему предоставляется возможность перейти на новый уровень. Скачайте изображение next.png, затем на слое scenery создайте новый спрайт. Загрузите в него это изображение и переименуйте спрайт как next. Затем на панели анимации рядом с первым кадром кликните ПКМ и в открывшемся меню выберите команду Add frame (Добавить кадр). Смотрите рис. 102.

Рис. 102.

Вы увидите, что рядом с первым кадром анимации появится новый пустой кадр. Смотрите рис. 103.

Рис. 103.

Этот кадр нам нужен для того, чтобы при появлении таблички next она периодически мигала. Если сейчас включить игру, то стрелка будет мигать слишком быстро, поэтому в свойствах анимации нужно изменить скорость, установив 3 кадра в секунду. Разместите спрайт на самой высокой платформе, в конце уровня. Смотрите рис. 104.

Рис. 104.

В свойствах спрайта найдите вкладку Appearance (Появление) и установите галочку напротив опции Invisible on start (Невидимый при старте). Это нужно для того, чтобы при запуске игры стрелка не была видна, она должна появляться лишь после того. как мы соберем все монеты и уничтожим врага.

Для создания события перейдите в редактор событий и в самом низу создайте новую группу Level Events. В этой группе добавьте новое событие: выберите объект System (Система), далее команду Compare global variable (Сравнить глобальную переменную). Откроется окно, в котором под надписью Variable (Переменная) в списке выберите глобальное значение score, а под надписью Comparison (Сравнение) в активном списке выберите Greater or equal (Больше или равно), в поле ввода текста впишите значение 1900. Смотрите рис. 105.

Рис. 105.

Теперь создайте действие: в списке объектов выберите спрайт next, затем команду Set visible (Установить видимость), далее в окне под надписью Visibility (Видимость) в активном списке выберите Visible (Видимый). У вас должно получится событие как на рис. 106.

Рис. 106.

Мы создали событие, которое работает следующим образом: когда наш герой собирает все монеты (всего 9 монет по 100 очков каждая) и уничтожает врага (за уничтожение врага герой зарабатывает 1000 очков), он набирает 1900 очков, и табличка next становится видимой.

Прежде чем создать событие перехода на новый уровень, нам необходимо создать этот самый уровень. Существует 2 способа создания нового уровня.

Способ первый: в правой нижней части окна интерфейса кликните на вкладку Project (Проект) — появится список ресурсов. В самом верху, кликните ПКМ по названию проекта (в данном случае проект называется Quickstart) или на папке Layouts (Уровни), откроется всплывающее меню, где нужно выбрать команду Add layout (Добавить уровень). Смотрите рис. 107.

Рис. 107 — Add layout (Добавить уровень).

Так вы создадите новый, совершенно пустой, уровень, который появится в списке ресурсов в папке Layouts (Уровни) с именем Layout 2 (Уровень 2). В нем вы можете либо заново создать объекты, либо скопировать их с предыдущего уровня. Но в таком случае не стоит забывать, что при изменении свойств объекта на созданном вами уровне это отразится и на других уровнях, где присутствует объект. Если, к примеру, на следующем уровне вы хотите изменить поведение вашего персонажа, то лучше создать спрайт заново или клонировать, а иначе любое изменение отразится на всех спрайтах героя на других уровнях.
Этот способ лучше всего подходит для создания меню, экрана Game Over, титров и т.д.

Способ второй: во вкладке Project (Проект) в списке ресурсов раскройте папку Layouts (Уровни), кликните ПКМ на названии вашего уровня (в данном случае Layout 1), и в появившемся всплывающем меню выберите команду Clone layout (Клонировать уровень). Смотрите рис. 108.

Рис. 108 — Clone layout (Клонировать уровень).

В списке ресурсов в папке Layouts (Уровни) появится новый Layout 2 (Уровень 2). Таким образом вы создадите точную копию первого уровня. Следует учитывать, что как и в первом способе, изменение свойств объекта на новом уровне может отразиться на копиях на других уровнях. Однако, данный способ лучше всего подходит для создания уровней, так как можно, не создавая объекты и события заново, просто изменить их местоположение и таким образом получится совершенно другой уровень.

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

Итак, для создания события перейдите в редактор событий и в группе Level Events создайте следующие событие: в списке объектов выберите спрайт next, далее команду Is visible? (Видимый). Теперь создайте под-событие: выберите спрайт character, далее команду On collision with another object (При столкновении с другим объектом) и в появившемся окне кликните на кнопке Pick an object (Указать объект), укажите спрайт next, нажмите OK и Finish. Напротив созданного условия добавляем следующее действие: выберите объект System (Система), далее команду Next layout (Следующий уровень), откроется окно, где ничего менять не надо, и просто нажимаем Finish.

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

Рис. 109.

Осталось лишь добавить событие, что когда у персонажа заканчиваются жизни — игра закрывается (ну или вы можете самостоятельно сделать переход на новый Layout (Уровень) с экраном Game Over (Конец игры)). Итак, в группе Character создайте новое событие: в списке объектов выберите спрайт character, затем команду Compare a private variable (Сравнить приватную переменную), далее в списке должно быть выбрано приватное значение lives, теперь под надписью Comparison (Сравнение) в активном списке выберите Less than (Меньше чем), а в поле ввода текста оставьте значение 0. Переходим к созданию действия: в списке объектов выберите объект System (Система), далее команду Close (Закрыть). Таким образом, как только у главного героя закончаться жизни (меньше нуля), игра закроется. Сравните событие с рисунком 110.

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

Рис. 110.

Звуки и музыка

Давайте добавим в игру музыку и звуки. Для начала скачайте архив sounds.zip, в котором содержатся все музыкальные файлы для данной игры. Затем в Layout Editor (Редактор уровня) добавьте новый объект, который называется Xaudio2 — он появится в списке объектов сцены. Смотрите рис. 111.

Рис. 111 — XAudio2.

Прежде чем создавать события для звуков и музыки, нужно загрузить все аудиофайлы в игру. Для этого во вкладке Project (Проект), в списке ресурсов нажмите ПКМ на папке Files (Файлы) — появится команда Add file(s) (Добавить файл(ы)). Смотрите рис. 112.

Рис. 112 — Add file(s) (Добавить файл(ы)).

Кликните по ней, и откроется проводник, в котором вам нужно выделить все музыкальные файлы в заранее скачанной папке sounds. В ресурсах отобразится список загруженных файлов. Смотрите рис. 113.

Рис. 113 — Список загруженных аудиофайлов.

Переходим к созданию событий. Создайте новую группу и назовите ее Sounds and Music. Добавьте следующие событие: в списке объектов выберите спрайт basis, далее вкладку Platform и в ней команду Is jumping (Прыгает). Затем создайте под-событие: выберите объект System (Система), далее команду Trigger once while true (Выполнить один раз, если правда). Теперь создаем действие: выбираем объект Xaudio2, далее команду Autoplay resource (Автопроигрывание ресурса) — откроется окно, в котором под надписью Resource to load (Ресурс, который нужно загрузить) в активном списке выберите файл с названием jump.wav и нажмите Finish. У вас должно получиться событие как на рис. 114.

Рис. 114.

Запустите игру и попробуйте попрыгать, нажимая клавишу Shift. Каждый раз при прыжке вы будете слышать звук. Теперь скопируйте это событие целиком и измените условие Is jumping (Прыгает) на Is on ground (На земле), а в действии кликнув 2 раза ЛКМ по строке jump.wav замените его на звук ground.wav. Должно получиться событие как на рис. 115.

Рис. 115.

Мы добавили звук соприкосновения персонажа с платформой. Теперь добавим звук собирания монет: скопируйте событие еще раз и замените условие Is jumping (Прыгает) на On collision with another object (При столкновении с другим объектом), далее кликните на кнопке Pick an object (Указать объект) и выберите спрайт money. Теперь поменяйте в действии звук ground.wav на звук money.wav. Должно получиться событие как на рис. 116.

Рис. 116.

Давайте создадим событие, чтобы озвучить уничтожение врага — выбираем спрайт enemy_croc, далее команду An animation is playing (Проигрывается анимация), в поле ввода текста вписываем имя анимации «destroyed». Создаем под-событие: в списке выберите объект System (Система), далее команду Trigger once while true (Выполнить один раз, если правда). Создаем действие: выбираем объект XAudio2, далее команду Autoplay resource (Автопроигрывание ресурса) и в окне выбора ресурса выбираем файл с названием destroy.wav, нажимаем Finish. Скопируйте это действие еще раз и измените destroy.wav на другой музыкальный файл с именем blow.wav. Таким образом, мы поставили 2 звуковых файла в одном событии, чтобы в момент, когда главный герой прыгнул на врага сверху, мы услышали звук удара и затем звук разрушения врага. У вас должно получиться событие как на рис. 117.

Рис. 117.

Скопируйте событие целиком и удалите одно действие — XAudio2: Autoplay resource »destroy.wav» (No loop), выделив его и нажав клавишу Delete на клавиатуре. Далее в условии замените имя анимации »destroyed» на »moving». Теперь не удаляя под-событие Trigger once (Выполнить один раз, если правда), создайте новое под-событие: выберите спрайт basis, далее команду On collision with another object (При столкновении с другим объектом) и, кликнув по кнопке Pick an object (Указать объект), выберите спрайт enemy_croc. Созданное вами новое под-событие скорее всего окажется в самом низу, поэтому выделив его и зажав ЛКМ, переместите его между событиями enemy_croc: Animation »moving» is playing и Trigger once, чтобы в итоге у вас получилось так же, как на рис. 118.

Рис. 118.

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

Давайте создадим событие для стрелки: выберите спрайт next, далее условие Object is on-screen? (Объект на экране?). Затем создаем под-событие: так же выбираем спрайт next, далее условие Is visible? (Видимый ли?). Добавим еще под-событие: выбираем спрайт next еще раз, затем команду Compare animation frame (Сравнить кадр анимации) и в поле ввода текста ставим значение 1, жмем Finish. Далее создаем еще одно под-событие: в списке выбираем объект System (Система), далее команду Trigger once while true (Выполнить один раз, если правда). Напротив добавляем действие: выбираем объект XAudio2, далее команду Autoplay resource (Автопроигрывание ресурса) и в активной вкладке выбираем signal.wav, жмем Finish. В итоге у вас должно получиться событие как на рис. 119.

Рис. 119.

Запустите игру и проверьте — после того, как вы соберете все монеты и уничтожите врага, появится табличка, и вы услышите периодически повторяющийся сигнал, но если вы отдалитесь от таблички, и она окажется за экраном — то звук исчезнет! За это отвечает событие Object is on-screen? (Объект на экране?).

Теперь создадим событие посложнее. Чтобы сделать озвучку для шагов главного героя, создаем новое событие: выбираем спрайт character, далее условие An animation is playing (Анимация проигрывается), в поле ввода текста вписываем имя анимации «moving».
Далее создаем под-событие: еще раз выбираем спрайт character, затем команду Compare animation frame (Сравнить кадр анимации) и в поле ввода текста ставим значение 6, жмем Finish.
Создаем еще одно под-событие: в списке выбираем объект System (Система), далее команду Trigger once while true (Выполнить один раз, если правда). Теперь создаем действие: выбираем объект XAudio2, далее команду Autoplay resource (Автопроигрывание ресурса), в активной вкладке выбираем звук character_step_left.wav и жмем Finish. Затем копируем под-событие character: Animation frame Equal to 6 вместе с под-событием Trigger once и в скопированном под-событии, в условии character: Animation frame Equal to 6 меняем 6 на кадр 14, а звук меняем на файл character_step_right.wav. В итоге у вас должно получится событие как на рисунке 120.

Рис. 120.

Запустите игру и попробуйте побегать, вы услышите характерные звуки шагов. То же самое мы должны сделать и для врага, но с одним небольшим изменением — звуки шагов врага должны быть слышны, только если мы увидим его на экране. Для этого создаем событие: выберите спрайт enemy_croc, далее команду Object is on-screen? (Объект на экране?). Теперь скопируйте событие предыдущее событие для персонажа (которое изображено на рис. 120) и вместе со всей цепочкой под-событий подставьте его под событие enemy_croc: is on-screen. Далее во всех под-событиях этого условия, где присутствует спрайт character нужно заменить его на спрайт enemy_croc, а в действиях заменить звуки character_step_left.wav и character_step_right.wav на звуки шагов для врага croc_step_left.wav и croc_step_right.wav. В итоге у вас должно получится событие как на рисунке 121.

Рис. 121.

Запустите игру и подойдите к врагу — вы услышите характерные металлические шаги противника.

Теперь нам остается создать последнее событие, чтобы добавить в игру музыкальное сопровождение. В списке объектов выберите объект System (Система), далее команду Start of layout (При старте уровня). Если вы хотите, чтобы музыка в игре играла по кругу, то для этого ее нужно загрузить на канал.

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

Итак, создаем 2 новых действия.

Действие первое: выберите объект XAudio2, далее команду Load resource (Загрузить ресурс), откроется окно, в котором под надписью Resource to load (Ресурс, который нужно загрузить) вам нужно выбрать файл music.wav, под надписью Channel (Канал) должен быть указан канал 1. Теперь в этом же окне под надписью Loop (Зацикливание) в активной вкладке выберите команду Loop (Зациклить) и жмите Finish.

Действие второе: выберите объект XAudio2, далее команду Play (Воспроизвести), откроется окно, в котором под надписью Channel (Канал) должен быть указан канал 1, нажмите Finish. У вас должно получиться событие как на рис. 122.

Рис. 122.

Запустите игру и вы услышите музыку. Поздравляю! Вы создали свою первую игру!

На этом урок закончен. Вы можете скачать исходник платформера, чтобы проверить, правильно ли вы все сделали, или посмотреть пример.

Постскриптум

В своих следующих статьях процесс создания событий я буду описывать значительно проще, в виде сокращений со знаком >, который будет иметь одно из следующих значений: переход на новое окно, выбрать команду, вкладку или кликнуть по кнопке.

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

Создайте новое событие:

EVENT:

[Событие] Условие: System > Always; Действие: character > Set position to another object > Pick an object > basis

То есть создаете событие, в списке объектов выбираете объект System (Система), затем выбираете команду Always (Всегда). Теперь действие: в списке объектов выбираете спрайт character, далее команду Set position to another object (Установить позицию относительно другого объекта), далее нажимаете на кнопку Pick an object (Указать объект) и в открывшемся окне выбираете спрайт basis.

Более сложные события (как например на рис. 120), которые имеют цепочки под-событий я буду описывать так:

EVENT:

[Событие] Условие: character > An animation is playing > «moving»
+ [под-событие] Условие: character > Compare animation frame > Equal to 6
+ [под-событие] Условие: System > Trigger once while true; Действие: XAaudio2 > Autoplay resource > character_step_left.wav (no loop).
+ [под-событие] Условие: character > Compare animation frame > Equal to 14.
+ [под-событие] Условие: System > Trigger once while true; Действие: XAaudio2 > Autoplay resource > character_step_right.wav (no loop).

Где знак "+" означает ступени в виде под-событий. Если в событии или под-событии несколько действий — то они будут идти друг над другом.

Статья написана специально для сайта ScirraConstruct.ru.

Понравилась статья? Подписывайтесь на нашу RSS-ленту , чтобы быть в курсе последних новостей!

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

Перед началом желательно иметь хоть какие-нибудь навыки работы с Game Maker Studio. Рекомендую ознакомиться с интерфейсом программы.

Передвижение игрока

Создадим объект oPlayer (в дальнейшем все названия ресурсов будут начинаться с соответствующей приставки, чтобы понимать, что это за ресурсы. Для объекта (object) — «o», для комнаты (room) — «r» и т. д.). Также понадобится нарисовать спрайт или загрузить уже готовый, и привязать его к объекту. В моем случае это простой белый квадрат 48×48.

В событии Create объявим следующие переменные:

xDir — направление движения игрока по горизонтали. Переменная будет равна:
-1 — если игрок идет налево;
1 — если игрок идет направо;
0 — если игрок стоит на месте.

stepLength — длина шага игрока в пикселях. Пускай переменная будет равна 10.

dx — сдвиг по горизонтали. Каждое обновление комнаты (по умолчанию — 60 обновлений в секунду) игрок будет сдвигаться на dx пикселей. dx будет вычисляться перемножением xDir и stepLength.

Переменные можно объявить другим способом: в специальном разделе Variable Definitions.

В событии Step напишем следующее:

xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
x += dx;

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

Первая строка вычисляет направление движения. Функция keyboard_check(key) принимает в качестве аргумента клавишу и возвращает true, если она нажата. Функция ord(string) принимает в качестве аргумента строку и преобразует ее в необходимый тип данных для аргумента функции keyboard_check(key). Таким образом, при удержании клавиши «A» переменная xDir становится равной -1, а при удержании клавиши «D» — 1.

Вторая строка вычисляет dx.

Третья строка увеличивает x на dx пикселей, то есть двигает игрока.

Создаем комнату и добавляем на слой Instances экземпляр объекта игрока.

Запускаем и убеждаемся, что все работает как надо.

Коллизия

Вместо того, чтобы обрабатывать каждый объект, в который можно врезаться и на котором можно стоять, создадим абстрактный объект oSolid («твердый» объект), от которого будем наследовать другие объекты, которые мы хотим наделить коллизией.

Здесь никаких переменных и событий создавать не надо: код для взаимодействия с объектом напишем внутри oPlayer в событии Step. Перепишем его так, чтобы событие выглядело следующим образом:

xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
if (!place_meeting(x + dx, y, oSolid)) //эта строчка новая
x += dx;

place_meeting(x, y, obj) нужна для проверки пересечения прямоугольников коллизий двух объектов. Она принимает три аргумента:
x — позиция первого объекта по оси x;
y — позиция первого объекта по оси y;
obj — имя второго объекта или id его экземпляра.
Функция возвращает true, если объекты пересекаются и false — если нет. Важно то, что проверка осуществляется не в одной лишь точке (x; y), а сразу во всех точках прямоугольника вызывающего объекта. Для проверки коллизии в одной точке имеется функция position_meeting(x, y, obj), но она нам не понадобится. Используя place_meeting(x + dx, y, oSolid), мы проверяем пересечение игрока с твердым объектом, как если бы игрок был сдвинут на dx пикселей. И только убедившись, что пересечения нет, меняем координату игрока.

Теперь нужно создать объект oWall, установить ему родителя oSolid и привязать к нему какой-нибудь спрайт. В моем случае это черный квадрат 64×64. Позже в редакторе комнаты экземпляры этого объекта можно будет растянуть.

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

Есть одна проблема: между игроком и стеной иногда появляется небольшой зазор, ведь объект либо двигается на stepLength пикселей, если нет столкновения, либо вообще не двигается, если оно обнаружено. Таким образом, если переменная stepLength, например, равна 10, а зазор между игроком и стеной — 5 пикселей, экземпляр объекта игрока так и не встанет вплотную к стене. Решается это тем, что мы попиксельно будем двигать игрока в сторону его движения, пока зазор не исчезнет. В этом поможет функция sign(n), которая возвращает:
-1 — если аргумент n отрицательный;
1 — если аргумент n положительный;
0 — если аргумент n равен нулю.

xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
if (!place_meeting(x + dx, y, oSolid))
x += dx;
else
while (!place_meeting(x + sign(dx), y, oSolid))
x += sign(dx);

Проверяем.

Теперь все работает.

Гравитация и прыжки

В событии Create объекта oPlayer объявим переменную onGround, она нужна для того, чтобы проверять, стоит ли игрок на поверхности или же находится в воздухе.

Помимо нее нам понадобится переменная dy, которая будет работать по тому же принципу, что и dx: y будет сдвигаться на dy пикселей каждое обновление комнаты при отсутствии препятствий.

Для гравитации понадобится переменная gravitation. dy будет постепенно складывать это значение, когда игрок находится в воздухе, тем самым увеличивая скорость падения. Пусть это значение будет равно 1.75.

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

Идея в том, что при нажатии на клавишу прыжка, dy становится равной jumpImpulse и какое-то время остается отрицательной, поднимая игрока вверх, но под воздействием gravitation dy увеличивается и со временем становится положительной, из-за чего игрок начинает падать.

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

В событии Step добавим следующий код:

if (onGround)
if (keyboard_check_pressed(ord(» «)))
dy = jumpImpulse;
if (!place_meeting(x, y + dy, oSolid))
y += dy;
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
dy = 0;
}
dy += gravitation;
onGround = place_meeting(x, y + 1, oSolid);

Разберем его по порядку:

if (onGround)
if (keyboard_check_pressed(ord(» «)))
dy = jumpImpulse;

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

Разница между функциями keyboard_check_pressed(key) и keyboard_check(key) в том, что при вызове первой она возвращает true только в момент нажатия клавиши (один раз), а при вызове второй — в любой момент, когда клавиша удерживается.

if (!place_meeting(x, y + dy, oSolid))
y += dy;
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
dy = 0;
}

Здесь все то же самое, что и в случае с проверкой коллизии по горизонтали, только при обнаружении препятствия нужно обнулить dy, иначе к ней так и будет прибавляться gravitation, пока dy не станет настолько большой, что игрок начнет проваливаться под «твердые» объекты.

dy += gravitation;

Здесь просто увеличиваем скорость падения объекта игрока за счет гравитации.

onGround = place_meeting(x, y + 1, oSolid);

Эта строчка проверяет, стоит ли на земле игрок.

Переход на следующий уровень

Создадим объект oDoor и привяжем к нему спрайт.

Создаем в oPlayer событие столкновения с oDoor.

Пишем в нем room_goto_next();.

Это все. Осталось лишь добавить еще одну комнату и поставить дверь.

Смерть от шипов и падения за карту

Создадим пустой абстрактный объект oKilling («убивающий» объект), от него унаследуем oTriangle (треугольник) и для разнообразия oTriangleSmall (маленький треугольник).

Объявим в событии Create объекта oPlayer переменную isDead. В том же oPlayer добавляем событие пересечения с oKilling и событие выхода за пределы комнаты (Outside Room). И там, и там пишем isDead = true;.

В событии Step добавляем две строчки:

if (isDead)
room_restart();

room_restart() перезагружает комнату. Каждый раз, когда игрок будет натыкаться на шип или падать в пропасть, комната будет перезапускаться, как если бы вы вошли в нее в первый раз: все экземпляры объектов пересоздадутся и появятся на начальной позиции, если до этого были подвинуты.

Порталы

Создадим объект oPortal и в событии Create объявим переменную pair (пара). Эта переменная будет хранить id другого портала, к которому игрок будет телепортироваться. Таким образом вы сможете получить доступ ко второму порталу через первый.

От oPortal я унаследовал два объекта: oPortalBlue и oPortalPink, у каждого свой соответствующий названию спрайт.

Создадим новую комнату и добавим туда несколько порталов.

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

При двойном нажатии на один из экземпляров высветится меню, где можно настроить этот конкретный экземпляр: изменить значения переменных, идентификатор или код создания. Изменение параметров выделенного экземпляра не затронет остальные экземпляры этого же объекта в комнате. В разделе Creation Code можно указать, какой портал будет являться парой для выделенного. Для этого пишем pair = *идентификатор другого экземпляра портала*;.

В моем случае id другого портала — inst_6CB6ED9F, у вас это название будет другим. По желанию это наименование можно изменить на более понятное, например, instPortalBlue1.

То же самое следует проделать с остальными тремя порталами.

Теперь в объекте oPlayer добавим событие пересечения с oPortal и добавим этот код:

if (keyboard_check_pressed(ord(«E»)))
{
x = other.pair.x;
y = other.pair.y;
}

other в любом событии пересечения двух объектов — это ссылка на экземпляр объекта, с которым пересекся экземпляр, вызвавший это событие.

В данном случае other — портал, с которым пересекается игрок, а other.pair — пара этому порталу, куда игрок будет телепортироваться при нажатии на клавишу «E».

Запускаем и смотрим результат.

Ускоряющая платформа и платформа для прыжков

Создадим объект oJumpPlatform, унаследуем его от oSolid, а также привяжем к нему какой-нибудь спрайт, в моем случае — синий квадрат 64×64.

Немного поменяем код в событии Step объекта oPlayer, а именно в том месте, где игрок попиксельно двигается по вертикали:

. . .
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
if (place_meeting(x, y + sign(dy), oJumpPlatform))
dy = jumpImpulse * 1.5 * sign(dy);
else
dy = 0;
}
. . .

Здесь дополнительно осуществляется проверка, находится ли oPlayer вплотную к oJumpPlatform или нет. Если да, то он отталкивается от платформы, но в полтора раза сильнее, чем при обычном прыжке, при чем отталкивание происходит не только тогда, когда игрок прыгает на платформу, но и когда ударяется об нее сверху.

Создадим еще один объект, назовем его oRunPlatform, также унаследуем от oSolid и привяжем к нему спрайт, в моем случае — оранжевый квадрат 64×64.

В событии Create объекта oPlayer объявим переменную dxBoost — число, на которое будет умножаться dx. Оно будет равно 1.5, когда игрок стоит на ускоряющей платформе, и 1 — когда не стоит.

Добавим выделенный участок кода в событии Step объекта oPlayer. Он должен располагаться между уже имеющимися строками кода onGround = place_meeting(x, y + 1, oSolid); и xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));:

. . .
if (onGround)
{
if (place_meeting(x, y + 1, oRunPlatform))
dxBoost = 1.5;
else
dxBoost = 1.0;
}
. . .

dxBoost не обязательно должна быть равна 1.5, можно сделать это значение равным 2, тогда игрок будет ускоряться ровно в 2 раза.

Еще необходимо немного изменить код для обработки столкновений по горизонтали:

. . .
if (!place_meeting(x + dx, y, oSolid)) //это удаляем
if (!place_meeting(x + (dx * dxBoost), y, oSolid)) //и заменяем на это
x += dx; //и это удаляем
x += dx * dxBoost; //заменяем на это
. . .

Возможно, здесь разница не так заметна, но она есть

Заключение

Всего написанного достаточно для того, чтобы строить комнаты и играть.

Не так давно, только начав изучать «платформеры», мои коллеги нашли прекрасные советы по работе с этим жанром от Скотта Роджерса (приложившего руку к появлению таких проектов, как God of War, Maximo series, Pac-man World, Dawn to Life series, Darksiders), а вскоре — и их перевод на русский язык. Оглядываясь на эти советы сквозь призму трех месяцев изучения самых разнообразных проектов этого жанра, мои коллеги сочли возможным дополнить эти советы. Буду рад, если и Вы, дорогой мой читатель, сможете добавить что-то свое. 

1.  Базовые механики

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

2. Персонаж и позиционирование

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

3. Управление

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

4. Прыжки

  • Платформы, на которые игрок может встать и любые другие фоновые элементы, должны четко друг от друга отличаться — игрок должен ясно видеть и понимать, что вот сюда он может встать, а сюда нет.
  • При проектировании нужно обдумать или предусмотреть возможность отпрыгивать от стен, прыгать с одной стены на другую. Это расширит возможности для дизайна уровней.
  • Герой должен прыгать на максимальную высоту только при зажатии кнопки (длинный тап).
  • Отличное решение для упрощения прыжков по микроскопическим платформам — автоматическое притягивание и прилипание персонажа к ним.

5. Подвижные платформы

  • Использовать группы платформ, движущихся с разным ритмом, можно на уровнях класса “Эксперт”. При этом неожиданных изменений скорости каждой конкретной платформы стоит избегать на любом уровне сложности.
  • В качестве дополнительного челленджа можно вводить особые типы платформ, которые реагируют на персонажа — продавливаются вниз под его весом, пружинят, наклоняются и т.д.

6. Организация препятствий

  • Если перед игроком пропасть и не видно, что происходит за экраном, игрок должен четко понимать — умрет ли он при падении (потеряет жизнь) или внизу что-то есть и туда вполне можно спрыгнуть (это необходимо визуально показывать игроку).
  • Следует делать границы уровня логически обоснованными: границей не должна быть дорожка, прегражденная невидимой стеной, зато вполне может быть препятствие в виде скалистого уступа, на который невозможно забраться.

7. Падение

  • При падении с большой высоты можно делать перекаты, но они не должны быть затяжными. Разная анимация при падении с разной высоты позволит игроку понять что персонаж может и разбиться (если это предусмотрено).
  • Стоит рассмотреть также скольжение по стенам — персонаж медленно спускается по краю стены вниз. Таким образом, если персонажу грозит гибель, игрок будет иметь возможность принять меры.

8. Движение

  • Лечение (распитие банки) может происходить не мгновенно. Это нормально, если подкрепляется другими базовыми механиками — отпрыгиванием, ускорением, щитом и другими, которые не имеют смысла в случае с мгновенным исцелением.
  • Транспорт необязательно именно ускоряет персонажа — он может служить для усиления игрока (дополнительная броня, урон).
  • Механика ходьбы — не нужна вовсе, если уровень не предполагает ее использование. Иначе это будет затрата человеко-часов программистов и аниматоров.

9. Экран

  • Уменьшить однообразие двухмерности можно за счет изменения вектора движения персонажа по уровню — по дуге, по вертикали, по диагонали.

10. Секреты

  • 2D мир вовсе не обязательно воспринимается менее реальным и более плоским. Если окружение (объекты) качественно выполенно в 3Д, с динамичной камерой, то плоский по движению мир вполне себе воспринимается как трехмерный..
  • Сопоставление характеристик для 2D и 2,5D необоснованно и не может быть применимо в 100% случаев. Все зависит от конкретного проекта. Например, и в 2D проекте желательно отрисовать вид персонажа со спины (когда забирается по лестнице); также вовсе не обязательно в 2D игроку будет легче найти секретку, чем в 2,5D — все зависит от базовых механик и сложности дизайна уровня.
  • Хороший вариант, когда активное использование базовых механик приводит к нахождению секреток. Например, если при обучении игрока лазить по стенам и потолкам, и там закладывать секреты и бонусы, то потом игрок будет пытается делать это чуть ли не везде (и с высокой вероятностью находит спрятанные сундуки).
  • При размещении секретов, равно как и видимых труднодоступных объектов с лутом, необходимо соразмерять баланс между трудностью доступа и вознаграждением.

11. Темы уровней

  • Выбор темы для уровней ограничивается лишь фантазией разработчиков и сеттингом самой игры.

12. История уровня

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

13. Сосиски

  • Для обозначения маршрута игрока можно использовать собираемые предметы, которые покажут основные и альтернативные маршруты прохождения уровня (морковка на веревочке).
  • Объекты, изображенные на карте, должны вызывать четкие ассоциации с игровыми объектами внутри уровней: игрок не должен теряться в догадках, какой именно объект изображен на карте.
  • Для облегчения ориентирования игрока на карте местности можно дать ему возможность оставлять “путевые заметки” на этой карте (особенно актуально с открытыми мирами, где игрок появляется в тех или иных уровнях по много раз).

14. Чекпоинты

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

15. Атаки/бой

  • Желательно включать механику откатов (неуязвимость и др.) при получении урона, чтобы у игрока была возможность отойти в безопасное место и он не потерял все жизни, попав в затруднительное (зажатое) положение.
  • Необязательно давать игроку именно большую дистанцию боя. Это может быть и любое другое преимущество (возможность зайти сверху/снизу; использование щита, тактические увороты за блоками и др.)

16. Наказание и штрафы

  • В этом разделе нам оказалось нечего добавить к указанным пунктам.

17. Набор механик

  • Подкаты/кувырки (для проникновения в “низкие” проходы, для сбивания противников).
  • Отталкивание от стен.
  • Смена персонажей с разными возможностями и их комбинирование в процессе геймплея для прохождения разного рода препятствий.
  • Появление у персонажа различных устройств или предметов, которые помогают проходить ранее недоступные препятствия (открывается возможность использования одних и тех же уровней и доступ к новым участкам этих уровней с появлением у персонажа новых механик).
  • Изменение гравитации (построение уровня на этом).
  • Изменение камеры в уровне — переход в другое измерение или под другой угол.
  • Перед началом проектирования уровней нужно определить полный (и по возможности конечный) набор доступных игроку механик (прыжки, стрельба, использование платформ, лестниц и веревок);

18. Тернистый путь игрока

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

19. Виды врагов

  • При разработке геймплейных элементов отталкивание от механики — не единственный из возможных путей. Оттолкнуться можно, к примеру, и от эстетики.

20. Сражения

  • Постоянное прибывание/возвращение врагов где-то может быть оправданным, а где-то — неожиданным и раздражающим (где-то моб — это только опасность, и использовать его для решения каких-то задач не нужно, а повторные появления таких мобов просто усложняют процесс и лишают игрока ощущения “зачистки уровня”).
  • Игрок не обязан сражаться с мобами, избегание боя — это тоже способ прохождения уровня. Заставлять игрока что-то делать силой не правильно.

21. Правило трех

  • 3 новых врага на каждый новый уровень — СЛИШКОМ много. Во-первых, это огромные объемы разрабатываемого и внедряемого контента, а во-вторых, игрок не будет успевать привыкать к противникам и препятствиям. Разнообразие это хорошо, но в меру.

22. Виды движений

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

23. Скорость игры (дополнительный пункт)

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

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

24. Обучение игроков (дополнительный пункт)

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

25. Размер видимой области (дополнительный пункт)

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

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

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

Для реализации игр на Питон мы можем выбрать одну из нескольких библиотек. Можно работать с: Kivy, Tkinter, PyQt или же с любой другой библиотекой, что обеспечивает построение графического интерфейса через Python. Таких библиотек много, но мы возьмем библиотеку PyGame, которая даже своим названием говорит о её предназначении.

PyGame появился в 2000 году. С тех пор на его основе было сделано много интересных проектов. К сожалению, PyGame не универсален и разработка на нём ведется лишь под Андроид устройства.

Настройка проекта

Перед использованием PyGame его нужно установить через терминал. Прописываем команду:

pip install pygame

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

import pygame

pygame.init()
win = pygame.display.set_mode((500, 500)) # размеры X и Y
pygame.display.set_caption("Название игры")

while(True):
	pass

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

import pygame

pygame.init()
win = pygame.display.set_mode((500, 500)) # размеры X и Y
pygame.display.set_caption("Название игры")

run = True
while(run):
	for event in pygame.event.get():
		if event.type == pygame.QUIT:
			run = False

pygame.quit()

Создание платформера

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

import pygame

# Переменные для установки ширины и высоты окна
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Подключение фото для заднего фона
# Здесь лишь создание переменной, вывод заднего фона ниже в коде
bg = pygame.image.load('bg.jpg')


# Класс, описывающий поведение главного игрока
class Player(pygame.sprite.Sprite):
	# Изначально игрок смотрит вправо, поэтому эта переменная True
	right = True

	# Методы
	def __init__(self):
		# Стандартный конструктор класса
		# Нужно ещё вызывать конструктор родительского класса
		super().__init__()

		# Создаем изображение для игрока
		# Изображение находится в этой же папке проекта
		self.image = pygame.image.load('idle.png')

		# Установите ссылку на изображение прямоугольника
		self.rect = self.image.get_rect()

		# Задаем вектор скорости игрока
		self.change_x = 0
		self.change_y = 0

	def update(self):
		# В этой функции мы передвигаем игрока
		# Сперва устанавливаем для него гравитацию
		self.calc_grav()

		# Передвигаем его на право/лево
		# change_x будет меняться позже при нажатии на стрелочки клавиатуры
		self.rect.x += self.change_x

		# Следим ударяем ли мы какой-то другой объект, платформы, например
		block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
		# Перебираем все возможные объекты, с которыми могли бы столкнуться
		for block in block_hit_list:
			# Если мы идем направо,
			# устанавливает нашу правую сторону на левой стороне предмета, которого мы ударили
			if self.change_x > 0:
				self.rect.right = block.rect.left
			elif self.change_x < 0:
				# В противном случае, если мы движемся влево, то делаем наоборот
				self.rect.left = block.rect.right

		# Передвигаемся вверх/вниз
		self.rect.y += self.change_y

		# То же самое, вот только уже для вверх/вниз
		block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
		for block in block_hit_list:
			# Устанавливаем нашу позицию на основе верхней / нижней части объекта, на который мы попали
			if self.change_y > 0:
				self.rect.bottom = block.rect.top
			elif self.change_y < 0:
				self.rect.top = block.rect.bottom

			# Останавливаем вертикальное движение
			self.change_y = 0

	def calc_grav(self):
		# Здесь мы вычисляем как быстро объект будет
		# падать на землю под действием гравитации
		if self.change_y == 0:
			self.change_y = 1
		else:
			self.change_y += .95

		# Если уже на земле, то ставим позицию Y как 0
		if self.rect.y >= SCREEN_HEIGHT - self.rect.height and self.change_y >= 0:
			self.change_y = 0
			self.rect.y = SCREEN_HEIGHT - self.rect.height

	def jump(self):
		# Обработка прыжка
		# Нам нужно проверять здесь, контактируем ли мы с чем-либо
		# или другими словами, не находимся ли мы в полете.
		# Для этого опускаемся на 10 единиц, проверем соприкосновение и далее поднимаемся обратно
		self.rect.y += 10
		platform_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
		self.rect.y -= 10

		# Если все в порядке, прыгаем вверх
		if len(platform_hit_list) > 0 or self.rect.bottom >= SCREEN_HEIGHT:
			self.change_y = -16

	# Передвижение игрока
	def go_left(self):
		# Сами функции будут вызваны позже из основного цикла
		self.change_x = -9 # Двигаем игрока по Х
		if(self.right): # Проверяем куда он смотрит и если что, то переворачиваем его
			self.flip()
			self.right = False

	def go_right(self):
		# то же самое, но вправо
		self.change_x = 9
		if (not self.right):
			self.flip()
			self.right = True


	def stop(self):
		# вызываем этот метод, когда не нажимаем на клавиши
		self.change_x = 0

	def flip(self):
		# переворот игрока (зеркальное отражение)
		self.image = pygame.transform.flip(self.image, True, False)


# Класс для описания платформы
class Platform(pygame.sprite.Sprite):
	def __init__(self, width, height):
		# Конструктор платформ
		super().__init__()
		# Также указываем фото платформы
		self.image = pygame.image.load('platform.png')

		# Установите ссылку на изображение прямоугольника
		self.rect = self.image.get_rect()


# Класс для расстановки платформ на сцене
class Level(object):
	def __init__(self, player):
		# Создаем группу спрайтов (поместим платформы различные сюда)
		self.platform_list = pygame.sprite.Group()
		# Ссылка на основного игрока
		self.player = player

	# Чтобы все рисовалось, то нужно обновлять экран
	# При вызове этого метода обновление будет происходить
	def update(self):
		self.platform_list.update()

	# Метод для рисования объектов на сцене
	def draw(self, screen):
		# Рисуем задний фон
		screen.blit(bg, (0, 0))

		# Рисуем все платформы из группы спрайтов
		self.platform_list.draw(screen)


# Класс, что описывает где будут находится все платформы
# на определенном уровне игры
class Level_01(Level):
	def __init__(self, player):
		# Вызываем родительский конструктор
		Level.__init__(self, player)

		# Массив с данными про платформы. Данные в таком формате:
		# ширина, высота, x и y позиция
		level = [
			[210, 32, 500, 500],
			[210, 32, 200, 400],
			[210, 32, 600, 300],
		]

		# Перебираем массив и добавляем каждую платформу в группу спрайтов - platform_list
		for platform in level:
			block = Platform(platform[0], platform[1])
			block.rect.x = platform[2]
			block.rect.y = platform[3]
			block.player = self.player
			self.platform_list.add(block)


# Основная функция прогарммы
def main():
	# Инициализация
	pygame.init()

	# Установка высоты и ширины
	size = [SCREEN_WIDTH, SCREEN_HEIGHT]
	screen = pygame.display.set_mode(size)

	# Название игры
	pygame.display.set_caption("Платформер")

	# Создаем игрока
	player = Player()

	# Создаем все уровни
	level_list = []
	level_list.append(Level_01(player))

	# Устанавливаем текущий уровень
	current_level_no = 0
	current_level = level_list[current_level_no]

	active_sprite_list = pygame.sprite.Group()
	player.level = current_level

	player.rect.x = 340
	player.rect.y = SCREEN_HEIGHT - player.rect.height
	active_sprite_list.add(player)

	# Цикл будет до тех пор, пока пользователь не нажмет кнопку закрытия
	done = False

	# Используется для управления скоростью обновления экрана
	clock = pygame.time.Clock()

	# Основной цикл программы
	while not done:
		# Отслеживание действий
		for event in pygame.event.get():
			if event.type == pygame.QUIT: # Если закрыл программу, то останавливаем цикл
				done = True

			# Если нажали на стрелки клавиатуры, то двигаем объект
			if event.type == pygame.KEYDOWN:
				if event.key == pygame.K_LEFT:
					player.go_left()
				if event.key == pygame.K_RIGHT:
					player.go_right()
				if event.key == pygame.K_UP:
					player.jump()

			if event.type == pygame.KEYUP:
				if event.key == pygame.K_LEFT and player.change_x < 0:
					player.stop()
				if event.key == pygame.K_RIGHT and player.change_x > 0:
					player.stop()

		# Обновляем игрока
		active_sprite_list.update()

		# Обновляем объекты на сцене
		current_level.update()

		# Если игрок приблизится к правой стороне, то дальше его не двигаем
		if player.rect.right > SCREEN_WIDTH:
			player.rect.right = SCREEN_WIDTH

		# Если игрок приблизится к левой стороне, то дальше его не двигаем
		if player.rect.left < 0:
			player.rect.left = 0

		# Рисуем объекты на окне
		current_level.draw(screen)
		active_sprite_list.draw(screen)

		# Устанавливаем количество фреймов
		clock.tick(30)

		# Обновляем экран после рисования объектов
		pygame.display.flip()

	# Корректное закртытие программы
	pygame.quit()

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

(фото игрока — оригинальный сайт)

(фото платформы — оригинальный сайт)

(фото на задний фон)

Видео на эту тему

Также вы можете просмотреть детальное видео по разработке 2D платформера на Python + PyGame:

Дополнительный курс

На нашем сайте также есть углубленный курс по изучению языка Питон. В ходе огромной программы вы изучите не только язык Питон, но также научитесь создавать веб сайты за счёт веб технологий и фреймворка Джанго. За курс вы изучите массу нового и к концу программы будете уметь работать с языком Питон, создавать на нём полноценные ПК приложения на основе библиотеки Kivy, а также создавать веб сайты на основе библиотеки Джанго.

Этот текст предназначен для тех, кто только осваивает программирование. Я читаю лекции по C++ на первом курсе местного университета, и в качестве практикума предлагаю запрограммировать любую игру (не выношу проектов типа «софт бронирования книг в местной библиотеке»). Соответственно, чтобы помочь начинающим, я сделал некоторое количество заготовок, с которых можно стартовать свой проект. Например, заготовку олдскульного 3д шутера в 486 строк C++ я уже описывал, а вот тут можно посмотреть, что из неё сделали первокурсники.

В этот раз всё будет ещё проще, я хочу сделать заготовку под простейший платформер, вот так выглядит результат:

На данный момент проект содержит менее трёхсот строчек цпп:

ssloy@khronos:~/sdl2-demo/src$ cat *.cpp *.h | wc -l
296

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

Итак, поехали!

Шаг первый: компилируем проект и открываем окно

Я ничего не знаю про оконные библиотеки, поэтому выбрал первую попавшуюся, а именно SDL. Первый шаг — самый сложный. Нужно суметь скомпилировать пустой проект, и слинковаться с выбранной библиотекой. Самая первая задача — открыть пустое окно, и отработать событие его закрытия. Вот тут можно найти соответствующий коммит:

image

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

Сборочный файл CMakeLists.txt лучше взять из последней версии, он линкуется с SDL2, если найдёт его в системе, а в противном случае подтягивает его исходники, и компилирует его сам. Должно работать без сучка-задоринки как под линухом, так и под виндой.

Вот так выглядит код, открывающий пустое окно:

#include <iostream>
#define SDL_MAIN_HANDLED
#include <SDL.h>

void main_loop(SDL_Renderer *renderer) {
    while (1) { // main game loop
        SDL_Event event; // handle window closing
        if (SDL_PollEvent(&event) && (SDL_QUIT==event.type || (SDL_KEYDOWN==event.type && SDLK_ESCAPE==event.key.keysym.sym)))
            break; // quit
        SDL_RenderClear(renderer); // re-draw the window
        SDL_RenderPresent(renderer);
    }
}

int main() {
    SDL_SetMainReady(); // tell SDL that we handle main() function ourselves, comes with the SDL_MAIN_HANDLED macro
    if (SDL_Init(SDL_INIT_VIDEO)) {
        std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl;
        return -1;
    }

    SDL_Window   *window   = nullptr;
    SDL_Renderer *renderer = nullptr;
    if (SDL_CreateWindowAndRenderer(1024, 768, SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS, &window, &renderer)) {
        std::cerr << "Failed to create window and renderer: " << SDL_GetError() << std::endl;
        return -1;
    }
    SDL_SetWindowTitle(window, "SDL2 game blank");
    SDL_SetRenderDrawColor(renderer, 210, 255, 179, 255);

    main_loop(renderer); // all interesting things happen here

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

Собственно, там ничего сверхъестественного: мы инициализируем SDL, создаём окно с зоной рендера и запускаем основной цикл игры. По событию закрытия окна или по нажатию эскейпа выходим из цикла и чистим память. Piece of cake.

Шаг второй: счётчик fps

На втором этапе я решил отобразить количество перерисовок экрана в секунду, хочу увидеть вот такой результат (не пугайтесь сорока тысяч fps, всё же мы ничего не делаем в основном цикле!):

Для этого нам нужно две вещи:

  • научиться считать количество перерисовок экрана в секунду
  • научиться его отображать на экране

Давайте начнём с отрисовки счётчика. Сперва я хотел рендерить текст при помощи библиотеки SDL_ttf, но потом выяснилось, что она тянет за собой ещё и другие зависимости, и мне стало лень автоматически собирать ещё и их, если они не установлены в системе. Поэтому я решил сделать существенно тупее: я нарисовал десять цифр размера 24×30 пикселей, и упаковал их в один .bmp файл размера 240 x 30 пикселей:

Всё едино мне нужно будет уметь работать со спрайтами, так почему бы не использовать эту же технику для отрисовки счётчика? Итак, вот структура для работы со спрайтами:

struct Sprite {
    Sprite(SDL_Renderer *renderer, const std::string filename, const int width) : width(width) {
        SDL_Surface *surface = SDL_LoadBMP((std::string(RESOURCES_DIR) + filename).c_str());
        if (!surface) {
            std::cerr << "Error in SDL_LoadBMP: " << SDL_GetError() << std::endl;
            return;
        }
        if (!(surface->w%width) && surface->w/width) { // image width must be a multiple of sprite width
            height  = surface->h;
            nframes = surface->w/width;
            texture = SDL_CreateTextureFromSurface(renderer, surface);
        } else
            std::cerr << "Incorrect sprite size" << std::endl;
        SDL_FreeSurface(surface);
    }

    SDL_Rect rect(const int idx) const { // choose the sprite number idx from the texture
        return { idx*width, 0, width, height };
    }

    ~Sprite() { // do not forget to free the memory!
        if (texture) SDL_DestroyTexture(texture);
    }

    SDL_Texture *texture = nullptr; // the image is to be stored here
    int width   = 0; // single sprite width (texture width = width * nframes)
    int height  = 0; // sprite height
    int nframes = 0; // number of frames in the animation sequence
};

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

А теперь давайте поговорим про счётчик. Я создал структуру под названием FPS_Counter, и просто вызываю её метод .draw() внутри основного цикла:

void main_loop(SDL_Renderer *renderer) {
    FPS_Counter fps_counter(renderer);
    while (1) { // main game loop
        [...]
        SDL_RenderClear(renderer); // re-draw the window
        fps_counter.draw();
        SDL_RenderPresent(renderer);
    }
}

Метод .draw() ведёт подсчёт вызовов, и отрисовывает счётчик, используя подгруженные спрайты с цифрами. Давайте внимательно посмотрим на эту структуру. Основая идея — измерять количество вызовов .draw() раз в некоторое время (у меня триста миллисекунд). Соответственно, у меня есть два инта — fps_prev хранит последнее измеренное значение fps, а fps_cur это текущий счётчик. Ещё нужно хранить временную метку timestamp для отслеживания этих самых трёхсот миллисекунд. Вот так выглядит полный код структуры:

struct FPS_Counter {
    FPS_Counter(SDL_Renderer *renderer) : renderer(renderer), numbers(renderer, "numbers.bmp", 24) {}

    void draw() {
        fps_cur++;
        double dt = std::chrono::duration<double>(Clock::now() - timestamp).count();
        if (dt>=.3) { // every 300 ms update current FPS reading
            fps_prev = fps_cur/dt;
            fps_cur = 0;
            timestamp = Clock::now();
        }
        SDL_Rect dst = {4, 16, numbers.width, numbers.height}; // first character will be drawn here
        for (const char c : std::to_string(fps_prev)) { // extract individual digits of fps_prev
            SDL_Rect src = numbers.rect(c-'0'); // crude conversion of numeric characters to int: '7'-'0'=7
            SDL_RenderCopy(renderer, numbers.texture, &src, &dst); // draw current digit
            dst.x += numbers.width + 4; // draw characters left-to-right, +4 for letter spacing (TODO: add padding directly to the .bmp file)
        }
    }

    int fps_cur  = 0; // the FPS readings are updated once in a while; fps_cur is the number of draw() calls since the last reading
    int fps_prev = 0; // and here is the last fps reading
    TimeStamp timestamp = Clock::now(); // last time fps_prev was updated
    SDL_Renderer *renderer; // draw here
    const Sprite numbers;   // "font" file
};

fps_counter.draw(); inside main loop while(1) { ... }.

Вот тут можно посмотреть коммит с рабочим кодом.

Шаг третий: сорок тысяч fps это многовато, давайте поменьше

На данный момент у меня на ноуте вентиляторы крутятся так, что он порывается улететь. Давайте-ка снизим нагрузку на проц. Как заставить основной цикл исполняться не больше 50 раз в секунду? Самый наивный вариант — это что-то вроде такого кода:

    while (1) { // main game loop
        do_something();
        sleep(20);
    }
}

Мы можем тупо вставить задержку на 20 миллисекунд в тело цикла, получив максимум 50 fps. Такой подход имеет право на жизнь, но он предполагает, что время работы do_nothing() пренебрежимо. А если вдруг оно будет исполняться, скажем, за 12мс? Тогда нам задержку нужно не 20, а 8, иначе сильно проседает FSP. А ведь это ещё зависит от компа… Поэтому я предлагаю следующий подход:

    TimeStamp timestamp = Clock::now();
    while (1) { // main game loop
        double dt = std::chrono::duration<double>(Clock::now() - timestamp).count();
        if (dt<.02) { // 50 FPS regulation
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
            continue;
        }
        timestamp = Clock::now();
        do_something();
    }
}

Мы просто храним временную метку timestamp, соответствующую последней отрисовке экрана, и не даём пройти внутрь цикла до тех пор, пока не истекут 20 миллисекунд. Задержка на 1мс вставлена для того, чтобы не грузить CPU на 100% пустыми проверками времени. Разумеется, в реальной игре за это время лучше делать что-нибудь полезное, считать физику, например.

Итак, вот результат:

Шаг четвёртый: отрисовываем уровень

Теперь давайте отрисуем карту уровня, вот тут соответствующий коммит. Я хочу видеть вот такой результат:

Для этого я сначала нарисовал текстуру 768 x 128 пикслей, в которую у меня упаковано шесть спрайтов каменюк размером 128×128:

Мой экран разбит на 192 клетки (16 по горизонтали и 12 по вертикали), и каждой клетке соответствует какая-то текстура.

Я создал структуру Map, которая используется следующим образом в основом цикле игры:

    Map map(renderer);
    while (1) { // main game loop
        [...]
        SDL_RenderClear(renderer); // re-draw the window
        map.draw();
        SDL_RenderPresent(renderer);
    }

Сама структура определена следующим образом:

struct Map {
    Map(SDL_Renderer *renderer) : renderer(renderer), textures(renderer, "ground.bmp", 128) {
        assert(sizeof(level) == w*h+1); // +1 for the null terminated string
        int window_w, window_h;
        if (!SDL_GetRendererOutputSize(renderer, &window_w, &window_h)) {
            tile_w = window_w/w;
            tile_h = window_h/h;
        } else
            std::cerr << "Failed to get renderer size: " << SDL_GetError() << std::endl;
    }

    void draw() { // draw the level in the renderer window
        for (int j=0; j<h; j++)
            for (int i=0; i<w; i++) {
                if (is_empty(i, j)) continue;
                SDL_Rect dst = { tile_w*i, tile_h*j, tile_w, tile_h };
                SDL_Rect src = textures.rect(get(i,j));
                SDL_RenderCopy(renderer, textures.texture, &src, &dst);
            }
    }

    int get(const int i, const int j) const { // retreive the cell, transform character to texture index
        assert(i>=0 && j>=0 && i<w && j<h);
        return level[i+j*w] - '0';
    }

    bool is_empty(const int i, const int j) const {
        assert(i>=0 && j>=0 && i<w && j<h);
        return level[i+j*w] == ' ';
    }

    SDL_Renderer *renderer; // draw here
    int tile_w = 0, tile_h = 0; // tile size in the renderer window

    const Sprite textures;         // textures to be drawn
    static constexpr int w = 16; // overall map dimensions, the array level[] has the length w*h+1 (+1 for the null character)
    static constexpr int h = 12; // space character for empty tiles, digits indicate the texture index to be used per tile
    static constexpr char level[w*h+1] = " 123451234012340"
                                         "5              5"
                                         "0              0"
                                         "5          5   5"
                                         "0          0   0"
                                         "512340   12345 5"
                                         "0              0"
                                         "5             51"
                                         "0     50      12"
                                         "5          51234"
                                         "0          12345"
                                         "1234012345052500";
};

Самое главное тут — массив level, который определяет, какой клетке соответствует какая текстура. В методе .draw() я прохожу по всем клеткам уровня, и для каждой незанятой отрисовываю соответствующий спрайт. Вспомогательные методы is_empty(i, j) и get(i, j) позволяют определить, пуста ли клетка с индексами i, j, и понять номер спрайта. Ну а в конструкторе я просто подтягиваю соответствующий .bmp файл и определяю размер клетки в пикселях экрана.

Шаг пятый: персонаж и его анимация

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

Я хочу получить вот такой результат (коммит брать тут):

Не бейте меня больно за кривые рисунки, я программист, а не художник! Как же нам их показать на экране? Для начала давайте опишем структуру, которая будет ответственна за анимации:

struct Animation : public Sprite {
    Animation(SDL_Renderer *renderer, const std::string filename, const int width, const double duration, const bool repeat) :
        Sprite(renderer, filename, width), duration(duration), repeat(repeat) {}

    bool animation_ended(const TimeStamp timestamp) const { // is the animation sequence still playing?
        double elapsed = std::chrono::duration<double>(Clock::now() - timestamp).count(); // seconds from timestamp to now
        return !repeat && elapsed >= duration;
    }

    int frame(const TimeStamp timestamp) const { // compute the frame number at current time for the the animation started at timestamp
        double elapsed = std::chrono::duration<double>(Clock::now() - timestamp).count(); // seconds from timestamp to now
        int idx = static_cast<int>(nframes*elapsed/duration);
        return repeat ? idx % nframes : std::min(idx, nframes-1);
    }

    SDL_Rect rect(const TimeStamp timestamp) const { // choose the right frame from the texture
        return { frame(timestamp)*width, 0, width, height };
    }

    const double duration = 1; // duration of the animation sequence in seconds
    const bool repeat = false; // should we repeat the animation?
};

Анимация не особо отличается от простых спрайтов, поэтому я её и унаследовал от структуры Sprite. У неё два дополнительных члена: время проигрывания анимации и булевская переменная, которая говорит, нужно ли играть анимацию в цикле. Конструктор просто наследуется от конструктора Sprite, а дополнительные методы позволяют узнать, закончилось ли проигрывание (animation_ended(timestamp)), и получить текущий кадр анимации (frame(timestamp) + rect(timestamp)).

Теперь осталось описать персонажа:

struct Player {
    enum States {
        REST=0, TAKEOFF=1, FLIGHT=2, LANDING=3, WALK=4, FALL=5
    };

    Player(SDL_Renderer *renderer) :
        renderer(renderer),
        sprites{Animation(renderer, "rest.bmp",    256, 1.0, true ),
                Animation(renderer, "takeoff.bmp", 256, 0.3, false),
                Animation(renderer, "flight.bmp",  256, 1.3, false),
                Animation(renderer, "landing.bmp", 256, 0.3, false),
                Animation(renderer, "walk.bmp",    256, 1.0, true ),
                Animation(renderer, "fall.bmp",    256, 1.0, true )} {
    }

    void draw() {
        SDL_Rect src = sprites[state].rect(timestamp);
        SDL_Rect dest = { int(x)-sprite_w/2, int(y)-sprite_h, sprite_w, sprite_h };
        SDL_RenderCopyEx(renderer, sprites[state].texture, &src, &dest, 0, nullptr, backwards ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
    }

    double x = 150, y = 200; // coordinates of the player
    bool backwards = false;  // left or right

    int state = WALK;
    TimeStamp timestamp = Clock::now();

    const int sprite_w = 256; // size of the sprite on the screen
    const int sprite_h = 128;

    SDL_Renderer *renderer;   // draw here
    std::array<Animation,6> sprites; // sprite sequences to be drawn
};

Я сразу сделал заготовку под то, что у персонажа будет несколько характерных состояний (ходьба, прыжок, падение). Положение игрока на экране я задаю переменными x и y, они соответствют середине подошвы. Направление лево/право задаётся булевской переменной backwards, а переменная timestamp задаёт метку времени начала проигрывания анимации.

Использование этой структуры пока что идентично использованию карты и счётчика fps:

    Player player(renderer);
    while (1) { // main game loop
        [...]
        SDL_RenderClear(renderer); // re-draw the window
        [...]
        player.draw();
        SDL_RenderPresent(renderer);
    }

Шаг шестой: опрос клавиатуры и обработка столкновений

А теперь давайте научимся опрашивать клавиатуру и обрабатывать столкновения с картой (вот коммит). Я хочу получить вот такой результат:

Для обработки клавиатуры я добавил функцию handle_keyboard(), которая вызывает функцию смены состояния set_state() в зависимости от того, какие курсорные стрелки нажаты:

    void handle_keyboard() {
        const Uint8 *kbstate = SDL_GetKeyboardState(NULL);
        if (state==WALK && !kbstate[SDL_SCANCODE_RIGHT] && !kbstate[SDL_SCANCODE_LEFT])
            set_state(REST);
        if (state==REST && (kbstate[SDL_SCANCODE_LEFT] || kbstate[SDL_SCANCODE_RIGHT])) {
            backwards = kbstate[SDL_SCANCODE_LEFT];
            set_state(WALK);
        }
    }

    void set_state(int s) {
        timestamp = Clock::now();
        state = s;
        if (state==REST)
            vx = 0;
        if (state==WALK)
            vx = backwards ? -150 : 150;
    }

Для изменения положения на экране я вызываю функцию update_state, которая и занимается тем, что изменяет переменную состояния x:

    void update_state(const double dt, const Map &map) {
        x += dt*vx; // candidate coordinates prior to collision detection
        if (!map.is_empty(x/map.tile_w, y/map.tile_h)) { // horizontal collision detection
            int snap = std::round(x/map.tile_w)*map.tile_w; // snap the coorinate to the boundary of last free tile
            x = snap + (snap>x ? 1 : -1);              // be careful to snap to the left or to the right side of the free tile
            vx = 0; // stop
        }
    }

Для начала я считаю координату на следующем шаге: x = x + dt*vx, а затем проверяю, не попадает ли эта координата в заполненную клетку карты. Если такое случается, то я останавливаю персонажа, и обновляю x таким образом, чтобы она оказалась на границе заполненной клетки.

Шаг седьмой: сила тяжести!

Сила тяжести добавляется элементарно, мы выписываем абсолютно такое же поведение и для вертикальной координаты, лишь добавив ещё и увеличение вертикальной скорости vy += dt*300, где 300 — это ускорение свободного падения в 300 пикселей в секунду за секунду.

    void update_state(const double dt, const Map &map) {
        [...]
        y  += dt*vy;  // prior to collision detection
        vy += dt*300;   // gravity
        [...]
    }

Обработку столкновения по вертикали даже описывать не буду, она ничем не отличается от горизонтали, коммит брать тут, ну а вот и результат:

Последние штрихи

Единственное, что осталось добавить нашу заготовку — это обработку прыжков. Это делается заданием начальных скоростей vx и vy в зависимости от комбинации курсорных клавиш. Чисто по приколу у меня есть прыжки в высоту и прыжки в длину:

Заключение

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

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

Have fun!

Introduction: How to Make a Platformer on Scratch

Scratch is a website where people create games and other programs using premade blocks that you drag and drop to a work space. Today I will show you how to make a platforming game on scratch.

Supplies

The only thing you will need is a phone or a computer and a browser that can run scratch

Step 1: Create Sprites

Start by logging in to scratch, this is needed so if you want to publish or come back to the game. Then click on create and it will put you on the project editor. This is were we will be making our project.

On the top left, you will find the costumes button. Click it if you want to change your player. Rename «sprite» to «player.»

Then create your platforms by making a new sprite. Rename that sprite «ground.»

Rename your game to whatever you want.

Step 2: Gravity

Add a «when green flag is clicked.» Then Place a «set position» block and put in the coordinates on where you want your player to spawn. Get a «forever» loop and a «repeat until» loop place the «forever» loop under the «set position» block.

Create a variable. This will act as our gravity. Mine was name «y vel» (short for y velocity). Right underneath the «set position» block, place a «set variable to ___.» Change the Variable to whatever you named your variable (I will be calling it y vel now) and change the number to zero. Next add a «change y by ___,» and put a «y vel» in it. Put that in the «forever loop.» Below that add a «change ‘yvel’ by ‘-1.'»

Create a block called Touch ground. MAKE SURE WHEN DOING THIS YOU CLICK RUN WITHOUT SCREEN REFRESH.

Insert a «repeat until» loop below the «touch ground». Add a «not» in the Boolean and in the «not» add a «touching ‘Ground.'» In that put a «change y by ‘1.’» Underneath that put a «set ‘y vel’ to ‘0.’»

If you followed all of that, your code should look like the picture above.

Step 3: Movement

Underneath the «when green flag is clicked,» put a «set rotation style ‘left-right.'»

Inside the «forever’ loop add two «if then blocks.» In the Boolean add two «key ___ pressed.» Set it to what keys you want to go left and right.

Make a block called «x detection» (it will help in next step). Click run without screen refresh. Add an input, call it «speed.»

In the right direction, put «detection x ‘5 (how fast you want your sprite to move),'» (speed depends on what you want), and «point in direction ’90.'» Do the same for the left but multiply all the numbers by negative one.

Make a block called jump. Put it in the «forever’ loop.

Create a new variable called «falling.» Under «touch ground,» place a «change ‘falling’ by ‘1.’» Under that, in the «repeat until» loop, place a «set ‘falling’ to ‘zero'»

Define jump. Add an «if then» block. In the Boolean put a «_____ and ____.» In one Boolean, put «key ‘what your jump key will be’ pressed,» then add «if ‘5’ is greater than ‘falling.'» In the «if then» block, put «set ‘y vel’ to ’12 (jump height.'»

You may notice that your player is not always touching the ground. Make a new costume, make it smaller on all sides. Inside the «forever» loop put in «switch costume to ‘costume2 (new costume).'» Below that, put a «switch costume to ‘costume1 (original costume).'»

If you have done this correctly, this should look like the picture above

Step 4: Horizontal Collision Detection

Create a new variable called slope. Underneath «change x by ‘speed,'» add a «set slope to ‘0.’»

Add a repeat until block underneath that. Put a «‘touching ‘ground’ or ‘slope’ equals ‘8.’» In that add a «change y by ‘1,’» and «change slope by ‘1.’» Under the «repeat until» loop, add a «if ‘slope’ equals ‘9’ then,» loop. In that add a «change x by ‘0’ minus ‘speed,'» then add a «change y by ‘0’ minus ‘slope.'»

If you did this step correctly, your code should look like the picture above.

Step 5: Vertical Collision Detection

Edit the block «touch ground» add a Boolean, call it «ceiling.» In our «touch ground» in the «forever» loop add «‘y vel’ is greater than ‘0.»

Take everything out of the «repeat until» loop. Put a «if then else» loop in the «repeat until» loop and add «ceiling» in the Boolean. In the «if then» part, add «change y by ‘-1.'» In the «else» part, add «change y by ‘1,’» and «set falling ‘0.’»

Below the «if then else» loop, add a «set ‘y vel’ to ‘0.’»

If you followed instructions, it should look like the picture above

Step 6: Extra

Create a new sprite. This will contain the extras.

In the forever loop, Get an «if ‘touch color red (you must get the exact color)’ then» loop. In that put a «set position» block. Enter in the coordinates where your player spawns. Get an «if ‘touch color blue’ then» loop. In that put a «set position» block. Enter in the coordinates where your player spawns. In it as well broadcast «message1»

In both ground and extras, get «when I receive ‘message1,'» and put «next costume.»

You now have the skills to make a basic platformer. Add to it.

For more help, click the link. He does everything this talks about, this is his code.

https://scratch.mit.edu/projects/68924432/

16 People Made This Project!

Recommendations

Introduction: How to Make a Platformer on Scratch

Scratch is a website where people create games and other programs using premade blocks that you drag and drop to a work space. Today I will show you how to make a platforming game on scratch.

Supplies

The only thing you will need is a phone or a computer and a browser that can run scratch

Step 1: Create Sprites

Start by logging in to scratch, this is needed so if you want to publish or come back to the game. Then click on create and it will put you on the project editor. This is were we will be making our project.

On the top left, you will find the costumes button. Click it if you want to change your player. Rename «sprite» to «player.»

Then create your platforms by making a new sprite. Rename that sprite «ground.»

Rename your game to whatever you want.

Step 2: Gravity

Add a «when green flag is clicked.» Then Place a «set position» block and put in the coordinates on where you want your player to spawn. Get a «forever» loop and a «repeat until» loop place the «forever» loop under the «set position» block.

Create a variable. This will act as our gravity. Mine was name «y vel» (short for y velocity). Right underneath the «set position» block, place a «set variable to ___.» Change the Variable to whatever you named your variable (I will be calling it y vel now) and change the number to zero. Next add a «change y by ___,» and put a «y vel» in it. Put that in the «forever loop.» Below that add a «change ‘yvel’ by ‘-1.'»

Create a block called Touch ground. MAKE SURE WHEN DOING THIS YOU CLICK RUN WITHOUT SCREEN REFRESH.

Insert a «repeat until» loop below the «touch ground». Add a «not» in the Boolean and in the «not» add a «touching ‘Ground.'» In that put a «change y by ‘1.’» Underneath that put a «set ‘y vel’ to ‘0.’»

If you followed all of that, your code should look like the picture above.

Step 3: Movement

Underneath the «when green flag is clicked,» put a «set rotation style ‘left-right.'»

Inside the «forever’ loop add two «if then blocks.» In the Boolean add two «key ___ pressed.» Set it to what keys you want to go left and right.

Make a block called «x detection» (it will help in next step). Click run without screen refresh. Add an input, call it «speed.»

In the right direction, put «detection x ‘5 (how fast you want your sprite to move),'» (speed depends on what you want), and «point in direction ’90.'» Do the same for the left but multiply all the numbers by negative one.

Make a block called jump. Put it in the «forever’ loop.

Create a new variable called «falling.» Under «touch ground,» place a «change ‘falling’ by ‘1.’» Under that, in the «repeat until» loop, place a «set ‘falling’ to ‘zero'»

Define jump. Add an «if then» block. In the Boolean put a «_____ and ____.» In one Boolean, put «key ‘what your jump key will be’ pressed,» then add «if ‘5’ is greater than ‘falling.'» In the «if then» block, put «set ‘y vel’ to ’12 (jump height.'»

You may notice that your player is not always touching the ground. Make a new costume, make it smaller on all sides. Inside the «forever» loop put in «switch costume to ‘costume2 (new costume).'» Below that, put a «switch costume to ‘costume1 (original costume).'»

If you have done this correctly, this should look like the picture above

Step 4: Horizontal Collision Detection

Create a new variable called slope. Underneath «change x by ‘speed,'» add a «set slope to ‘0.’»

Add a repeat until block underneath that. Put a «‘touching ‘ground’ or ‘slope’ equals ‘8.’» In that add a «change y by ‘1,’» and «change slope by ‘1.’» Under the «repeat until» loop, add a «if ‘slope’ equals ‘9’ then,» loop. In that add a «change x by ‘0’ minus ‘speed,'» then add a «change y by ‘0’ minus ‘slope.'»

If you did this step correctly, your code should look like the picture above.

Step 5: Vertical Collision Detection

Edit the block «touch ground» add a Boolean, call it «ceiling.» In our «touch ground» in the «forever» loop add «‘y vel’ is greater than ‘0.»

Take everything out of the «repeat until» loop. Put a «if then else» loop in the «repeat until» loop and add «ceiling» in the Boolean. In the «if then» part, add «change y by ‘-1.'» In the «else» part, add «change y by ‘1,’» and «set falling ‘0.’»

Below the «if then else» loop, add a «set ‘y vel’ to ‘0.’»

If you followed instructions, it should look like the picture above

Step 6: Extra

Create a new sprite. This will contain the extras.

In the forever loop, Get an «if ‘touch color red (you must get the exact color)’ then» loop. In that put a «set position» block. Enter in the coordinates where your player spawns. Get an «if ‘touch color blue’ then» loop. In that put a «set position» block. Enter in the coordinates where your player spawns. In it as well broadcast «message1»

In both ground and extras, get «when I receive ‘message1,'» and put «next costume.»

You now have the skills to make a basic platformer. Add to it.

For more help, click the link. He does everything this talks about, this is his code.

https://scratch.mit.edu/projects/68924432/

16 People Made This Project!

Recommendations

Понравилась статья? Поделить с друзьями:
  • Как написать игру на питоне со своей графикой
  • Как написать игру на питоне для андроид
  • Как написать игру на паскале abc
  • Как написать игру на пайтон
  • Как написать игру на котлин