С книгой-игрой ты всегда главгерой!

 
Во всех бочках затычка

Разработчикам игр на Atril
Часть 5: Пример – инвентарь

Актуально для: Atril 2.1

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

Массивы в Javascript
Поддерживаются два вида структуры "массив":
  • Ассоциативный массив (хеш), где данные хранятся по произвольному ключу.
  • Числовой массив Array, где данные хранятся по номерам.

Javascript – очень гибкий язык, поэтому технически в Array можно хранить произвольные ключи, как в Object. Но лучше использовать типы по назначению.

Для хранения данных по номеру предназначен тип Array.
     var arr = new Array()
2    arr.test = 5
3   arr[1] = "blabla"
4   ...

В типе Array есть специальные методы, ориентированные именно на работу с числовыми ключами.

Создание и изменение
Есть два эквивалентных способа создания массива:
var a = new Array()
var a = [ ]

Или, сразу со значениями
var a = new Array("a", 1, true)
var a = ["a", 1, true]

Эти способы работают одинаково, кроме объявления вида new Array(10), когда у конструктора есть единственный аргумент-число.

Такое объявление создаст пустой массив (все элементы undefined) длиной 10. По возможности, не используйте new Array.

Отсчет элементов начинается с нуля:
alert(a[ 0 ])   // => "a"

Массив хранит данные по численным ключам, но внутри он использует точно такой же хэш (ту же структуру данных), как и обычный объект, поэтому можно сделать так:
var a = [ ]
a[ 1 ] = 1
a[ 999999 ] = 2

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

Авто-длина length
У каждого массива есть свойство length, которое автоматом меняется при каждом обновлении массива. Длина массива – это не количество элементов, а максимальный целый ключ + 1:
alert(a.length) // всего 2 элемента, но выведет 1000000

Добавлять новый элемент можно эквивалентными вызовами
a[a.length] = "new element"
a.push("new element")

Перебор элементов
Перебор элементов обычно (когда индексы непрерывные) осуществляется простым циклом:
1   var arr = [ "array", "elements", "here" ]
2&nbsp; &nbsp;for&#40;var i=0; i<arr.length; i++&#41; &#123;
3&nbsp; &nbsp;&nbsp; ... сделать что-то с arr&#91;i&#93; ...
4&nbsp; &nbsp;&#125;

Если индексы – с разрывами, то перебор осуществляется так же, как в объектах:
1&nbsp; &nbsp;var arr = &#91;&#93;
2&nbsp; &nbsp;arr&#91; 1 &#93; = 123
3&nbsp; &nbsp;arr&#91; 9999 &#93; = 456
4&nbsp; &nbsp;
5&nbsp; &nbsp;for&#40;var i in arr&#41; &#123;
6&nbsp; &nbsp;&nbsp; &nbsp; if &#40;!arr.hasOwnProperty&#40;i&#41;&#41; continue;
7&nbsp; &nbsp;&nbsp; &nbsp;... сделать что-то с arr&#91; i &#93; ...
8&nbsp; &nbsp;&#125;

Очередь + стек
В массиве есть всё необходимое, чтобы работать с ним как с очередью или со стеком, или и с тем и другим одновременно.

Методы push и pop добавляют или вынимают значение с конца массива
1&nbsp; &nbsp;var arr = &#91;3,5,7&#93;
2&nbsp; &nbsp;arr.push&#40; 9 &#41;
3&nbsp; &nbsp;var last = arr.pop&#40;&#41;&nbsp; &nbsp; //= 9
4&nbsp; &nbsp;var last = arr.pop&#40;&#41;&nbsp; &nbsp;// = 7
5&nbsp; &nbsp;alert&#40;arr.length&#41;&nbsp; &nbsp;// = 2

Методы shift/unshift делают то же самое, с начала массива.
1&nbsp; &nbsp;var arr = &#91;4,6,8&#93;
2&nbsp; &nbsp;arr.unshift&#40;2&#41; // arr = &#91;2,4,6,8&#93;
3&nbsp; &nbsp;arr.unshift&#40;0&#41; // arr = &#91;0,2,4,6,8&#93;
4&nbsp; &nbsp;var last = arr.shift&#40;&#41; // last = 0, arr = &#91;2,4,6,8&#93;
5&nbsp; &nbsp;arr.shift&#40;&#41;&nbsp; // arr = &#91;4,6,8&#93;

shift/unshift обычно приводят к перенумерации всего массива. shift сдвигает все элементы на единицу влево, а unshift – вправо. Поэтому на больших массивах эти методы работают медленнее, чем push/pop.

