gini1 commited on
Commit
2c63faf
1 Parent(s): 24434a1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +31 -485
index.html CHANGED
@@ -1,498 +1,44 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>3D Open World Game</title>
5
- <style>
6
- body {
7
- margin: 0;
8
- font-family: Arial, sans-serif;
9
  }
10
- canvas {
11
- display: block;
12
- }
13
- #info {
14
- position: absolute;
15
- top: 10px;
16
- left: 10px;
17
- background: rgba(0,0,0,0.7);
18
- color: white;
19
- padding: 10px;
20
- border-radius: 5px;
21
- }
22
- #weather-controls {
23
- position: absolute;
24
- top: 10px;
25
- right: 10px;
26
- background: rgba(0,0,0,0.7);
27
- color: white;
28
- padding: 10px;
29
- border-radius: 5px;
30
- }
31
- #fileUpload {
32
- position: absolute;
33
- top: 50%;
34
- left: 50%;
35
- transform: translate(-50%, -50%);
36
- background: rgba(0,0,0,0.8);
37
- color: white;
38
- padding: 20px;
39
- border-radius: 10px;
40
- text-align: center;
41
- }
42
- #fileUpload.hidden {
43
- display: none;
44
- }
45
- .custom-file-upload {
46
- display: inline-block;
47
- padding: 12px 24px;
48
- background: #2196F3;
49
- color: white;
50
- border-radius: 5px;
51
- cursor: pointer;
52
- margin: 10px 0;
53
- transition: background 0.3s;
54
- }
55
- .custom-file-upload:hover {
56
- background: #1976D2;
57
- }
58
- input[type="file"] {
59
- display: none;
60
- }
61
- #startButton {
62
- margin-top: 10px;
63
- padding: 10px 20px;
64
- background: #4CAF50;
65
- border: none;
66
- color: white;
67
- border-radius: 5px;
68
- cursor: pointer;
69
- }
70
- #startButton:disabled {
71
- background: #666;
72
- cursor: not-allowed;
73
- }
74
- #selectedFileName {
75
- margin-top: 10px;
76
- color: #4CAF50;
77
- }
78
- #safeTimer {
79
- position: absolute;
80
- top: 50%;
81
- left: 50%;
82
- transform: translate(-50%, -50%);
83
- background: rgba(0,0,0,0.7);
84
- color: white;
85
- padding: 20px;
86
- border-radius: 10px;
87
- font-size: 24px;
88
- display: none;
89
- }
90
- </style>
91
- <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
92
- <script type="importmap">
93
- {
94
- "imports": {
95
- "three": "https://unpkg.com/three@0.149.0/build/three.module.js",
96
- "three/addons/": "https://unpkg.com/three@0.149.0/examples/jsm/"
97
- }
98
- }
99
- </script>
100
- </head>
101
- <body>
102
- <div id="info">
103
- Controls:<br>
104
- W/S/A/D - Move<br>
105
- SPACE - Jump<br>
106
- Mouse - Look Around<br>
107
- HP: <span id="hp">100</span>
108
- </div>
109
- <div id="weather-controls">
110
- Weather:
111
- <select id="weather-select" onchange="changeWeather(this.value)">
112
- <option value="clear">Clear</option>
113
- <option value="rain">Rain</option>
114
- <option value="fog">Fog</option>
115
- </select>
116
- </div>
117
- <div id="fileUpload">
118
- <h2>3D Open World Game</h2>
119
- <p>Upload your character model (GLB file)</p>
120
- <label class="custom-file-upload">
121
- Choose GLB File
122
- <input type="file" id="glbFile" accept=".glb" />
123
- </label>
124
- <div id="selectedFileName"></div>
125
- <button id="startButton" disabled>Start Game</button>
126
- </div>
127
- <div id="safeTimer"></div>
128
-
129
- <script type="module">
130
- import * as THREE from 'three';
131
- import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
132
- import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
133
-
134
- let scene, camera, renderer, controls;
135
- let character = null;
136
- let uploadedModel = null;
137
- let hp = 100;
138
- let gameStartTime = 0;
139
- let isSafePeriod = true;
140
- const SAFE_PERIOD = 30; // 30초 안전 시간
141
 
