Инвентарь на Массивах

Инвентарь на Массивах
Это текстовый урок по основам использования массивов в Construct 2 (3) и создание Инвентаря, Экипировки, и ячеек Лута (найденных предметов в игре). Вы узнаете, как пользоваться массивами, что они из себя представляют и как их эффективно использовать в своих играх. А затем, закрепим это, начав построение Инвентаря и продумав логику. Для прохождения урока, вам потребуется скачать архив с исходником Инвентаря прикреплённый в конце. Открыв исходник, начинайте читать этот текстовый урок. В уроке познакомимся с массивами и придумаем сохранение данных для Предметов Инвентаря, чтобы их можно было сохранять и загружать в своих играх.
Здесь не будет показано создание всего с ноля. Я покажу базовую логику и правильный (на мой взгляд) путь организации механики Инвентаря. В самом исходнике будет готовый код полного функционала веб-версии представленной ниже.

Здесь вы можете поиграть в результат исходника Инвентаря. Это html5 версия.

Описание и Возможности
Об исходнике Инвентаря:
• Около 70 событий в коде. При желании, можно ещё больше всё сократить.
• Все действия совершаются Тачем или Кликом Мыши. Прямого перетаскивания Предметов нет!
• R - Рестарт
• При каждом новом запуске уровня, в сундуках при Открытии будут генерироваться случайные Предметы.
• Предметы можно брать в Инвентарь ТАПнув по ним. Выкладывать их обратно в Лут сундука, а также надевать на Куклу персонажа (Экипировка).
• Складывание некоторых одинаковых предметов в Стопки/Стэки.
• Все данные о Предметах в сундуках сохраняются в Web Storage. Это позволяет как угодно менять вещи в сундуках, закрывать их и затем открывать - генерируя в сундуках Предметы по данным из хранилища
---
При наведении мышкой на 1 спрайт Инвентаря из 3-х - в Тексте показывается значения в 4-х первых ячейках по оси X и Y для конкретного Массива Инвентаря.
Ещё подписал, какая строка за что отвечает. Видно где ТИП предмета, где его КАДР, где КОЛИЧЕСТВО предмета, а где уникальный идентификатор UID.
Далее подписан UID активного ОТКРЫТОГО сундука. Общее количество Предметов на уровне. Общее количество спрайт-фонтов Цифр. И UID активного предмета - того, который Выбран и с кем возможно производить операции.
*** Возможны ошибки с отображением Fullscreen на мобильных и частично на ПК. Эта задача в исходник не входила!

В конце урока будет прикреплён Исходник .capx, который очень подробно прокомментирован. А здесь представлена html5 версия, в которую можно поиграться с ПК, или мобильного устройства.
Обсудить урок и Исходник можно на форуме:
http://c2community.ru/forum/viewtopic.php?f=4&t=16957

МАССИВЫ. ВВЕДЕНИЕ...

Массив - Это таблица с данными. Массивы многих пугают, но их использование в своих приложениях и играх, существенно облегчают разработку и расширяют возможности в программировании. И они лишь кажутся сложными. В этом уроке мы разберём что такое массивы и на конкретных примерах научимся с ними работать.
Википедия говорит нам, что массивы это:

Структура данных, хранящая набор значений (элементов массива), идентифицируемых по индексу или набору индексов, принимающих целые (или приводимые к целым) значения

