Aleksmorshen commited on
Commit
a030a20
·
verified ·
1 Parent(s): 758ebd2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +312 -344
index.html CHANGED
@@ -3,415 +3,383 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
- <title>BabylonCraft Demo</title>
 
 
 
 
 
 
 
 
 
 
 
 
7
  <style>
8
- /* Базовые стили */
9
  html, body {
10
  overflow: hidden;
11
  width: 100%;
12
  height: 100%;
13
  margin: 0;
14
  padding: 0;
15
- font-family: sans-serif;
16
- color: white;
17
- background-color: #333; /* Фон для областей вне канваса */
18
  }
19
 
20
  #renderCanvas {
21
  width: 100%;
22
  height: 100%;
23
- touch-action: none; /* Отключаем стандартные действия браузера при касании (скролл, зум) */
24
- outline: none; /* Убираем рамку фокуса */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
- /* Стили для оверлея с информацией и управлением */
28
- #infoOverlay {
 
 
 
 
 
29
  position: absolute;
30
- top: 10px;
31
- left: 10px;
32
  background-color: rgba(0, 0, 0, 0.5);
33
  padding: 10px;
34
  border-radius: 5px;
35
- font-size: 12px;
36
- max-width: calc(100% - 40px); /* Чтобы не вылезал на мобилках */
37
  }
38
 
39
- /* Крестик в центре экрана */
40
- #crosshair {
41
- position: absolute;
42
- top: 50%;
43
- left: 50%;
44
- width: 10px;
45
- height: 10px;
46
- border: 1px solid white;
47
- transform: translate(-50%, -50%);
48
- pointer-events: none; /* Чтобы не мешал кликам */
49
- mix-blend-mode: difference; /* Чтобы был виден на любом фоне */
50
  }
51
 
52
- /* Простые кнопки для мобильных действий */
53
- #mobileControls {
 
 
 
 
 
54
  position: absolute;
55
  bottom: 20px;
56
- right: 20px;
57
- display: none; /* По умолчанию скрыты, показываем через JS */
58
- flex-direction: column;
59
- gap: 15px;
60
- }
61
-
62
- .mobile-button {
63
- background-color: rgba(255, 255, 255, 0.3);
64
- border: 1px solid rgba(255, 255, 255, 0.5);
65
- color: white;
66
- width: 60px;
67
- height: 60px;
 
 
68
  border-radius: 50%;
69
- font-size: 18px;
70
- font-weight: bold;
71
  display: flex;
72
  justify-content: center;
73
  align-items: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  cursor: pointer;
75
- user-select: none; /* Запретить выделение текста на кнопке */
 
 
 
 
76
  }
77
- .mobile-button:active {
78
- background-color: rgba(255, 255, 255, 0.5);
79
- }
80
-
81
- /* Медиа-запрос для показа мобильных кнопок на тач-устройствах */
82
- @media (hover: none) and (pointer: coarse) {
83
- #mobileControls {
84
- display: flex;
85
- }
86
  }
87
 
88
  </style>
89
- <!-- Подключаем Babylon.js с CDN -->
90
- <script src="https://cdn.babylonjs.com/babylon.js"></script>
91
- <!-- (Опционально) PEP для поддержки Pointer Events в старых браузерах -->
92
- <script src="https://code.jquery.com/pep/0.4.3/pep.js"></script>
93
  </head>
94
  <body>
95
- <canvas id="renderCanvas" touch-action="none"></canvas>
96
- <div id="infoOverlay">
97
- Управление:<br>
98
- - WASD: Движение<br>
99
- - Мышь: Осмотреться<br>
100
- - Левый клик / Кнопка "B": Сломать блок<br>
101
- - Правый клик / Кнопка "P": Поставить блок (земля)<br>
102
- - Пробел: Вверх<br>
103
- - Shift: Вниз<br>
104
- <br>
105
- <span id="fpsCounter">FPS: </span><br>
106
- <span id="blockInfo">Наведите курсор на блок</span>
107
  </div>
108
- <div id="crosshair">+</div>
109
 
110
- <!-- Кнопки для мобильных -->
111
- <div id="mobileControls">
112
- <div id="placeButton" class="mobile-button">P</div>
113
- <div id="breakButton" class="mobile-button">B</div>
 
 
 
 
114
  </div>
115
 
116
  <script>
117
  const canvas = document.getElementById('renderCanvas');
118
- const engine = new BABYLON.Engine(canvas, true, { stencil: true, preserveDrawingBuffer: true }, true); // Enable stencil/preserveDrawingBuffer if needed later
 
 
 
 
 
 
 
 
119
 
