🇷🇺
MyWebAR Knowledge Base
Язык Ру
Язык Ру
  • База знаний для разработчиков MyWebAR
  • С ЧЕГО НАЧАТЬ
    • Регистрация
    • Обзор страницы Dashboard
    • Обзор редактора
    • Создание проекта в MyWebAR
  • ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ MYWEBAR
    • Создание сцены с трекингом на изогнутых изображениях
    • Создание AR-визитки
    • Создание портала в MyWebAR
    • Оживающая фотография
    • Интерактив с 3D-моделью
  • ПЛАНЫ И ПОДПИСКИ
    • Бесплатная пробная подписка
    • Обновление вашего плана
    • Коммерческие планы
    • Планы для образования
  • СОЗДАНИЕ ПРОЕКТОВ WEBAR
    • Типы проектов
    • Проекты с мультимаркерным трекингом (книги)
    • Добавление объектов
    • Как добавить 3D-модель из библиотеки моделей
    • Как добавить 3D-модель из Sketchfab
    • Свойства объекта
    • Кнопки и действия
    • Поведения объектов
    • Воспроизведение видео
    • 3D Анимации
    • Аналитика проектов
    • Как сделать хорошее отслеживаемое изображение для дополненной реальности
    • Оптимизация и подготовка 3D-моделей к загрузке
    • Брендирование проекта и его настройки
    • Работа с доступными плагинами
    • Существующие плагины и как с ними работать
    • Видео инструкции
  • КАСТОМИЗАЦИЯ WEBAR
    • Кастомный домен
    • Использование внешнего хранилища
    • Встраивание WebAR
  • Pro Editor
    • Как устроен Pro Editor
      • Описание интерфейса
      • Основные возможности
    • Требования по размещению кода
      • Пример интеграции готового скрипта
      • Создание частиц в Pro Editor
      • Работа с видео
      • Работа с анимациями 3D-объектов
    • Текущие ограничения
      • Работа с камерой
      • Создание UI
      • Загрузка объектов с помощью класса loader
      • Импорт частей кода
    • Кейсы
      • Добавление изображений
      • Эффект бликов на объективе (Lens Flare)
      • Пошаговое создание мини-игры
      • Переключение содержимого по нажатию
      • Пошаговое создание квеста
Powered by GitBook
On this page
  • Создание проектов
  • Добавление 3D-моделей на сцену
  • Добавление основной 3D-модели
  • Добавление аудиофайла
  • Добавление плагина окклюзии
  • Создание скриптовой части
  • Добавление объектов (включая пустые группы)
  • Создание таймера
  • Скрытие вспомогательной плоскости в группе border
  • Создание эффекта постоянного поворота
  • Работа с основной механикой скрипта
  • Экспорт и настройка сцены
  • Создание мультисцены
  1. Pro Editor
  2. Кейсы

Пошаговое создание квеста

PreviousПереключение содержимого по нажатию

Last updated 2 years ago

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

Создание проектов

Первым шагом в создании квеста является создание проектов. В данном случае такие проекты должны быть равны количеству шагов в квесте. То есть, если вы хотите, чтобы ваш квест состоял из сканирования пяти QR-кодов, сцены также должны быть равны числу пять.

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

Первое, что необходимо сделать, - это создать проект с отслеживанием QR-кодов. Для этого на вкладке Мои проекты необходимо нажать на кнопку Создать новый проект.

Откроется модальное окно с видами трекинга.

В данном случае это будет AR on a QR code. Нажмите кнопку Create, чтобы создать его.

Откроется пустая сцена с QR-кодом в центре.

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

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

Все пять проектов готовы и переименованы.

Добавление 3D-моделей на сцену

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

  • Основная 3D-модель,

  • Аудиодорожка с озвучиванием 3D-модели,

  • JSON, который определит, видел ли пользователь сцену,

  • Плагин окклюзии, позволяющий создавать эффект "портала",

Добавление основной 3D-модели

Добавление базовой 3D-модели ничем не отличается от добавления любого 3D-объекта.

Сначала нажмите + в поле Model 3D.

Откроется окно добавления 3D-объекта.

Чтобы загрузить объект, нажмите на кнопку Upload в нижней части окна.

Откроется окно загрузки 3D-объекта. Здесь вы можете нажать кнопку Upload или просто перетащить файл в это окно.

Через некоторое время объект появится на сцене.

Теперь его необходимо выровнять так, чтобы QR-код модели совпадал с QR-кодом сцены.

Выравнивание QR-кода является лишь особенностью этой сцены. В вашем случае вы можете расположить объект так, как вам нравится.

Добавление аудиофайла

Для добавления аудиообъекта нужно нажать на + в блоке Audio.

