vimp-engine 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/package.json +11 -4
  2. package/src/client/InputListener.js +36 -0
  3. package/src/client/SoundManager.js +498 -0
  4. package/src/client/boot.js +113 -0
  5. package/src/client/components/controller/Auth.js +44 -0
  6. package/src/client/components/controller/CanvasManager.js +26 -0
  7. package/src/client/components/controller/Chat.js +56 -0
  8. package/src/client/components/controller/Controls.js +51 -0
  9. package/src/client/components/controller/Game.js +37 -0
  10. package/src/client/components/controller/Lobby.js +81 -0
  11. package/src/client/components/controller/LobbyAuth.js +41 -0
  12. package/src/client/components/controller/Panel.js +23 -0
  13. package/src/client/components/controller/Stat.js +33 -0
  14. package/src/client/components/controller/Vote.js +55 -0
  15. package/src/client/components/model/Auth.js +92 -0
  16. package/src/client/components/model/CanvasManager.js +319 -0
  17. package/src/client/components/model/Chat.js +119 -0
  18. package/src/client/components/model/Controls.js +120 -0
  19. package/src/client/components/model/Game.js +172 -0
  20. package/src/client/components/model/Lobby.js +226 -0
  21. package/src/client/components/model/LobbyAuth.js +161 -0
  22. package/src/client/components/model/Panel.js +70 -0
  23. package/src/client/components/model/Stat.js +77 -0
  24. package/src/client/components/model/Vote.js +216 -0
  25. package/src/client/components/view/Auth.js +167 -0
  26. package/src/client/components/view/CanvasManager.js +44 -0
  27. package/src/client/components/view/Chat.js +90 -0
  28. package/src/client/components/view/Controls.js +30 -0
  29. package/src/client/components/view/Game.js +48 -0
  30. package/src/client/components/view/Lobby.js +303 -0
  31. package/src/client/components/view/LobbyAuth.js +111 -0
  32. package/src/client/components/view/Panel.js +314 -0
  33. package/src/client/components/view/Stat.js +238 -0
  34. package/src/client/components/view/Vote.js +108 -0
  35. package/src/client/debug.js +158 -0
  36. package/src/client/lib/autostart.js +61 -0
  37. package/src/client/lib/contextTracker.js +46 -0
  38. package/src/client/lib/formBuilder.js +280 -0
  39. package/src/client/lib/hostGate.js +16 -0
  40. package/src/client/main.js +2016 -0
  41. package/src/client/network/HostConnectionManager.js +196 -0
  42. package/src/client/network/HostController.js +422 -0
  43. package/src/client/network/InlineHostBridge.js +152 -0
  44. package/src/client/network/LoopbackTransport.js +51 -0
  45. package/src/client/network/SignalingClient.js +146 -0
  46. package/src/client/network/WebRtcManager.js +147 -0
  47. package/src/client/network/WebSocketTransport.js +78 -0
  48. package/src/client/network/policyClose.js +54 -0
  49. package/src/client/network/workerSupport.js +34 -0
  50. package/src/client/providers/BakingProvider.js +88 -0
  51. package/src/client/providers/DependencyProvider.js +41 -0
  52. package/src/client/style.css +898 -0
  53. package/src/client/views/gameShell.js +190 -0
  54. package/src/client/views/includes/auth.pug +20 -0
  55. package/src/client/views/includes/chat.pug +5 -0
  56. package/src/client/views/includes/informer.pug +2 -0
  57. package/src/client/views/includes/lobby.pug +48 -0
  58. package/src/client/views/includes/lobbyAuth.pug +19 -0
  59. package/src/client/views/includes/panel.pug +3 -0
  60. package/src/client/views/includes/stat.pug +2 -0
  61. package/src/client/views/index.pug +8 -0
  62. package/src/config/closeCodes.js +18 -0
  63. package/src/config/env.js +61 -0
  64. package/src/devtools/ScenarioRunner.js +3 -8
  65. package/src/devtools/pluginLoader.js +8 -93
  66. package/src/host/HostGame.js +41 -3
  67. package/src/host/PortMachine.js +294 -0
  68. package/src/host/host.worker.js +29 -246
  69. package/src/host/identity.js +100 -0
  70. package/src/lib/clientIp.js +39 -0
  71. package/src/lib/loadGamePackage.js +122 -0
  72. package/src/lib/offlinePlayerData.js +18 -0
  73. package/src/standalone/index.js +125 -0
  74. package/tests/fixtures/miniGame/config/game.js +4 -1
