Страницы

Показаны сообщения с ярлыком MRIMSHed. Показать все сообщения
Показаны сообщения с ярлыком MRIMSHed. Показать все сообщения

среда, 21 июня 2017 г.

Маленькие хитрости в разработке игр

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

1. «Прокрутка» по диапазону значений

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

Как это можно было сделать и как это делают чаще всего:
public int selectedWeapon;
public const int MAX_WEAPONS = 8;

public void Increment() {
 selectedWeapon++;
 if (selectedWeapon >= MAX_WEAPONS) {
  selectedWeapon = 0;
 }
}

public void Decrement() {
 selectedWeapon--;
 if (selectedWeapon < 0) {
  selectedWeapon = MAX_WEAPONS;
 }
}
Намного элегантнее это можно сделать с помощью операции вычисления остатка от деления:
public int selectedWeapon;
public const int MAX_WEAPONS = 8;

public void Increment() {
 selectedWeapon = (selectedWeapon + 1) % MAX_WEAPONS;
}

public void Decrement() {
 selectedWeapon =  (MAX_WEAPONS + selectedWeapon - 1) % MAX_WEAPONS;
}
Что же здесь произошло?! Дело в том, что мы всякий раз получаем лишь остаток от деления на на наше максимальное значение, в данном случае 8. Таким образом, при достижении максимального значения, остаток будет  равен 0.
С вычитанием операция несколько сложнее, так как мы уходим в отрицательную сторону:
(-1) % 8 = -1
Это решается простым добавлением максимального значения, так что остаток берется от:
8 + 0 - 1 = 7
7 % 8 = 7

2. Случайное значение из диапазона, за исключением заданного

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

Предположим, что персонажи хранятся в массиве. Тогда выбор случайного будет выглядеть так:
GameCharacter[] characters = new GameCharacter[16];
Random r = new Random();

public GameCharacter GetRandomCharacter(int playerCharIndex) {
 int enemyIndex = (playerCharIndex + 1 + r.Next(characters.Length - 1)) % characters.Length;
 return characters[enemyIndex];
}
Таким образом, мы совершаем выборку случайного значения, из диапазона всех значений после выбранного и до следующего его появления.
Для примера приведенного выше, если вызывать метод с параметром 5, модуль по числу 16 будет браться из диапазона значений (6; 20).

3. Быстрые вычисления

