411 lines
9.9 KiB
JavaScript
411 lines
9.9 KiB
JavaScript
let player1, player2;
|
|
let projectiles = [], specialProjectiles = [], bombs = [];
|
|
let groundY = 500, deathZoneY = 600;
|
|
let controlsP1, controlsP2;
|
|
|
|
function preload() {
|
|
preloadAssets();
|
|
}
|
|
|
|
function setup() {
|
|
createCanvas(800, 1600);
|
|
sliceAssets();
|
|
|
|
controlsP1 = { left:'ArrowLeft', right:'ArrowRight', jump:'ArrowUp', attack:'[', bomb:']' };
|
|
controlsP2 = { left:'a', right:'d', jump:'w', attack:'t', bomb:'y' };
|
|
|
|
player1 = new Player(100, 100, P1imgs, controlsP1);
|
|
player2 = new Player(200, 100, P2imgs, controlsP2);
|
|
}
|
|
|
|
function draw() {
|
|
backgroundManager.draw();
|
|
rect(0, groundY, width, 100);
|
|
|
|
player1.update(); player1.draw();
|
|
player2.update(); player2.draw();
|
|
|
|
handleProjectiles();
|
|
handleBombs();
|
|
handleSpecialProjectiles();
|
|
}
|
|
|
|
function keyPressed() {
|
|
if (key === ' ') backgroundManager.modeChange();
|
|
player1.handleKeyPressed(key);
|
|
player2.handleKeyPressed(key);
|
|
}
|
|
function keyReleased() {
|
|
player1.handleKeyReleased(key);
|
|
player2.handleKeyReleased(key);
|
|
}
|
|
|
|
class Background {
|
|
constructor(dayImg, nightImg) {
|
|
this.bgDay = dayImg;
|
|
this.bgNight = nightImg;
|
|
this.mode = 'day';
|
|
this.currentImg = this.bgDay;
|
|
this.tileW = dayImg.width;
|
|
this.tileH = dayImg.height;
|
|
}
|
|
|
|
setMode(mode) {
|
|
if (mode === "day") {
|
|
this.mode = "day";
|
|
this.currentImg = this.bgDay;
|
|
}
|
|
else if (mode === "night") {
|
|
this.mode = "night";
|
|
this.currentImg = this.bgNight;
|
|
}
|
|
else {
|
|
console.log("error");
|
|
}
|
|
}
|
|
modeChange(){
|
|
this.setMode(this.mode === "day" ? "night" : "day");
|
|
}
|
|
draw() {
|
|
for (let y = 0; y < height; y += this.tileH) {
|
|
for (let x = 0; x < width; x += this.tileW) {
|
|
image(this.currentImg, x, y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
class Player {
|
|
constructor(x, y, imgSet, controls) {
|
|
this.x = x; this.y = y;
|
|
this.vx = 0; this.vy = 0;
|
|
this.knockbackVX = 0;
|
|
this.width = 32; this.height = 32;
|
|
this.onGround = false;
|
|
this.jumpCount = 0;
|
|
this.deathCount = 10;
|
|
this.imgSet = imgSet;
|
|
this.controls = controls;
|
|
this.keys = {};
|
|
this.bombHoldStartTime = null;
|
|
this.skill = { resist:false, fastFire:false, isGiant:false, itemCount:0 };
|
|
this.facing = "right";
|
|
this.state = "idle";
|
|
this.frame = 0;
|
|
}
|
|
|
|
update() {
|
|
let inputVX = 0;
|
|
if (this.keys[this.controls.left]) { inputVX = -5; this.facing = "left"; }
|
|
else if (this.keys[this.controls.right]){ inputVX = 5; this.facing = "right"; }
|
|
|
|
this.vx = inputVX + this.knockbackVX;
|
|
this.vy += gravity;
|
|
this.x += this.vx;
|
|
this.y += this.vy;
|
|
|
|
if (this.y + this.height >= groundY) {
|
|
this.y = groundY - this.height;
|
|
this.vy = 0;
|
|
this.onGround = true;
|
|
this.jumpCount = 0;
|
|
this.state = "idle";
|
|
} else {
|
|
this.onGround = false;
|
|
this.state = "jump";
|
|
}
|
|
|
|
this.knockbackVX *= 0.9;
|
|
if (abs(this.knockbackVX) < 0.1) this.knockbackVX = 0;
|
|
|
|
if (this.y > deathZoneY) {
|
|
this.deathCount--;
|
|
this.state = "dead";
|
|
this.respawn();
|
|
}
|
|
}
|
|
|
|
respawn() {
|
|
this.x = 100; this.y = 100;
|
|
this.vx = this.vy = this.knockbackVX = 0;
|
|
}
|
|
|
|
jump() {
|
|
if (this.jumpCount < 2) {
|
|
this.vy = -15;
|
|
this.jumpCount++;
|
|
}
|
|
}
|
|
|
|
shoot() {
|
|
const spawnX = this.facing === 'right' ? this.x + this.width : this.x - 16;
|
|
const spawnY = this.y + this.height/2;
|
|
const dir = this.facing === 'right' ? 20 : -20;
|
|
projectiles.push(new Projectile(spawnX, spawnY, dir));
|
|
}
|
|
|
|
dropBomb() {
|
|
const bx = this.x + this.width/2;
|
|
const by = this.y + this.height;
|
|
bombs.push(new Bomb(bx, by));
|
|
}
|
|
|
|
fireBigMissile() {
|
|
const dir = this.facing === 'right' ? 5 : -5;
|
|
const mW = 64 * 2, mH = 64 * 2;
|
|
const spawnX = this.facing === 'right'
|
|
? this.x + this.width
|
|
: this.x - mW;
|
|
const spawnY = this.y + this.height - mH;
|
|
specialProjectiles.push(new BigMissile(spawnX, spawnY, dir));
|
|
}
|
|
|
|
handleKeyPressed(k) {
|
|
this.keys[k] = true;
|
|
if (k === this.controls.jump) this.jump();
|
|
if (k === this.controls.attack)this.shoot();
|
|
if (k === this.controls.bomb) this.bombHoldStartTime = millis();
|
|
}
|
|
|
|
handleKeyReleased(k) {
|
|
this.keys[k] = false;
|
|
if (k === this.controls.bomb && this.bombHoldStartTime !== null) {
|
|
const held = millis() - this.bombHoldStartTime;
|
|
if (held >= 1000 ) this.fireBigMissile();
|
|
else this.dropBomb();
|
|
this.bombHoldStartTime = null;
|
|
}
|
|
}
|
|
|
|
draw() {
|
|
const img = this.imgSet[this.state][this.frame];
|
|
push();
|
|
if (this.facing === 'left') {
|
|
translate(this.x + this.width, this.y);
|
|
scale(-1,1);
|
|
image(img, 0,0, this.width, this.height);
|
|
} else {
|
|
image(img, this.x, this.y, this.width, this.height);
|
|
}
|
|
pop();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class Projectile {
|
|
constructor(x, y, vx, w=8, h=8) {
|
|
this.x = x; this.y = y;
|
|
this.vx = vx; this.width = w; this.height = h;
|
|
this.spawnTime = millis(); this.lifetime = 10000;
|
|
this.shouldDestroy = false;
|
|
}
|
|
|
|
update(targets) {
|
|
this.x += this.vx;
|
|
|
|
for (const t of targets) {
|
|
if (this.hits(t)) {
|
|
t.knockbackVX += this.vx * 0.5;
|
|
this.shouldDestroy = true;
|
|
}
|
|
}
|
|
|
|
if (millis() - this.spawnTime > this.lifetime) {
|
|
this.shouldDestroy = true;
|
|
}
|
|
}
|
|
|
|
draw() {
|
|
image(itemimgs.fire[0], this.x, this.y, this.width * 2, this.height * 2);
|
|
}
|
|
|
|
destroy() {
|
|
return this.shouldDestroy;
|
|
}
|
|
|
|
hits(target) {
|
|
return (
|
|
this.x < target.x + target.width &&
|
|
this.x + this.width > target.x &&
|
|
this.y < target.y + target.height &&
|
|
this.y + this.height > target.y
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
class Bomb {
|
|
constructor(x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = 16;
|
|
this.height = 16;
|
|
|
|
this.timer = 60; // 폭발 대기 프레임 수
|
|
this.explodeTimer = 15; // 폭발 시각화 지속 프레임 수
|
|
this.exploded = false; // 폭발 상태 플래그
|
|
this.shouldRemove = false; // 완전 제거 플래그
|
|
|
|
this.radius = 100; // 넉백 및 시각화 반경(이 값을 키워 범위 확대)
|
|
}
|
|
|
|
update() {
|
|
if (!this.exploded) {
|
|
// 타이머가 0 이 되면 폭발 시작
|
|
this.timer--;
|
|
if (this.timer <= 0) {
|
|
this.exploded = true;
|
|
this.explode(); // 넉백 한 번 적용
|
|
}
|
|
} else {
|
|
// 폭발 시각화가 끝나면 제거
|
|
this.explodeTimer--;
|
|
if (this.explodeTimer <= 0) {
|
|
this.shouldRemove = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
explode() {
|
|
// 폭발 반경 내 모든 플레이어에게 넉백 적용
|
|
[player1, player2].forEach(p => {
|
|
const cx = this.x + this.width/2;
|
|
const cy = this.y + this.height/2;
|
|
const px = p.x + p.width/2;
|
|
const py = p.y + p.height/2;
|
|
const d = dist(cx, cy, px, py);
|
|
|
|
if (d < this.radius) {
|
|
const angle = atan2(py - cy, px - cx);
|
|
const force = map(d, 0, this.radius, 20, 5);
|
|
p.knockbackVX += cos(angle) * force * 2;
|
|
p.vy += sin(angle) * force * 2;
|
|
}
|
|
});
|
|
}
|
|
|
|
draw() {
|
|
if (!this.exploded) {
|
|
image(itemimgs.bomb[0], this.x, this.y, this.width * 2, this.height * 2);
|
|
} else {
|
|
// 폭발 중일 땐 커다란 반투명 원으로 시각화
|
|
noFill();
|
|
stroke(255, 150, 0);
|
|
strokeWeight(4);
|
|
ellipse(
|
|
this.x + this.width/2,
|
|
this.y + this.height/2,
|
|
this.radius * 2,
|
|
this.radius * 2
|
|
);
|
|
noStroke();
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
return this.shouldRemove;
|
|
}
|
|
}
|
|
class BigMissile {
|
|
constructor(x, y, vx) {
|
|
this.spawnTime = millis();
|
|
this.lifetime = 20000;
|
|
this.shouldDestroy = false;
|
|
|
|
this.width = 64;
|
|
this.height = 64;
|
|
this.vx = vx;
|
|
// 하단 정렬: 플레이어 바닥 높이에 맞추기 위해 y에서 2배 높이만큼 올림
|
|
this.x = x;
|
|
this.y = y - this.height;
|
|
}
|
|
|
|
update(targets) {
|
|
this.x += this.vx;
|
|
|
|
for (const t of targets) {
|
|
if (this.hits(t)) {
|
|
// 강력한 넉백
|
|
const force = this.vx * 2;
|
|
t.knockbackVX += force;
|
|
// 겹침 방지: 대상 위치 조정
|
|
const w = this.width * 2;
|
|
if (this.vx > 0) {
|
|
// 미사일이 오른쪽으로 이동 중이면 대상은 미사일 우측으로 밀어냄
|
|
t.x = this.x + w;
|
|
} else {
|
|
// 왼쪽 이동 중이면 대상은 미사일 좌측으로
|
|
t.x = this.x - t.width;
|
|
}
|
|
// 미사일은 충돌로 사라지지 않음
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (millis() - this.spawnTime > this.lifetime) {
|
|
this.shouldDestroy = true;
|
|
}
|
|
}
|
|
|
|
draw() {
|
|
const img = itemimgs.bigmissile[0];
|
|
push();
|
|
if (this.vx > 0) {
|
|
// 왼쪽 발사시 이미지 뒤집기
|
|
translate(this.x + this.width * 2, this.y);
|
|
scale(-1, 1);
|
|
image(img, 0, 0, this.width * 2, this.height * 2);
|
|
}
|
|
else {
|
|
image(img, this.x, this.y, this.width * 2, this.height * 2);
|
|
}
|
|
pop();
|
|
}
|
|
|
|
hits(target) {
|
|
// 미사일 자체 크기로 충돌 영역 계산 (2배 확대된 폭)
|
|
const w = this.width * 2;
|
|
const h = this.height * 2;
|
|
return (
|
|
this.x < target.x + target.width &&
|
|
this.x + w > target.x &&
|
|
this.y < target.y + target.height &&
|
|
this.y + h > target.y
|
|
);
|
|
}
|
|
|
|
destroy() {
|
|
return this.shouldDestroy;
|
|
}
|
|
}
|
|
class Item {
|
|
}
|
|
function handleProjectiles() {
|
|
for (let i = projectiles.length - 1; i >= 0; i--) {
|
|
projectiles[i].update([player1, player2]);
|
|
projectiles[i].draw();
|
|
if (projectiles[i].destroy()) {
|
|
projectiles.splice(i, 1);
|
|
console.log("shootend");
|
|
}
|
|
}
|
|
}
|
|
function handleBombs() {
|
|
for (let i = bombs.length - 1; i >= 0; i--) {
|
|
const b = bombs[i];
|
|
b.update();
|
|
b.draw();
|
|
if (b.destroy()) {
|
|
bombs.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
function handleSpecialProjectiles() {
|
|
for (let i = specialProjectiles.length - 1; i >= 0; i--) {
|
|
specialProjectiles[i].update([player1, player2]);
|
|
specialProjectiles[i].draw();
|
|
if (specialProjectiles[i].destroy()) {
|
|
specialProjectiles.splice(i, 1);
|
|
}
|
|
}
|
|
} |