120
- // --- Переменные и константы ---
121
- const BLOCK_SIZE = 1;
122
- const PLAYER_HEIGHT = 1.8;
123
- const GRAVITY = -9.81; // Условная гравитация
124
- const MOVE_SPEED = 0.15;
125
- const MOUSE_SENSITIVITY = 0.002;
126
- const TOUCH_SENSITIVITY = 0.004; // Чувствительность для тач-управления камерой
127
 
128
- let currentBlockType = 'grass'; // Тип блока для установки
129
- let blocks = {}; // Объект для хранения координат существующих блоков { "x_y_z": mesh }
130
 
131
  // --- Создание сцены ---
132
- const createScene = function () {
133
- const scene = new BABYLON.Scene(engine);
134
- scene.clearColor = new BABYLON.Color3(0.5, 0.8, 1.0); // Цвет неба
135
- scene.collisionsEnabled = true; // Включаем проверку столкновений на уровне сцены
136
- scene.gravity = new BABYLON.Vector3(0, GRAVITY / 60, 0); // Применяем гравитацию (делим на ~FPS)
137
 
138
  // --- Камера ---
139
- // FreeCamera подходит для управления в стиле Minecraft
140
- const camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(5, PLAYER_HEIGHT + 5, 5), scene);
141
- camera.setTarget(BABYLON.Vector3.Zero());
142
  camera.attachControl(canvas, true);
143
 
144
- // Настройки управления камерой
145
- camera.speed = MOVE_SPEED; // Скорость движения WASD
146
- camera.inertia = 0.9; // Плавность остановки
147
- camera.angularSensibility = 1 / MOUSE_SENSITIVITY; // Инвертируем, т.к. в Babylon это делитель
148
- camera.keysUp = [87]; // W
149
- camera.keysDown = [83]; // S
150
- camera.keysLeft = [65]; // A
151
- camera.keysRight = [68]; // D
152
- camera.keysUpward = [32]; // Space
153
- camera.keysDownward = [16]; // Shift
154
-
155
- // Включаем физику для камеры (чтобы она падала и сталкивалась)
156
- camera.checkCollisions = true;
157
  camera.applyGravity = true;
158
- // Задаем "тело" для камеры для столкновений (эллипсоид)
159
- camera.ellipsoid = new BABYLON.Vector3(0.4, PLAYER_HEIGHT / 2, 0.4);
160
- camera.ellipsoidOffset = new BABYLON.Vector3(0, PLAYER_HEIGHT / 2, 0); // Смещаем центр эллипсоида
 
161
 
162
  // --- Освещение ---
163
- const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
 
164
  light.intensity = 0.8;