Представьте любую таблицу в Excel, или даже игру "Морской бой" - оба этих простых примера, являются наглядным олицетворением массива с данными, так как в ячейках, или клетках может храниться какая-то информация. Но об этом позже.
По умолчанию, добавляя в проект объект Массив (Array), если посмотреть его свойства, то можно увидеть Размеры массива в разделе Properties
Массивы в Construct 2 (3)
Тут указана его Ширина (Width), Высота (Height), и Глубина (Depth)
Width = 10, говорит на о том, что этот массив имеет 10 Ячеек по ширине. Высота и Глубина выставлена = 1. Поэтому в данном примере они игнорируются, так как это минимальные значения.
И если визуализировать текущий массив, то он будет выглядеть так:
Визуальное представление Массива в Construct 2 (3)
Цифры над клетками показываю Индекс соответствующей клетки (Ячейки). Они специально расположены ВЫШЕ клеток, так как это лишь КООРДИНАТЫ, чтобы по ним ориентироваться. Первой (по счёту) Ячейке будет соответствовать 0-й индекс, а последней 9-й индекс. Так как отсчёт происходит всегда с Нуля, это стоит учитывать и не забывать!
Речь шла про КООРДИНАТЫ (Индексы), но сами Ячейки могут хранить любую информацию, например числа, или текст.
По умолчанию, при создании Массива, все его Ячейки внутри себя имеют значение = 0.
То есть данные каждой Ячейки имеют Цифру = 0
Визуальное представление Массива в Construct 2 (3)
Если мы хотим внести какие-нибудь свои данные в массив, то должны в C2 сделать действие установки значения через Set at X.
Что будет читаться, как: Установить значение для Ячейки с координатой Х
Запись значения в Массив C2 (C3)
Допустим, мы хотим записать в самую первую ячейку значение = 7
А в 4-ю ячейку текстовое значение "kk"
Не забываем учитывать, что отсчёт происходит с Нуля. Тогда в Construct 2 (3) это будет выглядеть так:
Запись значения в Массив Construct 2 (3)
И если, опять же, представить этот массив со значениями визуально, то он будет выглядеть так:
Визуальное представление Массива в Construct 2 (3)
Сразу сориентируйтесь по картинкам, что 3-му Индексу по Х будет соответствовать 4-я Ячейка с данными.
---
Хорошо. Таким образом мы смогли задать значения в определённые Ячейки массива. Дальше нам нужно научится считывать (получать) данные какой либо конкретной ячейки, чтобы использовать значения в массиве различным образом.
Самым простым примером, будет вывод в текст значения определённой ячейки. Для этого, добавляем в проект объект Text и в действии задания текста напишем следующее:
Array.At(3) - Чтобы получить значение в Ячейке с 3-м индексом.
Задание значения в Массив Construct 2 (3)
Поставим себе задачу вывести на экране в тексте значение какой-нибудь Ячейки массива. Для этого зададим действие установки текста для объекта Text.
Иногда, вручную в C2 сложно узнать, какие выражения можно производить с объектами и плагинами. Для этого, в C2 есть полупрозрачное окно, в котором можно кликать на список объектов и строка выражения будет заполняться значениями по умолчанию.
Выбираем сперва Array, кликнув 2 раза на Массив и затем на At
Запись значения в Массив Construct 2 (3)
Строка выражения заполняется так: Array.At(X).
Останется только изменить выражение, подставив вместо значений по умолчанию (Х) - нужное нам значение Индекса Ячейки. То есть, 3 в данном случае.
Если запустить предпросмотр, то увидим, что в тексте успешно отобразилось значение Ячейки
Вывод значения ячейки Массива в тексте
Бывает необходимо отслеживать изменения массива в тексте. Для этого можно более продвинуто расписать задание Текста, чтобы показать все Ячейки, или только нужные.
Сейчас попробуем вывести в Текст все 10 значений нашего массива. Делаем такую запись, где после указания каждой ячейки стоит выражение &newline&.
Символ "&" связывает несколько различных значений, собирая всё в единую строку для чтения программой
Вывод значения массива в Тексте на экране
На экране в увидим это:
Вывод значения массива в Тексте на экране
Отлично. Сравним с нашим массивом
Визуальное представление Массива в Construct 2 (3)
Отличие лишь в том, что Массив у нас заполнен как бы по горизонтали, а Текст мы выводим добавляя новые строки и от этого визуально кажется, что массив находится как бы по оси Y.
Изменяем выражение для вывода текста, заменив newline на "_". Это будет выступать неким разделителем текста. Просто для удобства чтения.
Вывод значения ячейки Массива в тексте
И получаем это
Показ значений ячеек Массива в тексте
Таким образом, занося данные в Массив, в последующем мы сможем получать данные из ячеек Массива для задания различных значений.
---
Например, мы хотим сделать динамическую систему сложности уровней в игре. Герой убивает врагов, зарабатывает очки, повышает свой уровень и вместе с тем, растёт сложность противников (их HP, скорость, сила атаки и т.д.). Добавив переменную, которая будет обозначать уровень нашего героя и создав массив, который будет хранить характеристики противников под разные уровни героя - мы сможем построить логику нарастающей сложности. При создании противника, укажем им характеристики (их HP, скорость, силу атаки и т.д.) используя переменную уровня героя вместо одной из координат.
То есть, к примеру на 5-м уровне героя мы для HP противников устанавливаем им шкалу здоровья из массива Array.At(5), где вместо 5-ти подставляем переменную уровня героя, например так
Array.At(Hero.Lvl)
Надеюсь, общая модель примерно понятна... Если пока нет, то не страшно, дальше будет понятнее )
---
Всё, о чём мы сейчас говорили, касалось одномерного Массива! То есть, Массив был представлен, как одна ось Х, поскольку мы использовали лишь его Ширину. Высота и Глубина равнялась =1 и не использовалась.