Откроется окно добавления аудио.

Чтобы загрузить объект, нажмите на кнопку Upload в нижней части окна.

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

После загрузки появится окно настройки аудиофайла. Здесь можно настроить такие параметры, как Autoplay и Loop.

В этом случае необходимо включить обе настройки и нажать кнопку Next. Аудиофайл появится на сцене.

Добавление плагина окклюзии

Последний (не считая файла JSON) объект, который мы добавим, — это плагин окклюзии. Чтобы добавить его, перейдите на вкладку Extensions.

Далее найдите Occlusion Cube в списке расширений и нажмите на него.

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

Затем закройте окно и вернитесь в редактор, нажав на вкладку Content.

Чтобы добавить куб окклюзии, нажмите + в поле Occlusion Cube.

Откроется модальное окно. Нажмите кнопку Save, чтобы добавить объект.

На сцене появится 3D-модель куба.

Теперь нужно покрыть поверхность сцены четырьмя кубами, а само "окно" в 3D-модели оставить открытым. Это поможет скрыть все, что находится под кубиками окклюзии, создавая эффект "окна" или "портала".

Создание скриптовой части

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

Сначала перейдите в PRO EDITOR, нажав на соответствующую кнопку в левой нижней части редактора.

Откроется продвинутый редактор.

Добавление объектов (включая пустые группы)

Первое, что нужно сделать, — добавить объекты и группы, с которыми мы будем работать.

Чтобы объекты в сцене не были темными, необходимо добавить источник света. Для этого на верхней панели выберите Add → Ambient Light.

На сцене появится освещение.

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

Чтобы создать группу на верхней панели, нажмите Add → Plane.

Две добавленные группы появятся в списке объектов на сцене справа.

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

Чтобы изменить название, выберите группу и слева, в блоке Name, впишите название.

Таким образом, названия будут следующими: border и game.

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

Добавление объектов во вспомогательную группу "border"

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

Чтобы добавить плоскость, на верхней панели нажмите Add → Plane.

Плоскость появится на сцене.

После этого в списке объектов переместите только что добавленную плоскость Plane внутрь группы border.

Плоскость должна быть повернута на -90 градусов. Параметры плоскости:

Position: 0.0, 0.0, 0.0. Rotation: -90.0, 0.0, 0.0. Scale: 1.0, 1.0, 1.0.

Далее необходимо увеличить размер плоскости, на вкладке GEOMETRY нужно изменить два параметра: Width: 10.00, Height: 10.00.

Добавление объектов в основную группу "game"

Следующим шагом будет добавление объектов для основной группы игры. Внутри объекта game будет пять групп. Эти группы будут называться порядковыми номерами от 1 до 5.

Количество групп в объекте game должно быть равно количеству сцен.

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

Для создания группы, как обычно, нажмите Add → Group.

Группа появится на сцене. Теперь ее нужно переименовать на цифру 1.

Каждая новая группа внутри группы game будет названа порядковым номером от одного до пяти.

После этого созданная группа помещается внутрь объекта game.

Аналогично, группы "2", "3", "4", "5" создаются и помещаются внутрь объекта game.

Теперь нужно поместить сами объекты внутрь созданных групп с порядковыми номерами. В моем случае это будет по два glb-файла (checked и unchecked).

Чтобы добавить 3D-объект, нажмите File → Import.

Таким образом, на сцену добавляются два объекта состояния: когда пользователь не видел сцену (back) и когда пользователь видел сцену (checked).

После этого эти объекты нужно переместить в группу game → 1.

Аналогичным образом те же самые объекты добавляются в группы 2, 3, 4 и 5. Кроме того, можно сразу же расположить эти объекты вдоль плоскости border.

Все готово.

Создание таймера

Все объекты добавлены, и теперь можно приступать непосредственно к работе со сценарием. Для начала нам нужно создать таймер. Этот таймер нужен для запуска анимации (например, анимации самой модели или некоторых действий, выполняемых в функции update).

Почему используется самописный таймер, а не встроенный в three.js? Это нужно для того, чтобы, если пользователь дополненной реальности потеряет маркер и направит камеру снова на маркер, сцена продолжит воспроизведение с точки прерывания, а не с самого начала.

Чтобы создать таймер, нужно щелкнуть на объекте Scene и создать новый сценарий, нажав на NEW.

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

Откроется редактор кода.

Здесь необходимо вставить следующий код:

class Timer {
    constructor(callback, delay) {
		this.delay = delay;
		this.callback = callback;
		this.currentTime = 0;
	}
	
	update(delta) {
		this.currentTime += delta;
		if (this.isInvoked()) {
			this.callback();
		}
	}
	
	isInvoked() {
		return this.currentTime >= this.delay;
	}
 }