142
- // 파일 업로드 처리
143
- const fileInput = document.getElementById('glbFile');
144
- const startButton = document.getElementById('startButton');
145
- const uploadDiv = document.getElementById('fileUpload');
146
- const selectedFileName = document.getElementById('selectedFileName');
147
- const safeTimer = document.getElementById('safeTimer');
148
- const hpElement = document.getElementById('hp');
149
-
150
- fileInput.addEventListener('change', function(e) {
151
- const file = e.target.files[0];
152
- if (file) {
153
- selectedFileName.textContent = `Selected: ${file.name}`;
154
- uploadedModel = URL.createObjectURL(file);
155
- startButton.disabled = false;
156
- }
157
- });
158
-
159
- startButton.addEventListener('click', function() {
160
- if (uploadedModel) {
161
- uploadDiv.classList.add('hidden');
162
- initGame();
163
- }
164
- });
165
-
166
- // NPC 클래스
167
- class NPC {
168
- constructor(position) {
169
- const geometry = new THREE.CapsuleGeometry(1, 2, 4, 8);
170
- const material = new THREE.MeshPhongMaterial({ color: 0xff0000 });
171
- this.mesh = new THREE.Mesh(geometry, material);
172
- this.mesh.position.copy(position);
173
- this.mesh.castShadow = true;
174
- this.velocity = new THREE.Vector3();
175
- this.direction = new THREE.Vector3();
176
- this.speed = 0.05; // 감소된 기본 속도
177
- this.damage = 0.5; // 감소된 데미지
178
- scene.add(this.mesh);
179
- }
180
-
181
- update(playerPosition) {
182
- // 안전 시간 체크
183
- if (isSafePeriod) {
184
- const elapsedTime = Math.floor((Date.now() - gameStartTime) / 1000);
185
- const remainingTime = SAFE_PERIOD - elapsedTime;
186
-
187
- if (remainingTime > 0) {
188
- safeTimer.style.display = 'block';
189
- safeTimer.textContent = `Safe Period: ${remainingTime}s`;
190
- return; // NPC 움직임 중지
191
- } else {
192
- safeTimer.style.display = 'none';
193
- isSafePeriod = false;
194
- }
195
- }
196
-
197
- // 거리에 따른 속도와 데미지 조절
198
- const distance = this.mesh.position.distanceTo(playerPosition);
199
- this.speed = Math.min(0.05 + (50 - distance) * 0.001, 0.15);
200
-
201
- this.direction.subVectors(playerPosition, this.mesh.position);
202
- this.direction.normalize();
203
- this.velocity.add(this.direction.multiplyScalar(this.speed));
204
- this.velocity.multiplyScalar(0.95);
205
- this.mesh.position.add(this.velocity);
206
-
207
- // 충돌 및 데미지 처리
208
- if (distance < 2 && !isSafePeriod) {
209
- hp -= this.damage;
210
- hpElement.textContent = Math.floor(hp);
211
- if (hp <= 0) {
212
- alert('Game Over! Play again?');
213
- location.reload();
214
- }
215
- }
216
- }
217
  }
218
 
