blobio / index.html
ChuckNoriss's picture
can you make it so the map is larger and the camera follows you, and there are other "players" but they are ai, and if you are close and bigger, they will try and chase you. also add the jiggle when ever you get a player or blob it jiggles from where you got the points - Follow Up Deployment
b10e9f7 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blob.io - Single Player Agar.io Clone</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
canvas {
display: block;
background-color: #f0f9ff;
}
#gameUI {
position: absolute;
top: 0;
left: 0;
width: 100%;
pointer-events: none;
}
#scoreContainer {
background-color: rgba(255, 255, 255, 0.8);
border-radius: 9999px;
padding: 0.5rem 1rem;
margin: 1rem;
display: inline-flex;
align-items: center;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
#startScreen, #gameOverScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
color: white;
}
.btn {
pointer-events: auto;
background-color: #3b82f6;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 0.375rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.btn:hover {
background-color: #2563eb;
transform: translateY(-2px);
}
.btn:active {
transform: translateY(0);
}
.hidden {
display: none !important;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="gameUI">
<div id="scoreContainer" class="flex items-center">
<i data-feather="award" class="mr-2"></i>
<span id="score">0</span>
</div>
</div>
<div id="startScreen">
<h1 class="text-4xl font-bold mb-6">Blob.io</h1>
<p class="text-xl mb-8 text-center max-w-md px-4">Eat smaller blobs to grow bigger. Avoid larger blobs!</p>
<button id="startBtn" class="btn">Start Game</button>
</div>
<div id="gameOverScreen" class="hidden">
<h1 class="text-4xl font-bold mb-4">Game Over</h1>
<p class="text-xl mb-2">Your score: <span id="finalScore">0</span></p>
<p class="text-lg mb-8">High score: <span id="highScore">0</span></p>
<button id="restartBtn" class="btn">Play Again</button>
</div>
<script>
feather.replace();
// Game variables
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
// World dimensions (larger than canvas)
const worldWidth = 5000;
const worldHeight = 5000;
let cameraOffset = { x: 0, y: 0 };
// Jiggle effect variables
let jiggleOffset = { x: 0, y: 0 };
let jiggleTime = 0;
const jiggleDuration = 0.3;
const finalScoreElement = document.getElementById('finalScore');
const highScoreElement = document.getElementById('highScore');
const startScreen = document.getElementById('startScreen');
const gameOverScreen = document.getElementById('gameOverScreen');
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
// Set canvas size
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Center camera on player initially
cameraOffset = {
x: player.x - canvas.width / 2,
y: player.y - canvas.height / 2
};
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Game state
let gameRunning = false;
let score = 0;
let highScore = localStorage.getItem('highScore') || 0;
highScoreElement.textContent = highScore;
// Player blob
const player = {
x: worldWidth / 2,
y: worldHeight / 2,
radius: 20,
color: '#3b82f6',
speed: 5,
targetX: worldWidth / 2,
targetY: worldHeight / 2
};
// Food blobs
let food = [];
const foodColors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6', '#ec4899'];
// Enemy blobs
let enemies = [];
const enemyColors = ['#dc2626', '#ea580c', '#059669', '#1d4ed8', '#7e22ce', '#be185d'];
// Mouse position
let mouseX = player.x;
let mouseY = player.y;
// Initialize game
function initGame() {
score = 0;
scoreElement.textContent = score;
player.radius = 20;
player.x = canvas.width / 2;
player.y = canvas.height / 2;
food = [];
enemies = [];
// Create initial food
for (let i = 0; i < 50; i++) {
createFood();
}
// Create initial enemies
for (let i = 0; i < 5; i++) {
createEnemy();
}
}
// Create food blob
function createFood() {
const radius = Math.random() * 10 + 5;
food.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: radius,
color: foodColors[Math.floor(Math.random() * foodColors.length)],
speedX: (Math.random() - 0.5) * 2,
speedY: (Math.random() - 0.5) * 2
});
}
// Create enemy blob
function createEnemy() {
const radius = Math.random() * 30 + 20;
enemies.push({
x: Math.random() * worldWidth,
y: Math.random() * worldHeight,
radius: radius,
color: enemyColors[Math.floor(Math.random() * enemyColors.length)],
speedX: (Math.random() - 0.5) * 2,
speedY: (Math.random() - 0.5) * 2,
state: 'wander', // 'wander' or 'chase'
chaseTimer: 0
});
}
// Check collision between two circles
function checkCollision(circle1, circle2) {
const dx = circle1.x - circle2.x;
const dy = circle1.y - circle2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < circle1.radius + circle2.radius;
}
// Game loop
function gameLoop() {
if (!gameRunning) return;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update camera to follow player
cameraOffset.x = player.x - canvas.width / 2;
cameraOffset.y = player.y - canvas.height / 2;
// Apply jiggle effect if active
if (jiggleTime > 0) {
jiggleTime -= 1/60;
const intensity = (jiggleTime / jiggleDuration) * 10;
cameraOffset.x += Math.sin(jiggleTime * 20) * intensity;
cameraOffset.y += Math.cos(jiggleTime * 20) * intensity;
}
// Save context and apply camera transform
ctx.save();
ctx.translate(-cameraOffset.x, -cameraOffset.y);
// Convert mouse position to world coordinates
const worldMouseX = mouseX + cameraOffset.x;
const worldMouseY = mouseY + cameraOffset.y;
// Move player toward mouse in world space
const dx = worldMouseX - player.x;
const dy = worldMouseY - player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
player.x += dx / distance * player.speed;
player.y += dy / distance * player.speed;
}
// Keep player within world bounds
player.x = Math.max(player.radius, Math.min(worldWidth - player.radius, player.x));
player.y = Math.max(player.radius, Math.min(worldHeight - player.radius, player.y));
// Draw player
ctx.beginPath();
ctx.arc(player.x, player.y, player.radius, 0, Math.PI * 2);
ctx.fillStyle = player.color;
ctx.fill();
ctx.closePath();
// Draw food
for (let i = 0; i < food.length; i++) {
const blob = food[i];
// Move food
blob.x += blob.speedX;
blob.y += blob.speedY;
// Bounce off walls
if (blob.x - blob.radius < 0 || blob.x + blob.radius > canvas.width) {
blob.speedX = -blob.speedX;
}
if (blob.y - blob.radius < 0 || blob.y + blob.radius > canvas.height) {
blob.speedY = -blob.speedY;
}
// Draw food
ctx.beginPath();
ctx.arc(blob.x, blob.y, blob.radius, 0, Math.PI * 2);
ctx.fillStyle = blob.color;
ctx.fill();
ctx.closePath();
// Check collision with player
if (checkCollision(player, blob)) {
// Player can only eat smaller or equal size food
if (player.radius >= blob.radius) {
// Increase player size and score
player.radius += blob.radius * 0.2;
score += Math.floor(blob.radius);
scoreElement.textContent = score;
// Remove food and create new one
food.splice(i, 1);
createFood();
i--;
// Trigger jiggle effect
jiggleTime = jiggleDuration;
}
}
}
// Draw enemies
for (let i = 0; i < enemies.length; i++) {
const enemy = enemies[i];
// AI behavior for enemies
const distToPlayer = Math.sqrt(
Math.pow(enemy.x - player.x, 2) +
Math.pow(enemy.y - player.y, 2)
);
// If player is close and bigger, chase them
if (distToPlayer < 300 && player.radius > enemy.radius * 1.2) {
enemy.state = 'chase';
enemy.chaseTimer = 3; // Chase for 3 seconds
// Move toward player
const angle = Math.atan2(player.y - enemy.y, player.x - enemy.x);
enemy.speedX = Math.cos(angle) * 2;
enemy.speedY = Math.sin(angle) * 2;
}
// If chasing but timer expired or player got too far/small
else if (enemy.state === 'chase') {
enemy.chaseTimer -= 1/60;
if (enemy.chaseTimer <= 0 || distToPlayer > 400 || player.radius < enemy.radius) {
enemy.state = 'wander';
// Random new direction
enemy.speedX = (Math.random() - 0.5) * 2;
enemy.speedY = (Math.random() - 0.5) * 2;
}
}
// Move enemy
enemy.x += enemy.speedX;
enemy.y += enemy.speedY;
// Bounce off world walls
if (enemy.x - enemy.radius < 0 || enemy.x + enemy.radius > worldWidth) {
enemy.speedX = -enemy.speedX;
}
if (enemy.y - enemy.radius < 0 || enemy.y + enemy.radius > worldHeight) {
enemy.speedY = -enemy.speedY;
}
// Draw enemy
ctx.beginPath();
ctx.arc(enemy.x, enemy.y, enemy.radius, 0, Math.PI * 2);
ctx.fillStyle = enemy.color;
ctx.fill();
ctx.closePath();
// Check collision with player
if (checkCollision(player, enemy)) {
// Enemy can eat player if larger
if (enemy.radius > player.radius * 1.1) {
gameOver();
return;
}
// Player can eat enemy if significantly larger
else if (player.radius > enemy.radius * 1.5) {
// Increase player size and score
player.radius += enemy.radius * 0.3;
score += Math.floor(enemy.radius * 2);
scoreElement.textContent = score;
// Remove enemy and create new one
enemies.splice(i, 1);
createEnemy();
i--;
// Trigger jiggle effect
jiggleTime = jiggleDuration;
}
}
// Check collision with food
for (let j = 0; j < food.length; j++) {
if (checkCollision(enemy, food[j]) && enemy.radius > food[j].radius) {
// Enemy eats food
enemy.radius += food[j].radius * 0.1;
food.splice(j, 1);
createFood();
j--;
}
}
}
// Occasionally add more food and enemies
if (Math.random() < 0.01) {
createFood();
}
if (Math.random() < 0.005) {
createEnemy();
}
// Restore context (after drawing everything)
ctx.restore();
requestAnimationFrame(gameLoop);
}
// Game over
function gameOver() {
gameRunning = false;
finalScoreElement.textContent = score;
// Update high score
if (score > highScore) {
highScore = score;
highScoreElement.textContent = highScore;
localStorage.setItem('highScore', highScore);
}
gameOverScreen.classList.remove('hidden');
}
// Event listeners
canvas.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
// Touch support for mobile
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
mouseX = e.touches[0].clientX;
mouseY = e.touches[0].clientY;
}, { passive: false });
startBtn.addEventListener('click', () => {
startScreen.classList.add('hidden');
gameRunning = true;
initGame();
gameLoop();
});
restartBtn.addEventListener('click', () => {
gameOverScreen.classList.add('hidden');
gameRunning = true;
initGame();
gameLoop();
});
</script>
</body>
</html>