class Timers {
    constructor() {
		 this.nextIndex = 0;
		this.timers = new Map();
    }
	add(callback, delay = 0) {
		this.nextIndex;
		this.timers.set(this.nextIndex, new Timer(callback, delay));
		this.nextIndex++;
	}
	update(delta) {
		this.timers.forEach((value, key, map) => {
  			value.update(delta);
			if (value.isInvoked()) {
				this.delete(key)
			}
		})
	}
	delete(key) {
		this.timers.delete(key);
	}
}

window.__timers = {Timers}

В редакторе:

Нет смысла объяснять сам сценарий, за исключением последней строки window.__timers = {Timers}. Эта строка помещает созданный объект timers в объект window. Это поможет нам использовать его из других скриптов.

Скрытие вспомогательной плоскости в группе border

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

Перейдя в редактор кода, нужно удалить все лишнее.

Далее необходимо создать функцию start. Эта функция будет автоматически запускаться MyWebAR при запуске сцены.

function start(){
	
}

Внутри функции, используя this изменяет параметр visible для группы, на которой находится этот скрипт (то есть группы со вспомогательной плоскостью внутри).

function start(){
	this.visible = false;
}

Создание эффекта постоянного поворота

Чтобы созданные объекты checked и back всегда смотрели в сторону пользователя, необходимо сделать скрипт для объекта game. Его можно назвать, например, lookAtCamera.

Затем нужно зайти в редактор кода и удалить все.

Сначала необходимо создать две переменные:

  • _obj — сам объект, на котором расположен данный скрипт,

  • active — статус активности данного объекта

const _obj = this.getObjectByName(this.name);
let active = true;

После этого необходимо создать четыре стандартные функции, которые будут запускаться самим MyWebAR, это функции: start (запускается при запуске сцены, т.е. после инициализации), stop (запускается при остановке проекта), update (запускается каждый кадр).

const _obj = this.getObjectByName(this.name);
let active = true;

function start() {

}

function stop() {

}

function update(event) {

}

Теперь внутри функции update создается условие: если active равно false, то функция не продолжается, то есть пропускает итерацию.

const _obj = this.getObjectByName(this.name);
let active = true;

function start() {

}

function stop() {

}

function update(event) {
        if (active == false) {
		return;
	}
}

Далее, после условия, создается новая переменная — THREE.Vector3 (3-мерный вектор), куда передается положение камеры (строка 17). В строке 18 мы задаем ориентацию нашей игровой группы. В строках 19-20 мы делаем так, чтобы эта группа была ориентирована на камеру.

const _obj = this.getObjectByName(this.name);
let active = true;

function start() {

}

function stop() {

}

function update(event) {
        if (active == false) {
		return;
	}
	
	const pos = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z);
	_obj.up.set(0,1,0);
	_obj.lookAt(pos);
	_obj.rotation.z = 0;
}

Кроме того, необходимо добавить изменение статуса объекта (переменная active). Для этого внутри функции start изменим статус на true, а внутри функции stop на false.

const _obj = this.getObjectByName(this.name);
let active = true;

function start() {
	active = true;
}

function stop() {
	active = false;
}

function update(event) {
	if (active == false) {
		return;
	}
	
	const pos = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z);
	_obj.up.set(0,1,0);
	_obj.lookAt(pos);
	_obj.rotation.z = 0;
}

Работа с основной механикой скрипта

Теперь пришло время создать механику самого скрипта. Во-первых, нам нужно создать userData для наших объектов. userData - это данные, благодаря которым вы можете работать с объектом более удобно.

Добавление userData для объекта game

Чтобы добавить userData, выберите объект game. На левой стороны вы увидите поле с фигурными скобками внутри.

Здесь необходимо добавить четыре значения:

{ 
    "localStorageKey": "MWA_QR-Quest", 
    "sceneKey": "1", 
    "delay": "1" 
}

Что все это означает? Давайте разберем каждое значение по порядку (для чего оно нужно).

  • localStorageKey — это значение поможет понять, есть ли у пользователя данные в localStorage (начал ли он проходить квест впервые), значение localStorageKey может быть любым, но не пустым,

  • sceneKey — этот параметр будет меняться в зависимости от того, на какой сцене вы собираетесь разместить этот скрипт (в данном случае это будет скрипт для сцены 1, в случае сцены 2 — значение будет равно 2, для сцены 3 — значение равно 3 и так далее),

  • delay — чтобы объекты состояния (checked и back) изменялись с небольшой анимацией (это скорость изменения параметра непрозрачности).

Добавление userData для объектов внутри групы game (объектов с именем 1, 2, 3, 4 и 5)

