HTML (비교)

r1 ➤ r2
4 웹사이트를 만드는데 유용한 언어이다. '<어쩌구>' 같은 태그를 사용한다.
5
6==HTML 파일을 만드는 법==
7 Visual Studio Code 같은 전문 프로그래밍 도구도 필요 없을 정도로 쉽다. '''메모장'''을 열어서 코드를 적은 다음에 파일 형식을 .html로 해 놓으면 끝이다.
8
9==이것을 이용한 작품==
10===대항해 시대([[손현준(교사)|손현준]])===
11 [[손현준(교사)]]이 자신의 블로그 '번데기의 최후'에 올려 놓은 항해 게임이다. 코드는 아래와 같다.
12
13{{{#!folding 전체 코드
14<!DOCTYPE html>
15<html lang="ko">
16<head>
17 <meta charset="UTF-8">
18 <meta name="viewport" content="width=device-width, initial-scale=1.0">
19 <title>🌋 화산 피하기 게임 🚗</title>
20 <style>
21 /* 기본 스타일 및 레이아웃 */
22 body {
23 display: flex;
24 justify-content: center;
25 align-items: center;
26 height: 100vh;
27 margin: 0;
28 background-color: #f0f0f0;
29 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
30 flex-direction: column;
31 }
32
33 #game-container {
34 position: relative;
35 width: 300px;
36 height: 500px;
37 background-color: #556b2f;
38 border: 5px solid #000;
39 overflow: hidden;
40 touch-action: none;
41 }
42
43 /* 플레이어 스타일 */
44 #player {
45 position: absolute;
46 width: 40px;
47 height: 40px;
48 font-size: 35px;
49 text-align: center;
50 line-height: 40px;
51 bottom: 10px;
52 transform: rotate(90deg);
53 transition: transform 0.1s linear;
54 z-index: 10;
55 }
56
57 /* 장애물 공통 스타일 */
58 .obstacle {
59 position: absolute;
60 box-sizing: border-box;
61 }
62
63 /* 화산 스타일 */
64 .volcano-spot {
65 border-radius: 50%;
66 opacity: 0;
67 transition: opacity 0.2s linear;
68 }
69 .warning {
70 background-color: sienna;
71 opacity: 0.6;
72 box-shadow: 0 0 8px rgba(160, 82, 45, 0.7);
73 }
74 .erupted {
75 background-color: red;
76 box-shadow: 0 0 15px 8px #ff4500;
77 transform: scale(1.1);
78 opacity: 1;
79 }
80
81 /* 검은 벽 장애물 스타일 */
82 .black-wall {
83 background-color: #2c2c2c;
84 box-shadow: 0 0 5px rgba(0, 0, 0, 0.7);
85 z-index: 5;
86 }
87
88 /* UI 요소 스타일 */
89 .ui-overlay {
90 position: absolute;
91 top: 0; left: 0;
92 width: 100%; height: 100%;
93 background-color: rgba(0, 0, 0, 0.8);
94 color: white;
95 display: flex;
96 flex-direction: column;
97 justify-content: center;
98 align-items: center;
99 text-align: center;
100 z-index: 20;
101 }
102
103 #game-title {
104 position: absolute;
105 top: 10px;
106 width: 100%;
107 text-align: center;
108 color: white;
109 font-size: 24px;
110 font-weight: bold;
111 text-shadow: 2px 2px 4px #000;
112 z-index: 15;
113 }
114
115 #info-board {
116 position: absolute;
117 top: 5px; right: 5px;
118 color: white; font-size: 14px;
119 font-weight: bold;
120 z-index: 15;
121 }
122
123 #game-over { display: none; }
124 #start-screen { display: flex; }
125
126 .game-button {
127 padding: 10px 20px;
128 font-size: 20px;
129 cursor: pointer;
130 margin-top: 30px;
131 border: none;
132 border-radius: 5px;
133 background-color: #4CAF50;
134 color: white;
135 }
136 </style>
137</head>
138<body>
139
140 <div id="game-container">
141 <div id="game-title">🌋 화산 피하기 🚗</div>
142 <div id="info-board">
143 거리: <span id="distance-display">0</span>m
144 </div>
145
146 <div id="player">🚗</div>
147
148 <div id="start-screen" class="ui-overlay">
149 <h1>화산 피하기</h1>
150 <p>화산 폭발과 벽을 피해<br>최대한 멀리 도망가세요!</p>
151 <button id="start-button" class="game-button">게임 시작</button>
152 </div>
153 <div id="game-over" class="ui-overlay">
154 <h1>💥 게임 오버! 💥</h1>
155 최종 점수: <span id="final-score">0</span><br>
156 이동 거리: <span id="final-distance">0</span>m<br>
157 <button id="restart-button" class="game-button">다시 시작</button>
158 </div>
159 </div>
160
161 <script>
162 // DOM 요소
163 const gameContainer = document.getElementById('game-container');
164 const player = document.getElementById('player');
165 const distanceDisplay = document.getElementById('distance-display');
166 const gameOverScreen = document.getElementById('game-over');
167 const finalScoreSpan = document.getElementById('final-score');
168 const finalDistanceSpan = document.getElementById('final-distance');
169 const restartButton = document.getElementById('restart-button');
170 const startScreen = document.getElementById('start-screen');
171 const startButton = document.getElementById('start-button');
172
173 // 게임 설정
174 const CONTAINER_WIDTH = gameContainer.clientWidth;
175 const CONTAINER_HEIGHT = gameContainer.clientHeight;
176 const PLAYER_SIZE = 40;
177 const PLAYER_SPEED = 4;
178 const BASE_SCROLL_SPEED = 3.0;
179
180 // 게임 상태 변수
181 let isGameRunning = false;
182 let score = 0, distance = 0;
183 let playerX, playerY, playerAngle, playerScaleX;
184 let keysPressed = {};
185 let lastVolcanoTime = 0, lastWallTime = 0;
186 let animationFrameId;
187
188 function initGame() {
189 isGameRunning = true;
190 score = 0; distance = 0;
191 playerX = CONTAINER_WIDTH / 2 - PLAYER_SIZE / 2;
192 playerY = CONTAINER_HEIGHT - PLAYER_SIZE - 10;
193 playerAngle = 90; playerScaleX = 1;
194 distanceDisplay.textContent = '0';
195 startScreen.style.display = 'none';
196 gameOverScreen.style.display = 'none';
197
198 document.querySelectorAll('.obstacle').forEach(el => el.remove());
199
200 player.style.left = `${playerX}px`;
201 player.style.top = `${playerY}px`;
202 player.style.transform = `rotate(90deg) scaleX(1)`;
203 player.style.visibility = 'visible';
204
205 lastVolcanoTime = performance.now();
206 lastWallTime = performance.now();
207
208 if(animationFrameId) cancelAnimationFrame(animationFrameId);
209 gameLoop();
210 }
211
212 function updatePlayer() {
213 let dx = 0, dy = 0;
214 if (keysPressed['ArrowLeft'] || keysPressed['a']) dx -= PLAYER_SPEED;
215 if (keysPressed['ArrowRight'] || keysPressed['d']) dx += PLAYER_SPEED;
216 if (keysPressed['ArrowUp'] || keysPressed['w']) dy -= PLAYER_SPEED;
217 if (keysPressed['ArrowDown'] || keysPressed['s']) dy += PLAYER_SPEED;
218
219 let nextX = playerX + dx;
220 let nextY = playerY + dy;
221
222 document.querySelectorAll('.black-wall').forEach(wall => {
223 const wallRect = wall.getBoundingClientRect();
224 const containerRect = gameContainer.getBoundingClientRect();
225 const wallGameRect = {
226 left: wallRect.left - containerRect.left, top: wallRect.top - containerRect.top,
227 right: wallRect.right - containerRect.left, bottom: wallRect.bottom - containerRect.top
228 };
229
230 const hPlayerRect = { left: nextX, top: playerY, right: nextX + PLAYER_SIZE, bottom: playerY + PLAYER_SIZE };
231 if (hPlayerRect.right > wallGameRect.left && hPlayerRect.left < wallGameRect.right &&
232 hPlayerRect.bottom > wallGameRect.top && hPlayerRect.top < wallGameRect.bottom) {
233 nextX = playerX;
234 }
235
236 const vPlayerRect = { left: playerX, top: nextY, right: playerX + PLAYER_SIZE, bottom: nextY + PLAYER_SIZE };
237 if (vPlayerRect.right > wallGameRect.left && vPlayerRect.left < wallGameRect.right &&
238 vPlayerRect.bottom > wallGameRect.top && vPlayerRect.top < wallGameRect.bottom) {
239 nextY = playerY;
240 }
241 });
242
243 playerX = Math.max(0, Math.min(CONTAINER_WIDTH - PLAYER_SIZE, nextX));
244 playerY = Math.max(0, Math.min(CONTAINER_HEIGHT - PLAYER_SIZE, nextY));
245
246 if (dx !== 0 || dy !== 0) {
247 if (dy < 0) playerAngle = dx === 0 ? 90 : (dx < 0 ? 45 : 135);
248 else if (dy > 0) playerAngle = dx === 0 ? -90 : (dx < 0 ? -45 : -135);
249 else playerAngle = 0;
250 if (dx < 0) playerScaleX = 1; else if (dx > 0) playerScaleX = -1;
251 }
252
253 player.style.left = `${playerX}px`;
254 player.style.top = `${playerY}px`;
255 player.style.transform = `rotate(${playerAngle}deg) scaleX(${playerScaleX})`;
256 }
257
258 function isOverlapping(newRect) {
259 let overlapping = false;
260 document.querySelectorAll('.obstacle').forEach(obstacle => {
261 const existingRect = obstacle.getBoundingClientRect();
262 const containerRect = gameContainer.getBoundingClientRect();
263 const existingGameRect = {
264 left: existingRect.left - containerRect.left, top: existingRect.top - containerRect.top,
265 right: existingRect.right - containerRect.left, bottom: existingRect.bottom - containerRect.top
266 };
267 const padding = 10;
268 if (newRect.right + padding > existingGameRect.left && newRect.left - padding < existingGameRect.right &&
269 newRect.bottom + padding > existingGameRect.top && newRect.top - padding < existingGameRect.bottom) {
270 overlapping = true;
271 }
272 });
273 return overlapping;
274 }
275
276 function createVolcano(timestamp, difficultyLevel) {
277 const interval = Math.max(500, 1600 - difficultyLevel * 150);
278 if (timestamp - lastVolcanoTime < interval) return;
279 lastVolcanoTime = timestamp;
280
281 let newRect, w, h, x, y;
282 for (let i = 0; i < 10; i++) {
283 const minSize = Math.min(50, 30 + difficultyLevel * 4);
284 const maxSize = Math.min(90, 60 + difficultyLevel * 6);
285 const size = Math.random() * (maxSize - minSize) + minSize;
286
287 w = size; h = size;
288 if (Math.random() < 0.5) h = Math.random() * (maxSize - minSize) + minSize;
289 x = Math.random() * (CONTAINER_WIDTH - w);
290 y = -h;
291 newRect = { left: x, top: y, right: x + w, bottom: y + h };
292 if (!isOverlapping(newRect)) break;
293 if (i === 9) return;
294 }
295
296 const volcano = document.createElement('div');
297 volcano.className = 'obstacle volcano-spot';
298 volcano.style.width = `${w}px`; volcano.style.height = `${h}px`;
299 volcano.style.left = `${x}px`; volcano.style.top = `${y}px`;
300 gameContainer.appendChild(volcano);
301
302 setTimeout(() => volcano.classList.add('warning'), 600);
303 setTimeout(() => {
304 volcano.classList.remove('warning');
305 volcano.classList.add('erupted');
306 }, 800);
307 setTimeout(() => volcano.remove(), 3000);
308 }
309
310 function createBlackWall(timestamp, difficultyLevel) {
311 const interval = Math.max(1200, 2800 - difficultyLevel * 200);
312 if (timestamp - lastWallTime < interval) return;
313 lastWallTime = timestamp;
314
315 let newRect, width, angle, x, y;
316 for (let i = 0; i < 10; i++) {
317 const minWidth = Math.min(PLAYER_SIZE * 1.5, PLAYER_SIZE + difficultyLevel * 5);
318 const maxWidth = Math.min(PLAYER_SIZE * 2.5, PLAYER_SIZE * 2 + difficultyLevel * 8);
319 const WALL_HEIGHT = 20;
320 width = Math.random() * (maxWidth - minWidth) + minWidth;
321
322 angle = Math.random() * 180 - 90;
323 x = Math.random() * (CONTAINER_WIDTH - width);
324 y = -WALL_HEIGHT;
325 newRect = { left: x, top: y, right: x + width, bottom: y + WALL_HEIGHT };
326 if (!isOverlapping(newRect)) break;
327 if (i === 9) return;
328 }
329
330 const wall = document.createElement('div');
331 wall.className = 'obstacle black-wall';
332 wall.style.width = `${width}px`; wall.style.height = `20px`;
333 wall.style.left = `${x}px`; wall.style.top = `${y}px`;
334 wall.style.transform = `rotate(${angle}deg)`;
335 gameContainer.appendChild(wall);
336 setTimeout(() => wall.remove(), 5000);
337 }
338
339 function updateObstacles(currentScrollSpeed) {
340 document.querySelectorAll('.obstacle').forEach(obstacle => {
341 let top = parseFloat(obstacle.style.top) || 0;
342 obstacle.style.top = `${top + currentScrollSpeed}px`;
343
344 if (obstacle.classList.contains('erupted')) {
345 checkVolcanoCollision(obstacle);
346 }
347
348 if (obstacle.classList.contains('black-wall')) {
349 const playerRect = { left: playerX, top: playerY, right: playerX + PLAYER_SIZE, bottom: playerY + PLAYER_SIZE };
350 const wallRect = obstacle.getBoundingClientRect();
351 const containerRect = gameContainer.getBoundingClientRect();
352 const wallGameRect = {
353 left: wallRect.left - containerRect.left, top: wallRect.top - containerRect.top,
354 right: wallRect.right - containerRect.left, bottom: wallRect.bottom - containerRect.top
355 };
356
357 if (playerRect.right > wallGameRect.left && playerRect.left < wallGameRect.right &&
358 playerRect.bottom > wallGameRect.top && playerRect.top < wallGameRect.bottom) {
359
360 // ⭐ [수정됨] 벽이 플레이어를 밀어낼 때, 즉시 화면에 반영
361 playerY = wallGameRect.bottom;
362 player.style.top = `${playerY}px`;
363 }
364 }
365 });
366 }
367
368 function checkVolcanoCollision(volcano) {
369 if (!isGameRunning) return;
370 const playerRect = player.getBoundingClientRect();
371 const volcanoRect = volcano.getBoundingClientRect();
372 if (playerRect.right > volcanoRect.left && playerRect.left < volcanoRect.right &&
373 playerRect.bottom > volcanoRect.top && playerRect.top < volcanoRect.bottom) {
374 endGame();
375 }
376 }
377
378 function endGame() {
379 if (!isGameRunning) return;
380 isGameRunning = false;
381 cancelAnimationFrame(animationFrameId);
382 player.style.visibility = 'hidden';
383 finalScoreSpan.textContent = Math.floor(score);
384 finalDistanceSpan.textContent = Math.floor(distance);
385 gameOverScreen.style.display = 'flex';
386 }
387
388 function gameLoop(timestamp) {
389 if (!isGameRunning) return;
390
391 const difficultyLevel = Math.floor(distance / 200);
392 const currentScrollSpeed = BASE_SCROLL_SPEED + difficultyLevel * 0.25;
393
394 updatePlayer();
395 createVolcano(timestamp, difficultyLevel);
396 createBlackWall(timestamp, difficultyLevel);
397 updateObstacles(currentScrollSpeed);
398
399 score += currentScrollSpeed * 0.5;
400 distance += currentScrollSpeed * 0.1;
401 distanceDisplay.textContent = `${Math.floor(distance)}`;
402
403 if (playerY >= CONTAINER_HEIGHT - PLAYER_SIZE) {
404 endGame();
405 }
406
407 animationFrameId = requestAnimationFrame(gameLoop);
408 }
409
410 // 이벤트 리스너
411 document.addEventListener('keydown', (e) => { keysPressed[e.key] = true; });
412 document.addEventListener('keyup', (e) => { keysPressed[e.key] = false; });
413 startButton.addEventListener('click', initGame);
414 restartButton.addEventListener('click', initGame);
415 </script>
416</body>
417</html>
418}}}