Другие методы

slice
slice(begin[, end])
Возвращает подмассив с индексами begin…end.

splice
splice(index, deleteCount[, element1,…, elementN])
Удалить deleteCount элементов, начиная с index, и вставить на их место element1…elementN

Есть и еще много методов:
join
reverse
...



Последний раз редактировалось: Jumangee (Пт Июн 06, 2014 15:31), всего редактировалось 1 раз
Во всех бочках затычка

Вариант реализации инвентаря в Atril могут отличаться в зависимости от книги-игры, потому что в книгах-играх нюансы механики могут повлиять на неё. Например, в инвентаре могут находиться вещи "в количестве штук", а могут просто находиться или нет. Во втором случае, довольно элементарно добавлять и проверять наличие вещей просто используя функционал хэш-массивов javascript.
В этом случае мы активно используем тот факт, что в случае, когда значение элемента массива по указанному ключу не задано, оно равно undefined.

Например (параграф simpleInventory в коде примера):
onload:
vars.inventory = {};

addItem: // добавлям предмет
var name = args.name; // получаем название проверяемого предмета
... // проверка адекватности значения (см. в редакторе)
vars.inventory[ name ] = true; // пишем в хэш-массив наличие

checkItem: // проверяет наличие предмета в инвентаре
var name = args.name; // получаем название проверяемого предмета
... // проверка адекватности значения (см. в редакторе)
return (vars.inventory[ name ] == true); // если в хэш-массиве указано наличие, возвращаем true

removeItem: //удаляем предмет
var name = args.name; // получаем название проверяемого предмета
... // проверка адекватности значения (см. в редакторе)
delete vars.inventory[ name ];

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

Например, при добавлении предмета можно присваивать не true, а количество предметов:
vars.inventory[ name ] = 1;

Принципиальное изменение тут только в проверках на типы, ведь для добавления/удаления предметов надо помнить про изначальное значение undefined:

addItem:
if (vars.inventory[ name ] == undefined){
vars.inventory[ name ] = 1;
return;
}
vars.inventory[ name ]++; // добавляем

removeItem:
if (vars.inventory[ name ] > 0){ // значение undefined не сработает
vars.inventory[ name ]—; // удаляем
}

checkItem:
return (vars.inventory[ name ] > 0);

В примерах выше name может принимать любое значение. Можно передавать название предмета на русском языке, т.е. при получении списка инвентаря использовать название для отображения:

getItemList&#58;
var list = &#91;&#93;; // результирующий список предметов инвентаря
for &#40;var name in vars.inventory&#41;&#123; // пробегаем по массиву
&nbsp; &nbsp; if &#40;!vars.inventory.hasOwnProperty&#40;name&#41;&#41;&#123;&nbsp; &nbsp;continue;&nbsp; &nbsp;&#125;&nbsp; // отбрасываем "технологические" свойства
&nbsp; &nbsp;
&nbsp; &nbsp; if &#40;vars.inventory&#91;name&#93; > 0&#41; &#123; // если в инвентаре предметы есть, добавляем в список
&nbsp; &nbsp; &nbsp; &nbsp; list.push&#40;name&#41;;
&nbsp; &nbsp; &#125;&nbsp; &nbsp;
&nbsp; &nbsp; return name;
&#125;

Но всегда помнить и использовать в параграфах книги-игры полное правильное название предмета (включая регистр!) проблематично и чревато ошибками, приводящими к наличию в инвентаре одного предмета с разными названиями, или попытку проверить наличие предмета по другому имени.
Чтобы избежать этого, можно использовать предопределённый массив названий:
onload&#58;
vars.inventory = &#123;&#125;;
vars.itemNames = &#123;
&nbsp; &nbsp; 'MAIN KEY'&#58; 'Ключ от главной двери',
&nbsp; &nbsp; 'BLUE KEY'&#58; 'Синий ключ от запасного выхода',
&nbsp; &nbsp; 'KEY'&#58; 'Какой-то ключ'
&#125;

Теперь, при всех операциях с инвентарем используем ключи, а при получении списка, делаем преобразование:
getItemList&#58;
...
&nbsp; &nbsp; if &#40;vars.inventory&#91; name &#93; > 0&#41;&#123;
&nbsp; &nbsp; &nbsp; &nbsp; list.push&#40;vars.itemNames&#91; vars.inventory&#91; name &#93; &#93;&#41;;
&nbsp; &nbsp; &#125;
...

Код примеров



Последний раз редактировалось: Jumangee (Пт Июн 06, 2014 21:24), всего редактировалось 1 раз