Несмотря на то, что вычислительные мощности компьютеров и даже мобильных устройств сегодня позволяют не задумываться о повышении производительности на низком уровне (хотя все еще остаются области, где нужно экономить самые крохи процессорного времени и памяти, например при написании шейдеров), следует быть знакомым с простейшими приемами, связанными с принципами работы процессора, к примеру:
  1. Деление с помощью умножения. Операция деления значительно затратнее, нежели умножение. Будет полезно взять в привычку, вместо деления, к примеру на 10, умножать на 0.1. По скромному мнению автора, это как минимум повышает читабельность сложных формул в коде.
  2. Так же деление/умножение на степени двойки можно совершать с помощью операции битового смещения:
    37 << 4 = 37 * 2 * 2 * 2 * 2 = 592
    136 >> 3 = (((136 / 2) / 2) /2 = 17
    Двоичные операции значительно быстрее десятичных!
  3. Операция побитового умножения «И» (&) может быть использована для проверки числа, является ли оно степенью двойки:
    (x & (x - 1)) == 0
Конечно, хороший компилятор может проанализировать и заменить многие вычисления на их быстрые аналоги, поэтому всегда нужно знать меру и не портить читабельность кода, в угоду сомнительной предварительной оптимизации.

4. Кодирование нескольких значений в одно поле

Этот прием крайне полезен для экономии памяти и упрощении обозначения каких либо связанных значений, например координат.
Предположим, что у нас есть карта, хранящаяся в виде двумерной сетки из множества ячеек. Тогда каждая ячейка будет иметь X и Y координату в этой сетке.
Таким образом, карту можно хранить в виде двухмерного массива, а обращаться к каждому элементу по двум индексам. Иногда такой подход оправдан, но с ростом сложности проекта, два значения начинают создавать проблемы. А если нужно хранить больше значений? (в одном проекте мне нужно было использовать адрес «между двумя клетками» – 4 значения)

Трюк заключается в записи двух чисел подряд в одно, с помощью операции битового смещения.
если нашему приложению хватает 65536 возможных вариантов для каждого значения, то два значения можно записать в переменную типа Integer.
Integer – хранит в себе 4 байта информации или 32 бита. В двоичной системе это вот столько:
1111 1111 1111 1111 1111 1111 1111 1111
65536 – это собственно половина Integer – 2 байта.

Для того чтобы сохранить числа X:43 (0010 1011) и Y:6435 (0001 1001 0010 0011), необходимо совершить следующие действия:
  1. Сместить 43 на 16 бит влево: получаем 0000 0000 0010 1011 0000 0000 0000 0000 или 2818048 в десятичной системе
  2. Добавить к полученному числу вторую координату: 2818048 + 6435 = 2824483. Однако, так как мы добавляем предыдущее значение в разряды заполненные нулями, можно воспользоваться операцией побитового «ИЛИ» ( | )
  3. Готово! Мы получили число 0000 0000 0010 1011 0001 1001 0010 0011. Можете проверить этот процесс в калькуляторе в режиме «Программист».
Для того чтобы раскодировать полученное значение, необходимо совершить следующие действия:
  1. Сместить код на 16 бит вправо. Так младшие два байта исчезнут, останутся только старшие два, которые будут соответствовать значению X:43
  2. Значение Y можно найти двумя способами:
    1. Самый примитивный, приходящий в голову людям, незнакомым с двоичной арифметикой – сместить значение X обратно, на 16 бит влево и вычесть полученное число из кода. Этот вариант дает возможность вычислить лишь последнюю часть кода.
    2. Более быстрым и "правильным" будет использование маски – побитового умножения «И» (&).
      Если совершить это действие над кодом с аргументом в 1111 1111 1111 1111 (65535), то мы получим именно последние два байта, соответствующие координате Y: 2824483 & 65535 = 6435.
      Для упрощения подобной операции рекомендуется использовать шестнадцатеричную запись числа 65535 – 0xFFFF. Это легко запомнить, ибо один байт заполненный единицами будет записан как 0xFF, три байта – 0xFFFFFF и т.д.

При всем при этом, существует проблема связанная с тем, что Integer – тип знаковый. И старший бит предназначен для хранения знака (0 – число положительное, 1 – отрицательное). В связи с этим, при декодировании происходит ошибка. Исправить этот эффект можно вычитая при кодировании из первой части кода половину максимально возможного кодируемого значения (для приведенного выше примера – это 65536 / 2 = 32768), а при декодировании, прибавления его к результату.

Пример кода:
public int Code (int x, int y) {
 return ((x - 32768) << 16) | y;
}

public int DecodeX (int code) {
 return (code >> 16) + 32768;
}

public int DecodeY (int code) {
 return code & 0xFFFF;
}
Следует учитывать, что полученный код может иметь отрицательное значение и потому не может быть использован, как индекс в массиве. Возможные решения:
  1. Хранение данных в коллекции ключ-значение
  2. Отказ от использования значений, приводящих к отрицательному коду (сокращение возможных значений вдвое)
  3. Использование типов данных бОльшего размера, для хранения кода, например long

понедельник, 4 ноября 2013 г.

Инди – больше чем просто мода

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

Дело даже не в том, что гиганты типа Gameloft (что еще со времен J2ME именуют «клонолофтом») или Mobage (насколько мне известно, это всего лишь издатель, но ситуация похожая) чаще всего выпускают однообразные игры. Нет, бывают и очень даже оригинальные игры, разнящиеся геймплеем..однако, не в последнюю очередь благодаря вопросу монетизации, совершенно разные игры становятся заложниками клише крохотных элементов геймплея.
Столько игр, но в каждой есть что-то, что я уже видел где-то..ах да, среди ваших же игр, Gameloft!
Перелистывая страницы на AppStore или Steam, я зачастую оставляю без внимания игры большинства крупных издательств/разработчиков. Максимум – посмотрю скриншоты, почитаю описание и комментарии, чтобы в лишний раз убедиться, что в итоге столкнусь с чем то похожим и приевшимся. Немудрено, что я так часто нахожу утешение в очень старых играх.
О, старые игрушечки! Столько необычного... Сейчас, в большом геймдеве так не принято.
Вот тут-то инди-разработчики и проявляют себя во всей красе! Известное дело – в последнее время термин Indie стал мейнстримом, частенько несущим негативный окрас, из-за разработчиков, что твердят о своей независимости, но предлагают все тот же геймплей наполненный теми же ключами, с которыми мы знакомы уже много лет. Но среди толп этих волков в овечьей шкуре, нередко попадаются и истинные бриллианты, способные удивить чем-то необычным: симулятор енота, игра про кошмарный сон, где ты мерзнешь и голодаешь или игра про игроков в настолку?! Они смогли удивить меня! А это что-то да значит.
Shelter
Don't starve
Knights Of Pen And Paper

среда, 25 сентября 2013 г.

Age Of Glory. Dungeon step

Прошло несколько дней и проект стал обретать некоторые формы.

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

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

Генератор, кстати, написан на основе замечательного туториала: http://gamedev.tutsplus.com/tutorials/game-design/create-a-procedurally-generated-dungeon-cave-system/

Работающий код и все ресурсы можно скачать https://github.com/MindEdg3/AgeOfGlory

 

воскресенье, 22 сентября 2013 г.

AgeOfGlory. init commit.

Решил я научиться моделить недавно. Maya, студенческая лицензия, все дела.. Осваиваю уроки один за другим.

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

В связи с этим я ставлю себе цель: Модели для игры! В свое свободное время, с крайней низким приоритетом, я буду делать классическую пещерную RPG, что уведена в героическую тематику, чуть ли не до пародийной степени. И название соответствующее: Age of Glory.

А вот и скетч, что задаст мне направление:
Скетч Age of Glory нарисованный за несколько минут.
Да я пока не умею рисовать, но у меня еще есть время)
Здесь видно Героя управляемого игрока, с видом от первого лица. Он вооружен простенькой секирой и сражается с Жабо-человеком, которого встретил на развилке в запутанной пещере.
Над Жабо-человеком висит окошко с минимумом информации. Больше можно получить нажав на кнопочку со знаком вопроса. Ниже кнопки-стрелочки для переключения текущей цели. В данном случае они не работают.
Справа панель игры с кнопкой выхода в меню, картой покрытой туманом войны в местах, где игрок еще не побывал, линейки его текущих/максимальных здоровья и маны, кнопки для открытия окон Персонажа, Инвентаря, Магии и Журнала. Так же на панели можно увидеть лог последних действий в игре:

  • Жабо-человек атаковал игрока на 7 ед
  • Настал ход игрока
