Видео-блог разработки игры # 18

intro


Всем привет. Это 18-ая часть блога о разработке игры.

Видео для ютуба я выкладываю уже 9 месяцев. Мне нравится это делать.

Вообще, мне нравится всё, что я делаю =)


За прошедшие 2 недели я сделал большой рефактор кода, прошёл всего Ведьмака с дополнениями и дочитал "Призраков" Чака Паланика.

Misery / Нищета


"Людям нравится смотреть на трэш. Где хуже, чем у них" - это цитата из "Призраков".

Испытание нищетой - не самое легкое.


Хорошо, когда ты родился в богатой семье, у тебя всё есть. Ты живёшь в красивом месте и тебя окружают умные и красивые люди. Ты находишься внутри красоты и сам становишься красивым.


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


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

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


Дыры в стенах в ванной комнате, и протекающие трубы. Эти дырки сделал сантехник, когда менял смеситель. Он делал не для себя, а для каких-то "незнакомых ему людей". Пофиг какой результат, будут жить и всю жизнь смотреть на эту дыру в стене.


Я чищу свои остатки зубов, стараюсь их как-то сохранить. С детства боюсь стоматологов. Когда мне было 5 лет, мне удалили зуб без наркоза. Родители говорили, что это лучший врач в городе - он всё делает отлично. Врач мне удалил зуб раскурочил два соседних. Одно из самых ярких воспоминаний из детства. Доброе утро!


Великолепные плинтуса, которые нельзя менять и выкидывать. "Они же ещё не испортились окончательно. Не выкидывай, вези на дачу." - так мне говорят родители.

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


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

Хотел бы я написать о чём-то красивом, что меня окружает. Я всё мечтаю о том, что с домов исчезнет реклама и будет видна архитектура. А не разноцветная мазня на стенах. Я мечтаю о том, что смогу надеть ботинки с кожанной подошвой, чтобы пройтись по асфальту без трещин и ям. Я мечтаю, что когда-нибудь одену черные брюки и сяду на скамейку, которая не порвёт мне брюки неотёсанными досками, или испачкает грязью и мусором. И красивая рекламная наклейка не приклеится ко мне в самое неприличное место. Но пока что у меня не достаточно денег, чтобы просто переклеить обои. Чак Паланик абсолютно прав, что людям нравится смотреть на трэш. Где сильно хуже, чем у них. Но если вся твоя беда в дырке на стене - это не достаточно хуже. Это не интересно. Есть ведь вещи поважнее этой дырки, на которую ты смотришь всю жизнь.


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


На эту мысль меня подтолкнуло видео с канала DANIL K

Он так и говорит: чтобы делать что-то красивое, надо постоянно это красивое видеть. Но я не со всем согласен. Смотреть красивые ролики в ютубе и красивые фотки в инстаграмме недостаточно, чтобы окружить себя красотой.

Core GamePlay loop


Базовое приложение на libgdx выглядит так:

public class MyGdxGame extends ApplicationAdapter {


// область переменных, которые доступны на всём протяжении жизни приложения


public MyGdxGame () {} // пустой конструктор

public void create () {} // инит основных синглетонов и инит работы со СкрИнами (Screen/Экран)

public void resize ( int width, int height ) {}

public void render () {} // бесконечный цикл, этот метод вызывается каждый кадр (очень часто)

public void pause () {} // при переводе приложения в режим Паузы (например, свернули приложение)

public void resume () {} // когда приложение возвращается из режима Паузы

public void dispose () {} // когда мы запустили метод Gdx.app.exit();

}


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


Делаем переключение Скринов.

В области переменных объявляем Screen screen = null; Структура класса Screen идентична нашему приложению, есть точно такие же методы. А ещё есть метод void show (), который выступает в роли конструктора.

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

enum SCREEN_TYPE { LOADING_MENU, LOADING_GAME, MENU, GAME, CUT, EXIT }

и метод переключения void switchTo ( SCREEN_TYPE screenType ){} в котором я свичом создаю нужный класс: screen = new LoadingScreen() или screen = new MenuScreen().

И здесь же вызываю метод screen.show();


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

screen = new GameScreen( this );


Теперь нужно передать из основного класса MyGdxGame управление Скринам. Сделать это просто, так как я уже говорил, что названия методов класса Screen дублируют методы нашего главного класса. Выход из приложения сделан в методе switchTo(). Я передаю в этот метод SCREEN_TYPE.EXIT, никаких Скринов не создаю, а просто вызываю Gdx.app.exit();


Теперь игровой цикл из MyGdxGame.render() мигрирует в класс render() определённого Скрина.

По сути, мы внутри нашего основного приложения делаем несколько маленьких приложений и можем их переключать из главного приложения. Больше можно ничего не писать в MyGdxGame и работать только со Скринами. Это удобно.


На википедии пишут, что для каждой игры нужно писать собственный игровой цикл, я считаю это бредом. Достаточно написать один шаблонный игровой цикл и делать на нём игры ЛЮБОГО жанра.


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


Для удобства, мы разделим каждый render() метод на 2 части: draw() и update(). Всё что связано с выводом на экран будет в методе draw(), а всё что касается внутренних вычислений - это в update().

