Spaces:
Running
Running
KaiShin1885
commited on
Commit
•
e5f4407
1
Parent(s):
df3d9b0
Update index.html
Browse files- index.html +193 -74
index.html
CHANGED
@@ -172,41 +172,94 @@
|
|
172 |
};
|
173 |
|
174 |
const SPRITES = {
|
175 |
-
stand: ['kstand1.png', 'kstand2.png']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
};
|
177 |
|
178 |
class Character {
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
|
193 |
-
|
194 |
-
|
195 |
-
|
|
|
|
|
|
|
|
|
196 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
197 |
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
}
|
208 |
}
|
209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
class Game {
|
211 |
constructor() {
|
212 |
this.lastFrameTime = 0;
|
@@ -224,6 +277,48 @@
|
|
224 |
this.setupControls();
|
225 |
this.startGame();
|
226 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
|
228 |
setupControls() {
|
229 |
document.addEventListener('keydown', (e) => {
|
@@ -258,8 +353,21 @@
|
|
258 |
});
|
259 |
}
|
260 |
|
261 |
-
|
|
|
|
|
262 |
attacker.isAttacking = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
263 |
const attackEl = document.createElement('div');
|
264 |
attackEl.className = `attack ${type}Attack`;
|
265 |
|
@@ -269,18 +377,25 @@
|
|
269 |
attackEl.style.left = `${attacker.pos.x + xOffset}px`;
|
270 |
attackEl.style.bottom = `${yOffset}px`;
|
271 |
document.getElementById('gameArea').appendChild(attackEl);
|
272 |
-
|
273 |
setTimeout(() => {
|
274 |
attackEl.remove();
|
275 |
if (!defender.isBlocking) {
|
276 |
defender.health -= SETTINGS.DAMAGE;
|
|
|
|
|
|
|
277 |
this.updateHealthBars();
|
278 |
this.checkGameOver();
|
279 |
}
|
280 |
}, SETTINGS.ATTACK_DELAY);
|
281 |
-
|
282 |
setTimeout(() => {
|
283 |
attacker.isAttacking = false;
|
|
|
|
|
|
|
|
|
284 |
}, SETTINGS.ATTACK_DELAY + 100);
|
285 |
}
|
286 |
|
@@ -346,55 +461,59 @@
|
|
346 |
'right' : 'left';
|
347 |
}
|
348 |
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
this.player.vel.x = -SETTINGS.MOVE_SPEED;
|
357 |
-
this.player.direction = 'left';
|
358 |
-
this.player.isMoving = true;
|
359 |
-
this.player.element.classList.add('facing-left');
|
360 |
-
} else if (this.keys['d']) {
|
361 |
-
this.player.vel.x = SETTINGS.MOVE_SPEED;
|
362 |
-
this.player.direction = 'right';
|
363 |
-
this.player.isMoving = true;
|
364 |
-
this.player.element.classList.remove('facing-left');
|
365 |
-
} else {
|
366 |
-
this.player.isMoving = false;
|
367 |
-
}
|
368 |
-
|
369 |
-
[this.player, this.enemy].forEach(char => {
|
370 |
-
if (char.isJumping) {
|
371 |
-
char.vel.y += SETTINGS.GRAVITY;
|
372 |
-
char.pos.y = Math.max(0, char.pos.y - char.vel.y);
|
373 |
-
if (char.pos.y === 0) {
|
374 |
-
char.isJumping = false;
|
375 |
-
char.vel.y = 0;
|
376 |
-
}
|
377 |
-
}
|
378 |
-
|
379 |
-
char.pos.x += char.vel.x;
|
380 |
-
char.pos.x = Math.max(0, Math.min(755, char.pos.x));
|
381 |
-
char.vel.x *= 0.8;
|
382 |
-
|
383 |
-
char.element.style.left = `${char.pos.x}px`;
|
384 |
-
char.element.style.bottom = `${char.pos.y}px`;
|
385 |
-
|
386 |
-
char.updateAnimation(timestamp);
|
387 |
-
});
|
388 |
|
389 |
-
|
390 |
-
|
391 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
392 |
|
393 |
-
|
394 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
395 |
}
|
396 |
}
|
397 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
updateHealthBars() {
|
399 |
document.getElementById('playerHealthFill').style.width =
|
400 |
`${(this.player.health / SETTINGS.INITIAL_HEALTH) * 100}%`;
|
|
|
172 |
};
|
173 |
|
174 |
const SPRITES = {
|
175 |
+
stand: ['kstand1.png', 'kstand2.png'],
|
176 |
+
midAttack: [
|
177 |
+
'kmidattack1.png',
|
178 |
+
'kmidattack2.png',
|
179 |
+
'kmidattack3.png',
|
180 |
+
'kmidattack4.png',
|
181 |
+
'kmidattack5.png'
|
182 |
+
]
|
183 |
};
|
184 |
|
185 |
class Character {
|
186 |
+
constructor(element, isPlayer = true) {
|
187 |
+
this.element = element;
|
188 |
+
this.isPlayer = isPlayer;
|
189 |
+
this.health = SETTINGS.INITIAL_HEALTH;
|
190 |
+
this.pos = { x: isPlayer ? 100 : 650, y: 0 };
|
191 |
+
this.vel = { x: 0, y: 0 };
|
192 |
+
this.direction = isPlayer ? 'right' : 'left';
|
193 |
+
this.isMoving = false;
|
194 |
+
this.isAttacking = false;
|
195 |
+
this.isJumping = false;
|
196 |
+
this.isBlocking = false;
|
197 |
+
this.currentFrame = 0;
|
198 |
+
this.lastAnimationUpdate = 0;
|
199 |
+
this.midAttackHits = 0;
|
200 |
+
this.isInCombo = false;
|
201 |
+
this.currentAnimation = 'stand';
|
202 |
+
this.animationFrame = 0;
|
203 |
+
this.lastAction = 0; // AI를 위한 마지막 행동 시간 추가
|
204 |
+
|
205 |
+
if (isPlayer) {
|
206 |
+
this.element.style.backgroundImage = `url(${SPRITES.stand[0]})`;
|
207 |
+
}
|
208 |
+
}
|
209 |
|
210 |
+
updateAnimation(timestamp) {
|
211 |
+
if (!this.isPlayer) return;
|
212 |
+
|
213 |
+
if (timestamp - this.lastAnimationUpdate >= SETTINGS.ANIMATION_INTERVAL) {
|
214 |
+
if (this.currentAnimation === 'stand' && !this.isMoving && !this.isAttacking) {
|
215 |
+
this.currentFrame = (this.currentFrame + 1) % SPRITES.stand.length;
|
216 |
+
this.element.style.backgroundImage = `url(${SPRITES.stand[this.currentFrame]})`;
|
217 |
}
|
218 |
+
else if (this.currentAnimation === 'midAttack') {
|
219 |
+
this.animationFrame = (this.animationFrame + 1) % SPRITES.midAttack.length;
|
220 |
+
this.element.style.backgroundImage = `url(${SPRITES.midAttack[this.animationFrame]})`;
|
221 |
+
}
|
222 |
+
this.lastAnimationUpdate = timestamp;
|
223 |
+
}
|
224 |
+
}
|
225 |
+
|
226 |
+
move(direction) {
|
227 |
+
this.vel.x = direction * SETTINGS.MOVE_SPEED;
|
228 |
+
this.direction = direction > 0 ? 'right' : 'left';
|
229 |
+
this.isMoving = true;
|
230 |
+
if (direction < 0) {
|
231 |
+
this.element.classList.add('facing-left');
|
232 |
+
} else {
|
233 |
+
this.element.classList.remove('facing-left');
|
234 |
+
}
|
235 |
+
}
|
236 |
|
237 |
+
stop() {
|
238 |
+
this.vel.x = 0;
|
239 |
+
this.isMoving = false;
|
240 |
+
}
|
241 |
+
|
242 |
+
updatePosition() {
|
243 |
+
// Update position based on velocity
|
244 |
+
this.pos.x += this.vel.x;
|
245 |
+
this.pos.x = Math.max(0, Math.min(755, this.pos.x));
|
246 |
+
|
247 |
+
// Update vertical position if jumping
|
248 |
+
if (this.isJumping) {
|
249 |
+
this.vel.y += SETTINGS.GRAVITY;
|
250 |
+
this.pos.y = Math.max(0, this.pos.y - this.vel.y);
|
251 |
+
if (this.pos.y === 0) {
|
252 |
+
this.isJumping = false;
|
253 |
+
this.vel.y = 0;
|
254 |
}
|
255 |
}
|
256 |
|
257 |
+
// Update DOM element position
|
258 |
+
this.element.style.left = `${this.pos.x}px`;
|
259 |
+
this.element.style.bottom = `${this.pos.y}px`;
|
260 |
+
}
|
261 |
+
}
|
262 |
+
|
263 |
class Game {
|
264 |
constructor() {
|
265 |
this.lastFrameTime = 0;
|
|
|
277 |
this.setupControls();
|
278 |
this.startGame();
|
279 |
}
|
280 |
+
checkMidAttackHit() {
|
281 |
+
// 중단 공격이 성공했을 때만 호출됨
|
282 |
+
if (this.player.currentAnimation === 'midAttack') {
|
283 |
+
this.player.midAttackHits++;
|
284 |
+
|
285 |
+
if (this.player.midAttackHits === 2) {
|
286 |
+
this.executeCombo(this.player, this.enemy);
|
287 |
+
}
|
288 |
+
}
|
289 |
+
}
|
290 |
+
executeCombo(attacker, defender) {
|
291 |
+
attacker.isInCombo = true;
|
292 |
+
|
293 |
+
// 첫번째 콤보 공격 (kmidattack3.png)
|
294 |
+
attacker.element.style.backgroundImage = `url(${SPRITES.midAttack[2]})`;
|
295 |
+
|
296 |
+
// 0.25초 후 두번째 이미지
|
297 |
+
setTimeout(() => {
|
298 |
+
attacker.element.style.backgroundImage = `url(${SPRITES.midAttack[3]})`;
|
299 |
+
if (!defender.isBlocking) {
|
300 |
+
defender.health -= SETTINGS.DAMAGE;
|
301 |
+
this.updateHealthBars();
|
302 |
+
}
|
303 |
+
}, 250);
|
304 |
+
|
305 |
+
// 0.5초 후 마지막 이미지
|
306 |
+
setTimeout(() => {
|
307 |
+
attacker.element.style.backgroundImage = `url(${SPRITES.midAttack[4]})`;
|
308 |
+
if (!defender.isBlocking) {
|
309 |
+
defender.health -= SETTINGS.DAMAGE;
|
310 |
+
this.updateHealthBars();
|
311 |
+
}
|
312 |
+
|
313 |
+
// 콤보 종료
|
314 |
+
setTimeout(() => {
|
315 |
+
attacker.isInCombo = false;
|
316 |
+
attacker.midAttackHits = 0;
|
317 |
+
attacker.currentAnimation = 'stand';
|
318 |
+
attacker.element.style.backgroundImage = `url(${SPRITES.stand[0]})`;
|
319 |
+
}, 250);
|
320 |
+
}, 500);
|
321 |
+
}
|
322 |
|
323 |
setupControls() {
|
324 |
document.addEventListener('keydown', (e) => {
|
|
|
353 |
});
|
354 |
}
|
355 |
|
356 |
+
startAttack(attacker, defender, type) {
|
357 |
+
if (attacker.isInCombo) return; // 콤보 중에는 새 공격 불가
|
358 |
+
|
359 |
attacker.isAttacking = true;
|
360 |
+
|
361 |
+
// 중단 공격일 경우 애니메이션 처리
|
362 |
+
if (type === 'mid' && attacker.isPlayer) {
|
363 |
+
attacker.currentAnimation = 'midAttack';
|
364 |
+
attacker.element.style.backgroundImage = `url(${SPRITES.midAttack[0]})`;
|
365 |
+
|
366 |
+
setTimeout(() => {
|
367 |
+
attacker.element.style.backgroundImage = `url(${SPRITES.midAttack[1]})`;
|
368 |
+
}, 100);
|
369 |
+
}
|
370 |
+
|
371 |
const attackEl = document.createElement('div');
|
372 |
attackEl.className = `attack ${type}Attack`;
|
373 |
|
|
|
377 |
attackEl.style.left = `${attacker.pos.x + xOffset}px`;
|
378 |
attackEl.style.bottom = `${yOffset}px`;
|
379 |
document.getElementById('gameArea').appendChild(attackEl);
|
380 |
+
|
381 |
setTimeout(() => {
|
382 |
attackEl.remove();
|
383 |
if (!defender.isBlocking) {
|
384 |
defender.health -= SETTINGS.DAMAGE;
|
385 |
+
if (type === 'mid' && attacker.isPlayer) {
|
386 |
+
this.checkMidAttackHit();
|
387 |
+
}
|
388 |
this.updateHealthBars();
|
389 |
this.checkGameOver();
|
390 |
}
|
391 |
}, SETTINGS.ATTACK_DELAY);
|
392 |
+
|
393 |
setTimeout(() => {
|
394 |
attacker.isAttacking = false;
|
395 |
+
if (!attacker.isInCombo) {
|
396 |
+
attacker.currentAnimation = 'stand';
|
397 |
+
attacker.element.style.backgroundImage = `url(${SPRITES.stand[0]})`;
|
398 |
+
}
|
399 |
}, SETTINGS.ATTACK_DELAY + 100);
|
400 |
}
|
401 |
|
|
|
461 |
'right' : 'left';
|
462 |
}
|
463 |
|
464 |
+
update(timestamp) {
|
465 |
+
if (timestamp - this.lastFrameTime >= SETTINGS.FRAME_TIME) {
|
466 |
+
// Jump handling
|
467 |
+
if (this.keys['w'] && !this.player.isJumping) {
|
468 |
+
this.player.isJumping = true;
|
469 |
+
this.player.vel.y = -SETTINGS.JUMP_FORCE;
|
470 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
471 |
|
472 |
+
// Movement handling
|
473 |
+
if (this.keys['a']) {
|
474 |
+
this.player.vel.x = -SETTINGS.MOVE_SPEED;
|
475 |
+
this.player.direction = 'left';
|
476 |
+
this.player.isMoving = true;
|
477 |
+
this.player.element.classList.add('facing-left');
|
478 |
+
} else if (this.keys['d']) {
|
479 |
+
this.player.vel.x = SETTINGS.MOVE_SPEED;
|
480 |
+
this.player.direction = 'right';
|
481 |
+
this.player.isMoving = true;
|
482 |
+
this.player.element.classList.remove('facing-left');
|
483 |
+
} else {
|
484 |
+
this.player.isMoving = false;
|
485 |
+
this.player.vel.x = 0; // 키를 떼면 속도를 즉시 0으로 설정
|
486 |
+
}
|
487 |
|
488 |
+
// Update characters
|
489 |
+
[this.player, this.enemy].forEach(char => {
|
490 |
+
if (char.isJumping) {
|
491 |
+
char.vel.y += SETTINGS.GRAVITY;
|
492 |
+
char.pos.y = Math.max(0, char.pos.y - char.vel.y);
|
493 |
+
if (char.pos.y === 0) {
|
494 |
+
char.isJumping = false;
|
495 |
+
char.vel.y = 0;
|
496 |
}
|
497 |
}
|
498 |
+
|
499 |
+
char.pos.x += char.vel.x;
|
500 |
+
char.pos.x = Math.max(0, Math.min(755, char.pos.x));
|
501 |
+
|
502 |
+
// Remove this line: char.vel.x *= 0.8;
|
503 |
+
|
504 |
+
char.element.style.left = `${char.pos.x}px`;
|
505 |
+
char.element.style.bottom = `${char.pos.y}px`;
|
506 |
+
char.updateAnimation(timestamp);
|
507 |
+
});
|
508 |
+
|
509 |
+
this.updateAI();
|
510 |
+
this.lastFrameTime = timestamp;
|
511 |
+
}
|
512 |
+
|
513 |
+
if (!this.isGameOver) {
|
514 |
+
requestAnimationFrame(this.update.bind(this));
|
515 |
+
}
|
516 |
+
}
|
517 |
updateHealthBars() {
|
518 |
document.getElementById('playerHealthFill').style.width =
|
519 |
`${(this.player.health / SETTINGS.INITIAL_HEALTH) * 100}%`;
|