165
-
166
- // --- Материалы для блоков ---
167
- const materials = {
168
- grass: new BABYLON.StandardMaterial("grassMat", scene),
169
- dirt: new BABYLON.StandardMaterial("dirtMat", scene),
170
- stone: new BABYLON.StandardMaterial("stoneMat", scene),
171
- wood: new BABYLON.StandardMaterial("woodMat", scene),
172
- leaves: new BABYLON.StandardMaterial("leavesMat", scene),
173
- };
174
- materials.grass.diffuseColor = new BABYLON.Color3(0.2, 0.8, 0.2); // Зеленый
175
- materials.dirt.diffuseColor = new BABYLON.Color3(0.6, 0.4, 0.2); // Коричневый
176
- materials.stone.diffuseColor = new BABYLON.Color3(0.5, 0.5, 0.5); // Серый
177
- materials.wood.diffuseColor = new BABYLON.Color3(0.7, 0.5, 0.3); // Дерево
178
- materials.leaves.diffuseColor = new BABYLON.Color3(0.1, 0.6, 0.1); // Листва
179
- materials.leaves.alpha = 0.9; // Немного прозрачности для листвы (не идеально)
180
-
181
- // --- Генерация начального мира ---
182
- const worldSize = 16; // Размер плоского мира
183
- const groundLevel = 0;
184
-
185
- for (let x = -worldSize / 2; x < worldSize / 2; x++) {
186
- for (let z = -worldSize / 2; z < worldSize / 2; z++) {
187
- // Верхний слой - трава
188
- createBlock(x, groundLevel, z, 'grass', scene, materials);
189
- // Несколько слоев земли под травой
190
- createBlock(x, groundLevel - 1, z, 'dirt', scene, materials);
191
- createBlock(x, groundLevel - 2, z, 'dirt', scene, materials);
192
- // Нижний слой - камень
193
- createBlock(x, groundLevel - 3, z, 'stone', scene, materials);
194
- }
195
- }
196
-
197
- // Добавим простое "дерево" для примера
198
- createBlock(5, groundLevel + 1, -5, 'wood', scene, materials);
199
- createBlock(5, groundLevel + 2, -5, 'wood', scene, materials);
200
- createBlock(5, groundLevel + 3, -5, 'wood', scene, materials);
201
- // Листва
202
- for(let lx = 4; lx <= 6; lx++) {
203
- for(let ly = 4; ly <= 5; ly++) {
204
- for(let lz = -6; lz <= -4; lz++) {
205
- if(lx === 5 && lz === -5 && ly <= 3) continue; // Не ставим листву внутри ствола
206
- createBlock(lx, groundLevel + ly, lz, 'leaves', scene, materials);
207
- }
208
- }
209
- }
210
-
211
-
212
- // --- Функция создания блока ---
213
- function createBlock(x, y, z, type, scene, materials) {
214
- const blockId = `${x}_${y}_${z}`;
215
- if (blocks[blockId]) return; // Не создаем блок, если он уже есть
216
-
217
- const box = BABYLON.MeshBuilder.CreateBox("box_" + blockId, { size: BLOCK_SIZE }, scene);
218
- box.position = new BABYLON.Vector3(x + BLOCK_SIZE / 2, y + BLOCK_SIZE / 2, z + BLOCK_SIZE / 2);
219
- box.material = materials[type] || materials.stone; // Используем камень, если тип не найден
220
- box.checkCollisions = true; // Включаем столкновения для блока
221
- box.isPickable = true; // Разрешаем блоку быть "выбираемым" лучом
222
- box.metadata = { type: type, x: x, y: y, z: z }; // Сохраняем тип и координаты
223
-
224
- blocks[blockId] = box; // Добавляем в наш учет блоков
225
- return box;
226
- }
227
-
228
- // --- Логика взаимодействия (ломать/ставить блоки) ---
229
- let pointerLocked = false;
230
-
231
- // Захват/освобождение мыши для удобного управления камерой на ПК
232
- canvas.addEventListener("click", () => {
233
- if (!pointerLocked && !isTouchDevice()) { // Не захватывать на тач-устройствах
234
- canvas.requestPointerLock = canvas.requestPointerLock || canvas.msRequestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock;
235
- if (canvas.requestPointerLock) {
236
- canvas.requestPointerLock();
237
- }
238
- }
239
- });
240
-
241
- const lockChangeAlert = () => {
242
- if (document.pointerLockElement === canvas || document.mozPointerLockElement === canvas || document.webkitPointerLockElement === canvas) {
243
- console.log('Pointer locked');
244
- pointerLocked = true;
245
- } else {
246
- console.log('Pointer unlocked');
247
- pointerLocked = false;
248
- }
249
- }
250
- document.addEventListener('pointerlockchange', lockChangeAlert, false);
251
- document.addEventListener('mozpointerlockchange', lockChangeAlert, false);
252
- document.addEventListener('webkitpointerlockchange', lockChangeAlert, false);
253
-
254
-
255
- // Обработка кликов/касаний для ломания/установки
256
- scene.onPointerDown = function (evt, pickResult) {
257
- // evt.button === 0: Левая кнопка мыши / Основное касание
258
- // evt.button === 1: Средняя кнопка мыши
259
- // evt.button === 2: Правая кнопка мыши
260
-
261
- const isBreakAction = (evt.button === 0);
262
- const isPlaceAction = (evt.button === 2);
263
-
264
- handleInteraction(isBreakAction, isPlaceAction, pickResult);
265
- };
266
-
267
- // Обработка нажатий мобильных кнопок
268
- const placeButton = document.getElementById('placeButton');
269
- const breakButton = document.getElementById('breakButton');
270
-
271
- breakButton.addEventListener('pointerdown', (e) => {
272
- e.preventDefault(); // Предотвращаем стандартное поведение (например, двойной тап зум)
273
- const pickResult = scene.pick(scene.pointerX, scene.pointerY, (mesh) => mesh.isPickable);
274
- handleInteraction(true, false, pickResult); // Имитируем "ломание"
275
- });
276
- placeButton.addEventListener('pointerdown', (e) => {
277
- e.preventDefault();
278
- const pickResult = scene.pick(scene.pointerX, scene.pointerY, (mesh) => mesh.isPickable);
279
- handleInteraction(false, true, pickResult); // Имитируем "установку"
280
- });
281
-
282
-
283
- function handleInteraction(isBreak, isPlace, pickResult) {
284
- if (pickResult.hit && pickResult.pickedMesh && blocks[pickResult.pickedMesh.name.replace('box_','')]) {
285
- const pickedMesh = pickResult.pickedMesh;
286
- const blockId = pickedMesh.name.replace('box_','');
287
-
288
- // Ломание блока
289
- if (isBreak) {
290
- console.log("Breaking block:", blockId);
291
- delete blocks[blockId]; // Удаляем из учета
292
- pickedMesh.dispose(); // Удаляем со сцены
293
  }
294
- // Установка блока
295
- else if (isPlace) {
296
- // Определяем нормаль грани, на которую попал луч
297
- const normal = pickResult.getNormal(true); // true = использовать локальные координаты нормали
298
- const position = pickedMesh.position.clone(); // Позиция блока, на который кликнули
299
-
300
- // Вычисляем координаты нового блока, примыкающего к грани
301
- const newBlockPos = position.add(normal.scale(BLOCK_SIZE));
302
-
303
- // Округляем координаты до ближайшей сетки (важно!)
304
- // Учитываем смещение центра блока (+BLOCK_SIZE/2)
305
- const newX = Math.floor(newBlockPos.x);
306
- const newY = Math.floor(newBlockPos.y);
307
- const newZ = Math.floor(newBlockPos.z);
308
- const newBlockId = `${newX}_${newY}_${newZ}`;
309
-
310
- // Проверяем, не пытаемся ли поставить блок на место игрока
311
- const playerHeadY = Math.floor(camera.position.y + camera.ellipsoidOffset.y + camera.ellipsoid.y / 2);
312
- const playerFeetY = Math.floor(camera.position.y + camera.ellipsoidOffset.y - camera.ellipsoid.y / 2);
313
- const playerX = Math.floor(camera.position.x);
314
- const playerZ = Math.floor(camera.position.z);
315
-
316
- const collisionWithPlayer = (newX === playerX && newZ === playerZ && (newY === playerFeetY || newY === playerHeadY));
317
-
318
- if (!blocks[newBlockId] && !collisionWithPlayer) { // Не ставим, если уже есть блок или там игрок
319
- console.log("Placing block at:", newX, newY, newZ);
320
- createBlock(newX, newY, newZ, currentBlockType, scene, materials);
321
- } else {
322
- console.log("Cannot place block here:", newBlockId, "Exists?", !!blocks[newBlockId], "Collision?", collisionWithPlayer);
323
- }
324
- }
325
- }
326
- }
327
-
328
-
329
- // --- Обновление информации на экране ---
330
- const fpsCounter = document.getElementById('fpsCounter');
331
- const blockInfo = document.getElementById('blockInfo');
332
- scene.onBeforeRenderObservable.add(() => {
333
- fpsCounter.textContent = `FPS: ${engine.getFps().toFixed()}`;
334
-
335
- // Показываем информацию о блоке под курсором
336
- const pickResult = scene.pick(canvas.width / 2, canvas.height / 2, (mesh) => mesh.isPickable);
337
- if (pickResult.hit && pickResult.pickedMesh && pickResult.pickedMesh.metadata) {
338
- const meta = pickResult.pickedMesh.metadata;
339
- blockInfo.textContent = `Блок: ${meta.type} (${meta.x}, ${meta.y}, ${meta.z})`;
340
- } else {
341
- blockInfo.textContent = 'Наведите курсор на блок';
342
- }
343
- });
344
-
345
- // --- Простое тач управление камерой (вращение) ---
346
- let lastTouchX = null;
347
- let lastTouchY = null;
348
- let isTouching = false;
349
-
350
- canvas.addEventListener('pointerdown', (evt) => {
351
- if(evt.pointerType === 'touch') {
352
- isTouching = true;
353
- lastTouchX = evt.clientX;
354
- lastTouchY = evt.clientY;
355
- // Важно: Отключаем стандартное управление мышью на время тача, чтобы избежать конфликтов
356
- // camera.detachControl(); // Это может быть слишком резко, нужно более тонкое управление
357
- }
358
- });
359
-
360
- canvas.addEventListener('pointermove', (evt) => {
361
- if(isTouching && evt.pointerType === 'touch') {
362
- const deltaX = evt.clientX - lastTouchX;
363
- const deltaY = evt.clientY - lastTouchY;
364
-
365
- // Вращаем камеру вручную
366
- // Коэффициенты подбираются экспериментально
367
- camera.cameraRotation.y += deltaX * TOUCH_SENSITIVITY;
368
- camera.cameraRotation.x += deltaY * TOUCH_SENSITIVITY;
369
-
370
- // Ограничиваем вертикальное вращение (чтобы не смотреть вверх ногами)
371
- camera.cameraRotation.x = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, camera.cameraRotation.x));
372
-
373
-
374
- lastTouchX = evt.clientX;
375
- lastTouchY = evt.clientY;
376
- }
377
- });
378
-
379
- const touchEndHandler = (evt) => {
380
- if(evt.pointerType === 'touch') {
381
- isTouching = false;
382
- lastTouchX = null;
383
- lastTouchY = null;
384
- // Возвращаем стандартное управление, если оно было отключено
385
- // if(!camera._attachedCanvas) {
386
- // camera.attachControl(canvas, true);
387
- // }
388
- }
389
- }
390
- canvas.addEventListener('pointerup', touchEndHandler);
391
- canvas.addEventListener('pointerout', touchEndHandler); // Также сбрасываем при выходе за пределы канваса
392
- canvas.addEventListener('pointercancel', touchEndHandler); // И при отмене касания
393
 
