@@ -0,0 +1,54 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> | |||||
<script src="общее/base64-js/base64js.min.js"></script> | |||||
<script src="общее/pako/pako.min.js"></script> | |||||
<script src="игра/matter-js/matter.min.js"></script> | |||||
<link rel="stylesheet" type="text/css" href="игра/uikit/uikit.min.css" /> | |||||
<script src="игра/uikit/uikit.min.js"></script> | |||||
<script src="игра/uikit/uikit-icons.min.js"></script> | |||||
<style> | |||||
html, body { | |||||
height: 100%; | |||||
} | |||||
#корень | |||||
{ | |||||
position: relative; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<div id="заставка"></div> | |||||
<div id="корень"></div> | |||||
<div id="отладка"></div> | |||||
<script src="общее/050.функции.js"></script> | |||||
<script src="общее/100.События.js"></script> | |||||
<script src="общее/150.Состояние.js"></script> | |||||
<script src="общее/200.обёртки.js"></script> | |||||
<script src="игра/050.Заголовок.js"></script> | |||||
<script src="игра/070.Виды.js"></script> | |||||
<script src="игра/075.Анимация.js"></script> | |||||
<script src="игра/080.Анимации.js"></script> | |||||
<script src="игра/100.Изображения.js"></script> | |||||
<script src="игра/120.Физика.js"></script> | |||||
<script src="игра/140.Тела.js"></script> | |||||
<script src="игра/160.Слежение.js"></script> | |||||
<script src="игра/180.Объекты.js"></script> | |||||
<script src="игра/190.Состояния.js"></script> | |||||
<script src="игра/220.Мышь.js"></script> | |||||
<script src="игра/240.Столкновения.js"></script> | |||||
<script src="игра/gr/100.grПодсказки.js"></script> | |||||
<script src="игра/gr/200.grМеню.js"></script> | |||||
<script src="игра/gr/300.grРеплики.js"></script> | |||||
<script src="игра/gr/400.grСцена.js"></script> | |||||
<script src="игра/700.Игра.js"></script> | |||||
<script> | |||||
var игра = new Игра(); | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,10 @@ | |||||
function Заголовок() | |||||
{ | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (ключ == "заголовок") | |||||
{ | |||||
document.title = значение; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,28 @@ | |||||
function Виды(события) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.задано = {}; | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] != "виды") | |||||
{ | |||||
return; | |||||
} | |||||
let имя = путь[1]; | |||||
let свойство = путь.slice(2).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
события.уведомить(`виды/${имя}`); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,77 @@ | |||||
function Анимация(элемент, параметры) | |||||
{ | |||||
this.запустить = function() | |||||
{ | |||||
let п = параметры; | |||||
let воспроизведений = Number(п["воспроизведений"]); | |||||
let скорость = Number(п["скорость"]); | |||||
let к0 = Number(п["кадр.0"]); | |||||
let к1 = Number(п["кадр.1"]); | |||||
let д0 = Number(п["диапазон.0"]); | |||||
let д1 = Number(п["диапазон.1"]); | |||||
let д2 = Number(п["диапазон.2"]); | |||||
let д3 = Number(п["диапазон.3"]); | |||||
if ( | |||||
воспроизведений == null || | |||||
скорость == null || | |||||
к0 == null || | |||||
к1 == null || | |||||
д0 == null || | |||||
д1 == null || | |||||
д2 == null || | |||||
д3 == null | |||||
) { | |||||
return; | |||||
} | |||||
let ширинаДиапазона = Math.abs(д2 - д0); | |||||
let высотаДиапазона = Math.abs(д3 - д1); | |||||
let ширинаКадра = Math.abs(к0); | |||||
let высотаКадра = Math.abs(к1); | |||||
let кадровПоГоризонтали = Math.floor(ширинаДиапазона / ширинаКадра); | |||||
let кадровПоВертикали = Math.floor(высотаДиапазона / высотаКадра); | |||||
this.номерВоспроизведения = 0; | |||||
this.колвоВоспроизведений = воспроизведений; | |||||
this.размерКадра = [к0, к1]; | |||||
this.поГоризонтали = кадровПоГоризонтали > кадровПоВертикали; | |||||
this.номерКадра = 0; | |||||
this.колвоКадров = this.поГоризонтали ? кадровПоГоризонтали : кадровПоВертикали; | |||||
// Возможно, в будущем стоит заменить частные таймеры на один общий. | |||||
// Вдруг в браузерах есть ограничение на количество таймеров от одной страницы. | |||||
this.таймер = setInterval(() => { this.анимировать(); }, скорость); | |||||
}; | |||||
this.остановить = function() | |||||
{ | |||||
if (this.таймер) | |||||
{ | |||||
clearInterval(this.таймер); | |||||
} | |||||
}; | |||||
this.анимировать = function() | |||||
{ | |||||
this.номерКадра += 1; | |||||
if (this.номерКадра >= this.колвоКадров) | |||||
{ | |||||
this.номерВоспроизведения += 1; | |||||
this.номерКадра = 0; | |||||
if ( | |||||
this.колвоВоспроизведений > 0 && | |||||
this.номерВоспроизведения >= this.колвоВоспроизведений | |||||
) { | |||||
this.остановить(); | |||||
return; | |||||
} | |||||
} | |||||
let x = this.поГоризонтали ? this.номерКадра * this.размерКадра[0] : 0; | |||||
let y = this.поГоризонтали ? 0 : this.номерКадра * this.размерКадра[1]; | |||||
элемент.style.backgroundPosition = `${x}px ${y}px`; | |||||
}; | |||||
// Конструктор. | |||||
this.запустить(); | |||||
} |
@@ -0,0 +1,48 @@ | |||||
function Анимации(события) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.задано = {}; | |||||
this.анимации = {}; | |||||
}; | |||||
this.запустить = function(имя, элемент) | |||||
{ | |||||
// Останавливаем прошлый экземпляр этой анимации на том же элементе. | |||||
this.остановить(имя, элемент); | |||||
// Запускаем новый экземпляр анимации на том же элементе. | |||||
let ключ = `${элемент.id}`;//-${имя}`; | |||||
this.анимации[ключ] = new Анимация(элемент, this.задано[имя]); | |||||
} | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] != "анимации") | |||||
{ | |||||
return; | |||||
} | |||||
let имя = путь[1]; | |||||
let свойство = путь.slice(2).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
события.уведомить(`анимации/${имя}`); | |||||
}; | |||||
this.остановить = function(имя, элемент) | |||||
{ | |||||
let ключ = `${элемент.id}`;//-${имя}`; | |||||
if (ключ in this.анимации) | |||||
{ | |||||
this.анимации[ключ].остановить(); | |||||
delete this.анимации[ключ]; | |||||
} | |||||
} | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,183 @@ | |||||
function Изображения(события, виды, анимации, корень) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.умолчание = { | |||||
пр: [0, 0, 100, 100], | |||||
угол: 0, | |||||
}; | |||||
this.задано = {}; | |||||
this.элементы = {}; | |||||
события.подписать(this); | |||||
}; | |||||
// Ключи и события. | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] == "изображения") | |||||
{ | |||||
var имя = путь[1]; | |||||
var свойство = путь.slice(2).join("."); | |||||
this.обновитьЭлемент(имя, свойство, значение); | |||||
} | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
let в = "виды/"; | |||||
let а = "анимации/"; | |||||
if (событие.startsWith(в)) | |||||
{ | |||||
let вид = событие.substring(в.length); | |||||
this.обновитьЭлементыВида(вид); | |||||
} | |||||
else if (событие.startsWith(а)) | |||||
{ | |||||
let анимация = событие.substring(а.length); | |||||
this.обновитьЭлементыАнимации(анимация); | |||||
} | |||||
}; | |||||
this.обновитьЭлементыАнимации = function(анимация) | |||||
{ | |||||
for (let имя in this.задано) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
let зан = за["анимация"]; | |||||
if (зан && зан == анимация) | |||||
{ | |||||
this.обновитьВидАнимацию(имя, за["вид"], за["анимация"]); | |||||
} | |||||
} | |||||
}; | |||||
this.обновитьЭлементыВида = function(вид) | |||||
{ | |||||
for (let имя in this.задано) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
let заданныйВид = за["вид"]; | |||||
if (заданныйВид && заданныйВид == вид) | |||||
{ | |||||
this.обновитьВидАнимацию(имя, за["вид"], за["анимация"]); | |||||
} | |||||
} | |||||
}; | |||||
// Без вида и анимации. | |||||
this.обновитьПозициюРазмерЭлемента = function(имя, эл) | |||||
{ | |||||
let пр = this.пр(имя); | |||||
эл.style.left = `${пр[0]}px`; | |||||
эл.style.top = `${пр[1]}px`; | |||||
эл.style.width = `${пр[2]}px`; | |||||
эл.style.height = `${пр[3]}px`; | |||||
}; | |||||
this.пр = function(имя) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
let пр = this.умолчание.пр; | |||||
return [ | |||||
за["пр.0"] ? за["пр.0"] : пр[0], | |||||
за["пр.1"] ? за["пр.1"] : пр[1], | |||||
за["пр.2"] ? за["пр.2"] : пр[2], | |||||
за["пр.3"] ? за["пр.3"] : пр[3], | |||||
]; | |||||
}; | |||||
this.создатьИлиПолучитьЭлемент = function(имя) | |||||
{ | |||||
var эл = this.элементы[имя]; | |||||
if (эл) | |||||
{ | |||||
return эл; | |||||
} | |||||
var ум = this.умолчание; | |||||
эл = document.createElement("div"); | |||||
эл.id = `изображения-${имя}`; | |||||
эл.style.position = "absolute"; | |||||
эл.style.display = "block"; | |||||
// Свойства по умолчанию. | |||||
this.обновитьПозициюРазмерЭлемента(имя, эл); | |||||
эл.style.transform = `rotate(${ум.угол}rad)`; | |||||
this.элементы[имя] = эл; | |||||
корень.appendChild(эл); | |||||
return эл; | |||||
}; | |||||
// Вид и анимация. | |||||
this.обновитьУгол = function(имя) | |||||
{ | |||||
var за = this.задано[имя]; | |||||
var ум = this.умолчание; | |||||
var эл = this.элементы[имя]; | |||||
var угол = за.угол ? за.угол : ум.угол; | |||||
var transform = `rotate(${угол}deg) `; | |||||
let вид = за["вид"]; | |||||
if (вид) | |||||
{ | |||||
let виза = виды.задано[вид]; | |||||
if (виза && виза["transform"]) | |||||
{ | |||||
transform += виза["transform"]; | |||||
} | |||||
} | |||||
эл.style.transform = transform; | |||||
}; | |||||
this.обновитьВидАнимацию = function(имя, вид, анимация) | |||||
{ | |||||
var эл = this.создатьИлиПолучитьЭлемент(имя); | |||||
if (вид) | |||||
{ | |||||
let за = виды.задано[вид]; | |||||
for (let параметр in за) | |||||
{ | |||||
эл.style.setProperty(параметр, за[параметр]); | |||||
} | |||||
} | |||||
if (анимация) | |||||
{ | |||||
анимации.запустить(анимация, эл); | |||||
} | |||||
this.обновитьУгол(имя); | |||||
}; | |||||
this.обновитьЭлемент = function(имя, свойство, значение) | |||||
{ | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
var за = this.задано[имя]; | |||||
var эл = this.создатьИлиПолучитьЭлемент(имя); | |||||
if (свойство.startsWith("пр")) | |||||
{ | |||||
this.обновитьПозициюРазмерЭлемента(имя, эл); | |||||
} | |||||
else if ( | |||||
(свойство == "угол") || | |||||
(свойство == "вид") || | |||||
(свойство == "анимация") | |||||
) { | |||||
this.обновитьВидАнимацию(имя, за["вид"], за["анимация"]); | |||||
} | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,28 @@ | |||||
function Физика(события) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.задано = {}; | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] != "физика") | |||||
{ | |||||
return; | |||||
} | |||||
let имя = путь[1]; | |||||
let свойство = путь.slice(2).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
события.уведомить(`физика/${имя}`); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,149 @@ | |||||
function Тела(события, физика, физмир) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.умолчание = { | |||||
пр: [0, 0, 40, 20], | |||||
части: [], | |||||
часть: false, | |||||
}; | |||||
this.задано = {}; | |||||
this.тела = {}; | |||||
this.имена = {}; | |||||
события.подписать(this); | |||||
}; | |||||
this.именаЧастей = function(за) | |||||
{ | |||||
var имена = []; | |||||
for (var ключ in за) | |||||
{ | |||||
if (ключ.startsWith("части")) | |||||
{ | |||||
имена.push(за[ключ]); | |||||
} | |||||
} | |||||
return имена; | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] != "тела") | |||||
{ | |||||
return; | |||||
} | |||||
var имя = путь[1]; | |||||
var свойство = путь.slice(2).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
this.пересоздатьТело(имя); | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
let префикс = "физика/"; | |||||
if (событие.startsWith(префикс)) | |||||
{ | |||||
let физ = событие.substring(префикс.length); | |||||
this.пересоздатьТелаФизики(физ); | |||||
} | |||||
}; | |||||
this.пересоздатьТелаФизики = function(физ) | |||||
{ | |||||
for (let имя in this.задано) | |||||
{ | |||||
let заданнаяФизика = this.задано[имя]["физика"]; | |||||
if (заданнаяФизика && заданнаяФизика == физ) | |||||
{ | |||||
this.пересоздатьТело(имя); | |||||
} | |||||
} | |||||
}; | |||||
this.пересоздатьТело = function(имя) | |||||
{ | |||||
// Удаляем старое тело. | |||||
if (имя in this.тела) | |||||
{ | |||||
var тело = this.тела[имя]; | |||||
delete this.имена[тело.id]; | |||||
// Всегда удаляем из мира: и составные, и несоставные тела. | |||||
Matter.Composite.remove(физмир, тело); | |||||
} | |||||
var за = this.задано[имя]; | |||||
var ум = this.умолчание; | |||||
let пр = this.пр(имя); | |||||
// Переводим x,y из левого верхнего угла в центр. | |||||
пр[0] = пр[0] + пр[2] / 2.0; | |||||
пр[1] = пр[1] + пр[3] / 2.0; | |||||
// Параметры тела. | |||||
var параметры = {}; | |||||
if (за.физика) | |||||
{ | |||||
let заф = физика.задано[за.физика]; | |||||
for (let параметр in заф) | |||||
{ | |||||
мир.задатьПолныйКлюч(параметры, параметр, заф[параметр]); | |||||
} | |||||
} | |||||
var тело = null; | |||||
// Создаём новое составное тело. | |||||
if (за["части.0"]) | |||||
{ | |||||
параметры["parts"] = this.телаЧастей(this.именаЧастей(за)); | |||||
тело = Matter.Body.create(параметры); | |||||
} | |||||
// Создаём новое несоставное тело. | |||||
// Вполне может быть частью другого составного тела. | |||||
else | |||||
{ | |||||
тело = Matter.Bodies.rectangle(пр[0], пр[1], пр[2], пр[3], параметры); | |||||
} | |||||
this.тела[имя] = тело; | |||||
this.имена[тело.id] = имя; | |||||
// Добавляем тело в физический мир, если оно не является частью другого составного тела. | |||||
var часть = за.часть ? за.часть : ум.часть; | |||||
if (!часть) | |||||
{ | |||||
Matter.Composite.add(физмир, тело); | |||||
} | |||||
}; | |||||
this.пр = function(имя) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
let пр = this.умолчание.пр; | |||||
return [ | |||||
за["пр.0"] ? за["пр.0"] : пр[0], | |||||
за["пр.1"] ? за["пр.1"] : пр[1], | |||||
за["пр.2"] ? за["пр.2"] : пр[2], | |||||
за["пр.3"] ? за["пр.3"] : пр[3], | |||||
]; | |||||
}; | |||||
this.телаЧастей = function(имена) | |||||
{ | |||||
var тела = []; | |||||
for (var н in имена) | |||||
{ | |||||
var имя = имена[н]; | |||||
var тело = this.тела[имя]; | |||||
тела.push(тело); | |||||
} | |||||
return тела; | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} | |||||
@@ -0,0 +1,119 @@ | |||||
function Слежение(состояние, изображения, тела) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.умолчание = { | |||||
смещение: [0, 0], | |||||
скорость: 1, | |||||
предел: 0, | |||||
}; | |||||
this.задано = {}; | |||||
}; | |||||
this.обновить = function() | |||||
{ | |||||
for (var имя in this.задано) | |||||
{ | |||||
this.расположитьИзображение(имя); | |||||
} | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] != "слежение") | |||||
{ | |||||
return; | |||||
} | |||||
var имя = путь[1]; | |||||
var свойство = путь.slice(2).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
}; | |||||
this.расположитьИзображение = function(имя) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
let тело = тела.тела[за.тело]; | |||||
let элемент = изображения.элементы[за.изображение]; | |||||
if (!тело || !элемент) | |||||
{ | |||||
return; | |||||
} | |||||
let ум = this.умолчание; | |||||
// Параметры. | |||||
var скорость = за.скорость ? за.скорость : ум.скорость; | |||||
var смещениеX = за["смещение.0"] ? за["смещение.0"] : ум.смещение[0]; | |||||
var смещениеY = за["смещение.1"] ? за["смещение.1"] : ум.смещение[1]; | |||||
var предел = за.предел ? за.предел : ум.предел; | |||||
// Текущая позиция. | |||||
var x0 = 0; | |||||
if (элемент.dataset.слежениеX) | |||||
{ | |||||
x0 = элемент.dataset.слежениеX; | |||||
} | |||||
var y0 = 0; | |||||
if (элемент.dataset.слежениеY) | |||||
{ | |||||
y0 = элемент.dataset.слежениеY; | |||||
} | |||||
// Целевая позиция. | |||||
var x1 = тело.position.x + смещениеX; | |||||
var y1 = тело.position.y + смещениеY; | |||||
// Radians -> Degrees. | |||||
var угол = тело.angle * 180 / Math.PI; | |||||
// Устанавливаемая плавно позиция. | |||||
var x = this.lerp(x0, x1, скорость, предел); | |||||
var y = this.lerp(y0, y1, скорость, предел); | |||||
// Ничего не делаем, если разница ничтожна. | |||||
let ничтожно = 0.00001; | |||||
if ( | |||||
элемент.dataset.слежениеX != null && | |||||
элемент.dataset.слежениеY != null && | |||||
элемент.dataset.слежениеУгол != null && | |||||
Math.abs(элемент.dataset.слежениеX - x) < ничтожно && | |||||
Math.abs(элемент.dataset.слежениеY - y) < ничтожно && | |||||
Math.abs(элемент.dataset.слежениеУгол - угол) < ничтожно | |||||
) { | |||||
return; | |||||
} | |||||
// Иначе применяем новые значения. | |||||
элемент.dataset.слежениеX = x; | |||||
элемент.dataset.слежениеY = y; | |||||
элемент.dataset.слежениеУгол = угол; | |||||
var описание = { | |||||
пр: [x, y], | |||||
угол: угол, | |||||
}; | |||||
состояние.разобратьЛишьНовое({ | |||||
изображения: { | |||||
[за.изображение]: описание, | |||||
}, | |||||
}); | |||||
}; | |||||
this.lerp = function(v0, v1, t, предел) { | |||||
// Убираем мельтешение в случае наличия предела. | |||||
if (предел) | |||||
{ | |||||
var delta = Math.abs(v0 - v1); | |||||
if (delta < предел) { | |||||
return v0; | |||||
} | |||||
} | |||||
return v0 * (1 - t) + v1 * t; | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,170 @@ | |||||
function Объекты(состояние, события) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.умолчание = { | |||||
пр: [0, 0, 40, 20], | |||||
}; | |||||
this.задано = {}; | |||||
события.подписать(this); | |||||
}; | |||||
// Ключи и события. | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] != "объекты") | |||||
{ | |||||
return; | |||||
} | |||||
let имя = путь[1]; | |||||
let свойство = путь.slice(2).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
let производноеИмя = `объекты-${имя}`; | |||||
if (свойство.startsWith("пр.")) | |||||
{ | |||||
let пр = this.пр(имя); | |||||
this.обновитьПозициюРазмер(производноеИмя, пр); | |||||
this.обновитьСлежение(производноеИмя, пр); | |||||
let вид = this.задано[имя]["вид"]; | |||||
let анимация = this.задано[имя]["анимация"]; | |||||
this.обновитьИзображение(производноеИмя, пр, вид, анимация); | |||||
} | |||||
else if (свойство == "физика") | |||||
{ | |||||
this.обновитьФизику(производноеИмя, значение); | |||||
} | |||||
else if ( | |||||
свойство == "вид" || | |||||
свойство == "анимация" | |||||
) { | |||||
let вид = this.задано[имя]["вид"]; | |||||
let анимация = this.задано[имя]["анимация"]; | |||||
this.обновитьИзображение(производноеИмя, null, вид, анимация); | |||||
} | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
let в = "виды/"; | |||||
let а = "анимации/"; | |||||
if (событие.startsWith(в)) | |||||
{ | |||||
let вид = событие.substring(в.length); | |||||
this.обновитьОбъектыВида(вид); | |||||
} | |||||
else if (событие.startsWith(а)) | |||||
{ | |||||
let анимация = событие.substring(а.length); | |||||
this.обновитьОбъектыАнимации(анимация); | |||||
} | |||||
}; | |||||
this.обновитьОбъектыАнимации = function(анимация) | |||||
{ | |||||
for (let имя in this.задано) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
let зан = за["анимация"]; | |||||
if (зан && зан == анимация) | |||||
{ | |||||
this.обновитьВидАнимацию(имя); | |||||
} | |||||
} | |||||
}; | |||||
this.обновитьОбъектыВида = function(вид) | |||||
{ | |||||
for (let имя in this.задано) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
let зви = за["вид"]; | |||||
if (зви && зви == вид) | |||||
{ | |||||
this.обновитьВидАнимацию(имя); | |||||
} | |||||
} | |||||
}; | |||||
this.обновитьВидАнимацию = function(имя) | |||||
{ | |||||
let вид = this.задано[имя]["вид"]; | |||||
let анимация = this.задано[имя]["анимация"]; | |||||
let производноеИмя = `объекты-${имя}`; | |||||
this.обновитьИзображение(производноеИмя, null, вид, анимация); | |||||
}; | |||||
this.обновитьИзображение = function(имя, пр, вид, анимация) | |||||
{ | |||||
var описание = { | |||||
вид: вид, | |||||
анимация: анимация, | |||||
}; | |||||
if (пр) | |||||
{ | |||||
описание["пр"] = пр; | |||||
} | |||||
состояние.разобрать({ | |||||
изображения: { | |||||
[имя]: описание, | |||||
}, | |||||
}); | |||||
}; | |||||
this.обновитьПозициюРазмер = function(имя, пр) | |||||
{ | |||||
состояние.разобрать({ | |||||
тела: { | |||||
[имя]: { | |||||
пр: пр, | |||||
}, | |||||
}, | |||||
}); | |||||
}; | |||||
this.обновитьСлежение = function(имя, пр) | |||||
{ | |||||
состояние.разобрать({ | |||||
слежение: { | |||||
[имя]: { | |||||
изображение: имя, | |||||
тело: имя, | |||||
смещение: [-пр[2] / 2, -пр[3] / 2], | |||||
}, | |||||
}, | |||||
}); | |||||
}; | |||||
this.обновитьФизику = function(имя, значение) | |||||
{ | |||||
состояние.разобрать({ | |||||
тела: { | |||||
[имя]: { | |||||
физика: значение, | |||||
}, | |||||
}, | |||||
}); | |||||
}; | |||||
this.пр = function(имя) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
let пр = this.умолчание.пр; | |||||
return [ | |||||
за["пр.0"] ? за["пр.0"] : пр[0], | |||||
за["пр.1"] ? за["пр.1"] : пр[1], | |||||
за["пр.2"] ? за["пр.2"] : пр[2], | |||||
за["пр.3"] ? за["пр.3"] : пр[3], | |||||
]; | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,31 @@ | |||||
function Состояния(состояние) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.задано = {}; | |||||
}; | |||||
this.применить = function(имя) | |||||
{ | |||||
состояние.разобратьВыпрямленный(this.задано[имя]); | |||||
} | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] != "состояния") | |||||
{ | |||||
return; | |||||
} | |||||
let имя = путь[1]; | |||||
let свойство = путь.slice(2).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,16 @@ | |||||
function Мышь(события) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
window.addEventListener("click", function(o) { | |||||
события.уведомить(`мышь/нажатие/${o.clientX}/${o.clientY}/${o.target.id}`); | |||||
}); | |||||
window.addEventListener("mousemove", function(o) { | |||||
события.уведомить(`мышь/перемещение/${o.clientX}/${o.clientY}/${o.target.id}`); | |||||
}); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; | |||||
@@ -0,0 +1,60 @@ | |||||
function Столкновения(события, тела, физдвижок) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.задано = {}; | |||||
Matter.Events.on( | |||||
физдвижок, | |||||
"collisionActive", | |||||
(событие) => { this.обработатьСтолкновение(событие) } | |||||
); | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] != "столкновения") | |||||
{ | |||||
return; | |||||
} | |||||
let имя = путь[1]; | |||||
let свойство = путь.slice(2).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
}; | |||||
this.обработатьСтолкновение = function(событие) | |||||
{ | |||||
for (var имя in this.задано) | |||||
{ | |||||
var ст = this.задано[имя]; | |||||
var имя1 = ст["тела.0"]; | |||||
var имя2 = ст["тела.1"]; | |||||
if (!имя1 || !имя2 || !ст.событие) | |||||
{ | |||||
continue; | |||||
} | |||||
var пары = событие.pairs; | |||||
for (var номер in пары) | |||||
{ | |||||
var пара = пары[номер]; | |||||
var п1 = тела.имена[пара.bodyA.id]; | |||||
var п2 = тела.имена[пара.bodyB.id]; | |||||
if ( | |||||
!((имя1 == п1) && (имя2 == п2)) && | |||||
!((имя1 == п2) && (имя2 == п1)) | |||||
) { | |||||
continue; | |||||
} | |||||
мир.события.уведомить(ст.событие); | |||||
} | |||||
} | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,100 @@ | |||||
function Игра() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
мир.физдвижок = Matter.Engine.create({ | |||||
enableSleeping: true, | |||||
gravity: { | |||||
y: 2, | |||||
}, | |||||
}); | |||||
var корень = document.getElementById("корень"); | |||||
мир.события = new События(мир.uuid); | |||||
мир.состояние = new Состояние(); | |||||
мир.состояние.обработчик = (к, п, з) => { this.обработатьКлюч(к, п, з) }; | |||||
мир.виды = new Виды(мир.события); | |||||
мир.анимации = new Анимации(мир.события); | |||||
мир.изображения = new Изображения(мир.события, мир.виды, мир.анимации, корень), | |||||
мир.физика = new Физика(мир.события); | |||||
мир.тела = new Тела(мир.события, мир.физика, мир.физдвижок.world); | |||||
мир.слежение = new Слежение(мир.состояние, мир.изображения, мир.тела); | |||||
мир.объекты = new Объекты(мир.состояние, мир.события); | |||||
мир.состояниЯ = new Состояния(мир.состояние); | |||||
мир.мышь = new Мышь(мир.события); | |||||
мир.столкновения = new Столкновения(мир.события, мир.тела, мир.физдвижок); | |||||
this.ключники = [ | |||||
new Заголовок(), | |||||
мир.виды, | |||||
мир.анимации, | |||||
мир.изображения, | |||||
мир.физика, | |||||
мир.тела, | |||||
мир.слежение, | |||||
мир.объекты, | |||||
мир.состояниЯ, | |||||
мир.столкновения, | |||||
]; | |||||
this.настроитьКлючиGR() | |||||
var z64 = мир.параметрыЗапуска()["z64"]; | |||||
this.исполнитьКод(z64); | |||||
this.отслеживатьОбновленияКода(); | |||||
this.обновить(); | |||||
}; | |||||
this.исполнитьКод = function(z64) | |||||
{ | |||||
if (z64) | |||||
{ | |||||
var код = мир.изZ64(z64); | |||||
eval(код); | |||||
} | |||||
}; | |||||
this.настроитьКлючиGR = function() | |||||
{ | |||||
let адресИгры = window.location.pathname + "/../игра.html?z64="; | |||||
мир.grСцена = new grСцена(мир.состояние, мир.состояниЯ, мир.события, мир.тела, адресИгры); | |||||
this.ключники.push(мир.grСцена); | |||||
мир.grПодсказки = new grПодсказки(); | |||||
this.ключники.push(мир.grПодсказки); | |||||
мир.grРеплики = new grРеплики(мир.события); | |||||
this.ключники.push(мир.grРеплики); | |||||
мир.grМеню = new grМеню(мир.события, мир.grПодсказки, мир.grРеплики); | |||||
} | |||||
this.обновить = function() | |||||
{ | |||||
Matter.Engine.update(мир.физдвижок); | |||||
мир.слежение.обновить(); | |||||
мир.grСцена.обновить(); | |||||
var тут = this; | |||||
requestAnimationFrame(function() { | |||||
тут.обновить(); | |||||
}); | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
for (var номер in this.ключники) | |||||
{ | |||||
var ключник = this.ключники[номер]; | |||||
ключник.обработатьКлюч(ключ, путь, значение); | |||||
} | |||||
}; | |||||
this.отслеживатьОбновленияКода = function() | |||||
{ | |||||
var тут = this; | |||||
window.addEventListener("message", function(событие) { | |||||
тут.исполнитьКод(событие.data); | |||||
}); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,58 @@ | |||||
function grПодсказки() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.задано = {}; | |||||
}; | |||||
this.задатьПодсказку = function(имя) | |||||
{ | |||||
let за = this.задано[имя]; | |||||
if (!за.элемент || !за.текст) | |||||
{ | |||||
return; | |||||
} | |||||
let элемент = document.getElementById(за.элемент); | |||||
if (!элемент) | |||||
{ | |||||
return; | |||||
} | |||||
элемент.setAttribute("uk-tooltip", `pos: right; title: ${за.текст}`); | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (!ключ.startsWith("gr.подсказки")) | |||||
{ | |||||
return; | |||||
} | |||||
let имя = путь[2]; | |||||
let свойство = путь.slice(3).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
this.задатьПодсказку(имя); | |||||
}; | |||||
this.подсказкаДляЭлемента = function(элемент) | |||||
{ | |||||
for (var имя in this.задано) | |||||
{ | |||||
let подсказка = this.задано[имя]; | |||||
if (подсказка.элемент == элемент) | |||||
{ | |||||
return имя; | |||||
} | |||||
} | |||||
return null; | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,104 @@ | |||||
function grМеню(события, подсказки, реплики) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
события.подписать(this); | |||||
this.установитьМеню(); | |||||
this.улавливатьВыбор(); | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
let мышь = "мышь/нажатие/"; | |||||
let сами = "меню/"; | |||||
if (событие.startsWith(мышь)) | |||||
{ | |||||
let значения = событие.substring(мышь.length).split("/"); | |||||
let элемент = значения[2]; | |||||
let имя = подсказки.подсказкаДляЭлемента(элемент); | |||||
if (имя && имя in реплики.задано) | |||||
{ | |||||
this.отобразитьМеню(имя); | |||||
} | |||||
} | |||||
else if (событие.startsWith(сами)) | |||||
{ | |||||
UIkit.modal("#меню").hide(); | |||||
} | |||||
}; | |||||
this.отобразитьМеню = function(имя) | |||||
{ | |||||
this.активнаяПодсказка = имя; | |||||
let подсказка = подсказки.задано[имя]; | |||||
document.getElementById("меню-заголовок").innerHTML = подсказка.текст; | |||||
UIkit.modal("#меню").show(); | |||||
}; | |||||
this.улавливатьВыбор = function() | |||||
{ | |||||
var тут = this; | |||||
let глаз = document.getElementById("меню-кнопка-глаз"); | |||||
глаз.addEventListener( | |||||
"click", | |||||
function() { | |||||
события.уведомить(`меню/глаз/${тут.активнаяПодсказка}`); | |||||
} | |||||
); | |||||
let рука = document.getElementById("меню-кнопка-рука"); | |||||
рука.addEventListener( | |||||
"click", | |||||
function() { | |||||
события.уведомить(`меню/рука/${тут.активнаяПодсказка}`); | |||||
} | |||||
); | |||||
let рот = document.getElementById("меню-кнопка-рот"); | |||||
рот.addEventListener( | |||||
"click", | |||||
function() { | |||||
события.уведомить(`меню/рот/${тут.активнаяПодсказка}`); | |||||
} | |||||
); | |||||
let нога = document.getElementById("меню-кнопка-нога"); | |||||
нога.addEventListener( | |||||
"click", | |||||
function() { | |||||
события.уведомить(`меню/нога/${тут.активнаяПодсказка}`); | |||||
} | |||||
); | |||||
}; | |||||
this.установитьМеню = function() | |||||
{ | |||||
let html = ` | |||||
<div id="меню" uk-modal> | |||||
<div class="uk-modal-dialog uk-modal-body"> | |||||
<button class="uk-modal-close-default" type="button" uk-close></button> | |||||
<h2 id="меню-заголовок" class="uk-modal-title">Заголовок</h2> | |||||
<div class="uk-child-width-1-4 uk-text-center" uk-grid> | |||||
<div> | |||||
<div id="меню-кнопка-глаз" class="uk-card uk-card-small uk-card-hover uk-card-default uk-card-body">👁️</div> | |||||
</div> | |||||
<div> | |||||
<div id="меню-кнопка-рука" class="uk-card uk-card-small uk-card-hover uk-card-default uk-card-body">🤚</div> | |||||
</div> | |||||
<div> | |||||
<div id="меню-кнопка-рот" class="uk-card uk-card-small uk-card-hover uk-card-default uk-card-body">🗣</div> | |||||
</div> | |||||
<div> | |||||
<div id="меню-кнопка-нога" class="uk-card uk-card-small uk-card-hover uk-card-default uk-card-body">🥾</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
`; | |||||
document.body.insertAdjacentHTML("beforeend", html); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; | |||||
@@ -0,0 +1,69 @@ | |||||
function grРеплики(события) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.задано = {}; | |||||
события.подписать(this); | |||||
}; | |||||
this.номерРеплики = function(инструмент) | |||||
{ | |||||
if (инструмент == "рука") | |||||
{ | |||||
return 1; | |||||
} | |||||
else if (инструмент == "рот") | |||||
{ | |||||
return 2; | |||||
} | |||||
else if (инструмент == "нога") | |||||
{ | |||||
return 3; | |||||
} | |||||
// "глаз" | |||||
return 0; | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (!ключ.startsWith("gr.реплики")) | |||||
{ | |||||
return; | |||||
} | |||||
let имя = путь[2]; | |||||
let свойство = путь.slice(3).join("."); | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
let меню = "меню/"; | |||||
if (событие.startsWith(меню)) | |||||
{ | |||||
let значения = событие.substring(меню.length).split("/"); | |||||
let инструмент = значения[0]; | |||||
let подсказка = значения[1]; | |||||
this.отобразитьРеплику(инструмент, подсказка); | |||||
} | |||||
}; | |||||
this.отобразитьРеплику = function(инструмент, подсказка) | |||||
{ | |||||
let номер = this.номерРеплики(инструмент); | |||||
let реплика = this.задано[подсказка][номер]; | |||||
let частей = реплика.split(/ /); | |||||
let длительность = частей.length * 1000; | |||||
UIkit.notification({ | |||||
message: реплика, | |||||
timeout: длительность, | |||||
}); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1,190 @@ | |||||
function grСцена(состояние, состояниЯ, события, тела, адресИгры) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.задано = {}; | |||||
this.сцены = null; | |||||
this.состояниеИгрока = null; | |||||
события.подписать(this); | |||||
}; | |||||
this.загрузитьСцену = function(имя) | |||||
{ | |||||
let z64 = this.сцены[имя]; | |||||
window.location = адресИгры + z64; | |||||
}; | |||||
this.загрузитьОглавление = function(адрес) | |||||
{ | |||||
var тут = this; | |||||
var запрос = new XMLHttpRequest(); | |||||
запрос.onreadystatechange = function() | |||||
{ | |||||
if (this.readyState == 4) | |||||
{ | |||||
if (this.status == 200) | |||||
{ | |||||
тут.разобратьОглавление(this.responseText); | |||||
} | |||||
else | |||||
{ | |||||
console.error("Не удалось загрузить оглавление сцен:", this.status); | |||||
} | |||||
} | |||||
} | |||||
запрос.open("GET", адрес); | |||||
запрос.send(); | |||||
}; | |||||
this.задатьСостояниеИгрока = function(идёт, направление) | |||||
{ | |||||
let стоятьЛ = this.задано["игрок.стоять.0"]; | |||||
let стоятьП = this.задано["игрок.стоять.1"]; | |||||
let идтиЛ = this.задано["игрок.идти.0"]; | |||||
let идтиП = this.задано["игрок.идти.1"]; | |||||
if (!стоятьЛ || !стоятьП || !идтиЛ || !идтиП) | |||||
{ | |||||
return; | |||||
} | |||||
var название = идтиЛ; | |||||
if (идёт && направление > 0) | |||||
{ | |||||
название = идтиП; | |||||
} | |||||
else if (!идёт && направление < 0) | |||||
{ | |||||
название = стоятьЛ; | |||||
} | |||||
else if (!идёт && направление > 0) | |||||
{ | |||||
название = стоятьП; | |||||
} | |||||
if (this.состояниеИгрока != название) | |||||
{ | |||||
состояниЯ.применить(название); | |||||
this.состояниеИгрока = название; | |||||
} | |||||
}; | |||||
this.обновить = function() | |||||
{ | |||||
this.переместитьИгрока(); | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (!ключ.startsWith("gr.сцена")) | |||||
{ | |||||
return; | |||||
} | |||||
var свойство = путь.slice(2).join("."); | |||||
this.задано[свойство] = значение; | |||||
if (свойство == "оглавление") | |||||
{ | |||||
this.загрузитьОглавление(значение); | |||||
} | |||||
}; | |||||
this.обработатьНажатиеМышиУказатьИНажать = function(x, y, указатель) | |||||
{ | |||||
let цель = this.задано["цель"]; | |||||
if (!this.типИгрыУказатьИНажать() || !цель) | |||||
{ | |||||
return; | |||||
} | |||||
let тело = тела.тела[цель]; | |||||
if (!тело) | |||||
{ | |||||
return; | |||||
} | |||||
var пр = [ | |||||
тело.bounds.min.x, | |||||
тело.bounds.min.y, | |||||
тело.bounds.max.x - тело.bounds.min.x, | |||||
тело.bounds.max.y - тело.bounds.min.y, | |||||
] | |||||
let ширина = пр[2]; | |||||
пр[0] = x - ширина; | |||||
состояние.разобрать({ | |||||
тела: { | |||||
[цель]: { | |||||
пр: пр, | |||||
}, | |||||
}, | |||||
}); | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
let мышь = "мышь/нажатие/"; | |||||
let сцена = "сцена/"; | |||||
if (событие.startsWith(мышь)) | |||||
{ | |||||
let значения = событие.substring(мышь.length).split("/"); | |||||
let x = Number(значения[0]); | |||||
let y = Number(значения[1]); | |||||
let указатель = значения[2]; | |||||
this.обработатьНажатиеМышиУказатьИНажать(x, y, указатель); | |||||
} | |||||
else if (событие.startsWith(сцена)) | |||||
{ | |||||
let название = событие.substring(сцена.length); | |||||
this.загрузитьСцену(название); | |||||
} | |||||
}; | |||||
this.переместитьИгрока = function() | |||||
{ | |||||
let цель = this.задано["цель"]; | |||||
let объект = this.задано["игрок.объект"]; | |||||
let скоростьX = this.задано["игрок.скорость.0"]; | |||||
if (!this.типИгрыУказатьИНажать() || !цель || !объект || !скоростьX) | |||||
{ | |||||
return; | |||||
} | |||||
let игрок = `объекты-${объект}`; | |||||
let телоЦели = тела.тела[цель]; | |||||
let телоИгрока = тела.тела[игрок]; | |||||
if (!телоЦели || !телоИгрока) | |||||
{ | |||||
return; | |||||
} | |||||
let игрокX = телоИгрока.position.x; | |||||
let цельX = телоЦели.position.x; | |||||
let ширина = телоЦели.bounds.max.x - телоЦели.bounds.min.x; | |||||
let расстояние = цельX - игрокX; | |||||
let направление = расстояние > 0 ? 1 : -1; | |||||
if (Math.abs(расстояние) < ширина / 2) | |||||
{ | |||||
this.задатьСостояниеИгрока(false, направление); | |||||
return; | |||||
} | |||||
this.задатьСостояниеИгрока(true, направление); | |||||
Matter.Sleeping.set(телоИгрока, false); | |||||
Matter.Body.setVelocity(телоИгрока, { x: скоростьX * направление, y: 0 }); | |||||
}; | |||||
this.разобратьОглавление = function(содержимое) | |||||
{ | |||||
let код = "this.сцены = " + содержимое; | |||||
eval(код); | |||||
}; | |||||
this.типИгрыУказатьИНажать = function() | |||||
{ | |||||
var тип = this.задано["тип"]; | |||||
return тип && тип == "указать и нажать"; | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
}; |
@@ -0,0 +1 @@ | |||||
../../стороннее/matter-js |
@@ -0,0 +1 @@ | |||||
../../стороннее/uikit |
@@ -0,0 +1,86 @@ | |||||
var мир = {}; | |||||
мир.uuid = function() | |||||
{ | |||||
// https://stackoverflow.com/a/2117523 | |||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( | |||||
/[xy]/g, | |||||
function(c) | |||||
{ | |||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | |||||
return v.toString(16); | |||||
} | |||||
); | |||||
}; | |||||
мир.вZ64 = function(строка) | |||||
{ | |||||
var байты = new TextEncoder("utf-8").encode(строка); | |||||
var архив = pako.deflate(байты, { to: 'string' }); | |||||
return base64js.fromByteArray(архив); | |||||
}; | |||||
мир.изZ64 = function(строка) | |||||
{ | |||||
var архив = base64js.toByteArray(строка); | |||||
var байты = pako.inflate(архив); | |||||
return new TextDecoder("utf-8").decode(байты); | |||||
}; | |||||
мир.задатьПолныйКлюч = function(словарь, полныйКлюч, значение) { | |||||
var путь = полныйКлюч.split("."); | |||||
var пройти = путь.length - 1; | |||||
var вложенность = словарь; | |||||
for (var номер = 0; номер < пройти; ++номер) | |||||
{ | |||||
var ключ = путь[номер]; | |||||
if (!(ключ in вложенность)) | |||||
{ | |||||
вложенность[ключ] = {}; | |||||
вложенность = вложенность[ключ]; | |||||
} | |||||
} | |||||
// Значение в конце вложенности. | |||||
var ключ = путь[пройти]; | |||||
вложенность[ключ] = значение; | |||||
}; | |||||
мир.назначитьКнопкамСобытия = function(события, список) | |||||
{ | |||||
for (var номер in список) | |||||
{ | |||||
const пара = список[номер]; | |||||
var кнопка = document.getElementById(пара[0]); | |||||
кнопка.addEventListener("click", function(_) { | |||||
события.уведомить(пара[1]); | |||||
}); | |||||
} | |||||
}; | |||||
мир.параметрыЗапуска = function() | |||||
{ | |||||
var параметры = {}; | |||||
var запрос = window.location.search.substring(1); | |||||
var аргументы = запрос.split("&"); | |||||
for (var номер in аргументы) | |||||
{ | |||||
var арг = аргументы[номер]; | |||||
var позицияЗнака = арг.indexOf("="); | |||||
// Лишь ключ. | |||||
if (позицияЗнака == -1) | |||||
{ | |||||
var ключ = decodeURIComponent(арг); | |||||
параметры[ключ] = null; | |||||
} | |||||
// Ключ со значением. | |||||
else | |||||
{ | |||||
var сыройКлюч = арг.slice(0, позицияЗнака); | |||||
var сыроеЗначение = арг.slice(позицияЗнака + 1); | |||||
var ключ = decodeURIComponent(сыройКлюч); | |||||
var значение = decodeURIComponent(сыроеЗначение); | |||||
параметры[ключ] = значение; | |||||
} | |||||
} | |||||
return параметры; | |||||
}; |
@@ -0,0 +1,36 @@ | |||||
function События(uuid) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.обработчики = []; | |||||
this.обработчики = {}; | |||||
}; | |||||
this.подписать = function(обработчик, имя) | |||||
{ | |||||
let указатель = имя || uuid(); | |||||
this.обработчики[указатель] = обработчик; | |||||
}; | |||||
this.отписать = function(обработчик) { | |||||
for (var указатель in this.обработчики) { | |||||
let обр = this.обработчики[указатель]; | |||||
if (обр == обработчик) | |||||
{ | |||||
delete this.обработчики[указатель]; | |||||
return; | |||||
} | |||||
} | |||||
}; | |||||
this.уведомить = function(событие) { | |||||
for (var номер in this.обработчики) | |||||
{ | |||||
var обработчик = this.обработчики[номер]; | |||||
обработчик.обработатьСобытие(событие); | |||||
} | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,72 @@ | |||||
function Состояние() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.обработчик = null; | |||||
this.значения = {}; | |||||
}; | |||||
this.выпрямить = function(obj) | |||||
{ | |||||
// https://stackoverflow.com/a/42121920 | |||||
var newObj = {}; | |||||
for (var key in obj) | |||||
{ | |||||
if (typeof obj[key] === 'object' && obj[key] !== null) | |||||
{ | |||||
var temp = this.выпрямить(obj[key]) | |||||
for (var key2 in temp) | |||||
{ | |||||
newObj[key + "." + key2] = temp[key2]; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
newObj[key] = obj[key]; | |||||
} | |||||
} | |||||
return newObj; | |||||
}; | |||||
this.лишьНовыеЗначения = function(значения) | |||||
{ | |||||
var новые = {}; | |||||
for (var ключ in значения) | |||||
{ | |||||
var было = this.значения[ключ]; | |||||
var стало = значения[ключ]; | |||||
if (!(было != null && было == стало)) | |||||
{ | |||||
новые[ключ] = стало; | |||||
} | |||||
} | |||||
return новые; | |||||
}; | |||||
this.разобрать = function(словарь) | |||||
{ | |||||
this.разобратьВыпрямленный(this.выпрямить(словарь)); | |||||
}; | |||||
this.разобратьВыпрямленный = function(выпрямленныйСловарь) | |||||
{ | |||||
for (var ключ in выпрямленныйСловарь) | |||||
{ | |||||
let путь = ключ.split("."); | |||||
let значение = выпрямленныйСловарь[ключ]; | |||||
if (this.обработчик) | |||||
{ | |||||
this.обработчик(ключ, путь, значение); | |||||
} | |||||
this.значения[ключ] = значение; | |||||
} | |||||
}; | |||||
this.разобратьЛишьНовое = function(словарь) | |||||
{ | |||||
this.разобратьВыпрямленный(this.лишьНовыеЗначения(this.выпрямить(словарь))); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,4 @@ | |||||
мир.разобрать = function(словарь) | |||||
{ | |||||
мир.состояние.разобратьЛишьНовое(словарь); | |||||
}; |
@@ -0,0 +1 @@ | |||||
../../стороннее/base64-js |
@@ -0,0 +1 @@ | |||||
../../стороннее/pako |
@@ -0,0 +1,123 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> | |||||
<title>M-5.1</title> | |||||
<script src="общее/base64-js/base64js.min.js"></script> | |||||
<script src="общее/pako/pako.min.js"></script> | |||||
<script src="редактор/ace/ace.js"></script> | |||||
<script src="редактор/ace/mode-javascript.js"></script> | |||||
<script src="редактор/ace/worker-javascript.js"></script> | |||||
<link rel="stylesheet" type="text/css" href="редактор/uikit/uikit.min.css" /> | |||||
<script src="редактор/uikit/uikit.min.js"></script> | |||||
<script src="редактор/uikit/uikit-icons.min.js"></script> | |||||
<style> | |||||
#редактор | |||||
{ | |||||
position: absolute; | |||||
left: 0; | |||||
top: 0; | |||||
bottom: 0; | |||||
width: calc(50% - 2em); | |||||
} | |||||
#пульт | |||||
{ | |||||
position: absolute; | |||||
left: calc(50% - 2em); | |||||
top: 0; | |||||
bottom: 0; | |||||
width: 4em; | |||||
display: flex; | |||||
flex-direction: column; | |||||
border: 1px dashed gainsboro; | |||||
} | |||||
#проигрыватель | |||||
{ | |||||
position: absolute; | |||||
right: 0; | |||||
top: 0; | |||||
bottom: 0; | |||||
width: calc(50% - 2em); | |||||
height: 100%; | |||||
border: 0px none; | |||||
} | |||||
.впульте | |||||
{ | |||||
margin: 0 auto; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<div id="редактор"></div> | |||||
<div id="пульт"> | |||||
<span id="ошибка" class="впульте"> </span> | |||||
<a id="перезапустить" class="впульте uk-icon-button uk-margin-top" uk-icon="refresh" uk-tooltip="Перезапустить"></a> | |||||
<a id="отдельно" class="впульте uk-icon-button uk-margin-top" uk-icon="desktop" uk-tooltip="Запустить в отдельной вкладке"></a> | |||||
<a id="ссылка" class="впульте uk-icon-button uk-margin-top" uk-icon="link" uk-tooltip="Скопировать ссылку"></a> | |||||
<a id="документация" class="впульте uk-icon-button uk-margin-top" uk-icon="question" uk-tooltip="Документация"></a> | |||||
<a id="версия" class="впульте uk-icon-button uk-margin-top" uk-icon="git-branch" uk-tooltip="Версия"></a> | |||||
<div uk-dropdown="mode: click"> | |||||
<table class="uk-table"> | |||||
<tbody> | |||||
<tr> | |||||
<td>5.1</td> | |||||
<td>html, body height = 100%</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../5.0/редактор.html">5.0</a></td> | |||||
<td>мир.разобрать, мир.события.подписать, gr.подсказки, gr.реплики, gr.сцена</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../4.1/редактор.html">4.1</a></td> | |||||
<td>анимации, подсказки, реплики, состояния, столкновения, сцена</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../4.0/редактор.html">4.0</a></td> | |||||
<td>виды, физика; переработанные объекты, изображения, тела</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../3.5/редактор.html">3.5</a></td> | |||||
<td>объекты</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../3.4/редактор.html">3.4</a></td> | |||||
<td>Документация</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../3.3/редактор/index.html">3.3</a></td> | |||||
<td>тела, слежение</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../3.2/редактор/index.html">3.2</a></td> | |||||
<td>мир.параметрыЗапуска, мир.разобрать, заголовок, изображения, состояние, физика</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../3.1/редактор/index.html">3.1</a></td> | |||||
<td>Кнопки перезапуска, копирования ссылки, запуска в отдельной вкладке</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../3.0/редактор/index.html">3.0</a></td> | |||||
<td>Проверка введённого кода на отсутствие синтаксических ошибок</td> | |||||
</tr> | |||||
</tbody> | |||||
</table> | |||||
</div> | |||||
</div> | |||||
<iframe id="проигрыватель" name="проигрыватель"></iframe> | |||||
<script src="общее/050.функции.js"></script> | |||||
<script src="общее/100.События.js"></script> | |||||
<script src="общее/150.Состояние.js"></script> | |||||
<script src="общее/200.обёртки.js"></script> | |||||
<script src="редактор/100.Редактор.js"></script> | |||||
<script src="редактор/200.Пульт.js"></script> | |||||
<script src="редактор/700.Муром.js"></script> | |||||
<script> | |||||
var муром = new Муром(); | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,127 @@ | |||||
function Редактор(события, имяРедактора) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.первоначальныйЗаголовок = document.title; | |||||
this.установитьAce(); | |||||
this.улавливатьЗавершениеРедактирования(); | |||||
this.задатьКодПриЗапуске(); | |||||
события.подписать(this); | |||||
}; | |||||
this.задатьКодПриЗапуске = function() | |||||
{ | |||||
var z64 = мир.параметрыЗапуска()["z64"]; | |||||
if (z64) | |||||
{ | |||||
var код = мир.изZ64(z64); | |||||
this.ace.session.setValue(код); | |||||
} | |||||
}; | |||||
this.запуститьОтдельно = function() | |||||
{ | |||||
var содержимое = this.ace.session.getValue(); | |||||
var z64 = мир.вZ64(содержимое); | |||||
var путь = window.location.pathname + "/../игра.html?z64=" + z64; | |||||
window.open(путь); | |||||
}; | |||||
this.исполнитьКод = function() | |||||
{ | |||||
var содержимое = this.ace.session.getValue(); | |||||
var z64 = мир.вZ64(содержимое); | |||||
// Исполняем явно код лишь первый раз. | |||||
var проигрыватель = document.getElementById("проигрыватель"); | |||||
if (!проигрыватель.src) | |||||
{ | |||||
проигрыватель.src = "игра.html?z64=" + z64; | |||||
} | |||||
// После запуска уведомляем iframe о новом коде без перезагрузки. | |||||
else | |||||
{ | |||||
window.frames.проигрыватель.postMessage(z64, "*"); | |||||
} | |||||
}; | |||||
this.обновитьАдреснуюСтрокуИЗаголовок = function() | |||||
{ | |||||
var содержимое = this.ace.session.getValue(); | |||||
var z64 = мир.вZ64(содержимое); | |||||
var путь = window.location.pathname + "?z64=" + z64; | |||||
history.pushState(null, "", путь); | |||||
document.title = `${this.первоначальныйЗаголовок} ${z64.length}:${z64.slice(z64.length - 5)}`; | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
if (событие == "завершили редактирование") | |||||
{ | |||||
this.обновитьАдреснуюСтрокуИЗаголовок(); | |||||
this.проверитьКорректностьКода(); | |||||
} | |||||
else if (событие == "код корректен") | |||||
{ | |||||
this.исполнитьКод(); | |||||
} | |||||
else if (событие == "перезапустить") | |||||
{ | |||||
this.перезапуститьКод(); | |||||
} | |||||
else if (событие == "запустить отдельно") | |||||
{ | |||||
this.запуститьОтдельно(); | |||||
} | |||||
}; | |||||
this.перезапуститьКод = function() | |||||
{ | |||||
var содержимое = this.ace.session.getValue(); | |||||
var z64 = мир.вZ64(содержимое); | |||||
var проигрыватель = document.getElementById("проигрыватель"); | |||||
проигрыватель.src = "игра.html?z64=" + z64; | |||||
}; | |||||
this.проверитьКорректностьКода = function() | |||||
{ | |||||
try | |||||
{ | |||||
eval(this.ace.session.getValue()); | |||||
события.уведомить("код корректен"); | |||||
} | |||||
catch (ошибка) | |||||
{ | |||||
события.уведомить("код некорректен"); | |||||
} | |||||
}; | |||||
this.улавливатьЗавершениеРедактирования = function() | |||||
{ | |||||
var тут = this; | |||||
this.ace.session.on("change", function(дельта) { | |||||
const билет = мир.uuid(); | |||||
тут.билет = билет; | |||||
setTimeout( | |||||
function() | |||||
{ | |||||
if (билет == тут.билет) | |||||
{ | |||||
события.уведомить("завершили редактирование"); | |||||
} | |||||
}, | |||||
300 | |||||
); | |||||
}); | |||||
}; | |||||
this.установитьAce = function() | |||||
{ | |||||
var область = document.getElementById(имяРедактора); | |||||
this.ace = window.ace.edit(имяРедактора); | |||||
this.ace.session.setMode("ace/mode/javascript"); | |||||
this.ace.session.setUseWrapMode(true); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,53 @@ | |||||
function Пульт(события, имяОшибки, имяПерезапуска, имяОтдельно, имяСсылки, имяДокументации) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
var ошибка = document.getElementById(имяОшибки); | |||||
мир.назначитьКнопкамСобытия( | |||||
события, | |||||
[ | |||||
[имяПерезапуска, "перезапустить"], | |||||
[имяОтдельно, "запустить отдельно"], | |||||
[имяСсылки, "скопировать ссылку"], | |||||
[имяДокументации, "открыть документацию"], | |||||
] | |||||
); | |||||
события.подписать(this); | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
if (событие == "код корректен") | |||||
{ | |||||
this.показатьОшибку(false); | |||||
} | |||||
else if (событие == "код некорректен") | |||||
{ | |||||
this.показатьОшибку(true); | |||||
} | |||||
else if (событие == "скопировать ссылку") | |||||
{ | |||||
navigator.clipboard.writeText(window.location) | |||||
.then(() => { | |||||
UIkit.notification("Скопировали ссылку", { status: "success", timeout: 1000 }); | |||||
}) | |||||
.catch(err => { | |||||
UIkit.notification("Не удалось скопировать ссылку :(", { status: "danger", timeout: 5000 }); | |||||
}); | |||||
} | |||||
else if (событие == "открыть документацию") | |||||
{ | |||||
var путь = window.location.pathname + "/../../doc/ru/index.html"; | |||||
window.open(путь); | |||||
} | |||||
}; | |||||
this.показатьОшибку = function(показать) | |||||
{ | |||||
ошибка.innerHTML = показать ? "<span uk-icon=\"icon: warning\"></span>" : " "; | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,13 @@ | |||||
function Муром() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
мир.события = new События(мир.uuid); | |||||
мир.состояние = new Состояние(); | |||||
мир.редактор = new Редактор(мир.события, "редактор"); | |||||
мир.пульт = new Пульт(мир.события, "ошибка", "перезапустить", "отдельно", "ссылка", "документация"); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1 @@ | |||||
../../стороннее/ace |
@@ -0,0 +1 @@ | |||||
../../стороннее/uikit |