ИНВЕНТАРЬ НА ОСНОВЕ МАССИВА

Начнём заводить разговор ближе к теме Инвентаря, которым будем заниматься. Ведь многие пришли именно за ним ).
Теперь, зная базово, как устроен одномерный массив, нарисуем себе задачу:
Нужно хранить в массиве данные о предметах/объектах этого самого инвентаря (вещи, оружие, ресурсы, еда). Как это сделать ?
Для начала уясним, что физически мы НЕ можем в массиве хранить Спрайты предметов! Мы можем хранить лишь данные о них, например, какой ТИП предмета должен "лежать" в определённой ячейке. Перчатки ли это, оружие, ресурсы, или еда...

Мы нарисовали, или нашли несколько спрайтов, которые будут выступать в роли Предметов, которые можно будет собирать в нашей будущей игре
Предметы для Инвентаря в Construct 2 (3)
Эти 8 спрайтов разные объекты, разные спрайты. Мы их разделили как бы по ТИПу предмета. Например, в спрайт Еды загружены 3 кадра различных съедобных продуктов. В спрайте Шапок - различные головные уборы и т.д.
Разделение Предметов Инвентаря  по ТИПу
0 кадр НЕ ИСПОЛЬЗУЕМ в конкретно этом исходнике вообще, потому что это приведёт к возникновению Нулей в Массиве и будет неудобно читать и отслеживать это в тексте. Будет казаться, что что-то обнулилось, какая-то ячейка очистилась, хотя на деле там просто указано, что у предмета Нулевой кадр.
Но вообще, потом отдельно, в своей игре ты можешь спокойно использовать Нулевой кадр, главное помнить о том, что тогда 0 может обозначать кадр предмета.

В C2, поскольку мы разделили все объекты на несколько разных спрайтов, нам будет НЕ удобно работать в коде со множеством спрайтов предметов. Например, нам нужно будет переместить предмет из найденного сундука Лута в Инвентарь игрока, придётся дублировать одинаковых код для ВСЕХ спрайтов предметов, потому что мы укажем переместить спрайт Еды - и переместим его. Но если хотим перемещать Перчатки, то мы тот же самый код должны написать и для спрайта Перчаток. В итоге это всё сильно усложняет код с большим количеством разных спрайтов.
Для упрощения всего этого и в целом, более правильно будет объединить все наши спрайты Предметов в ОДНУ Семью объектов. Назовём эту семью "Object" и добавим в неё все 8 наших спрайтов предметов
Добавление объектов в Семью в Construct 2 (3)
Затем кликнем на саму Семью в списке объектов проекта и добавим ей переменную "Type", которая будет определять ТИП предмета
Добавление переменной Семье объектов
Поскольку мы добавили переменную Семье спрайтов - эта переменная автоматически появляется у каждого отдельного спрайта из семьи
Переменная Семьи появляется у всех спрайтов в Семье
И мы вручную записываем каждому из 8-ми спрайтов в эту переменную своё обозначение ТИПа предмета. Это удобно делать, расположив все спрайты на отдельном НЕ игровом уровне, который будет служить неким Хранилищем всех (или большинства) объектов на уровне. В исходнике это уровень с именем Storage.
Сразу добавим все нужные переменные. Далее представлены названия переменных и то, что на русском языке они будут отображать.
Спрайты Предметов для Инвентаря
food - еда
res - ресурсы
head - голова
weap - оружие (от слова weapon, просто удобнее чуть покороче в тексте выводить на экране)
hand - руки
tors - тело, торс
leg - ноги
shoes - обувь