394
  return scene;
395
  };
396
 
397
- // --- Вспомогательная функция для определения тач-устройства ---
398
- function isTouchDevice() {
399
- return (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));
400
- }
401
-
402
  // --- Запуск ---
403
- const scene = createScene();
404
-
405
- engine.runRenderLoop(function () {
406
- if (scene) {
407
- scene.render();
408
- }
 
 
 
 
 
 
 
 
 
409
  });
410
 
 
 
411
  window.addEventListener('resize', function () {
412
  engine.resize();
413
  });
414
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  </script>
 
416
  </body>
417
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>Metaverse Prototype - Babylon.js Demo</title>
7
+ <meta name="description" content="Прототип метавселенной, созданный с помощью Babylon.js. Демонстрация 3D-мира для презентации.">
8
+
9
+ <!-- Подключение Babylon.js -->
10
+ <script src="https://cdn.babylonjs.com/babylon.js"></script>
11
+ <!-- Опционально: загрузчики для моделей (например, glTF) -->
12
+ <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
13
+ <!-- Опционально: библиотека для GUI Babylon.js (здесь используется HTML/CSS) -->
14
+ <!-- <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script> -->
15
+ <!-- Опционально: Post-processing эффекты -->
16
+ <script src="https://cdn.babylonjs.com/postProcessesLibrary/babylon.postProcessLibrary.min.js"></script>
17
+
18
+
19
  <style>
 
