KaiShin1885 commited on
Commit
6d3fee3
·
verified ·
1 Parent(s): e3ff964

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +235 -193
index.html CHANGED
@@ -1,222 +1,264 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <style>
5
- #gameArea {
6
- width: 800px;
7
- height: 400px;
8
- border: 2px solid black;
9
- position: relative;
10
- overflow: hidden;
11
- background: #2a2a2a;
12
- margin: auto;
13
- }
14
 
15
- .character {
16
- width: 45px; /* 수정된 캐릭터 너비 */
17
- height: 100px; /* 수정된 캐릭터 높이 */
18
- position: absolute;
19
- bottom: 0;
20
- transition: height 0.2s;
21
- background-size: contain;
22
- background-repeat: no-repeat;
23
- background-position: center;
24
- }
25
 
26
- #player {
27
- left: 100px;
28
- background-image: url('kstand1.png');
29
- }
 
 
 
 
30
 
31
- #enemy {
32
- right: 100px;
33
- background: #f44;
34
- }
 
 
35
 
36
- .healthBar {
37
- width: 200px;
38
- height: 20px;
39
- background: #333;
40
- position: fixed;
41
- top: 20px;
42
- border: 2px solid #fff;
43
- }
44
 
45
- .healthFill {
46
- height: 100%;
47
- width: 100%;
48
- transition: width 0.1s;
49
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- #playerHealthBar { left: 20px; }
52
- #enemyHealthBar { right: 20px; }
53
 
54
- #playerHealthFill { background: linear-gradient(90deg, #44f, #88f); }
55
- #enemyHealthFill { background: linear-gradient(90deg, #f44, #f88); }
 
 
 
 
 
 
 
 
56
 
57
- .attack {
58
- position: absolute;
59
- pointer-events: none;
60
- }
61
 
62
- .midAttack {
63
- width: 30px;
64
- height: 30px;
65
- background: rgba(255,255,0,0.6);
66
- border: 2px solid #ff0;
67
- }
68
 
69
- .highAttack {
70
- width: 30px;
71
- height: 30px;
72
- background: rgba(255,128,0,0.6);
73
- border: 2px solid #f80;
74
- }
 
 
 
 
75
 
76
- .ultimate {
77
- width: 300px;
78
- height: 100px;
79
- background: rgba(255,0,0,0.6);
80
- border: 3px solid #f00;
81
- animation: ultimateEffect 0.3s linear;
82
- }
 
83
 
84
- #timer {
85
- position: fixed;
86
- top: 20px;
87
- left: 50%;
88
- transform: translateX(-50%);
89
- font-size: 30px;
90
- font-weight: bold;
91
- color: #fff;
92
- text-shadow: 2px 2px #000;
93
  }
94
 
95
- #instructions {
96
- position: fixed;
97
- right: 20px;
98
- top: 60px;
99
- padding: 15px;
100
- background: rgba(0,0,0,0.8);
101
- color: white;
102
- border-radius: 5px;
103
- font-family: monospace;
104
- }
105
 
106
- .status {
107
- position: fixed;
108
- left: 20px;
109
- bottom: 20px;
110
- padding: 10px;
111
- background: rgba(0,0,0,0.8);
112
- color: white;
113
- font-family: monospace;
114
- }
115
 
116
- .facing-left {
117
- transform: scaleX(-1);
 
 
118
  }
119
 
120
- .counter-effect {
121
- position: absolute;
122
- width: 45px;
123
- height: 100px;
124
- border: 2px solid #0ff;
125
- animation: counterAnim 0.4s linear;
126
- }
127
 
128
- @keyframes ultimateEffect {
129
- 0% { opacity: 0.3; }
130
- 50% { opacity: 0.8; }
131
- 100% { opacity: 0.3; }
 
 
 
 
 
 
 
 
 
 
 
132
  }
 
133
 
134
- @keyframes counterAnim {
135
- 0% { transform: scale(1); opacity: 1; }
136
- 100% { transform: scale(1.2); opacity: 0; }
137
- }
 
138
 
139
- .crouch {
140
- height: 50px !important;
 
 
 
 
 
 
 
141
  }
