izuemon commited on
Commit
161b71e
·
verified ·
1 Parent(s): 4a42e3a

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +474 -19
index.html CHANGED
@@ -1,19 +1,474 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>WebCraft - Minecraft Clone</title>
7
+ <style>
8
+ /* ==========================================
9
+ UI/UX & CSS
10
+ ========================================== */
11
+ @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
12
+
13
+ body {
14
+ margin: 0;
15
+ overflow: hidden;
16
+ font-family: 'Press Start 2P', monospace;
17
+ background-color: #000;
18
+ color: #fff;
19
+ user-select: none;
20
+ }
21
+
22
+ /* 1. ホーム画面 & 2. スタート方法 */
23
+ #home-screen {
24
+ position: absolute;
25
+ top: 0; left: 0; width: 100vw; height: 100vh;
26
+ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAIklEQVQIW2NkQAKrVq36zwjjgzhhYWGMYAEYB8RmROaABADeOQ8CXl/xfgAAAABJRU5ErkJggg==') repeat;
27
+ background-size: 64px;
28
+ display: flex;
29
+ flex-direction: column;
30
+ align-items: center;
31
+ justify-content: center;
32
+ z-index: 100;
33
+ }
34
+
35
+ .title { font-size: 48px; margin-bottom: 40px; text-shadow: 4px 4px 0 #000; color: #aaa; }
36
+ .btn {
37
+ width: 300px; padding: 15px; margin: 10px;
38
+ background: #777; border: 4px solid #000;
39
+ border-top-color: #ccc; border-left-color: #ccc;
40
+ color: #fff; font-family: 'Press Start 2P', monospace;
41
+ font-size: 16px; cursor: pointer; text-align: center;
42
+ box-shadow: inset -4px -4px 0px rgba(0,0,0,0.5);
43
+ }
44
+ .btn:hover { background: #888; }
45
+ .btn:active {
46
+ border-top-color: #000; border-left-color: #000;
47
+ border-bottom-color: #ccc; border-right-color: #ccc;
48
+ box-shadow: inset 4px 4px 0px rgba(0,0,0,0.5);
49
+ }
50
+
51
+ /* ゲーム中HUD */
52
+ #game-ui { display: none; }
53
+ #crosshair {
54
+ position: absolute; top: 50%; left: 50%;
55
+ width: 20px; height: 20px;
56
+ transform: translate(-50%, -50%);
57
+ pointer-events: none;
58
+ background: linear-gradient(to right, transparent 40%, white 40%, white 60%, transparent 60%),
59
+ linear-gradient(to bottom, transparent 40%, white 40%, white 60%, transparent 40%);
60
+ mix-blend-mode: difference;
61
+ }
62
+
63
+ #hotbar {
64
+ position: absolute; bottom: 20px; left: 50%;
65
+ transform: translateX(-50%);
66
+ display: flex; background: rgba(0,0,0,0.5);
67
+ border: 4px solid #222; border-radius: 4px;
68
+ }
69
+ .slot {
70
+ width: 40px; height: 40px; border: 2px solid #555;
71
+ margin: 2px; position: relative;
72
+ }
73
+ .slot.active { border-color: #fff; }
74
+
75
+ #info-panel {
76
+ position: absolute; top: 10px; left: 10px;
77
+ font-size: 10px; text-shadow: 1px 1px 0 #000;
78
+ line-height: 1.5;
79
+ }
80
+
81
+ /* ロード画面 */
82
+ #loading {
83
+ position: absolute; top: 0; left: 0; width: 100vw; height: 100vh;
84
+ background: #111; display: none; flex-direction: column;
85
+ align-items: center; justify-content: center; z-index: 200;
86
+ }
87
+ </style>
88
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
89
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
90
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
91
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
92
+ </head>
93
+ <body>
94
+
95
+ <div id="home-screen">
96
+ <div class="title">WEBCRAFT</div>
97
+ <button class="btn" onclick="startGame('survival')">Survival Mode</button>
98
+ <button class="btn" onclick="startGame('creative')">Creative Mode</button>
99
+ <button class="btn" onclick="alert('【Controls】\nW/A/S/D: Move\nSpace: Jump\nClick: Break/Place\n1-5: Select Item')">Controls</button>
100
+ </div>
101
+
102
+ <div id="loading">
103
+ <div style="font-size: 20px; margin-bottom: 20px;">Loading Assets...</div>
104
+ <div id="loading-text" style="font-size: 12px; color: #aaa;">Reading ZIP file</div>
105
+ </div>
106
+
107
+ <div id="game-ui">
108
+ <div id="crosshair"></div>
109
+ <div id="info-panel">
110
+ <span id="fps">FPS: 0</span><br>
111
+ <span id="pos">XYZ: 0, 0, 0</span><br>
112
+ <span id="dim">Dim: Overworld</span>
113
+ </div>
114
+ <div id="hotbar">
115
+ <div class="slot active" id="slot-1"></div>
116
+ <div class="slot" id="slot-2"></div>
117
+ <div class="slot" id="slot-3"></div>
118
+ <div class="slot" id="slot-4"></div>
119
+ <div class="slot" id="slot-5"></div>
120
+ </div>
121
+ </div>
122
+
123
+ <script>
124
+ /* ==========================================
125
+ 3. ゲームの仕様 & コアシステム
126
+ ========================================== */
127
+ let scene, camera, renderer, controls;
128
+ let worldBlocks = {}; // 座標をキーにしたブロックデータ
129
+ let textures = {}; // 読み込んだテクスチャ
130
+ let mobs = []; // モブの配列
131
+
132
+ const CHUNK_SIZE = 32;
133
+ const blockSize = 1;
134
+ let gameMode = 'survival';
135
+ let currentDimension = 'overworld';
136
+ let selectedBlock = 1;
137
+
138
+ // ブロックID定義 (特別ブロック含む)
139
+ const BLOCKS = {
140
+ AIR: 0, DIRT: 1, GRASS: 2, STONE: 3, LOG: 4, LEAVES: 5,
141
+ SAND: 6, WATER: 7,
142
+ CHEST: 8, CRAFTING_TABLE: 9, FURNACE: 10, NETHER_PORTAL: 11 // 9. 特別なブロック
143
+ };
144
+
145
+ const blockMaterials = {}; // 材質キャッシュ
146
+
147
+ /* ==========================================
148
+ アセット管理 (ZIPパース)
149
+ ========================================== */
150
+ async function loadAssets() {
151
+ const loadingScreen = document.getElementById('loading');
152
+ const loadingText = document.getElementById('loading-text');
153
+ loadingScreen.style.display = 'flex';
154
+
155
+ try {
156
+ loadingText.innerText = "Fetching ZIP... (InventivetalentDev-minecraft-assets)";
157
+ const response = await fetch('InventivetalentDev-minecraft-assets-26.1.2-0-g44dd81e.zip');
158
+ if (!response.ok) throw new Error("ZIP not found");
159
+
160
+ const blob = await response.blob();
161
+ loadingText.innerText = "Extracting ZIP via JSZip...";
162
+ const zip = await JSZip.loadAsync(blob);
163
+
164
+ // 簡易テクスチャローダー (一部のブロックを抽出)
165
+ const textureFiles = {
166
+ 1: 'assets/minecraft/textures/block/dirt.png',
167
+ 2: 'assets/minecraft/textures/block/grass_block_side.png',
168
+ 3: 'assets/minecraft/textures/block/stone.png',
169
+ 4: 'assets/minecraft/textures/block/oak_log.png',
170
+ 8: 'assets/minecraft/textures/block/chest_front.png' // チェスト
171
+ };
172
+
173
+ const loader = new THREE.TextureLoader();
174
+ for (let id in textureFiles) {
175
+ const path = textureFiles[id];
176
+ if (zip.file(path)) {
177
+ const base64 = await zip.file(path).async("base64");
178
+ const tex = loader.load('data:image/png;base64,' + base64);
179
+ tex.magFilter = THREE.NearestFilter; // マイクラ風ドット感
180
+ textures[id] = tex;
181
+ }
182
+ }
183
+ console.log("Assets loaded successfully.");
184
+ } catch (err) {
185
+ console.warn("Asset Load Failed. Using fallback colors.", err);
186
+ // フォールバック: テクスチャがない場合は単一カラー
187
+ }
188
+
189
+ // マテリアル生成
190
+ blockMaterials[BLOCKS.DIRT] = new THREE.MeshLambertMaterial({ map: textures[1] || null, color: textures[1] ? 0xffffff : 0x5c4033 });
191
+ blockMaterials[BLOCKS.GRASS] = new THREE.MeshLambertMaterial({ map: textures[2] || null, color: textures[2] ? 0xffffff : 0x41980a });
192
+ blockMaterials[BLOCKS.STONE] = new THREE.MeshLambertMaterial({ map: textures[3] || null, color: textures[3] ? 0xffffff : 0x888888 });
193
+ blockMaterials[BLOCKS.LOG] = new THREE.MeshLambertMaterial({ map: textures[4] || null, color: textures[4] ? 0xffffff : 0x6b4423 });
194
+ blockMaterials[BLOCKS.LEAVES] = new THREE.MeshLambertMaterial({ color: 0x228b22, transparent: true, opacity: 0.8 });
195
+ blockMaterials[BLOCKS.CHEST] = new THREE.MeshLambertMaterial({ map: textures[8] || null, color: textures[8] ? 0xffffff : 0xd2b48c });
196
+ blockMaterials[BLOCKS.NETHER_PORTAL] = new THREE.MeshLambertMaterial({ color: 0x800080, transparent: true, opacity: 0.7 });
197
+
198
+ loadingScreen.style.display = 'none';
199
+ }
200
+
201
+ /* ==========================================
202
+ 7. 地形生成 & 6. ディメンション
203
+ ========================================== */
204
+ const simplex = new SimplexNoise();
205
+ let instancedMesh; // 描画軽量化のためのInstancedMesh
206
+
207
+ function generateWorld(dimension) {
208
+ // ワールドリセット
209
+ if (instancedMesh) scene.remove(instancedMesh);
210
+ worldBlocks = {};
211
+
212
+ const geometry = new THREE.BoxGeometry(blockSize, blockSize, blockSize);
213
+ // 簡易的に全ブロック共通のマテリアル配列を渡し、インスタンスごとに変更する処理は複雑になるため
214
+ // 今回はパフォーマンスとコード量削減のため、ブロック種別ごとにMeshGroupを作成します。
215
+
216
+ const groups = {};
217
+ for(let key in BLOCKS) groups[BLOCKS[key]] = [];
218
+
219
+ // 地形生成アルゴリズム
220
+ for (let x = -CHUNK_SIZE/2; x < CHUNK_SIZE/2; x++) {
221
+ for (let z = -CHUNK_SIZE/2; z < CHUNK_SIZE/2; z++) {
222
+
223
+ if (dimension === 'overworld') {
224
+ // 平原・山の複合ノイズ
225
+ let yNoise = Math.floor(simplex.noise2D(x / 20, z / 20) * 5 + 5);
226
+ for (let y = 0; y <= yNoise; y++) {
227
+ let type = (y === yNoise) ? BLOCKS.GRASS : (y > yNoise - 3 ? BLOCKS.DIRT : BLOCKS.STONE);
228
+ setBlockData(x, y, z, type);
229
+ groups[type].push(new THREE.Vector3(x, y, z));
230
+ }
231
+ // 木の生成 (簡易)
232
+ if (yNoise > 2 && Math.random() < 0.05) {
233
+ for(let ty=1; ty<=4; ty++) { setBlockData(x, yNoise+ty, z, BLOCKS.LOG); groups[BLOCKS.LOG].push(new THREE.Vector3(x, yNoise+ty, z)); }
234
+ setBlockData(x, yNoise+5, z, BLOCKS.LEAVES); groups[BLOCKS.LEAVES].push(new THREE.Vector3(x, yNoise+5, z));
235
+ }
236
+ scene.background = new THREE.Color(0x87ceeb); // 空色
237
+ }
238
+ else if (dimension === 'nether') {
239
+ // ネザー地形 (天井と床)
240
+ let floorNoise = Math.floor(simplex.noise2D(x / 15, z / 15) * 3);
241
+ setBlockData(x, floorNoise, z, BLOCKS.STONE);
242
+ groups[BLOCKS.STONE].push(new THREE.Vector3(x, floorNoise, z));
243
+ // 色を赤暗くするフォールバック処理は今回は背景色で表現
244
+ scene.background = new THREE.Color(0x2a0000);
245
+ }
246
+ }
247
+ }
248
+
249
+ // ネザーゲートのテスト配置
250
+ if(dimension === 'overworld') {
251
+ setBlockData(0, 10, 5, BLOCKS.NETHER_PORTAL);
252
+ groups[BLOCKS.NETHER_PORTAL].push(new THREE.Vector3(0, 10, 5));
253
+ }
254
+
255
+ // メッシュのビルド (コード短縮のためInstancedMesh使用)
256
+ for (let type in groups) {
257
+ if(groups[type].length === 0) continue;
258
+ let mesh = new THREE.InstancedMesh(geometry, blockMaterials[type] || blockMaterials[BLOCKS.STONE], groups[type].length);
259
+ let dummy = new THREE.Object3D();
260
+ groups[type].forEach((pos, i) => {
261
+ dummy.position.copy(pos);
262
+ dummy.updateMatrix();
263
+ mesh.setMatrixAt(i, dummy.matrix);
264
+ });
265
+ mesh.name = "blockGroup_" + type;
266
+ scene.add(mesh);
267
+ }
268
+ }
269
+
270
+ function setBlockData(x, y, z, type) {
271
+ worldBlocks[`${x},${y},${z}`] = type;
272
+ }
273
+
274
+ /* ==========================================
275
+ 5. モブについて (簡易AI)
276
+ ========================================== */
277
+ function spawnMob(x, y, z, type) {
278
+ const geo = new THREE.BoxGeometry(0.8, 1.8, 0.8);
279
+ const mat = new THREE.MeshLambertMaterial({ color: type === 'zombie' ? 0x00ff00 : 0xffffff });
280
+ const mesh = new THREE.Mesh(geo, mat);
281
+ mesh.position.set(x, y, z);
282
+ scene.add(mesh);
283
+ mobs.push({ mesh: mesh, type: type, speed: 0.02 });
284
+ }
285
+
286
+ function updateMobs() {
287
+ // プレイヤーを追跡する簡易AI
288
+ const playerPos = camera.position;
289
+ mobs.forEach(mob => {
290
+ if (mob.type === 'zombie') {
291
+ const dx = playerPos.x - mob.mesh.position.x;
292
+ const dz = playerPos.z - mob.mesh.position.z;
293
+ const dist = Math.sqrt(dx*dx + dz*dz);
294
+ if(dist > 1.5 && dist < 16) { // 近づきすぎず、遠すぎない範囲で追尾
295
+ mob.mesh.position.x += (dx/dist) * mob.speed;
296
+ mob.mesh.position.z += (dz/dist) * mob.speed;
297
+ }
298
+ }
299
+ });
300
+ }
301
+
302
+ /* ==========================================
303
+ 初期化 & コントロール
304
+ ========================================== */
305
+ function initGame() {
306
+ document.getElementById('home-screen').style.display = 'none';
307
+ document.getElementById('game-ui').style.display = 'block';
308
+
309
+ scene = new THREE.Scene();
310
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
311
+
312
+ renderer = new THREE.WebGLRenderer({ antialias: false }); // マイクラ風にするためあえてオフ
313
+ renderer.setSize(window.innerWidth, window.innerHeight);
314
+ document.body.appendChild(renderer.domElement);
315
+
316
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
317
+ scene.add(ambientLight);
318
+ const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
319
+ dirLight.position.set(10, 20, 10);
320
+ scene.add(dirLight);
321
+
322
+ generateWorld(currentDimension);
323
+
324
+ // プレイヤー初期位置
325
+ camera.position.set(0, 15, 0);
326
+
327
+ controls = new THREE.PointerLockControls(camera, document.body);
328
+ document.addEventListener('click', () => { if(!controls.isLocked) controls.lock(); });
329
+
330
+ // スポーン
331
+ spawnMob(5, 10, 5, 'zombie');
332
+
333
+ setupInputs();
334
+ animate();
335
+ }
336
+
337
+ let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
338
+ let velocity = new THREE.Vector3();
339
+ let direction = new THREE.Vector3();
340
+ const clock = new THREE.Clock();
341
+
342
+ function setupInputs() {
343
+ document.addEventListener('keydown', (e) => {
344
+ switch (e.code) {
345
+ case 'KeyW': moveForward = true; break;
346
+ case 'KeyA': moveLeft = true; break;
347
+ case 'KeyS': moveBackward = true; break;
348
+ case 'KeyD': moveRight = true; break;
349
+ case 'Space': if (velocity.y === 0) velocity.y = 10; break; // ジャンプ
350
+ case 'Digit1': selectHotbar(1, BLOCKS.DIRT); break;
351
+ case 'Digit2': selectHotbar(2, BLOCKS.STONE); break;
352
+ case 'Digit3': selectHotbar(3, BLOCKS.LOG); break;
353
+ case 'Digit4': selectHotbar(4, BLOCKS.CHEST); break; // 8. 特別なアイテム
354
+ case 'Digit5': selectHotbar(5, BLOCKS.NETHER_PORTAL); break;
355
+ }
356
+ });
357
+ document.addEventListener('keyup', (e) => {
358
+ switch (e.code) {
359
+ case 'KeyW': moveForward = false; break;
360
+ case 'KeyA': moveLeft = false; break;
361
+ case 'KeyS': moveBackward = false; break;
362
+ case 'KeyD': moveRight = false; break;
363
+ }
364
+ });
365
+
366
+ // 9. ブロックの破壊・設置 (特別なブロックのインタラクション含む)
367
+ document.addEventListener('mousedown', (e) => {
368
+ if (!controls.isLocked) return;
369
+
370
+ // 画面中央からレイキャスト
371
+ const raycaster = new THREE.Raycaster();
372
+ raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
373
+ // 対象はすべてのInstancedMesh
374
+ const intersects = raycaster.intersectObjects(scene.children.filter(c => c.isInstancedMesh));
375
+
376
+ if (intersects.length > 0) {
377
+ const hit = intersects[0];
378
+ const hitObj = hit.object;
379
+
380
+ // 簡易的な座標計算
381
+ const p = hit.point.clone().sub(hit.face.normal.clone().multiplyScalar(0.1));
382
+ const blockX = Math.round(p.x);
383
+ const blockY = Math.round(p.y);
384
+ const blockZ = Math.round(p.z);
385
+
386
+ // 特別なブロックの動作チェック
387
+ const targetBlock = worldBlocks[`${blockX},${blockY},${blockZ}`];
388
+ if (targetBlock === BLOCKS.NETHER_PORTAL) {
389
+ currentDimension = currentDimension === 'overworld' ? 'nether' : 'overworld';
390
+ generateWorld(currentDimension);
391
+ return;
392
+ }
393
+ if (targetBlock === BLOCKS.CHEST && e.button === 2) {
394
+ alert("Chest opened! (Inventory system placeholder)");
395
+ return;
396
+ }
397
+
398
+ if (e.button === 0) { // 左クリック: 破壊 (配列・メッシュ再生成を省くため透明化アプローチなどはコード長くなるため、今回は割愛しコンソールのみ)
399
+ console.log(`Broke block at ${blockX}, ${blockY}, ${blockZ}`);
400
+ // ※完全な破壊・追加実装はChunk管理クラスが必要なため、このプロトタイプではHit判定のみ
401
+ } else if (e.button === 2) { // 右クリック: 設置
402
+ const np = hit.point.clone().add(hit.face.normal.clone().multiplyScalar(0.5));
403
+ console.log(`Placed block at ${Math.round(np.x)}, ${Math.round(np.y)}, ${Math.round(np.z)}`);
404
+ }
405
+ }
406
+ });
407
+ }
408
+
409
+ function selectHotbar(slot, blockId) {
410
+ document.querySelectorAll('.slot').forEach(el => el.classList.remove('active'));
411
+ document.getElementById(`slot-${slot}`).classList.add('active');
412
+ selectedBlock = blockId;
413
+ }
414
+
415
+ /* ==========================================
416
+ メインループ
417
+ ========================================== */
418
+ function animate() {
419
+ requestAnimationFrame(animate);
420
+
421
+ if (controls.isLocked) {
422
+ const delta = clock.getDelta();
423
+
424
+ // 物理演算 (簡易)
425
+ velocity.x -= velocity.x * 10.0 * delta;
426
+ velocity.z -= velocity.z * 10.0 * delta;
427
+ velocity.y -= 9.8 * 3.0 * delta; // 重力
428
+
429
+ direction.z = Number(moveForward) - Number(moveBackward);
430
+ direction.x = Number(moveRight) - Number(moveLeft);
431
+ direction.normalize();
432
+
433
+ const speed = gameMode === 'creative' ? 80.0 : 40.0;
434
+ if (moveForward || moveBackward) velocity.z -= direction.z * speed * delta;
435
+ if (moveLeft || moveRight) velocity.x -= direction.x * speed * delta;
436
+
437
+ controls.moveRight(-velocity.x * delta);
438
+ controls.moveForward(-velocity.z * delta);
439
+ controls.getObject().position.y += (velocity.y * delta);
440
+
441
+ // 簡易床衝突判定
442
+ if (controls.getObject().position.y < 10) {
443
+ velocity.y = 0;
444
+ controls.getObject().position.y = 10;
445
+ }
446
+
447
+ updateMobs();
448
+
449
+ // UI更新
450
+ document.getElementById('pos').innerText = `XYZ: ${Math.floor(camera.position.x)}, ${Math.floor(camera.position.y)}, ${Math.floor(camera.position.z)}`;
451
+ document.getElementById('dim').innerText = `Dim: ${currentDimension.charAt(0).toUpperCase() + currentDimension.slice(1)}`;
452
+ }
453
+
454
+ renderer.render(scene, camera);
455
+ }
456
+
457
+ // 画面リサイズ対応
458
+ window.addEventListener('resize', () => {
459
+ if(camera) {
460
+ camera.aspect = window.innerWidth / window.innerHeight;
461
+ camera.updateProjectionMatrix();
462
+ renderer.setSize(window.innerWidth, window.innerHeight);
463
+ }
464
+ });
465
+
466
+ // 起動処理
467
+ async function startGame(mode) {
468
+ gameMode = mode;
469
+ await loadAssets(); // ZIPからアセット読み込み
470
+ initGame();
471
+ }
472
+ </script>
473
+ </body>
474
+ </html>