219
- function initGame() {
220
- // 기본 설정
221
- scene = new THREE.Scene();
222
- camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
223
- renderer = new THREE.WebGLRenderer({ antialias: true });
224
- renderer.setSize(window.innerWidth, window.innerHeight);
225
- renderer.shadowMap.enabled = true;
226
- document.body.appendChild(renderer.domElement);
227
-
228
- // 게임 시작 시간 기록
229
- gameStartTime = Date.now();
230
- isSafePeriod = true;
231
-
232
- // 물리 시스템 변수
233
- const gravity = -0.5;
234
- let velocity = new THREE.Vector3();
235
- let isJumping = false;
236
- let canJump = true;
237
- let playerHeight = 2;
238
-
239
- // 포인터 락 컨트롤
240
- controls = new PointerLockControls(camera, document.body);
241
-
242
- // 키보드 입력 처리
243
- const moveState = {
244
- forward: false,
245
- backward: false,
246
- left: false,
247
- right: false,
248
- jump: false
249
- };
250
-
251
- document.addEventListener('keydown', (event) => {
252
- switch (event.code) {
253
- case 'KeyW': moveState.forward = true; break;
254
- case 'KeyS': moveState.backward = true; break;
255
- case 'KeyA': moveState.left = true; break;
256
- case 'KeyD': moveState.right = true; break;
257
- case 'Space':
258
- if (canJump) {
259
- moveState.jump = true;
260
- velocity.y = 10;
261
- isJumping = true;
262
- canJump = false;
263
- }
264
- break;
265
- }
266
- });
267
-
268
- document.addEventListener('keyup', (event) => {
269
- switch (event.code) {
270
- case 'KeyW': moveState.forward = false; break;
271
- case 'KeyS': moveState.backward = false; break;
272
- case 'KeyA': moveState.left = false; break;
273
- case 'KeyD': moveState.right = false; break;
274
- case 'Space': moveState.jump = false; break;
275
- }
276
- });
277
-
278
- // 클릭으로 게임 시작
279
- document.addEventListener('click', () => {
280
- controls.lock();
281
- });
282
-
283
- // 날씨 시스템
284
- let raindrops = [];
285
- const rainCount = 1000;
286
- const rainGeometry = new THREE.BufferGeometry();
287
- const rainMaterial = new THREE.PointsMaterial({
288
- color: 0xaaaaaa,
289
- size: 0.1,
290
- transparent: true
291
- });
292
-
293
- function createRain() {
294
- const positions = new Float32Array(rainCount * 3);
295
- for (let i = 0; i < rainCount * 3; i += 3) {
296
- positions[i] = (Math.random() - 0.5) * 1000;
297
- positions[i + 1] = Math.random() * 500;
298
- positions[i + 2] = (Math.random() - 0.5) * 1000;
299
- }
300
- rainGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
301
- const rain = new THREE.Points(rainGeometry, rainMaterial);
302
- scene.add(rain);
303
- return rain;
304
- }
305
-
306
- let rain = createRain();
307
- rain.visible = false;
308
-
309
- // 안개 설정
310
- scene.fog = new THREE.Fog(0xcce0ff, 1, 1000);
311
-
312
- // 날씨 변경 함수
313
- window.changeWeather = function(weather) {
314
- switch(weather) {
315
- case 'clear':
316
- rain.visible = false;
317
- scene.fog.far = 1000;
318
- break;
319
- case 'rain':
320
- rain.visible = true;
321
- scene.fog.far = 100;
322
- break;
323
- case 'fog':
324
- rain.visible = false;
325
- scene.fog.far = 50;
326
- break;
327
- }
328
- };
329
-
330
- // NPC 생성
331
- const npcs = [];
332
- for (let i = 0; i < 5; i++) {
333
- const angle = (i / 5) * Math.PI * 2;
334
- const radius = 100; // 더 멀리 배치
335
- const position = new THREE.Vector3(
336
- Math.cos(angle) * radius,
337
- 2,
338
- Math.sin(angle) * radius
339
- );
340
- npcs.push(new NPC(position));
341
- }
342
-
343
- // 지형 생성
344
- const terrainGeometry = new THREE.PlaneGeometry(1000, 1000, 100, 100);
345
- const terrainMaterial = new THREE.MeshStandardMaterial({
346
- color: 0x3a8c3a,
347
- wireframe: false
348
- });
349
- const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
350
- terrain.rotation.x = -Math.PI / 2;
351
- terrain.receiveShadow = true;
352
- scene.add(terrain);
353
-
354
- // 지형 높낮이 설정
355
- const vertices = terrainGeometry.attributes.position.array;
356
- for (let i = 0; i < vertices.length; i += 3) {
357
- vertices[i + 2] = Math.random() * 10;
358
- }
359
- terrainGeometry.attributes.position.needsUpdate = true;
360
- terrainGeometry.computeVertexNormals();
361
-
362
- // 조명 설정
363
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
364
- scene.add(ambientLight);
365
-
366
- const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
367
- directionalLight.position.set(100, 100, 100);
368
- directionalLight.castShadow = true;
369
- scene.add(directionalLight);
370
-
371
- // 환경 오브젝트 추가
372
- function addEnvironmentObject(geometry, material, count, yOffset) {
373
- for (let i = 0; i < count; i++) {
374
- const mesh = new THREE.Mesh(geometry, material);
375
- const x = (Math.random() - 0.5) * 900;
376
- const z = (Math.random() - 0.5) * 900;
377
- const y = yOffset;
378
- mesh.position.set(x, y, z);
379
- mesh.castShadow = true;
380
- mesh.receiveShadow = true;
381
- scene.add(mesh);
382
- }
383
- }
384
-
385
- // 나무 생성
386
- const treeGeometry = new THREE.ConeGeometry(2, 8, 8);
387
- const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x0d5c0d });
388
- addEnvironmentObject(treeGeometry, treeMaterial, 100, 4);
389
-
390
- // 바위 생성
391
- const rockGeometry = new THREE.DodecahedronGeometry(2);
392
- const rockMaterial = new THREE.MeshSt
393
-
394
- const rockMaterial = new THREE.MeshStandardMaterial({ color: 0x666666 });
395
- addEnvironmentObject(rockGeometry, rockMaterial, 50, 1);
396
-
397
- // 캐릭터 모델 로드
398
- const loader = new GLTFLoader();
399
- loader.load(uploadedModel, (gltf) => {
400
- character = gltf.scene;
401
- character.scale.set(0.5, 0.5, 0.5);
402
- character.position.y = 2;
403
- character.castShadow = true;
404
- character.receiveShadow = true;
405
- scene.add(character);
406
-
407
- character.traverse((node) => {
408
- if (node.isMesh) {
409
- node.castShadow = true;
410
- node.receiveShadow = true;
411
- }
412
- });
413
- });
414
-
415
- // 카메라 초기 위치
416
- camera.position.set(0, playerHeight, 0);
417
-
418
- // 애니메이션 루프
419
- function animate() {
420
- requestAnimationFrame(animate);
421
-
422
- if (controls.isLocked) {
423
- const direction = new THREE.Vector3();
424
- const rotation = camera.getWorldDirection(new THREE.Vector3());
425
-
426
- if (moveState.forward) direction.add(rotation);
427
- if (moveState.backward) direction.sub(rotation);
428
- if (moveState.left) direction.cross(camera.up).negate();
429
- if (moveState.right) direction.cross(camera.up);
430
-
431
- direction.y = 0;
432
- direction.normalize();
433
-
434
- // 물리 시스템 적용
435
- velocity.y += gravity;
436
- camera.position.y += velocity.y * 0.1;
437
-
438
- // 바닥 충돌 검사
439
- if (camera.position.y <= playerHeight) {
440
- camera.position.y = playerHeight;
441
- velocity.y = 0;
442
- isJumping = false;
443
- canJump = true;
444
- }
445
-
446
- // 이동 속도 적용
447
- const moveSpeed = 0.5;
448
- controls.moveRight(-direction.z * moveSpeed);
449
- controls.moveForward(direction.x * moveSpeed);
450
-
451
- // 캐릭터 모델 업데이트
452
- if (character) {
453
- character.position.copy(camera.position);
454
- character.position.y -= playerHeight;
455
- if (direction.length() > 0) {
456
- const angle = Math.atan2(direction.x, direction.z);
457
- character.rotation.y = angle;
458
- }
459
- }
460
 
461
- // 업데이트
462
- if (rain.visible) {
463
- const positions = rain.geometry.attributes.position.array;
464
- for (let i = 1; i < positions.length; i += 3) {
465
- positions[i] -= 2;
466
- if (positions[i] < 0) {
467
- positions[i] = 500;
468
- }
469
- }
470
- rain.geometry.attributes.position.needsUpdate = true;
471
- }
472
 
473
- // NPC 업데이트
474
- npcs.forEach(npc => npc.update(camera.position));
475
- }
 
476
 
477
- renderer.render(scene, camera);
478
- }
479
 