20
  html, body {
21
  overflow: hidden;
22
  width: 100%;
23
  height: 100%;
24
  margin: 0;
25
  padding: 0;
26
+ font-family: Arial, sans-serif;
27
+ color: #fff;
28
+ background-color: #000; /* Фон на время загрузки */
29
  }
30
 
31
  #renderCanvas {
32
  width: 100%;
33
  height: 100%;
34
+ touch-action: none; /* Отключает стандартные действия браузера при касании (важно для управления) */
35
+ }
36
+
37
+ /* --- Экран загрузки --- */
38
+ #loadingScreen {
39
+ position: absolute;
40
+ top: 0;
41
+ left: 0;
42
+ width: 100%;
43
+ height: 100%;
44
+ background-color: #1a1a1a;
45
+ display: flex;
46
+ justify-content: center;
47
+ align-items: center;
48
+ font-size: 2em;
49
+ color: #ccc;
50
+ z-index: 100;
51
+ transition: opacity 0.5s ease-out;
52
  }
53
 
54
+ #loadingScreen.hidden {
55
+ opacity: 0;
56
+ pointer-events: none;
57
+ }
58
+
59
+ /* --- UI Оверлей (Пример) --- */
60
+ #ui-overlay {
61
  position: absolute;
62
+ top: 15px;
63
+ left: 15px;
64
  background-color: rgba(0, 0, 0, 0.5);
65
  padding: 10px;
66
  border-radius: 5px;
67
+ z-index: 10;
 
68
  }
69
 
70
+ #ui-overlay h1 {
71
+ margin: 0 0 5px 0;
72
+ font-size: 1.2em;
 
 
 
 
 
 
 
 
73
  }
74
 
75
+ #ui-overlay p {
76
+ margin: 0;
77
+ font-size: 0.9em;
78
+ }
79
+
80
+ /* --- Мобильные элементы управления (визуальные) --- */
81
+ .mobile-controls {
82
  position: absolute;
83
  bottom: 20px;
84
+ width: 100%;
85
+ display: none; /* По умолчанию скрыты */
86
+ justify-content: space-between;
87
+ align-items: flex-end;
88
+ padding: 0 20px;
89
+ box-sizing: border-box;
90
+ z-index: 10;
91
+ pointer-events: none; /* Сами контейнеры не должны перехватывать события */
92
+ }
93
+
94
+ .joystick-area {
95
+ width: 120px;
96
+ height: 120px;
97
+ background-color: rgba(80, 80, 80, 0.4);
98
  border-radius: 50%;
99
+ position: relative;
100
+ pointer-events: auto; /* Область джойстика должна быть интерактивна */
101
  display: flex;
102
  justify-content: center;
103
  align-items: center;
104
+ }
105
+
106
+ .joystick-thumb { /* Просто визуальный индикатор центра */
107
+ width: 50px;
108
+ height: 50px;
109
+ background-color: rgba(200, 200, 200, 0.6);
110
+ border-radius: 50%;
111
+ }
112
+
113
+ .action-button {
114
+ width: 80px;
115
+ height: 80px;
116
+ background-color: rgba(0, 150, 255, 0.6);
117
+ border: 2px solid rgba(255, 255, 255, 0.7);
118
+ border-radius: 50%;
119
+ display: flex;
120
+ justify-content: center;
121
+ align-items: center;
122
+ font-size: 1.1em;
123
+ font-weight: bold;
124
+ color: #fff;
125
  cursor: pointer;
126
+ pointer-events: auto; /* Кнопка должна быть интерактивна */
127
+ user-select: none; /* Предотвратить выделение текста на кнопке */
128
+ -webkit-user-select: none;
129
+ -moz-user-select: none;
130
+ -ms-user-select: none;
131
  }