ПОСТРОЕНИЕ ЛОГИКИ ИНВЕНТАРЯ НА МАССИВАХ

Начнём построение с базовой вещи - с появления Предметов в игре. Создаваться они по задумке будут при открытии сундука/ящика с Лутом. В исходнике этот момент подробно прокомментирован. Здесь же нужно обсудить саму механику на простом примере:
Открываем сундук - создаём от 1-го до 4-х предметов случайным образом через действие:
Create object "Object" - то есть, мы создаём объект Семьи спрайтов. Сам спрайт при создании будет выбираться случайным образом из 8-ми имеющихся в Семье
Создание объектов Семьи в цикле повторов
С позиционирование спрайтов рассказано в исходнике. А здесь просто представим, что при создании выпало 2 вот таких Предмета:
Создание Предметов Инвентаря в Construct 2 (3)
Поскольку сундук можно открывать и закрывать. Возможно в игре, созданные объекты на данным момент нам будут НЕ нужны. Мы закрываем сундук и уходим в игре в другое место. Значит исчезают визуальные клетки инвентаря Лута и сами созданные предметы нам не нужны. Чтобы оптимизировать игру, ненужные предметы лучше удалять!
Но! А вдруг мы захотим к ним вернуться в игре позже ? Это вполне возможно. Но при открытии того же самого сундука мы НЕ сможем случайным образом создать точно такие же Предметы.
Чтобы этого добиться, мы должны /b>при первом открытии сундука записать данные о созданных предметах в Массив. Или наоборот, при закрытии сундука проверять, остались ли в нём ещё предметы и только тогда записывать данные в массив. В целом, сейчас это не так важно, главное понять принцип работы озвученный далее.

Для удобства, будем изображать Массив на более простом визуальном примере из начала статьи.
Итак, Массив для Лута у нас будет иметь Ширину в 4-ре Ячейки по оси Х.
Так как у только что созданных объектов изначально прописан ТИП предмета в переменной Семьи, а значит и самого спрайта - то мы знаем значение этой переменной при создании Предметов.
Повторяем для наглядности:
Создание объектов Семьи в цикле повторов
Запускаем Цикл Повторений. В количестве повторений пишем choose(1,2,3,4)
choose( ) делает Случайный выбор из того, что перечислено в скобках ( ). То есть, тут случайно выберется число от 1 до 4, чтобы сделать это количество случайных предметов.
У нас по задумке выпало 2 повтора из условия. Чтобы записать ТИП предмета в Массив для обоих предметов, мы делаем подобную запись в действии для массива
Запись данных о созданных Предметах в ячейку Массива
В значение Value мы указываем Семью и её переменную ТИП, поскольку все действия мы выполняем для объекта Семьи, а не для конкретного спрайта из неё.
В КООРДИНАТЕ Индекса для определения в какую Ячейку Массива произвести эту запись мы пишем loopindex. Если это слово перевести грубо, то это индекс повторения.

Выражение loopindex - это Номер витка повторения Цикла. Например, у нас через choose( ) выпало так, что должны создаться 2 предмета. Значит Цикл запустится 2 раза и будет 2 витка повторения. loopindex первого витка будет равен = 0, а второго равен = 1. Так как отсчёт происходит с 0 по аналогии со считыванием Индексов в Массиве. Надеюсь это понятно...