Данные userData будут добавлены для каждой группы с порядковым номером в названии (1, 2, 3, 4, 5). Например, можно выбрать объект 1. С правой стороны вы увидите поле с фигурными скобками.

Здесь необходимо добавить три значения:

{
  "showEventName": "item.show",
  "hideEventName": "item.hide",
  "key": "1"
}

Давайте разберем каждое значение по порядку:

  • showEventName — управление событием, связанными с появлением объекта,

  • hideEventName — управлять событием скрытия объекта,

  • key — порядковый номер объекта (для объекта 2 это будет 2, для объекта 3 — 3 и так далее).

Например, userData для объекта 5:

Как видите, значение key равно 5.

Добавление userData для объекта checked (плашка, показывающая что сцена n была просканирована)

Чтобы добавить userData, необходимо выбрать объект checked в каждой из пяти групп (группы с порядковым номером вместо имени), например, для объекта 1. С правой стороны вы увидите поле с фигурными скобками.

Здесь необходимо добавить четыре значения:

{
  "showEventName": "checked.show",
  "hideEventName": "checked.hide",
  "key": "1"
}

Как вы можете видеть, значения здесь такие же, как и для Добавление userData для объектов внутри групы game (объектов с именем 1, 2, 3, 4 и 5). Обратите внимание на значение key, оно должно соответствовать той группе, в которой находится.

Работа со скриптом для объекта game

Чтобы создать скрипт, нужно выбрать игровой объект и создать другой скрипт. Я назову его localStorage, так как этот скрипт необходим для работы с данными.

Чтобы открыть редактор кода, нажмите EDIT напротив имени localStorage и удалите все содержимое в редакторе кода.

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

const {Timers} = window.__timers;
timers = new Timers();

Далее создаются еще три переменные:

  • _userData — данные, которые были записаны в userDataДобавление userData для объекта game,

  • _name — имя объекта,

  • _owner — сам объект на сцене.

const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

Далее создаются восемь функций:

  • init() — функция init, которая запускается перед запуском сцены,

  • getDataWithAddedSceneKey() — работа с данными из localStorage, в зависимости от параметра sceneKey, который находится в userData (смотрите Добавление userData для объекта game),

  • getData() — получение данные из localStorage,

  • saveData(data) — сохранение данных в localStorage,

  • showProgress(data) — отображать результат в зависимости от того, что находится в localStorage (работа с объектами checked и back),

  • sendShowItemsEvent(data) — анимация для отображения результата,

  • update(event) — функция update, которая запускается каждый кадр.

const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {

}

function getDataWithAddedSceneKey() {

} 

function getData() {

}

function saveData(data) {

}

function showProgress(data) {
}

function sendShowItemsEvent(data) {

}

function update(event) {

}

Внутри init() нужно создать переменную data, в которую нужно записать вывод getDataWithAddedSceneKey() (то, что вернет эта функция).

Также необходимо запустить функции saveData() и sendShowItemsEvent() и передать туда data.

const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {
	const data = getDataWithAddedSceneKey();
	saveData(data);
	sendShowItemsEvent(data);
}

function getDataWithAddedSceneKey() {

} 

function getData() {

}

function saveData(data) {

}

function showProgress(data) {
}

function sendShowItemsEvent(data) {

}

function update(event) {

}

Внутри функции getDataWithAddedSceneKey() мы снова создаем data, которая является результатом функции getData().

Кроме того, нам нужно создать условие — если в объекте data нет значения в пункте sceneKey, то нам нужно изменить его на true. Если значение есть, то мы просто возвращаем data.

const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {
	const data = getDataWithAddedSceneKey();
	saveData(data);
	sendShowItemsEvent(data);
}

function getDataWithAddedSceneKey() {
	const data = getData();
	if (data[_userData.sceneKey] === undefined) {
		data[_userData.sceneKey] = true;
	}
	return data;
} 

function getData() {

}

function saveData(data) {

}

function showProgress(data) {
}

function sendShowItemsEvent(data) {

}

function update(event) {

}

Функция getData() содержит две переменные: str и data. Внутри str находится localStorageKey, который извлекается из localStorage. Внутрь data передается str в формате JSON (в случае, если localStorage не пуст).

const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {
	const data = getDataWithAddedSceneKey();
	saveData(data);
	sendShowItemsEvent(data);
}

function getDataWithAddedSceneKey() {
	const data = getData();
	if (data[_userData.sceneKey] === undefined) {
		data[_userData.sceneKey] = true;
	}
	return data;
} 

function getData() {
	const str = localStorage.getItem(_userData.localStorageKey);
	const data = str !== null && str !== 'null' ? JSON.parse(str) : {};
	return data;
}

function saveData(data) {

}

function showProgress(data) {
}

function sendShowItemsEvent(data) {

}