132
+
133
+ /* Показываем мобильные контролы только на тач-устройствах (простая проверка) */
134
+ body.is-mobile .mobile-controls {
135
+ display: flex;
 
 
 
 
 
136
  }
137
 
138
  </style>
 
 
 
 
139
  </head>
140
  <body>
141
+
142
+ <canvas id="renderCanvas"></canvas>
143
+
144
+ <div id="loadingScreen">
145
+ <div>Загрузка метавселенной...</div>
146
+ </div>
147
+
148
+ <div id="ui-overlay">
149
+ <h1>Metaverse Demo</h1>
150
+ <p>Используйте WASD/Стрелки + Мышь или Тач</p>
 
 
151
  </div>
 
152
 
153
+ <!-- Визуальные элементы для мобильного управления -->
154
+ <div class="mobile-controls">
155
+ <div id="joystickArea" class="joystick-area">
156
+ <div class="joystick-thumb"></div>
157
+ </div>
158
+ <div id="actionButton" class="action-button">
159
+ Действие
160
+ </div>
161
  </div>
162
 
163
  <script>
164
  const canvas = document.getElementById('renderCanvas');
165
+ const loadingScreen = document.getElementById('loadingScreen');
166
+ const mobileControls = document.querySelector('.mobile-controls');
167
+ const actionButton = document.getElementById('actionButton');
168
+
169
+ // Простая проверка на тач-устройство
170
+ const isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
171
+ if (isMobile) {
172
+ document.body.classList.add('is-mobile');
173
+ }
174
 
175
+ // --- Настройка Babylon.js ---
176
+ const engine = new BABYLON.Engine(canvas, true, {
177
+ preserveDrawingBuffer: true,
178
+ stencil: true,
179
+ antialias: true // Включаем сглаживание
180
+ });
181
+ engine.displayLoadingUI = function() { /* Отключаем стандартный загрузчик Babylon */ };
182
 
183
+ let scene;
 
184
 
185
  // --- Создание сцены ---
186
+ const createScene = async function () {
187
+ scene = new BABYLON.Scene(engine);
188
+ scene.clearColor = new BABYLON.Color4(0.1, 0.1, 0.2, 1.0); // Цвет фона, если скайбокс не загрузится
 
 
189
 
190
  // --- Камера ---
191
+ // UniversalCamera подходит для всех типов ввода (клавиатура, мышь, тач, геймпад)
192
+ const camera = new BABYLON.UniversalCamera("UniversalCamera", new BABYLON.Vector3(0, 1.8, -10), scene);
193
+ camera.setTarget(new BABYLON.Vector3(0, 1, 0));
194
  camera.attachControl(canvas, true);
195
 
196
+ // Параметры движения камеры
197
+ camera.speed = 0.2; // Скорость движения
198
+ camera.angularSensibility = 4000; // Чувствительность мыши/тачпада
199
+ camera.touchAngularSensibility = 4000; // Чувствительность тач-поворота
200
+ camera.touchMoveSensibility = 250.0; // Чувствительность тач-перемещения (для джойстика лучше другая логика)
201
+
202
+ // Включаем гравитацию и коллизии для камеры
 
 
 
 
 
 
203
  camera.applyGravity = true;
204
+ camera.ellipsoid = new BABYLON.Vector3(0.5, 0.9, 0.5); // Размеры "капсулы" игрока для коллизий
205
+ scene.gravity = new BABYLON.Vector3(0, -0.981, 0); // Стандартная гравитация
206
+ scene.collisionsEnabled = true; // Включаем систему коллизий в сцене
207
+ camera.checkCollisions = true; // Камера будет проверять коллизии
208
 
209
  // --- Освещение ---
210
+ // HemisphericLight - простое общее освещение (сверху/снизу)
211
+ const light = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene);
212
  light.intensity = 0.8;