Таким образом, для первого созданного объекта координата для записи по Х будет равна = 0, а второго предмета равна = 1
И визуально мы видим такой итог:
Визуальное представление записи значений в Массив
Как видим, 2 первые Ячейки стали заняты данными о предметах. В двух других остались Нули по умолчанию.
И тут мы понимаем, что данных о ТИПе предмета недостаточно, чтобы его потом воссоздать по требованию. Так как у каждого типа может несколько кадров в анимации, отображающих разный вид предмета визуально
Разделение Предметов Инвентаря  по ТИПу
Значит продумываем, что нам нужно ещё для точной идентификации предмета. И заводим так же как ранее под это дело дополнительные переменные для Семьи предметов. Это будут переменные:
Type - Тип предмета. О нём мы уже поговорили
Frame - Кадр анимации
Amount - Количество единиц предмета. Для вещей и оружия оно всегда будет равно = 1
Stack - булевая переменная, которая принимает лишь два значения true и false (правда и ложь). Её мы при записи в массив использовать НЕ будем. Она нужна чтобы изначально разделить предметы на Стэкуемые (которые можно складывать кучкой/стопкой в несколько единиц одного предмета) и НЕ стэкуемые. И дополнительно в зависимости от неё, при создании предмета, если так вышло, что предмет может Стэковаться - мы будем создавать такому предмету Спрайт Фонт, в котором будем указывать количество единиц предмета отталкиваясь от переменной Amount.
У всех Предметов, которые могут складываться в Стэки по нескольку штук - ставим в булевой переменной Stack значение на true
Добавление переменных Семье в Construct 2 (3)
Ещё раз глядя на это, понимаем, что нам нужны дополнительные Ячейки в Массиве, чтобы уместить нужные нам переменные об объекте
Визуальное представление записи значений в Массив
Думаем как можем расширить массив, чтобы хранить все нужные нам данные... Всё это время мы использовали одномерный массив, которого теперь нам не стало хватать.
Изменяем размер Массива в С2 задав в его свойствах 4 Ячейки по Ширине и 4 Ячейки по Высоте Заодно переименовываем Массив в "Arr_Loot" чтобы было чуть короче и понятно что этот массив будет служить для предметов в Луте
Задание размера Массива в C2 (C3)
Если визуализировать такой массив, то выглядеть он будет так:
Вид Двумерного Массива
Теперь, образовавшиеся дополнительные Ячейки по высоте мы можем использовать, чтобы записывать в них недостающие данные о предметах, для точной идентификации. Вид записи данных в Такой массив слегка изменится, потому что массив стал двумерным. Теперь нужно задействовать и ось Y тоже.
Ранее мы использовали запись вида Set at X чтобы задать определённое значение
Запись значения в Массив Construct 2 (3)
Теперь же, мы должны задействовать и ось Y. И запись станет такого вида:
Set at XY
Вид Двумерного Массива
Тут нужно сделать оговорку, которая иногда может пригодиться!
Если в двумерном массиве нам требуется записать данные в какую либо из Ячеек выделенных красным, то нам достаточно записывать по старой схеме через Set at X, потому что будет задействована лишь ось Х при этом
Вид Двумерного Массива
Но как только, нам нужно использовать Высоту массива - записываем данные указывая Индексы Ячейки для обеих осей

Обращение к Ячейкам с данными, чтобы их прочесть, тоже слегка изменится. Ранее мы использовали запись Array.At(3).
Теперь нужно в скобках, через запятую указывать Индексы осей. Сперва по Х, затем по Y.
Array.At(3,0)
Вид Двумерного Массива
Закрепим позиционирование данных по Координатам Индексов массива. В примере, нужно указать координаты массива, чтобы получить данные "tv" и "79"
Получение значений из Массива в Construct 2 (3)
Для этого, нужно будет написать:
Array.At(2,1) и Array.At(3,2) соответственно. Я надеюсь, это стало понятным.
---
Возвращаемся к нашему Инвентарю.
Используя свободные ячейки от добавления оси Y по Высоте, мы можем записывать данные о переменных предметов, а именно:
Type, Frame и Amount
Тогда с учётом внедрения новой оси по Высоте запись в Массив при создании предмета станет такой
Запись в Массив при создании Предметов
Нижнее действие введено дополнительно. Об этом речь будет в комментариях исходника. Тут лишь стоит сказать то, что мы будет записывать и Уникальный Идентификатор UID создаваемого объекта. Он нам понадобится в дальнейшем, когда нужно будет поменять местами два вида предметов, например при надевании Перчаток, когда на Кукле Экипировке уже были надеты другие Перчатки. И ещё для некоторых операций.

