🎮ArcadeLab

Orbits by XAWPHAD

by PixelRider35
167 lines5.6 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Orbits by XAWPHAD</title>
<style>
  body { margin: 0; overflow: hidden; background: black; }
  canvas { display: block; }
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

window.addEventListener('resize', () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
});

class Ball {
    constructor(x, y, radius, color, vx=0, vy=0, fixed=false) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.color = color;
        this.vx = vx;
        this.vy = vy;
        this.fixed = fixed;
        this.hits = 0;
    }

    draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
    }

    update() {
        if (this.fixed) return;
        if (this.x + this.radius > canvas.width || this.x - this.radius < 0) this.vx *= -1;
        if (this.y + this.radius > canvas.height || this.y - this.radius < 0) this.vy *= -1;
        this.x += this.vx;
        this.y += this.vy;
    }

    applyGravity(target) {
        if (this.fixed) return;

        const dx = target.x - this.x;
        const dy = target.y - this.y;
        const distSq = dx*dx + dy*dy;
        const minDist = this.radius + target.radius;
        if (distSq < minDist*minDist) return;

        const G = 0.05; // gentle gravity
        const force = (G * target.radius) / distSq;
        const accelFactor = 20 / this.radius; // smaller balls accelerate more

        const ax = dx * force * accelFactor;
        const ay = dy * force * accelFactor;

        const maxAccel = 0.3; // cap acceleration
        this.vx += Math.max(Math.min(ax, maxAccel), -maxAccel);
        this.vy += Math.max(Math.min(ay, maxAccel), -maxAccel);
    }
}

// Helper functions
function rand(min, max) { return Math.random() * (max - min) + min; }
function randColor() { return `hsl(${Math.random() * 360}, 100%, 50%)`; }

let balls = [];

// Center white ball
const centerBall = new Ball(canvas.width/2, canvas.height/2, 40, 'yellow', 0, 0, true);
balls.push(centerBall);

// Click to create new balls (max 10)
canvas.addEventListener('click', (e) => {
    const nonFixedBalls = balls.filter(ball => !ball.fixed);
    if (nonFixedBalls.length >= 7) return;

    const radius = rand(10, 30)/2;
    const color = randColor();
    const speed = rand(2, 5);
    const angle = rand(0, Math.PI*2);
    const vx = Math.cos(angle) * speed;
    const vy = Math.sin(angle) * speed;

    balls.push(new Ball(e.clientX, e.clientY, radius, color, vx, vy));
});

// Collision detection
function handleCollisions() {
    for (let i = 0; i < balls.length; i++) {
        for (let j = i + 1; j < balls.length; j++) {
            const dx = balls[j].x - balls[i].x;
            const dy = balls[j].y - balls[i].y;
            const dist = Math.sqrt(dx*dx + dy*dy);

            if (dist < balls[i].radius + balls[j].radius) {
                if (balls[i].fixed && !balls[j].fixed) balls[j].hits = 30;
                else if (!balls[i].fixed && balls[j].fixed) balls[i].hits = 30;
                else if (!balls[i].fixed && !balls[j].fixed) {
                    balls[i].hits++;
                    balls[j].hits++;

                    const angle = Math.atan2(dy, dx);
                    const speed1 = Math.sqrt(balls[i].vx**2 + balls[i].vy**2);
                    const speed2 = Math.sqrt(balls[j].vx**2 + balls[j].vy**2);
                    const dir1 = Math.atan2(balls[i].vy, balls[i].vx);
                    const dir2 = Math.atan2(balls[j].vy, balls[j].vx);

                    const vx1 = speed1 * Math.cos(dir1 - angle);
                    const vy1 = speed1 * Math.sin(dir1 - angle);
                    const vx2 = speed2 * Math.cos(dir2 - angle);
                    const vy2 = speed2 * Math.sin(dir2 - angle);

                    const finalVx1 = ((balls[i].radius - balls[j].radius) * vx1 + 2 * balls[j].radius * vx2) / (balls[i].radius + balls[j].radius);
                    const finalVx2 = ((balls[j].radius - balls[i].radius) * vx2 + 2 * balls[i].radius * vx1) / (balls[i].radius + balls[j].radius);

                    balls[i].vx = Math.cos(angle) * finalVx1 + Math.cos(angle + Math.PI/2) * vy1;
                    balls[i].vy = Math.sin(angle) * finalVx1 + Math.sin(angle + Math.PI/2) * vy1;
                    balls[j].vx = Math.cos(angle) * finalVx2 + Math.cos(angle + Math.PI/2) * vy2;
                    balls[j].vy = Math.sin(angle) * finalVx2 + Math.sin(angle + Math.PI/2) * vy2;

                    const overlap = 0.5 * (balls[i].radius + balls[j].radius - dist + 1);
                    balls[i].x -= overlap * (dx / dist);
                    balls[i].y -= overlap * (dy / dist);
                    balls[j].x += overlap * (dx / dist);
                    balls[j].y += overlap * (dy / dist);
                }
            }
        }
    }
}

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    handleCollisions();

    balls.forEach(ball => {
        if (!ball.fixed) ball.applyGravity(centerBall);
    });

    balls = balls.filter(ball => ball.fixed || ball.hits < 30);

    balls.forEach(ball => {
        ball.update();
        ball.draw();
    });

    requestAnimationFrame(animate);
}

animate();
</script>
</body>
</html>

Game Source: Orbits by XAWPHAD

Creator: PixelRider35

Libraries: none

Complexity: moderate (167 lines, 5.6 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: orbits-by-xawphad-pixelrider35" to link back to the original. Then publish at arcadelab.ai/publish.