213
+ light.diffuse = new BABYLON.Color3(1, 1, 1); // Белый свет сверху
214
+ light.groundColor = new BABYLON.Color3(0.4, 0.4, 0.4); // Серый свет снизу
215
+
216
+ // DirectionalLight - имитация солнца (дает тени)
217
+ const dirLight = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-0.5, -1, -0.5), scene);
218
+ dirLight.intensity = 0.6;
219
+ dirLight.position = new BABYLON.Vector3(20, 40, 20);
220
+
221
+ // --- Тени (могут быть ресурсоемкими, особенно на мобильных) ---
222
+ const shadowGenerator = new BABYLON.ShadowGenerator(1024, dirLight); // Размер карты теней
223
+ shadowGenerator.useBlurExponentialShadowMap = true; // Более мягкие тени
224
+ shadowGenerator.blurKernel = 32;
225
+ shadowGenerator.setDarkness(0.5); // Насколько темные тени
226
+
227
+ // --- Окружение: Скайбокс (Небо) ---
228
+ const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", { size: 1000.0 }, scene);
229
+ const skyboxMaterial = new BABYLON.StandardMaterial("skyBoxMat", scene);
230
+ skyboxMaterial.backFaceCulling = false; // Видеть текстуру изнутри куба
231
+ // Используем процедурную текстуру неба для простоты (не нужны внешние файлы)
232
+ // Либо можно использовать CubeTexture с внешними изображениями:
233
+ // skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("URL_TO_TEXTURES/skybox", scene);
234
+ // skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
235
+ const skyMaterial = new BABYLON.SkyMaterial("skyMaterial", scene);
236
+ skyMaterial.backFaceCulling = false;
237
+ skyMaterial.turbidity = 10; // облачность
238
+ skyMaterial.luminance = 1; // яркость
239
+ skyMaterial.useSunPosition = true; // Использовать позицию "солнца"
240
+ skyMaterial.sunPosition = dirLight.position; // Установить позицию солнца как у направленного света
241
+ skybox.material = skyMaterial;
242
+
243
+
244
+ // --- Окружение: Земля ---
245
+ const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 100, height: 100, subdivisions: 2 }, scene);
246
+ const groundMaterial = new BABYLON.StandardMaterial("groundMat", scene);
247
+ // Простая текстура (можно заменить на URL к изображению)
248
+ // groundMaterial.diffuseTexture = new BABYLON.Texture("URL_TO_GROUND_TEXTURE.jpg", scene);
249
+ // groundMaterial.diffuseTexture.uScale = 10; // Повторение текстуры
250
+ // groundMaterial.diffuseTexture.vScale = 10;
251
+ groundMaterial.diffuseColor = new BABYLON.Color3(0.5, 0.7, 0.3); // Зеленоватый цвет
252
+ groundMaterial.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1); // Небольшой блик
253
+ ground.material = groundMaterial;
254
+ ground.checkCollisions = true; // Земля участвует в коллизиях
255
+ ground.receiveShadows = true; // Земля может принимать тени
256
+
257
+ // --- Добавим несколько объектов в мир ---
258
+
259
+ // Сфера
260
+ const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2 }, scene);
261
+ sphere.position = new BABYLON.Vector3(5, 1, 5);
262
+ const sphereMaterial = new BABYLON.StandardMaterial("sphereMat", scene);
263
+ sphereMaterial.diffuseColor = new BABYLON.Color3(1, 0.5, 0); // Оранжевый
264
+ sphere.material = sphereMaterial;
265
+ sphere.checkCollisions = true;
266
+ shadowGenerator.addShadowCaster(sphere); // Сфера отбрасывает тень
267
+
268
+ // Куб
269
+ const box = BABYLON.MeshBuilder.CreateBox("box", { size: 2 }, scene);
270
+ box.position = new BABYLON.Vector3(-5, 1, -3);
271
+ const boxMaterial = new BABYLON.StandardMaterial("boxMat", scene);
272
+ boxMaterial.diffuseColor = new BABYLON.Color3(0.2, 0.5, 1); // Синий
273
+ box.material = boxMaterial;
274
+ box.checkCollisions = true;
275
+ shadowGenerator.addShadowCaster(box);
276
+
277
+ // "Здание" (просто вытянутый куб)
278
+ const building = BABYLON.MeshBuilder.CreateBox("building", { width: 5, height: 10, depth: 5 }, scene);
279
+ building.position = new BABYLON.Vector3(10, 5, -10);
280
+ const buildingMaterial = new BABYLON.StandardMaterial("buildingMat", scene);
281
+ buildingMaterial.diffuseColor = new BABYLON.Color3(0.8, 0.8, 0.8); // Светло-серый
282
+ building.material = buildingMaterial;
283
+ building.checkCollisions = true;
284
+ shadowGenerator.addShadowCaster(building);
285
+
286
+
287
+ // --- Интерактивность (Пример: клик по сфере) ---
288
+ sphere.actionManager = new BABYLON.ActionManager(scene);
289
+ sphere.actionManager.registerAction(
290
+ new BABYLON.ExecuteCodeAction(
291
+ BABYLON.ActionManager.OnPickTrigger, // Действие при клике/тапе
292
+ function (evt) {
293
+ console.log("Клик по сфере!");
294
+ // Пример действия: изменить цвет
295
+ sphereMaterial.diffuseColor = sphereMaterial.diffuseColor.equals(BABYLON.Color3.Red())
296
+ ? new BABYLON.Color3(1, 0.5, 0) // Вернуть оранжевый
297
+ : BABYLON.Color3.Red(); // Сделать красным
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  }
299
+ )
300
+ );
301
+
302
+ // --- Пост-обработка (для "флагманской" графики) ---
303
+ // DefaultRenderingPipeline применяет несколько эффектов сразу
304
+ const pipeline = new BABYLON.DefaultRenderingPipeline(
305
+ "defaultPipeline", // Имя
306
+ true, // Использовать HDR текстуры (лучше качество)
307
+ scene, // Сцена
308
+ [camera] // Камеры, к которым применяется
309
+ );
310
+
311
+ // Настройки эффектов (можно настроить или отключить)
312
+ pipeline.samples = 4; // Сглаживание MSAA (если поддерживается)
313
+
314
+ pipeline.bloomEnabled = true; // Эффект свечения (Bloom)
315
+ pipeline.bloomThreshold = 0.7;
316
+ pipeline.bloomWeight = 0.5;
317
+ pipeline.bloomKernel = 64;
318
+ pipeline.bloomScale = 0.5;
319
+
320
+ pipeline.fxaaEnabled = true; // Сглаживание FXAA (быстрее MSAA)
321
+
322
+ // pipeline.imageProcessingEnabled = true; // Общая обработка изображения
323
+ // if (pipeline.imageProcessingEnabled) {
324
+ // pipeline.imageProcessing.contrast = 1.2;
325
+ // pipeline.imageProcessing.exposure = 1.0;
326
+ // // Можно добавить виньетку, зернистость и т.д.
327
+ // }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
  return scene;