Итак, мы записали в массив данные о двух созданных предметах
Создание Предметов Инвентаря в Construct 2 (3)
Данные в массиве будут выглядеть так:
Запись данных о Предметах в Массив
А если воссоздать эту ситуацию в исходнике, то в тексте, который я добавил, можно тоже отслеживать данные из массива.
При наведении мышкой на 1 спрайт Инвентаря из 3-х - в Тексте показывается значения в 4-х первых ячейках по оси X и Y для конкретного Массива Инвентаря.
Ещё подписал, какая строка за что отвечает. Видно где ТИП предмета, где его КАДР, где КОЛИЧЕСТВО предмета, а где уникальный идентификатор UID.
Общее количество Предметов на уровне. Общее количество спрайт-фонтов Цифр. И UID активного предмета - того, который Выбран и с кем возможно производить операции.
Чтение данных о Предметах в Массиве
Верхнаая сторока, просто показывает Индексы Ячеек по Х
Далее 4-ре строки, в левой части которых подписана от руки переменная предметов, которая по сути записана при создании предметов.
UID - уникальный идентификатор, присваивается КАЖДОМУ новому созданному объекту в игре вообще и не важно спрайт ли это, тайл бэкграунд, или тайлмап. И всегда идёт по нарастающей.
Здесь 52 UID присвоился спрайту морковки, 53 присвоился Спрайт Фонту количества морковок, а 54 UID уже принадлежит предмету Одежды

Стоит понимать, что в тексте я отобразил лишь первые 4-ре Ячейки инвентаря, иначе не удобно отслеживать, будет много текста и возникнет путаница. Поэтому учитывай это.
Так же, нужно понимать, что Массивы Куклы Экипировки и большого Инвентаря лишь расположены в несколько рядов! На самом деле, их правильно стоило бы расположить в одну линию по горизонтали, например вот так, чтобы проще представлять их Массивы
Визуальное представление Массива
Таким образом, в исходнике, для поиска какого либо предмета по массиву будем использовать лишь ось Х ! Если там пусто, а вернее лежит значение = 0, то значит в этой ячейке НЕТ Предмета !
Поэтому НЕ нужно проверять и ось Y по высоте, так как там тоже должны и будут Ноли.
Вспоминаем на этом примере:
Вид Двумерного Массива
Но это для поиска предмета! Если же мы будем переносить предмет из одного инвентаря в другой, то мы устанавливаем значения и для Х Координаты массива и под ней по Y в 3+ Ячейки.
---
В целом, думаю должно быть понятно начальное представление о массивах.
Можно приступать к ознакомлению с исходником, периодически поглядывая сюда, если что-то непонятно. Там будет готовый результат с прокомментированным кодом. Предметы будут создаваться, их данными заполняться 3 массива (массив Инвентаря, Экипировки и Лута).
---
Хотелось бы немного дополнить по массивам. Если представить массив, как 3Д модель, то наш двумерный массив выглядел бы так:
Визуальное представление двумерного Массива
Ячейки, это по сути ёмкость с информацией, маленький отдельно взятый кубик, внутри которого могут быть какие-то данные.

Если к массиву добавить ось Z по Глубине, то массив размером 4х4х4 станет выглядеть так:
Визуальное представление трёхмерного Массива
Ось Z добавляет ещё одно пространство, в ячейки которого можно так же заносить данные и извлекать их при необходимости.
В инвентаре исходника эта ось вообще НЕ задействована. Для новичка эта ось усложняет представление, но технические возможности использования трёхмерного массива весьма и весьма могущественны ))

Обсудить урок и Исходник можно на форуме:
http://c2community.ru/forum/viewtopic.php?f=4&t=16957

Возможна модернизация исходника под ваши нужды за вознаграждение:
Добавление новых Предметов и их различных свойств и качеств. Сохранение и загрузка всего при переходах между уровнями. Внедрение перетаскивания Предметов мышкой или тачем. Добавление новых механик, разделение стопок предметов надвое, складывание предметов при опускании одного на другой. Смена местами. Внедрение Крафта, Рецептов, влияние Экипировки на характеристики и многое другое...
По всем вопросам писать в тему на форуме, или мне на почту studio.indiewolf@gmail.com

Прикреплённый Архив
В архиве исходник Инвентаря и 2 сторонних плагина (Плагин и Поведение "Nickname"). Потребуется установить плагин Никнейм и поведение Никнейм, которые будут использованы для создания объектов Семьи по Имени. Затем нужно перезапустить Construct 2. Без этих плагинов исходник НЕ откроется.
Папку rex_bnickname нужно закинь по пути установки С2 в папку Поведений
C:\Program Files\Construct 2\exporters\html5\behaviors
А папку с плагином rex_nickname, закинь по пути установки С2 в папку Плагинов
C:\Program Files\Construct 2\exporters\html5\plugins
Разумеется, используй свои правильные пути