480
- // 윈도우 리사이즈 처리
481
- window.addEventListener('resize', () => {
482
- camera.aspect = window.innerWidth / window.innerHeight;
483
- camera.updateProjectionMatrix();
484
- renderer.setSize(window.innerWidth, window.innerHeight);
485
- });
486
 
487
- // 애니메이션 시작
488
- animate();
 
 
489
  }
490
 
491
- // 날씨 변경 함수를 전역으로 노출
492
- window.changeWeather = function(weather) {
493
- // 이 함수는 게임 초기화 후 재정의됩니다
494
- console.log('Game not initialized yet');
495
- };
496
  </script>
497
  </body>
498
- </html>
 
1
+ keys[event.key.toLowerCase()] = true;
 
 
 
 
 
 
 
2
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ function onKeyUp(event) {
5
+ keys[event.key.toLowerCase()] = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  }
7
 
8
+ function onMouseMove(event) {
9
+ const rotSpeed = 0.002;
10
+ player.rotation.y -= event.movementX * rotSpeed;
11
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ function movePlayer() {
14
+ const speed = 0.1;
15
+ const direction = new THREE.Vector3();
 
 
 
 
 
 
 
 
16
 
17
+ if (keys['w']) direction.z -= speed;
18
+ if (keys['s']) direction.z += speed;
19
+ if (keys['a']) direction.x -= speed;
20
+ if (keys['d']) direction.x += speed;
21
 
22
+ direction.applyAxisAngle(new THREE.Vector3(0, 1, 0), player.rotation.y);
23
+ player.position.add(direction);
24
 
25
+ // Camera follows player
26
+ camera.position.x = player.position.x;
27
+ camera.position.z = player.position.z + 5;
28
+ camera.lookAt(player.position);
29
+ }
 
30
 
31
+ function animate() {
32
+ requestAnimationFrame(animate);
33
+ movePlayer();
34
+ renderer.render(scene, camera);
35
  }
36
 
37
+ window.addEventListener('resize', () => {
38
+ camera.aspect = window.innerWidth / window.innerHeight;
39
+ camera.updateProjectionMatrix();
40
+ renderer.setSize(window.innerWidth, window.innerHeight);
41
+ });
42
  </script>
43
  </body>
44
+ </html>