Add Asteroid Field game (asteroids clone with UFOs)
This commit is contained in:
1 parent
78f75a866b
commit
00ad946ea4
1 file changed
+219
@@ -0,0 +1,219 @@
|
|||||||
|
/* ============================================================
|
||||||
|
ASTEROID FIELD — Asteroids clone
|
||||||
|
============================================================ */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
const { Game, STATE } = window.DSP;
|
||||||
|
const S = window.DSP_SPRITES;
|
||||||
|
|
||||||
|
const W = 800, H = 600;
|
||||||
|
|
||||||
|
class AsteroidField extends Game {
|
||||||
|
constructor(canvas) {
|
||||||
|
super({ name: 'asteroid', title: 'ASTEROID FIELD', width: W, height: H, canvas,
|
||||||
|
loadSteps: ['LOADING ROCKS', 'CHARGING THRUSTERS', 'SCANNING DEBRIS', 'SYNCING GRID'] });
|
||||||
|
this.ship = null;
|
||||||
|
this.bullets = [];
|
||||||
|
this.asteroids = [];
|
||||||
|
this.ufos = [];
|
||||||
|
this.stars = [];
|
||||||
|
for (let i = 0; i < 80; i++) this.stars.push({ x: Math.random() * W, y: Math.random() * H, s: Math.random() < 0.2 ? 2 : 1, v: 0.2 + Math.random() * 0.5 });
|
||||||
|
this.respawnTimer = 0;
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
this.bullets = []; this.asteroids = []; this.ufos = [];
|
||||||
|
this.spawnWave();
|
||||||
|
this.respawn();
|
||||||
|
}
|
||||||
|
respawn() {
|
||||||
|
this.ship = { x: W / 2, y: H / 2, a: -Math.PI / 2, vx: 0, vy: 0, thrust: false, inv: 120 };
|
||||||
|
this.respawnTimer = 0;
|
||||||
|
}
|
||||||
|
spawnWave() {
|
||||||
|
const n = 3 + this.level;
|
||||||
|
for (let i = 0; i < n; i++) this.spawnAsteroid(3);
|
||||||
|
if (this.level >= 2) this.ufos.push({ x: -30, y: Math.random() * H, vx: 1.2, vy: 0, t: 0, fire: 60 });
|
||||||
|
}
|
||||||
|
spawnAsteroid(size, x, y) {
|
||||||
|
const speed = (4 - size) * 0.6 + Math.random() * 0.8 + this.level * 0.1;
|
||||||
|
const a = Math.random() * Math.PI * 2;
|
||||||
|
this.asteroids.push({
|
||||||
|
x: x !== undefined ? x : (Math.random() < 0.5 ? (Math.random() < 0.5 ? -20 : W + 20) : Math.random() * W),
|
||||||
|
y: y !== undefined ? y : (Math.random() < 0.5 ? (Math.random() < 0.5 ? -20 : H + 20) : Math.random() * H),
|
||||||
|
vx: Math.cos(a) * speed, vy: Math.sin(a) * speed,
|
||||||
|
size, rot: Math.random() * Math.PI * 2, vr: (Math.random() - 0.5) * 0.05,
|
||||||
|
verts: Array.from({ length: 9 }, () => 0.7 + Math.random() * 0.5),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
wrap(o) {
|
||||||
|
if (o.x < -30) o.x = W + 30; if (o.x > W + 30) o.x = -30;
|
||||||
|
if (o.y < -30) o.y = H + 30; if (o.y > H + 30) o.y = -30;
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
const sh = this.ship;
|
||||||
|
for (const s of this.stars) { s.x -= s.v; if (s.x < 0) s.x = W; }
|
||||||
|
|
||||||
|
if (this.respawnTimer > 0) { this.respawnTimer--; if (this.respawnTimer === 0) this.respawn(); }
|
||||||
|
else if (sh) {
|
||||||
|
const left = this.input.down('ArrowLeft', 'KeyA');
|
||||||
|
const right = this.input.down('ArrowRight', 'KeyD');
|
||||||
|
const thrust = this.input.down('ArrowUp', 'KeyW');
|
||||||
|
if (left) sh.a -= 0.06;
|
||||||
|
if (right) sh.a += 0.06;
|
||||||
|
if (thrust) {
|
||||||
|
sh.vx += Math.cos(sh.a) * 0.12;
|
||||||
|
sh.vy += Math.sin(sh.a) * 0.12;
|
||||||
|
if (this.time % 4 === 0) {
|
||||||
|
window.DSP_AUDIO.sfx('thrust');
|
||||||
|
this.particles.burst(sh.x - Math.cos(sh.a) * 12, sh.y - Math.sin(sh.a) * 12, '#ff9d2d', 2, 1.5, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sh.vx *= 0.99; sh.vy *= 0.99;
|
||||||
|
sh.x += sh.vx; sh.y += sh.vy;
|
||||||
|
this.wrap(sh);
|
||||||
|
if (sh.inv > 0) sh.inv--;
|
||||||
|
if (this.input.wasPressed('Space') && this.bullets.length < 6) {
|
||||||
|
this.bullets.push({ x: sh.x + Math.cos(sh.a) * 12, y: sh.y + Math.sin(sh.a) * 12, vx: Math.cos(sh.a) * 6 + sh.vx, vy: Math.sin(sh.a) * 6 + sh.vy, life: 60 });
|
||||||
|
window.DSP_AUDIO.sfx('shoot');
|
||||||
|
}
|
||||||
|
if (this.input.wasPressed('KeyX')) {
|
||||||
|
window.DSP_AUDIO.sfx('jump');
|
||||||
|
this.particles.burst(sh.x, sh.y, '#7df9ff', 16, 3, 25);
|
||||||
|
sh.x = Math.random() * W; sh.y = Math.random() * H;
|
||||||
|
sh.inv = 90;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = this.bullets.length - 1; i >= 0; i--) {
|
||||||
|
const b = this.bullets[i];
|
||||||
|
b.x += b.vx; b.y += b.vy;
|
||||||
|
this.wrap(b);
|
||||||
|
if (--b.life <= 0) { this.bullets.splice(i, 1); continue; }
|
||||||
|
let hit = false;
|
||||||
|
for (let j = this.asteroids.length - 1; j >= 0; j--) {
|
||||||
|
const a = this.asteroids[j];
|
||||||
|
const r = a.size * 10;
|
||||||
|
if ((b.x - a.x) ** 2 + (b.y - a.y) ** 2 < r * r) {
|
||||||
|
this.hitAsteroid(j);
|
||||||
|
hit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hit) for (let j = this.ufos.length - 1; j >= 0; j--) {
|
||||||
|
const u = this.ufos[j];
|
||||||
|
if (Math.abs(b.x - u.x) < 18 && Math.abs(b.y - u.y) < 10) {
|
||||||
|
this.ufos.splice(j, 1);
|
||||||
|
this.addScore(200);
|
||||||
|
window.DSP_AUDIO.sfx('explode');
|
||||||
|
this.particles.burst(u.x, u.y, '#9d7dff', 20, 3, 30);
|
||||||
|
this.shake.trigger(4, 15);
|
||||||
|
hit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hit) this.bullets.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const a of this.asteroids) {
|
||||||
|
a.x += a.vx; a.y += a.vy; a.rot += a.vr;
|
||||||
|
this.wrap(a);
|
||||||
|
if (sh && sh.inv <= 0) {
|
||||||
|
const r = a.size * 10 + 8;
|
||||||
|
if ((sh.x - a.x) ** 2 + (sh.y - a.y) ** 2 < r * r) this.killShip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = this.ufos.length - 1; i >= 0; i--) {
|
||||||
|
const u = this.ufos[i];
|
||||||
|
u.x += u.vx; u.t++;
|
||||||
|
if (u.x < -40 || u.x > W + 40) this.ufos.splice(i, 1);
|
||||||
|
if (u.t % 90 === 0 && sh) {
|
||||||
|
const ang = Math.atan2(sh.y - u.y, sh.x - u.x);
|
||||||
|
this.bullets.push({ x: u.x, y: u.y, vx: Math.cos(ang) * 3, vy: Math.sin(ang) * 3, life: 120, enemy: true });
|
||||||
|
window.DSP_AUDIO.sfx('laser');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = this.bullets.length - 1; i >= 0; i--) {
|
||||||
|
const b = this.bullets[i];
|
||||||
|
if (!b.enemy) continue;
|
||||||
|
if (sh && sh.inv <= 0 && (b.x - sh.x) ** 2 + (b.y - sh.y) ** 2 < 100) {
|
||||||
|
this.bullets.splice(i, 1);
|
||||||
|
this.killShip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.asteroids.length === 0 && this.ufos.length === 0) {
|
||||||
|
this.level++;
|
||||||
|
window.DSP_AUDIO.sfx('levelup');
|
||||||
|
this.spawnWave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hitAsteroid(j) {
|
||||||
|
const a = this.asteroids[j];
|
||||||
|
this.asteroids.splice(j, 1);
|
||||||
|
this.addScore(a.size === 3 ? 20 : a.size === 2 ? 50 : 100);
|
||||||
|
window.DSP_AUDIO.sfx('explode');
|
||||||
|
this.particles.burst(a.x, a.y, '#c9b8ff', 14, 2.5, 25);
|
||||||
|
this.shake.trigger(a.size === 3 ? 5 : 3, 12);
|
||||||
|
if (a.size > 1) {
|
||||||
|
this.spawnAsteroid(a.size - 1, a.x, a.y);
|
||||||
|
this.spawnAsteroid(a.size - 1, a.x, a.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
killShip() {
|
||||||
|
if (!this.ship) return;
|
||||||
|
window.DSP_AUDIO.sfx('bigboom');
|
||||||
|
this.particles.burst(this.ship.x, this.ship.y, '#ff4d6d', 30, 4, 40);
|
||||||
|
this.shake.trigger(8, 25);
|
||||||
|
this.lives--;
|
||||||
|
this.ship = null;
|
||||||
|
if (this.lives <= 0) this.gameOver();
|
||||||
|
else this.respawnTimer = 90;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const ctx = this.ctx;
|
||||||
|
ctx.fillStyle = '#05010d';
|
||||||
|
ctx.fillRect(0, 0, W, H);
|
||||||
|
for (const s of this.stars) {
|
||||||
|
ctx.fillStyle = s.s > 1 ? '#7df9ff' : '#4a3f6b';
|
||||||
|
ctx.fillRect(s.x, s.y, s.s, s.s);
|
||||||
|
}
|
||||||
|
for (const a of this.asteroids) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(a.x, a.y);
|
||||||
|
ctx.rotate(a.rot);
|
||||||
|
ctx.strokeStyle = '#c9b8ff';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
const r = a.size * 10;
|
||||||
|
for (let i = 0; i < a.verts.length; i++) {
|
||||||
|
const ang = (i / a.verts.length) * Math.PI * 2;
|
||||||
|
const rr = r * a.verts[i];
|
||||||
|
const px = Math.cos(ang) * rr, py = Math.sin(ang) * rr;
|
||||||
|
i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
for (const u of this.ufos) {
|
||||||
|
S.draw(ctx, S.get('ufo', 2), u.x, u.y);
|
||||||
|
}
|
||||||
|
for (const b of this.bullets) {
|
||||||
|
ctx.fillStyle = b.enemy ? '#ff4d6d' : '#ffe14d';
|
||||||
|
ctx.fillRect(b.x - 1.5, b.y - 1.5, 3, 3);
|
||||||
|
}
|
||||||
|
const sh = this.ship;
|
||||||
|
if (sh && (sh.inv <= 0 || Math.floor(this.time / 4) % 2 === 0)) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(sh.x, sh.y);
|
||||||
|
ctx.rotate(sh.a + Math.PI / 2);
|
||||||
|
S.draw(ctx, S.get('ship', 2), 0, 0);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.DSP_ACTIVE_GAME = new AsteroidField(document.getElementById('game'));
|
||||||
|
window.DSP_AUDIO.playMusic('asteroid');
|
||||||
|
})();
|
||||||
Reference in new issue
Block a user