@@ -0,0 +1,77 @@
1
+ import Publisher from '../../../lib/Publisher.js';
2
+
3
+ // Singleton StatModel
4
+
5
+ let statModel;
6
+
7
+ export default class StatModel {
8
+ constructor(data) {
9
+ if (statModel) {
10
+ return statModel;
11
+ }
12
+
13
+ statModel = this;
14
+
15
+ this._heads = data.heads;
16
+ this._bodies = data.bodies;
17
+ this._sortList = data.sortList;
18
+ this.publisher = new Publisher();
19
+ }
20
+
21
+ // открывает статистику
22
+ open() {
23
+ this.publisher.emit('open');
24
+ this.publisher.emit('mode', { name: 'stat', status: 'opened' });
25
+ }
26
+
27
+ // закрывает статистику
28
+ close() {
29
+ this.publisher.emit('close');
30
+ this.publisher.emit('mode', { name: 'stat', status: 'closed' });
31
+ }
32
+
33
+ // обновляет данные статистики
34
+ update(data) {
35
+ const tBodiesData = data[0];
36
+ const tHeadData = data[1];
37
+ const fullStatFlag = data[2];
38
+
39
+ // если обновление полное, требуется очистить таблицы <tbody>
40
+ // очищать <thead> не требуется, в tHeadData есть актуальные данные
41
+ if (fullStatFlag === true) {
42
+ this.publisher.emit('clearBodies', Object.values(this._bodies));
43
+ }
44
+
45
+ // если есть данные для <tbody>
46
+ if (tBodiesData) {
47
+ for (let i = 0, len = tBodiesData.length; i < len; i += 1) {
48
+ const tableId = this._bodies[tBodiesData[i][1]];
49
+
50
+ if (tableId) {
51
+ this.publisher.emit('tBody', {
52
+ id: tBodiesData[i][0],
53
+ tableId,
54
+ cellsData: tBodiesData[i][2],
55
+ sortData: this._sortList[tableId],
56
+ bodyNumber: tBodiesData[i][3] || 0,
57
+ });
58
+ }
59
+ }
60
+ }
61
+
62
+ // если есть данные для <thead>
63
+ if (tHeadData) {
64
+ for (let i = 0, len = tHeadData.length; i < len; i += 1) {
65
+ const tableId = this._heads[tHeadData[i][0]];
66
+
67
+ if (tableId) {
68
+ this.publisher.emit('tHead', {
69
+ tableId,
70
+ cellsData: tHeadData[i][1],
71
+ rowNumber: tHeadData[i][2] || 0,
72
+ });
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,216 @@
1
+ import Publisher from '../../../lib/Publisher.js';
2
+
3
+ // Singleton VoteModel
4
+
5
+ let voteModel;
6
+
7
+ export default class VoteModel {
8
+ constructor(data) {
9
+ if (voteModel) {
10
+ return voteModel;
11
+ }
12
+
13
+ voteModel = this;
14
+
15
+ this._formatMessage = data.formatMessage;
16
+
17
+ this._menu = data.menu; // меню
18
+ this._templates = data.templates; // шаблоны голосований
19
+
20
+ this._type = ''; // тип ('menu', 'vote')
21
+ this._waitingValues = false; // ожидания значений
22
+
23
+ this._time = data.time || 10000; // время жизни голосования
24
+ this._timerId = null; // id таймера
25
+
26
+ this._timeOff = false; // флаг отключения времени жизни голосования
27
+
28
+ this._voteName = ''; // название голосования
29
+
30
+ this._title = null; // заголовок голосования
31
+ this._values = []; // все значения голосования
32
+
33
+ this._back = false; // флаг back
34
+ this._more = false; // флаг more
35
+ this._currentPage = 0; // текущая страница вывода значений
36
+ this._currentValues = []; // значения текущей страницы
37
+
38
+ this.publisher = new Publisher();
39
+ }
40
+
41
+ // открывает голосование
42
+ open() {
43
+ this.publisher.emit('mode', { name: 'vote', status: 'opened' });
44
+ }
45
+
46
+ createWithTemplate({ name, params, values }) {
47
+ const templateArr = this._templates[name];
48
+
49
+ if (templateArr) {
50
+ let title = templateArr[0];
51
+ values = values || templateArr[1];
52
+ const timeOff = templateArr[2] ? true : false;
53
+
54
+ if (params) {
55
+ title = this._formatMessage(title, params);
56
+ }
57
+
58
+ this.createVote(name, title, values, timeOff);
59
+ }
60
+ }
61
+
62
+ // создает голосование
63
+ createVote(name, title, values, timeOff) {
64
+ if (this._waitingValues) {
65
+ return;
66
+ }
67
+
68
+ this._type = 'vote';
69
+ this._back = false;
70
+ this._more = false;
71
+ this._currentPage = 0;
72
+
73
+ this._voteName = name;
74
+ this._timeOff = timeOff;
75
+ this._title = title;
76
+
77
+ if (typeof values === 'string') {
78
+ this._waitingValues = true;
79
+ this.publisher.emit('socket', values);
80
+ } else {
81
+ this._values = values;
82
+ this.show();
83
+ }
84
+ }
85
+
86
+ // создает меню
87
+ createMenu() {
88
+ if (this._waitingValues) {
89
+ return;
90
+ }
91
+
92
+ this._timeOff = false;
93
+
94
+ this._type = 'menu';
95
+ this._back = false;
96
+ this._more = false;
97
+ this._currentPage = 0;
98
+
99
+ this._title = 'Menu';
100
+ this._values = [];
101
+ this._voteName = '';
102
+
103
+ for (let i = 0, len = this._menu.length; i < len; i += 1) {
104
+ this._values.push(this._menu[i][1][0]);
105
+ }
106
+
107
+ this.show();
108
+ }
109
+
110
+ // обновляет массив значений
111
+ updateValues(values) {
112
+ if (this._waitingValues) {
113
+ this._values = values;
114
+ this._waitingValues = false;
115
+ this.show();
116
+ }
117
+ }
118
+
119
+ // обновляет голосование
120
+ update(keyCode) {
121
+ let number;
122
+
123
+ if (this._waitingValues) {
124
+ return;
125
+ }
126
+
127
+ // если keyCode это число от 0 до 9
128
+ if (48 <= keyCode && keyCode <= 57) {
129
+ number = String.fromCharCode(keyCode);
130
+ number = parseInt(number, 10);
131
+
132
+ // exit
133
+ if (number === 0) {
134
+ this.complete();
135
+ // back
136
+ } else if (number === 8) {
137
+ if (this._back) {
138
+ this._currentPage -= 1;
139
+ this.show();
140
+ }
141
+ // more
142
+ } else if (number === 9) {
143
+ if (this._more) {
144
+ this._currentPage += 1;
145
+ this.show();
146
+ }
147
+
148
+ // иначе, число от 1 до 7
149
+ } else {
150
+ number = number - 1;
151
+
152
+ // если тип данных для голосования это массив
153
+ if (this._type === 'menu') {
154
+ const data = this._menu[number];
155
+
156
+ // если число есть в массиве значений
157
+ if (data) {
158
+ const [name, [title, values, timeOff]] = data;
159
+
160
+ this.createVote(name, title, values, timeOff);
161
+ }
162
+
163
+ // иначе, если тип данных для голосования это объект
164
+ } else if (this._type === 'vote') {
165
+ const value = this._currentValues[number];
166
+
167
+ if (value) {
168
+ this.publisher.emit('socket', [this._voteName, value]);
169
+ this.complete();
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ // отображает голосование
177
+ show() {
178
+ const begin = this._currentPage * 7;
179
+ const max = begin + 7;
180
+ let currentValues = [];
181
+
182
+ this._currentValues = this._values.slice(begin, max);
183
+ this._back = this._currentPage > 0 ? true : false;
184
+ this._more = this._values.length > max ? true : false;
185
+
186
+ if (this._type === 'vote') {
187
+ for (let i = 0, len = this._currentValues.length; i < len; i += 1) {
188
+ currentValues.push(this._currentValues[i]);
189
+ }
190
+ } else {
191
+ currentValues = this._currentValues;
192
+ }
193
+
194
+ this.publisher.emit('clear', this._timerId);
195
+
196
+ this.publisher.emit('vote', {
197
+ title: this._title,
198
+ list: currentValues,
199
+ back: this._back,
200
+ more: this._more,
201
+ time: this._timeOff === true ? null : this._time,
202
+ });
203
+ }
204
+
205
+ // завершает голосование
206
+ complete() {
207
+ this._waitingValues = false;
208
+ this.publisher.emit('clear', this._timerId);
209
+ this.publisher.emit('mode', { name: 'vote', status: 'closed' });
210
+ }
211
+
212
+ // добавляет id таймера голосования
213
+ assignTimer(timerId) {
214
+ this._timerId = timerId || null;
215
+ }
216
+ }
@@ -0,0 +1,167 @@
1
+ import Publisher from '../../../lib/Publisher.js';
2
+ import { buildForm, reportFormValidity } from '../../lib/formBuilder.js';
3
+
4
+ // Singleton AuthView
5
+
6
+ let authView;
7
+
8
+ export default class AuthView {
9
+ // texts — игровые тексты формы (authSchema.texts: title, sections);
10
+ // сам каркас (auth.pug) нейтрален и текстов игры не содержит.
11
+ // params — та же дескрипторная схема формы, что и у room-формы
12
+ // (docs/en/plugin-api.md "Form schema"), едет по сети в PS_AUTH_DATA
13
+ constructor(model, elems, texts = null, params = null) {
14
+ if (authView) {
15
+ return authView;
16
+ }
17
+
18
+ authView = this;
19
+
20
+ this._mPublic = model.publisher;
21
+
22
+ this._auth = document.getElementById(elems.authId);
23
+ this._error = document.getElementById(elems.errorId);
24
+ this._enter = document.getElementById(elems.enterId);
25
+ this._fieldsContainer = document.getElementById(elems.fieldsId);
26
+ this._fields = new Map();
27
+
28
+ this.publisher = new Publisher();
29
+
30
+ this._renderTexts(elems, texts);
31
+ this._renderFields(elems, params);
32
+
33
+ // форма заполнена; нативная проверка (pattern/required) — сервер всё
34
+ // равно валидирует своими validators (renderError), это лишь UX-фильтр
35
+ // до отправки
36
+ this._enter.onclick = () => {
37
+ if (reportFormValidity(this._fieldsContainer)) {
38
+ authView.publisher.emit('enter');
39
+ }
40
+ };
41
+
42
+ this._mPublic.on('form', 'renderData', this);
43
+ this._mPublic.on('error', 'renderError', this);
44
+ this._mPublic.on('ok', 'hideAuth', this);
45
+ }
46
+
47
+ // строит контролы формы игрока из той же дескрипторной схемы, что и
48
+ // room-форма (единый formBuilder, docs/en/plugin-api.md "Form schema")
49
+ _renderFields(elems, params) {
50
+ if (!Array.isArray(params)) {
51
+ return;
52
+ }
53
+
54
+ const container = document.getElementById(elems.fieldsId);
55
+
56
+ if (!container) {
57
+ return;
58
+ }
59
+
60
+ // param.options — дескриптор-хвост (control/label/min/max/... —
61
+ // "options" здесь ключ протокола PS_AUTH_DATA, не список выбора select
62
+ const descriptors = params.map(({ name, value, options: descriptorRest }) => ({
63
+ name,
64
+ default: value,
65
+ ...descriptorRest,
66
+ }));
67
+
68
+ this._fields = buildForm(descriptors, container, {}, ({ name, value }) => {
69
+ this.publisher.emit('input', { name, value });
70
+ });
71
+ }
72
+
73
+ // заполняет нейтральный каркас текстами игры: заголовок и help-секции
74
+ // (sections: [{ heading, lines: [{ keys, text, last? } | { separator }] }])
75
+ _renderTexts(elems, texts) {
76
+ if (!texts) {
77
+ return;
78
+ }
79
+
80
+ const title = document.getElementById(elems.titleId);
81
+ const informs = document.getElementById(elems.informsId);
82
+
83
+ if (title && texts.title) {
84
+ title.textContent = texts.title;
85
+ }
86
+
87
+ if (!informs || !Array.isArray(texts.sections)) {
88
+ return;
89
+ }
90
+
91
+ informs.textContent = '';
92
+
93
+ for (const section of texts.sections) {
94
+ const block = document.createElement('div');
95
+
96
+ block.className = 'auth-inform';
97
+
98
+ if (section.heading) {
99
+ const heading = document.createElement('h4');
100
+
101
+ heading.textContent = section.heading;
102
+ block.appendChild(heading);
103
+ }
104
+
105
+ for (const line of section.lines || []) {
106
+ if (line.separator) {
107
+ block.appendChild(document.createElement('hr'));
108
+ continue;
109
+ }
110
+
111
+ const p = document.createElement('p');
112
+
113
+ if (line.last) {
114
+ p.className = 'last';
115
+ }
116
+
117
+ const keys = document.createElement('b');
118
+
119
+ keys.textContent = line.keys;
120
+ p.appendChild(keys);
121
+ p.appendChild(document.createTextNode(` - ${line.text}`));
122
+ block.appendChild(p);
123
+ }
124
+
125
+ informs.appendChild(block);
126
+ }
127
+ }
128
+
129
+ // показывает форму
130
+ showAuth() {
131
+ this._auth.style.display = 'block';
132
+ }
133
+
134
+ // скрывает форму
135
+ hideAuth(data) {
136
+ if (data) {
137
+ data.forEach(item => {
138
+ localStorage[item.name] = item.value;
139
+ });
140
+ }
141
+
142
+ this._auth.style.display = 'none';
143
+ }
144
+
145
+ // обновляет форму
146
+ renderData(data) {
147
+ const { name, value } = data;
148
+
149
+ this._error.textContent = '';
150
+ this._fields.get(name)?.setValue(value);
151
+ }
152
+
153
+ // отображает ошибки
154
+ renderError(data) {
155
+ this._error.textContent = '';
156
+
157
+ data.forEach(item => {
158
+ const name = item.name.toUpperCase();
159
+ const err = item.error;
160
+ const line = document.createElement('div');
161
+
162
+ line.textContent = err ? `${name}: ${err}` : `${name} is not correctly!`;
163
+
164
+ this._error.appendChild(line);
165
+ });
166
+ }
167
+ }
@@ -0,0 +1,44 @@
1
+ import Publisher from '../../../lib/Publisher.js';
2
+
3
+ // Singleton CanvasManagerView
4
+
5
+ let canvasManagerView;
6
+
7
+ export default class CanvasManagerView {
8
+ constructor(model, apps) {
9
+ if (canvasManagerView) {
10
+ return canvasManagerView;
11
+ }
12
+
13
+ canvasManagerView = this;
14
+
15
+ this._model = model;
16
+ this._apps = apps;
17
+
18
+ this.publisher = new Publisher();
19
+
20
+ this._mPublic = this._model.publisher;
21
+
22
+ this._mPublic.on('resize', 'resize', this);
23
+ this._mPublic.on('updateCoords', 'updateCoords', this);
24
+ }
25
+
26
+ // изменяет размеры canvas
27
+ resize({ id, sizes }) {
28
+ const app = this._apps[id];
29
+
30
+ app.renderer.resize(sizes.width, sizes.height);
31
+ }
32
+
33
+ // вычисляет координаты для отображения и обновляет полотно
34
+ updateCoords({ id, coords, scale }) {
35
+ const app = this._apps[id];
36
+ const { width, height } = app.canvas;
37
+ const x = width / 2 - coords.x * scale;
38
+ const y = height / 2 - coords.y * scale;
39
+
40
+ app.stage.position.set(x, y);
41
+ app.stage.scale.set(scale);
42
+ app.render();
43
+ }
44
+ }
@@ -0,0 +1,90 @@
1
+ import Publisher from '../../../lib/Publisher.js';
2
+
3
+ // Singleton ChatView
4
+
5
+ let chatView;
6
+
7
+ export default class ChatView {
8
+ constructor(model, elems) {
9
+ if (chatView) {
10
+ return chatView;
11
+ }
12
+
13
+ chatView = this;
14
+
15
+ this._chat = document.getElementById(elems.chatBox);
16
+ this._cmd = document.getElementById(elems.cmd);
17
+
18
+ this.publisher = new Publisher();
19
+
20
+ this._mPublic = model.publisher;
21
+
22
+ this._mPublic.on('open', 'openCmd', this);
23
+ this._mPublic.on('close', 'closeCmd', this);
24
+ this._mPublic.on('newLine', 'createLine', this);
25
+ this._mPublic.on('oldLine', 'removeLine', this);
26
+ this._mPublic.on('newTimer', 'createTimer', this);
27
+ this._mPublic.on('oldTimer', 'removeTimer', this);
28
+ }
29
+
30
+ // открывает командную строку
31
+ openCmd() {
32
+ this._cmd.value = '';
33
+ this._cmd.style.display = 'block';
34
+ this._cmd.focus();
35
+ }
36
+
37
+ // закрывает командную строку
38
+ closeCmd(success) {
39
+ if (success) {
40
+ this.publisher.emit('message', this._cmd.value);
41
+ }
42
+
43
+ this._cmd.style.display = 'none';
44
+ this._cmd.value = '';
45
+ }
46
+
47
+ // добавляет сообщение в чат-лист
48
+ createLine(data) {
49
+ const line = document.createElement('div');
50
+ const id = data.id;
51
+ const message = data.message;
52
+ const text = message[0];
53
+ const name = message[1] || 'System';
54
+ const type = typeof message[2] === 'number' ? message[2] : '';
55
+
56
+ line.id = `line_${id}`;
57
+ line.className = `line${type}`;
58
+ line.setAttribute('data-name', `${name}: `);
59
+ line.textContent = text;
60
+
61
+ this._chat.appendChild(line);
62
+ }
63
+
64
+ // удаляет сообщение в чат-листе
65
+ removeLine(id) {
66
+ const line = document.getElementById(`line_${id}`);
67
+
68
+ line.style.opacity = 0;
69
+
70
+ setTimeout(() => {
71
+ this._chat.removeChild(line);
72
+ }, 2000);
73
+ }
74
+
75
+ // устанавливает таймер
76
+ createTimer(data) {
77
+ const messageId = data.id;
78
+ const time = data.time;
79
+ const timerId = setTimeout(() => {
80
+ this.publisher.emit('oldTimer');
81
+ }, time);
82
+
83
+ this.publisher.emit('newTimer', { messageId, timerId });
84
+ }
85
+
86
+ // снимает таймер
87
+ removeTimer(timer) {
88
+ clearTimeout(timer);
89
+ }
90
+ }
@@ -0,0 +1,30 @@
1
+ // Singleton controlsView
2
+
3
+ let controlsView;
4
+
5
+ export default class ControlsView {
6
+ constructor(model) {
7
+ if (controlsView) {
8
+ return controlsView;
9
+ }
10
+
11
+ controlsView = this;
12
+
13
+ this._model = model;
14
+
15
+ this._cursorTimerId = null;
16
+ }
17
+
18
+ // показывает курсор и запускает таймер его скрытия
19
+ resetCursorHideTimer() {
20
+ // сбрасывает старый таймер
21
+ clearTimeout(this._cursorTimerId);
22
+
23
+ document.body.classList.remove('hide-cursor');
24
+
25
+ // запускает новый таймер через 3 секунды бездействия мыши
26
+ this._cursorTimerId = setTimeout(() => {
27
+ document.body.classList.add('hide-cursor');
28
+ }, 3000);
29
+ }
30
+ }
@@ -0,0 +1,48 @@
1
+ import Publisher from '../../../lib/Publisher.js';
2
+
3
+ // GameView
4
+
5
+ export default class GameView {
6
+ constructor(model, app) {
7
+ this._app = app;
8
+
9
+ this._model = model;
10
+
11
+ this.publisher = new Publisher();
12
+
13
+ // подписка на события модели
14
+ this._mPublic = this._model.publisher;
15
+
16
+ this._mPublic.on('create', 'add', this);
17
+ this._mPublic.on('createEffect', 'addEffect', this);
18
+ this._mPublic.on('remove', 'remove', this);
19
+ }
20
+
21
+ // создает экземпляр на полотне
22
+ add(instance) {
23
+ this._app.stage.addChild(instance);
24
+
25
+ this._app.stage.sortChildren((a, b) => {
26
+ if (a.layer < b.layer) {
27
+ return -1;
28
+ }
29
+
30
+ if (a.layer > b.layer) {
31
+ return 1;
32
+ }
33
+
34
+ return 0;
35
+ });
36
+ }
37
+
38
+ // создаёт эффект и запускает его
39
+ addEffect(instance) {
40
+ this.add(instance);
41
+ instance.run();
42
+ }
43
+
44
+ // удаляет экземпляр с полотна
45
+ remove(instance) {
46
+ instance.destroy();
47
+ }
48
+ }