function update(event) {

}

Функция SaveData() созданиет переменную str, в которую помещается data (преобразуется в строку, которая будет передана в localStorage). После этого str помещается в localStorage.

const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {
	const data = getDataWithAddedSceneKey();
	saveData(data);
	sendShowItemsEvent(data);
}

function getDataWithAddedSceneKey() {
	const data = getData();
	if (data[_userData.sceneKey] === undefined) {
		data[_userData.sceneKey] = true;
	}
	return data;
} 

function getData() {
	const str = localStorage.getItem(_userData.localStorageKey);
	const data = str !== null && str !== 'null' ? JSON.parse(str) : {};
	return data;
}

function saveData(data) {
	const str = JSON.stringify(data);
	localStorage.setItem(_userData.localStorageKey, str);
}

function showProgress(data) {
}

function sendShowItemsEvent(data) {

}

function update(event) {

}

В функции showProgress() будет два цикла. Эти циклы необходимы для отображения объектов в зависимости от состояния сцены.

const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {
	const data = getDataWithAddedSceneKey();
	saveData(data);
	sendShowItemsEvent(data);
}

function getDataWithAddedSceneKey() {
	const data = getData();
	if (data[_userData.sceneKey] === undefined) {
		data[_userData.sceneKey] = true;
	}
	return data;
} 

function getData() {
	const str = localStorage.getItem(_userData.localStorageKey);
	const data = str !== null && str !== 'null' ? JSON.parse(str) : {};
	return data;
}

function saveData(data) {
	const str = JSON.stringify(data);
	localStorage.setItem(_userData.localStorageKey, str);
}

function showProgress(data) {
	for (const elem of _owner.children) {
		document.dispatchEvent(new CustomEvent('item.show', { detail: { key: elem.name } }));
	}
	for (key in data) {
		document.dispatchEvent(new CustomEvent('checked.show', { detail: { key } }));
	}
}

function sendShowItemsEvent(data) {

}

function update(event) {

}

Функция sendShowItemsEvent() содержит переменную data, в которую помещается значение delay из userData. После этого создается таймер, где значение времени равно delay.

const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {
	const data = getDataWithAddedSceneKey();
	saveData(data);
	sendShowItemsEvent(data);
}

function getDataWithAddedSceneKey() {
	const data = getData();
	if (data[_userData.sceneKey] === undefined) {
		data[_userData.sceneKey] = true;
	}
	return data;
} 

function getData() {
	const str = localStorage.getItem(_userData.localStorageKey);
	const data = str !== null && str !== 'null' ? JSON.parse(str) : {};
	return data;
}

function saveData(data) {
	const str = JSON.stringify(data);
	localStorage.setItem(_userData.localStorageKey, str);
}

function showProgress(data) {
	for (const elem of _owner.children) {
		document.dispatchEvent(new CustomEvent('item.show', { detail: { key: elem.name } }));
	}
	for (key in data) {
		document.dispatchEvent(new CustomEvent('checked.show', { detail: { key } }));
	}
}

function sendShowItemsEvent(data) {
	const delay = parseFloat(_userData.delay);
	timers.add(() => {
		showProgress(data);
    	}, delay);
}

function update(event) {

}

Перед работой с функцией update(), из-за технических особенностей редактора, необходимо добавить новую переменную в строке 1:

const isMae = typeof window.editor === 'object' && typeof window.editor.fromJSON === 'function';
const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {
	const data = getDataWithAddedSceneKey();
	saveData(data);
	sendShowItemsEvent(data);
}

function getDataWithAddedSceneKey() {
	const data = getData();
	if (data[_userData.sceneKey] === undefined) {
		data[_userData.sceneKey] = true;
	}
	return data;
} 

function getData() {
	const str = localStorage.getItem(_userData.localStorageKey);
	const data = str !== null && str !== 'null' ? JSON.parse(str) : {};
	return data;
}

function saveData(data) {
	const str = JSON.stringify(data);
	localStorage.setItem(_userData.localStorageKey, str);
}

function showProgress(data) {
	for (const elem of _owner.children) {
		document.dispatchEvent(new CustomEvent('item.show', { detail: { key: elem.name } }));
	}
	for (key in data) {
		document.dispatchEvent(new CustomEvent('checked.show', { detail: { key } }));
	}
}

function sendShowItemsEvent(data) {
	const delay = parseFloat(_userData.delay);
	timers.add(() => {
		showProgress(data);
    	}, delay);
}

function update(event) {

}

Окончательная версия кода для объекта game:

const isMae = typeof window.editor === 'object' && typeof window.editor.fromJSON === 'function';
const {Timers} = window.__timers;
timers = new Timers();