Таким образом игра является пошаговой, что традиционно для подобных игр.

Говорят начать что-то делать – значит сделать это наполовину. Ну вот, игра наполовину готова! Осталось еще немного.

Скрывать мне нечего, буду все держать в открытом виде на GitHub: https://github.com/MindEdg3/AgeOfGlory

понедельник, 2 сентября 2013 г.

Летняя практика студентов в FOSS Labs Gamedev

Вот и завершилась летняя производственная студенческая практика 2013 года, посвященная разработке игр, что прошла в организации FOSS Labs.

Условия были следующими:
  • студенты, не имеющие опыта (или совсем крохотный опыт) в разработке игр объединялись в группы по 3 человека
  • с помощью Game Idea Slot Machine для каждой группы генерировалось задание
    • выбирались только проекции 2D и 3D
    • если команду не устраивал результат первой рандомизации, они могли сгенерировать задание еще 1 раз
  • 2 недели на разработку прототипа
  • строго рекомендуемое условие: использование VCS, давались некоторые инструкции и рекомендации по использованию Git и GitHub (хотя все равно находились те, кто считал, что они справятся и без VCS)
  • 10 из 14 дней (будние дни, ПН-ПТ) необходимо было проводить в офисе, минимум по 6 часов, включая перерыв на обед. По желанию, участники могли задержаться еще на пару часиков, а так же устраивать встречи по выходным

суббота, 31 августа 2013 г.

Как написать концепцию игры

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

Например:
В "Trick or Treat", персонажи игрока оказались в ловушке лабиринта, старого дома с привидениями. Они должны сбежать из дома, уничтожая встречающихся монстров, избегая ловушек и решая загадки. "Trick or Treat" это игра жанра action, с видом от третьего лица.