Dynamic Levels logic (подробный туториал на 17 листах https://docs.google.com/document/d/1bKQQjRncCmYqcC9405jfC2Rv...)


Основная логика Динамических уровней.

Пусть у нас есть несколько уровней: A, B, C, D, E.

Текущий уровень - A.

Уровни B и E - соседние с A.

Уровни C и D - соседние с B.

Уровень E соседний с D.


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

A-B

A-E

B-A

B-C

B-D

С-B

D-B

D-E

E-A

E-D


Уровни D и C "не видят" уровня A.

В программе это дело записывается направленным графом.


Сначала наш персонаж стоит на уровне А. Уровни A, B, E - загружены, больше ничего не загружено.

[A] [b] [e]


Персонаж перемещается на уровень B. В этот момент он теряет контакт с землёй A, Игрок_стоит_на_земле = false;

Но сразу же получает новый контакт с землёй B, Игрок_стоит_на_земле = true;

Текущий уровень под ногами меняется, происходит загрузка того, что нужно, согласно списку связей - нужно догрузить С и D. Уровень A - не загружается, так как уже загружен. Но кроме этого нужно выгрузить из памяти E, так как его нет в текущем списке связей.

[a] [B] [c] [d]


Флаг Игрок_стоит_на_земле - важен. Игрок может подпрыгнуть или перепрыгнуть на новую локацию.


Персонаж перемещается на уровень C. Смотрим на список связей: всё что лишнее - выгружаем. Дополнительно загружать ничего не нужно. Отсюда можно отправится только в сторону уровня B.

[b] [C]


Опять перемещаемся на уровень B. Уровни D и A догружаются, согласно списку связей.

[B] [c] [d] [a]


Персонаж переходит на уровень D. Отсюда можно попасть в B или в E.

[b] [D] [e]


Dynamic Levels code


DynamicLevel - синглетон. За время работы программы он 1 раз инициализируется, и при закрытии приложения сам будет удалён, если этого захочет System.GC =)


Этот синглетон работает не со списком уровней, а со связями между уровнями - connections. Для каждого уровня связи свои, они указывают на смежные уровни. Эти списки связей очень часто пересекаются: из разных локаций можно попасть в одни и те же места разными дорогами. Все связи у меня указаны в LevelAsset, я заполняю этот список согласно карте уровней, причем первый идентификатор должен всегда указывать на текущий уровень (это сделано для упрощения и оптимизации кода DynamicLevel).


Сердце DynamicLevel - это два метода: update и reload. Update работает всегда и он при необходимости что-то подгружает и что-то выгружает. А reload - даёт знать, что именно нужно подгружать.


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


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


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


GAME DESIGN


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


Готовые механики:

- перемещение персонажа по уровням

- динамические ресурсы на уровнях

- работа с триггерами

- скрипты NPC

- инвентарь


Не готовые механики - это то, чем я сейчас занимаюсь:

- стрельба

- разрушаемые объекты

- использование предметов из инвентаря, взять предмет в руку

- скрипты камеры для кат-сцен

- диалоги между персонажами

- передача анимации из референц-модели в другие модели

- раздельная анимация для верха и низа модели

- тени (к сожалению не смогу сделать)


LITERATURE


Пока я не купил "Бойцовский клуб" буду читать старые книги свой библиотеки. Сначала небольшая поэма Пушкина "Руслан и Людмила". Потом книги Эдгара Берроуза про Тарзана. А потом фантастика Герберт Уэллса.


outro


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


Господа, напишите в комментариях, что именно вам нравится или не нравится в моих блогах. И что стоило бы добавить, а что никогда не показывать?


Спасибо за внимание.


Открытый исходный код игры: https://github.com/cyberbach/dunno

Правила сообщества

ОБЩИЕ ПРАВИЛА:

- Уважайте чужой труд и используйте конструктивную критику

- Не занимайтесь саморекламой, пишите качественные и интересные посты

- Никакой политики


СТОИТ ПУБЛИКОВАТЬ:

- Посты о Вашей игре с историей её разработки и описанием полученного опыта

- Обучающие материалы, туториалы

- Интервью с опытными разработчиками

- Анонсы бесплатных мероприятий для разработчиков и истории их посещения;
- Ваши работы, если Вы художник/композитор и хотите поделиться ими на безвозмездной основе

НЕ СТОИТ ПУБЛИКОВАТЬ:

- Посты, содержащие только вопрос или просьбу помочь
- Посты, содержащие только идею игры

- Посты, единственная цель которых - набор команды для разработки игры

- Посты, не относящиеся к тематике сообщества

Подобные посты по решению администрации могут быть перемещены из сообщества в общую ленту.

ЗАПРЕЩЕНО:

- Публиковать бессодержательные посты с рекламой Вашего проекта (см. следующий пункт), а также все прочие посты, содержащие рекламу/рекламные интеграции

- Выдавать чужой труд за свой

Подобные посты будут перемещены из сообщества в общую ленту, а их авторы по решению администрации могут быть внесены в игнор-лист сообщества.


О РАЗМЕЩЕНИИ ССЫЛОК:

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

- Пост должен быть содержательным и интересным для пользователей, нести пользу для сообщества

- Ссылка должна размещаться непосредственно в начале или конце поста и только один раз

- Cсылка размещается в формате: "Страница игры в Steam: URL"