const _userData = this.userData;
const _name = this.name;
const _owner = this.getObjectByName(_name);

function init() {
	const data = getDataWithAddedSceneKey();
	saveData(data);
	sendShowItemsEvent(data);
}

function getDataWithAddedSceneKey() {
	const data = getData();
	if (data[_userData.sceneKey] === undefined) {
		data[_userData.sceneKey] = true;
	}
	return data;
} 

function getData() {
	const str = localStorage.getItem(_userData.localStorageKey);
	const data = str !== null && str !== 'null' ? JSON.parse(str) : {};
	return data;
}

function saveData(data) {
	const str = JSON.stringify(data);
	localStorage.setItem(_userData.localStorageKey, str);
}

function showProgress(data) {
	for (const elem of _owner.children) {
		document.dispatchEvent(new CustomEvent('item.show', { detail: { key: elem.name } }));
	}
	for (key in data) {
		document.dispatchEvent(new CustomEvent('checked.show', { detail: { key } }));
	}
}

function sendShowItemsEvent(data) {
	const delay = parseFloat(_userData.delay);
	timers.add(() => {
		showProgress(data);
    }, delay);
}

function update(event) {
    	let delta = event.delta;
    	if (isMae) delta /= 1000;
	timers.update(delta);
}

Работа со скриптом для объекта внутри game (группы 1, 2, 3, 4 и 5)

После написания сценария для объекта игра можно перейти к написанию двух сценариев для каждой из групп с порядковым номером в названии. В данном случае будет показан сценарий для объекта 1. Оба сценария должны быть добавлены для каждой группы (то есть для групп 1, 2, 3, 4, 5).

Первое, что мы сделаем, это напишем сценарий для появления/скрытия объектов путем изменения параметра Opacity.

ВАЖНО: Чтобы избежать путаницы с кодом, описание скриптов будет сделано в более общем виде.

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

Первое, что нужно сделать, это создать двенадцать переменных:

  • _userData — данные userData для этой группы, который были созданы в подразделе Добавление userData для объектов внутри групы game (объектов с именем 1, 2, 3, 4 и 5)

  • _owner — непосредственно переменная с самим объектом,

  • _obj — объект, checked в группе,

  • _objsWithMaterials — результат выполнения функции getObjectsWithMaterial(),

  • _duration — время работы анимации,

  • _showed — состояние объекта в данный момент времени (виден ли он),

  • _ended — закончилась ли анимация,

  • _startTime — время обратного отсчета,

  • _endTime — длительность анимации,

  • _startValue — стартовое значение параметра opacity,

  • _endValue — конечное значение параметра opacity,

  • _minValue — минимальное значение параметра opacity

const _userData = this.userData;
const _owner = this.getObjectByName(this.name);
const _obj = this.getObjectByName(this.name).children[0];
const _objsWitMaterials = getObjectsWithMaterial();
const _duration = 250;
let _showed = false;
let _ended = false;
let _startTime = null;
let _endTime = null;
let _startValue;
let _endValue = 0;
const _minValue = 0.5;

Теперь нам нужно создать тринадцать функций:

  • init() — функция инициализации сцены (запускается перед началом сцены),

  • getObjectsWithMaterial() — необходима для поиска материальных объектов,

  • start() — функция, которая запускается после инициализации сцены (здесь создаются новые слушатели событий появления/скрытия объектов),

  • stop() — функция, которая запускается при завершении сцены (здесь удаляются слушатели событий появления/скрытия объекта),

  • _show(event) — функция, вызывающая появление объекта

  • _hide(event) — функция, вызывающая скрытие объекта,

  • _init() — запускается при каждом цикле появления/скрытия объекта, необходима сбросить время,

  • update(event) — функция, выполняемая каждый кадр,

  • updateScale(now) — функция для изменения непрозрачности объекта,

  • _getValue(now) — функция, которая генерирует значение value,

  • tryStopAnimation(now) — функция для остановки процесса скрытия/появления объектов

  • getOpacity() — функция, возвращающая текущее значение параметра непрозрачности материала,

  • setOpacity(value) — функция, изменяющая значение непрозрачности для каждого объекта.

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

const _userData = this.userData;
const _owner = this.getObjectByName(this.name);
const _obj = this.getObjectByName(this.name).children[0];
const _objsWitMaterials = getObjectsWithMaterial();
const _duration = 250;
let _showed = false;
let _ended = false;
let _startTime = null;
let _endTime = null;
let	_startValue;
let	_endValue = 0;
const _minValue = 0.5;


function init() {
	_showed = false;
	_ended = true;
	setOpacity(0);
	_startValue = getOpacity();
}