Цель этого текста состоит в том, чтобы дать читающему понять смысл игры. Он должен отвечать на несколько основных вопросов:
  • Каковы цели игры?
  • Как можно будет достичь этих целей?
  • В чем будет сложность игры?
  • Где происходит действие игры? 

Коллективная разработка на Unity3D и GitHub

Данная статья рассматривает подготовку проекта
Unity3D к работе с GitHub на Windows.

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

Один из вариантов - это Unity Asset Server, однако для его использования, каждому пользователю необходимо купить Team License, стоимостью $500. По понятным причинам, это не является лучшим решением для начинающих команд и компаний с малым бюджетом.

Вторым вариантом, является использование бесплатных систем контроля версий, а Unity3D с некоторых пор, как раз таки начала их поддерживать во Free версии. По статистике, лучшим, на данный момент, себя показывает Git.

пятница, 30 августа 2013 г.

Unity3D.basic.Step_1 – Создание/Открытие проекта

 [<]назад[<] -- [^]оглавление[^] -- [>]вперед[>]

Терминология:

  • Ассеты (Assets) – ресурсы игры любых типов: файлы кода, текстуры, музыка, модели, шейдеры и т.д.;
  • Пакет (Package) – набор ассетов(в т.ч. различных типов), упакованных в один файл, для удобства процесса импорта-экспорта;
  • Проект – набор файлов, состоящий из ассетов, метаинформации, настроек и других данных, которые будут влиять на создание конечного приложения. В узком смысле проект – это файлы и свойства нескомпилированной игры;
  • Папка проекта – корневая папка, в которой находятся все файлы проекта.


После запуска Unity3D откроет диалоговое окно, состоящее из двух вкладок.

В первой "Open project" можно открыть готовый проект, выбрав из списка последних или нажав на "Open other..."

Unity3D.basic.Step_2 – Взаимодействие с окнами Unity3D

Терминология:

  • Сцена (Scene) – условно это игровой уровень. Это список игровых объектов находящихся в данный момент в памяти. Подробнее о структуре объектов читайте на следующей странице;
  • Камера – объект сцены, который выполняет роль настоящей кинокамеры. То что видит в пространстве камера, то и будет выведено на экран пользователя.


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

Каждая область имеет свою иконку и название на вкладке в верхнем левом углу. Вы всегда можете переместить любую из этих областей в другое место.
В верхнем правом углу каждой области есть маленький символ стрелочки с выпадающим меню(это меню можно открыть и нажатием правой кнопки мыши по вкладке области), через которое область можно развернуть на все окно(Maximize), закрыть (Close Tab), либо добавить новую (Add Tab).

Unity3D.basic.Step_3 – Структура приложения Unity3D

Перед тем как приступить к созданию игры на Unity3D, необходимо понять каким же образом тут все устроено:

В основе всего лежит Игровой объект (Game Object, часто сокращаемый до аббревиатуры GO). Объекты следует ассоциировать с физическими объектами из реального мира.

Unity3D.basic.Step_4 - Создание и расстановка объектов

 [<]назад[<] -- [^]оглавление[^] -- [>]вперед[>]

Терминология:

  • Материал (Material) - это компоненты Unity3D, обеспечивающие связку шейдера с текстурами. Почти все компоненты прорисовывающие графику, принимают не текстуры, а именно материалы. Подробнее о материалах в конце статьи(чтобы лучше понять их смысл - посмотрите изображение в статье по ссылке);
  • Меш (Mesh) - "набор вершин и многоугольников, определяющих форму трёхмерного объекта". Проще говоря - трехмерная модель;
  • Рендеринг (англ. rendering — «визуализация») - превращение неких данных(даже не обязательно изначально графических), в изображение сохраняемое или выводимое, к примеру, на экран;
  • Спрайт (Sprite) - это не напиток, а "графический объект в компьютерной графике". Проще говоря картинка;
  • Шейдер (Shader) - программная инструкция видеокарте, о том как именно нужно прорисовывать картинки. Обычная текстура, в зависимости от выбранного шейдера, может оказаться размыта, деформирована или изменена намного более сложными алгоритмами, однако обо всем этом заботится шейдерный программист. Это редкий случай, когда необходимо писать свой шейдер, а не пользоваться готовыми.


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