142
- </style>
143
- </head>
144
- <body>
145
- <div id="gameArea">
146
- <div id="timer">60</div>
147
- <div id="playerHealthBar" class="healthBar">
148
- <div id="playerHealthFill" class="healthFill"></div>
149
- </div>
150
- <div id="enemyHealthBar" class="healthBar">
151
- <div id="enemyHealthFill" class="healthFill"></div>
152
- </div>
153
- <div id="player" class="character"></div>
154
- <div id="enemy" class="character"></div>
155
- <div class="status"></div>
156
- <div id="instructions">
157
- Controls:<br><br>
158
- W - Jump<br>
159
- A - Left<br>
160
- D - Right<br>
161
- S - Crouch<br>
162
- J - Mid Attack (10dmg)<br>
163
- K - High Attack (10dmg)<br>
164
- Q - Ultimate (300dmg)<br>
165
- L - Counter
166
- </div>
167
- </div>
168
-
169
- <script>
170
- const SETTINGS = {
171
- FPS: 60,
172
- FRAME_TIME: 1000/60,
173
- MOVE_SPEED: 5,
174
- JUMP_FORCE: 15,
175
- GRAVITY: 0.8,
176
- INITIAL_HEALTH: 1000,
177
- DAMAGE: 10,
178
- SPECIAL_DAMAGE: 300,
179
- ATTACK_DELAY: 200,
180
- COUNTER_WINDOW: 400,
181
- COUNTER_COOLDOWN: 5000,
182
- SPECIAL_COOLDOWN: 30000,
183
- ANIMATION_INTERVAL: 500
184
- };
185
-
186
- const SPRITES = {
187
- stand: ['kstand1.png', 'kstand2.png', 'kstand3.png']
188
- };
189
-
190
- class Character {
191
- constructor(element, isPlayer = true) {
192
- this.element = element;
193
- this.isPlayer = isPlayer;
194
- this.health = SETTINGS.INITIAL_HEALTH;
195
- this.pos = { x: isPlayer ? 100 : 650, y: 0 };
196
- this.vel = { x: 0, y: 0 };
197
- this.direction = isPlayer ? 'right' : 'left';
198
- this.isMoving = false;
199
- this.isAttacking = false;
200
- this.isJumping = false;
201
- this.currentFrame = 0;
202
- this.lastAnimationUpdate = 0;
203
  }
204
 
205
- updateAnimation(timestamp) {
206
- if (this.isPlayer && !this.isMoving && !this.isAttacking) {
207
- if (timestamp - this.lastAnimationUpdate >= SETTINGS.ANIMATION_INTERVAL) {
208
- this.currentFrame = (this.currentFrame + 1) % SPRITES.stand.length;
209
- this.element.style.backgroundImage =
210
- `url(${SPRITES.stand[this.currentFrame]})`;
211
- this.lastAnimationUpdate = timestamp;
 
 
212
  }
213
- } else if (this.isPlayer) {
214
- this.element.style.backgroundImage = "url('kstand1.png')";
215
  }
216
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  }
218
 
219
- // Game 클래스를 이어서 작성할까요?
220
- </script>
221
- </body>
222
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Game {
2
+ constructor() {
3
+ this.lastFrameTime = 0;
4
+ this.gameTime = 60;
5
+ this.specialCooldown = Date.now() + SETTINGS.SPECIAL_COOLDOWN;
6
+ this.counterCooldown = 0;
7
+ this.canCounter = false;
8
+ this.lastHit = null;
9
+ this.isGameOver = false;
 
 
 
 
10
 
11
+ // 이미지 프리로드
12
+ this.preloadImages();
 
 
 
 
 
 
 
 
13
 
14
+ // 캐릭터 초기화
15
+ this.player = new Character(document.getElementById('player'), true);
16
+ this.enemy = new Character(document.getElementById('enemy'), false);
17
+
18
+ this.keys = {};
19
+ this.setupControls();
20
+ this.startGame();
21
+ }
22
 
23
+ preloadImages() {
24
+ SPRITES.stand.forEach(src => {
25
+ const img = new Image();
26
+ img.src = src;
27
+ });
28
+ }
29
 
30
+ setupControls() {
31
+ document.addEventListener('keydown', (e) => {
32
+ if (this.isGameOver) return;
33
+ this.keys[e.key.toLowerCase()] = true;
 
 
 
 
34
 
35
+ switch(e.key.toLowerCase()) {
36
+ case 'j':
37
+ case 'k':
38
+ this.startAttack(this.player, this.enemy, e.key === 'k' ? 'high' : 'mid');
39
+ break;
40
+ case 'q':
41
+ if (Date.now() >= this.specialCooldown) {
42
+ this.useUltimate(this.player, this.enemy);
43
+ }
44
+ break;
45
+ case 'l':
46
+ this.tryCounter();
47
+ break;
48
+ case 's':
49
+ this.player.element.classList.add('crouch');
50
+ break;
51
+ }
52
+ });
53
+
54
+ document.addEventListener('keyup', (e) => {
55
+ this.keys[e.key.toLowerCase()] = false;
56
+ if (e.key.toLowerCase() === 's') {
57
+ this.player.element.classList.remove('crouch');
58
+ }
59
+ });
60
+ }
61
 
62
+ startAttack(attacker, defender, type) {
63
+ attacker.isAttacking = true;
64
 
65
+ // 공격 차지 이펙트 생성
66
+ const chargeEffect = document.createElement('div');
67
+ chargeEffect.className = `attack ${type}Attack`;
68
+
69
+ const xOffset = attacker.direction === 'right' ? 45 : -30;
70
+ const yOffset = type === 'high' ? 70 : 35;
71
+
72
+ chargeEffect.style.left = `${attacker.pos.x + xOffset}px`;
73
+ chargeEffect.style.bottom = `${yOffset}px`;
74
+ document.getElementById('gameArea').appendChild(chargeEffect);
75
 
76
+ setTimeout(() => {
77
+ chargeEffect.remove();
78
+ this.executeAttack(attacker, defender, type);
79
+ }, SETTINGS.ATTACK_DELAY);
80
 
81
+ setTimeout(() => {
82
+ attacker.isAttacking = false;
83
+ }, SETTINGS.ATTACK_DELAY + 100);
84
+ }
 
 
85
 
86
+ executeAttack(attacker, defender, type) {
87
+ const attackEl = document.createElement('div');
88
+ attackEl.className = `attack ${type}Attack`;
89
+
90
+ const xOffset = attacker.direction === 'right' ? 45 : -30;
91
+ const yOffset = type === 'high' ? 70 : 35;
92
+
93
+ attackEl.style.left = `${attacker.pos.x + xOffset}px`;
94
+ attackEl.style.bottom = `${yOffset}px`;
95
+ document.getElementById('gameArea').appendChild(attackEl);
96
 
97
+ const distance = Math.abs(attacker.pos.x - defender.pos.x);
98
+ if (distance < 100) {
99
+ this.lastHit = {
100
+ time: Date.now(),
101
+ target: defender,
102
+ damage: SETTINGS.DAMAGE
103
+ };
104
+ this.canCounter = true;
105
 
106
+ setTimeout(() => {
107
+ if (this.lastHit && !this.lastHit.countered) {
108
+ defender.health -= SETTINGS.DAMAGE;
109
+ this.updateHealthBars();
110
+ }
111
+ this.lastHit = null;
112
+ this.canCounter = false;
113
+ }, SETTINGS.COUNTER_WINDOW);
 
114
  }
115
 
116
+ setTimeout(() => attackEl.remove(), 100);
117
+ }
 
 
 
 
 
 
 
 
118
 
119
+ useUltimate(attacker, defender) {
120
+ const ultimateEl = document.createElement('div');
121
+ ultimateEl.className = 'attack ultimate';
122
+
123
+ const xOffset = attacker.direction === 'right' ? 45 : -300;
124
+ ultimateEl.style.left = `${attacker.pos.x + xOffset}px`;
125
+ ultimateEl.style.bottom = '0px';
126
+
127
+ document.getElementById('gameArea').appendChild(ultimateEl);
128
 
129
+ const distance = Math.abs(attacker.pos.x - defender.pos.x);
130
+ if (distance < 300) {
131
+ defender.health -= SETTINGS.SPECIAL_DAMAGE;
132
+ this.updateHealthBars();
133
  }
134
 
135
+ this.specialCooldown = Date.now() + SETTINGS.SPECIAL_COOLDOWN;
136
+ setTimeout(() => ultimateEl.remove(), 300);
137
+ }
 
 
 
 
138
 
139
+ tryCounter() {
140
+ if (Date.now() < this.counterCooldown) return;
141
+
142
+ if (this.canCounter && this.lastHit &&
143
+ Date.now() - this.lastHit.time <= SETTINGS.COUNTER_WINDOW) {
144
+ const counterEffect = document.createElement('div');
145
+ counterEffect.className = 'counter-effect';
146
+ counterEffect.style.left = `${this.player.pos.x}px`;
147
+ counterEffect.style.bottom = '0px';
148
+ document.getElementById('gameArea').appendChild(counterEffect);
149
+
150
+ setTimeout(() => counterEffect.remove(), 400);
151
+ this.lastHit.countered = true;
152
+ } else {
153
+ this.counterCooldown = Date.now() + SETTINGS.COUNTER_COOLDOWN;
154
  }
155
+ }
156
 
157
+ updateAI() {
158
+ if (Date.now() - this.enemy.lastAction < 300) return;
159
+
160
+ const distance = Math.abs(this.player.pos.x - this.enemy.pos.x);
161
+ const healthRatio = this.enemy.health / this.player.health;
162
 
163
+ if (healthRatio < 0.7 || distance < 80) {
164
+ this.enemy.vel.x = SETTINGS.MOVE_SPEED *
165
+ (this.player.pos.x > this.enemy.pos.x ? -1 : 1);
166
+ } else if (distance > 150) {
167
+ this.enemy.vel.x = SETTINGS.MOVE_SPEED *
168
+ (this.player.pos.x > this.enemy.pos.x ? 1 : -1);
169
+ } else if (Math.random() < 0.05) {
170
+ this.startAttack(this.enemy, this.player,
171
+ Math.random() > 0.5 ? 'high' : 'mid');
172
  }
173
+
174
+ this.enemy.direction = this.player.pos.x > this.enemy.pos.x ?
175
+ 'right' : 'left';
176
+ this.enemy.lastAction = Date.now();
177
+ }
178
+
179
+ update(timestamp) {
180
+ if (timestamp - this.lastFrameTime >= SETTINGS.FRAME_TIME) {
181
+ // 플레이어 입력 처리
182
+ if (this.keys['w'] && !this.player.isJumping) {
183
+ this.player.isJumping = true;
184
+ this.player.vel.y = -SETTINGS.JUMP_FORCE;
185
+ }
186
+ if (this.keys['a']) {
187
+ this.player.vel.x = -SETTINGS.MOVE_SPEED;
188
+ this.player.direction = 'left';
189
+ this.player.isMoving = true;
190
+ this.player.element.classList.add('facing-left');
191
+ } else if (this.keys['d']) {
192
+ this.player.vel.x = SETTINGS.MOVE_SPEED;
193
+ this.player.direction = 'right';
194
+ this.player.isMoving = true;
195
+ this.player.element.classList.remove('facing-left');
196
+ } else {
197
+ this.player.isMoving = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  }
199
 
200
+ // 물리 업데이트
201
+ [this.player, this.enemy].forEach(char => {
202
+ // 중력
203
+ if (char.isJumping) {
204
+ char.vel.y += SETTINGS.GRAVITY;
205
+ char.pos.y = Math.max(0, char.pos.y - char.vel.y);
206
+ if (char.pos.y === 0) {
207
+ char.isJumping = false;
208
+ char.vel.y = 0;
209
  }
 
 
210
  }
211
+
212
+ // 위치 업데이트
213
+ char.pos.x += char.vel.x;
214
+ char.pos.x = Math.max(0, Math.min(755, char.pos.x));
215
+ char.vel.x *= 0.8;
216
+
217
+ // 시각적 업데이트
218
+ char.element.style.left = `${char.pos.x}px`;
219
+ char.element.style.bottom = `${char.pos.y}px`;
220
+
221
+ // 애니메이션 업데이트
222
+ char.updateAnimation(timestamp);
223
+ });
224
+
225
+ this.updateAI();
226
+ this.lastFrameTime = timestamp;
227
  }
228
 
229
+ if (!this.isGameOver) {
230
+ requestAnimationFrame(this.update.bind(this));
231
+ }
232
+ }
233
+
234
+ updateHealthBars() {
235
+ document.getElementById('playerHealthFill').style.width =
236
+ `${(this.player.health/SETTINGS.INITIAL_HEALTH)*100}%`;
237
+ document.getElementById('enemyHealthFill').style.width =
238
+ `${(this.enemy.health/SETTINGS.INITIAL_HEALTH)*100}%`;
239
+ }
240
+
241
+ startGame() {
242
+ this.updateHealthBars();
243
+ requestAnimationFrame(this.update.bind(this));
244
+
245
+ const timer = setInterval(() => {
246
+ this.gameTime--;
247
+ document.getElementById('timer').textContent = this.gameTime;
248
+
249
+ if (this.gameTime <= 0) {
250
+ clearInterval(timer);
251
+ this.endGame();
252
+ }
253
+ }, 1000);
254
+ }
255
+
256
+ endGame() {
257
+ this.isGameOver = true;
258
+ const winner = this.player.health > this.enemy.health ? 'Player' : 'Enemy';
259
+ alert(`Game Over! ${winner} wins!`);
260
+ }
261
+ }
262
+
263
+ // 게임 시작
264
+ new Game();