function getObjectsWithMaterial() {
	const objectsWithMaterial = [];
	_owner.traverse(child => {
	      	const material = child.material;
	      	if (material != undefined && typeof (material) === 'object' && "dispose" in material) {
			objectsWithMaterial.push(child);
		}
  	});
	return objectsWithMaterial;
}

function start() {
    	document.addEventListener(_userData.showEventName, _show);
    	document.addEventListener(_userData.hideEventName, _hide);
}

function stop() {
    	document.removeEventListener(_userData.showEventName, _show);
    	document.removeEventListener(_userData.hideEventName, _hide);
	init();
}

function _show(event) {
	if (_userData.key !== event.detail.key) {
		return;
	}
	_init();
	_startValue = getOpacity();
	_endValue = 1;
}

function _hide(event) {
	if (_userData.key !== event.detail.key) {
		return;
	}
	_init();
	_startValue = getOpacity();
	_endValue = _minValue;
}

function _init() {
	_startTime = Date.now();
	_endTime = _startTime + _duration;
	_ended = false;
}

function update(event) {
	if (_ended) {
		return
	}
    	const now = Date.now();
	updateScale(now);
	tryStopAnimation(now);
}

function updateScale(now) {
	const value = _getValue(now);
	setOpacity(value);
}

function _getValue(now) {
	const deltaValue = _endValue - _startValue;
	const deltaTime = (now - _startTime) / _duration;
	const newValue = _startValue + deltaValue * deltaTime;
	return newValue;
}

function tryStopAnimation(now) {
	if (now >= _endTime) {
		_startTime = null;
		_endTime = null;
		_ended = true;
		setOpacity(_endValue);
	}
}

function getOpacity() {
	return _objsWitMaterials[0].material.opacity;
}

function setOpacity(value) {
	_objsWitMaterials.forEach(e => {
		e.material.opacity = value;
	});
}

Теперь нам нужно создать второй скрипт, который будет показывать/скрывать объекты, изменяя параметр Scale. Для этого создайте новый сценарий с именем showHideByScale.

Нужно создать одиннадцать переменных:

  • _userData — данные из userData для этой группы, который были созданы в подразделе Добавление userData для объектов внутри групы game (объектов с именем 1, 2, 3, 4 и 5)

  • _obj — непосредственно переменная с самим объектом,

  • defaultScale — значение по умолчания для масштаба,

  • _duration — время работы анимации,

  • _showed — состояние объекта в данный момент времени (виден ли он),

  • _ended — закончилась ли анимация,

  • _startTime — время обратного отсчета анимации,

  • _endTime — длительность анимации,

  • _startValue — стартовое значение масштаба объекта,

  • _endValue —конечное значение масштаба объекта,

  • _minValue — минимальное значение параметра opмасштабаacity

const _userData = this.userData;
const _obj = this.getObjectByName(this.name);
const defaultScale = _obj.scale.clone();
const _duration = 250;
let _showed = false;
let _ended = false;
let _startTime = null;
let _endTime = null;
let	_startValue;
let	_endValue = 0;
let _minValue = 0.95 * defaultScale.x;

Теперь нам нужно создать десять функций:

  • init() — функция инициализации сцены (запускается перед началом сцены),

  • start() — функция, которая запускается после инициализации сцены (здесь создаются новые слушатели событий появления/скрытия объектов),

  • stop() — функция, которая запускается при завершении сцены (здесь удаляются слушатели событий появления/скрытия объекта),

  • _show(event) — функция, вызывающая появление объекта

  • _hide(event) — функция, вызывающая скрытие объекта,

  • _init() — запускается при каждом цикле появления/скрытия объекта, необходима сбросить время,

  • update(event) — функция, выполняемая каждый кадр,

  • updateScale(now) — функция для изменения непрозрачности объекта,

  • _getValue(now) — функция, которая генерирует значение value,

  • tryStopAnimation(now) — функция для остановки процесса скрытия/появления объектов

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

const _userData = this.userData;
const _obj = this.getObjectByName(this.name);
const defaultScale = _obj.scale.clone();
const _duration = 250;
let _showed = false;
let _ended = false;
let _startTime = null;
let _endTime = null;
let	_startValue;
let	_endValue = 0;
let _minValue = 0.95 * defaultScale.x;


function init() {
	_showed = false;
	_ended = true;
	_obj.scale.set(0, 0, 0);
	_startValue = 0;
}

function start() {
	document.addEventListener(_userData.showEventName, _show);
	document.addEventListener(_userData.hideEventName, _hide);
}

function stop() {
    	document.removeEventListener(_userData.showEventName, _show);
    	document.removeEventListener(_userData.hideEventName, _hide);
	init();
}

function _show(event) {
	if (_userData.key !== event.detail.key) {
		return;
	}
	_init();
	_startValue = _obj.scale.x;
	_endValue = defaultScale.x;
}