330
  };
331
 
 
 
 
 
 
332
  // --- Запуск ---
333
+ createScene().then((createdScene) => {
334
+ scene = createdScene;
335
+ // Убираем экран загрузки после инициализации сцены
336
+ engine.hideLoadingUI();
337
+ loadingScreen.classList.add('hidden');
338
+
339
+ // Основной цикл рендеринга
340
+ engine.runRenderLoop(function () {
341
+ if (scene) {
342
+ scene.render();
343
+ }
344
+ });
345
+ }).catch(error => {
346
+ console.error("Ошибка создания сцены:", error);
347
+ loadingScreen.innerHTML = "<div>Ошибка загрузки :(</div>";
348
  });
349
 
350
+
351
+ // --- Обработка изменения размера окна ---
352
  window.addEventListener('resize', function () {
353
  engine.resize();
354
  });
355
 
356
+ // --- Обработка мобильной кнопки "Действие" (пример) ---
357
+ if (isMobile && actionButton) {
358
+ actionButton.addEventListener('pointerdown', function(event) {
359
+ event.preventDefault(); // Предотвратить стандартное поведение (например, прокрутку)
360
+ console.log("Нажата кнопка Действие!");
361
+ // Здесь можно добавить логику действия:
362
+ // Например, эмулировать взаимодействие с объектом перед камерой
363
+ // или выполнить другое игровое действие.
364
+
365
+ // Визуальный фидбек нажатия
366
+ actionButton.style.backgroundColor = 'rgba(0, 100, 200, 0.8)';
367
+ });
368
+ actionButton.addEventListener('pointerup', function(event) {
369
+ actionButton.style.backgroundColor = 'rgba(0, 150, 255, 0.6)';
370
+ });
371
+ actionButton.addEventListener('pointerleave', function(event) { // Если палец ушел с кнопки
372
+ actionButton.style.backgroundColor = 'rgba(0, 150, 255, 0.6)';
373
+ });
374
+ }
375
+
376
+ // --- Дополнительно: Логика для виртуального джойстика (Упрощенная) ---
377
+ // UniversalCamera уже обрабатывает перетаскивание по экрану для движения/поворота.
378
+ // Этот код просто показывает визуальные элементы.
379
+ // Для полноценного джойстика потребовалась бы сложная логика отслеживания
380
+ // пальца в пределах joystickArea и преобразования смещения в векторы движения камеры.
381
+
382
  </script>
383
+
384
  </body>
385
  </html>