function _hide(event) {
	if (_userData.key !== event.detail.key) {
		return;
	}
	_init();
	_startValue = _obj.scale.x;
	_endValue = _minValue;
}

function _init() {
	_startTime = Date.now();
	_endTime = _startTime + _duration;
	_ended = false;
}

function update(event) {
	if (_ended) {
		return
	}
    	const now = Date.now();
	updateScale(now);
	tryStopAnimation(now);
}

function updateScale(now) {
	const value = _getValue(now);
	_obj.scale.set(value , value , value);
}

function _getValue(now) {
	const deltaValue = _endValue - _startValue;
	const deltaTime = (now - _startTime) / _duration;
	const newValue = _startValue + deltaValue * deltaTime;
	return newValue;
}

function tryStopAnimation(now) {
	if (now >= _endTime) {
		_startTime = null;
		_endTime = null;
		_ended = true;
	}
}

Эти два скрипта добавляются для всех объектов с порядковым номером в названии (для объектов 1, 2, 3, 4, 5).

Работа со скриптом для объекта checked

Остался последний сценарий — работа с самим объектом checked.

В открывшемся редакторе кода нужно удалить все лишнее.

Здесь вставлен тот же сценарий, что и в подглаве showHideByScaleРабота со скриптом для объекта внутри game (группы 1, 2, 3, 4 и 5).

Сам скрипт:

const _userData = this.userData;
const _obj = this.getObjectByName(this.name);
const defaultScale = _obj.scale.clone();
const _duration = 250;
let _showed = false;
let _ended = false;
let _startTime = null;
let _endTime = null;
let	_startValue;
let	_endValue = 0;
let _minValue = 0.95 * defaultScale.x;


function init() {
	_showed = false;
	_ended = true;
	_obj.scale.set(0, 0, 0);
	_startValue = 0;
}

function start() {
    	document.addEventListener(_userData.showEventName, _show);
    	document.addEventListener(_userData.hideEventName, _hide);
}

function stop() {
    	document.removeEventListener(_userData.showEventName, _show);
    	document.removeEventListener(_userData.hideEventName, _hide);
	init();
}

function _show(event) {
	if (_userData.key !== event.detail.key) {
		return;
	}
	_init();
	_startValue = _obj.scale.x;
	_endValue = defaultScale.x;
}

function _hide(event) {
	if (_userData.key !== event.detail.key) {
		return;
	}
	_init();
	_startValue = _obj.scale.x;
	_endValue = _minValue;
}

function _init() {
	_startTime = Date.now();
	_endTime = _startTime + _duration;
	_ended = false;
}

function update(event) {
	if (_ended) {
		return
	}
    	const now = Date.now();
	updateScale(now);
	tryStopAnimation(now);
}

function updateScale(now) {
	const value = _getValue(now);
	_obj.scale.set(value , value , value);
}

function _getValue(now) {
	const deltaValue = _endValue - _startValue;
	const deltaTime = (now - _startTime) / _duration;
	const newValue = _startValue + deltaValue * deltaTime;
	return newValue;
}

function tryStopAnimation(now) {
	if (now >= _endTime) {
		_startTime = null;
		_endTime = null;
		_ended = true;
	}
}

Этот сценарий добавляется для всех объектов с именем checked.

Экспорт и настройка сцены

Сцена готова. Теперь необходимо экспортировать ее и настроить. Для этого первым делом нужно удалить источник света.

После этого на верхней панели нажмите File → Publish.

После этого нужно перейти в простой редактор, на сцене 1. Далее, чтобы добавить файл JSON, нажмите + в блоке JSON MAE.

На сцене появится такой же вспомогательная плоскость.

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

Таким же образом нужно создать еще четыре файла JSON.

Не забудьте, что для корректной работы каждого файла необходимо изменить параметр в userData.

Это должен быть параметр sceneKey объекта game (смотрите в подглаве Добавление userData для объекта game).

Создание мультисцены

После того как вы добавили JSON-файлы в сцену и все готово, необходимо создать мультисцену. Для этого нужно перейти на вкладку Multiscene.

Далее необходимо создать проект, нажав Create a Multiscene Project.

Откроется модальное окно добавления мультисцены.

В поле Multiscene Name нужно написать название вашей мультисцены, в поле Multiscene URL — ссылку. Далее необходимо добавить проекты, нажав на кнопку Add Projects.

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

Для добавления необходимо нажать кнопку Add.

После этого мультисцена будет создана и появится в списке.

Если вы не хотите/можете использовать 3D-модели, вы можете создать две плоскости и заменить их материал на вкладке Material (о том, как работать с материалами, читайте в статье ).

Детальная настройка материалов