Created
March 28, 2025 17:56
-
-
Save ofou/58efcff32571c6e7831b0e54a4325da3 to your computer and use it in GitHub Desktop.
Dinosaur game vibecoding
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Pixelated Dinosaur Runner - Endless Runner Game | |
// Controls: Press SPACE, UP ARROW, or CLICK to jump | |
// Game variables | |
let dino; | |
let obstacles = []; | |
let grounds = []; | |
let clouds = []; | |
let score = 0; | |
let gameSpeed = 5; | |
let gravity = 0.6; | |
let isGameOver = false; | |
let backgroundLayers = []; | |
let highScore = 0; | |
// Pixel art for dinosaur | |
let dinoPixelArt = [ | |
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0], | |
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0], | |
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0], | |
[0, 1, 1, 0, 1, 1, 1, 0, 0, 0], | |
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0], | |
[0, 0, 1, 1, 1, 1, 0, 0, 0, 0], | |
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0], | |
[0, 0, 0, 1, 0, 1, 0, 0, 0, 0], | |
[0, 0, 0, 1, 0, 1, 0, 0, 0, 0], | |
]; | |
// Pixel art for cactus obstacles | |
let cactusPixelArt = [ | |
[0, 0, 0, 1, 1, 0, 0, 0], | |
[0, 0, 1, 1, 1, 1, 0, 0], | |
[0, 0, 0, 1, 1, 0, 0, 0], | |
[0, 0, 0, 1, 1, 0, 0, 0], | |
[0, 0, 0, 1, 1, 0, 0, 0], | |
[0, 0, 0, 1, 1, 0, 0, 0], | |
[0, 1, 0, 1, 1, 0, 1, 0], | |
[1, 1, 1, 1, 1, 1, 1, 1], | |
[0, 1, 1, 1, 1, 1, 1, 0], | |
[0, 0, 1, 1, 1, 1, 0, 0], | |
]; | |
// Dinosaur class | |
class Dinosaur { | |
constructor() { | |
this.x = 50; | |
this.y = height - 100; | |
this.velocityY = 0; | |
this.width = 50; | |
this.height = 50; | |
this.isJumping = false; | |
this.runFrame = 0; | |
this.runFrameCount = 0; | |
this.runFrameSpeed = 5; | |
} | |
jump() { | |
if (!this.isJumping) { | |
this.velocityY = -15; | |
this.isJumping = true; | |
} | |
} | |
update() { | |
// Apply gravity | |
this.velocityY += gravity; | |
this.y += this.velocityY; | |
// Ground collision | |
if (this.y > height - 100) { | |
this.y = height - 100; | |
this.velocityY = 0; | |
this.isJumping = false; | |
} | |
// Animation frame | |
if (!this.isJumping) { | |
this.runFrameCount++; | |
if (this.runFrameCount > this.runFrameSpeed) { | |
this.runFrame = (this.runFrame + 1) % 2; // Alternate between 0 and 1 | |
this.runFrameCount = 0; | |
} | |
} | |
} | |
draw() { | |
push(); | |
translate(this.x, this.y); | |
noStroke(); | |
// Draw the pixelated dinosaur | |
let pixelSize = this.width / dinoPixelArt[0].length; | |
for (let y = 0; y < dinoPixelArt.length; y++) { | |
for (let x = 0; x < dinoPixelArt[y].length; x++) { | |
if (dinoPixelArt[y][x] === 1) { | |
fill(50, 150, 50); // Green dinosaur | |
rect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); | |
} | |
} | |
} | |
// Draw legs based on animation frame (if not jumping) | |
if (!this.isJumping) { | |
if (this.runFrame === 0) { | |
// Left leg up | |
fill(50, 150, 50); | |
rect(3 * pixelSize, 9 * pixelSize, pixelSize, pixelSize); | |
rect(6 * pixelSize, 9 * pixelSize, pixelSize, pixelSize); | |
rect(6 * pixelSize, 10 * pixelSize, pixelSize, pixelSize); | |
} else { | |
// Right leg up | |
fill(50, 150, 50); | |
rect(3 * pixelSize, 9 * pixelSize, pixelSize, pixelSize); | |
rect(3 * pixelSize, 10 * pixelSize, pixelSize, pixelSize); | |
rect(6 * pixelSize, 9 * pixelSize, pixelSize, pixelSize); | |
} | |
} else { | |
// Both legs down for jumping | |
fill(50, 150, 50); | |
rect(3 * pixelSize, 9 * pixelSize, pixelSize, pixelSize); | |
rect(6 * pixelSize, 9 * pixelSize, pixelSize, pixelSize); | |
} | |
pop(); | |
} | |
getHitbox() { | |
// Return a slightly smaller hitbox than the actual sprite for better gameplay | |
return { | |
x: this.x + 10, | |
y: this.y + 10, | |
width: this.width - 20, | |
height: this.height - 20 | |
}; | |
} | |
} | |
// Obstacle class | |
class Obstacle { | |
constructor() { | |
this.width = 30; | |
this.height = 50; | |
this.x = width; | |
this.y = height - 100; | |
this.speed = gameSpeed; | |
this.type = floor(random(3)); // 0: small, 1: medium, 2: large | |
// Adjust size based on type | |
if (this.type === 0) { | |
this.height = 30; | |
} else if (this.type === 2) { | |
this.height = 70; | |
this.width = 40; | |
} | |
} | |
update() { | |
this.x -= this.speed; | |
} | |
draw() { | |
push(); | |
translate(this.x, this.y - this.height); | |
noStroke(); | |
// Draw the pixelated cactus | |
let pixelSize = this.width / cactusPixelArt[0].length; | |
for (let y = 0; y < cactusPixelArt.length; y++) { | |
for (let x = 0; x < cactusPixelArt[y].length; x++) { | |
if (cactusPixelArt[y][x] === 1) { | |
fill(30, 120, 30); // Darker green cactus | |
rect(x * pixelSize, y * pixelSize, pixelSize * (this.type + 1), pixelSize * (this.type + 1)); | |
} | |
} | |
} | |
pop(); | |
} | |
getHitbox() { | |
return { | |
x: this.x, | |
y: this.y - this.height, | |
width: this.width, | |
height: this.height | |
}; | |
} | |
isOffScreen() { | |
return this.x < -this.width; | |
} | |
} | |
// Ground class for scrolling ground | |
class Ground { | |
constructor(x) { | |
this.x = x; | |
this.y = height - 50; | |
this.width = width; | |
this.speed = gameSpeed; | |
} | |
update() { | |
this.x -= this.speed; | |
if (this.x < -this.width) { | |
this.x = width; | |
} | |
} | |
draw() { | |
noStroke(); | |
fill(120, 100, 40); | |
rect(this.x, this.y, this.width, 50); | |
// Draw pixelated ground details | |
fill(150, 120, 50); | |
for (let i = 0; i < this.width; i += 20) { | |
rect(this.x + i, this.y - 2, 10, 2); | |
} | |
} | |
} | |
// Cloud class for background | |
class Cloud { | |
constructor() { | |
this.x = width; | |
this.y = random(50, 150); | |
this.width = random(60, 120); | |
this.height = random(20, 40); | |
this.speed = gameSpeed * 0.3; | |
} | |
update() { | |
this.x -= this.speed; | |
} | |
draw() { | |
noStroke(); | |
fill(250); | |
// Draw pixelated cloud | |
let pixelSize = 5; | |
for (let y = 0; y < this.height / pixelSize; y++) { | |
for (let x = 0; x < this.width / pixelSize; x++) { | |
// Create a cloud-like pattern | |
if (random() > 0.3 && | |
(y > 0 || x > 0) && | |
(y < this.height / pixelSize - 1 || x < this.width / pixelSize - 1)) { | |
rect(this.x + x * pixelSize, this.y + y * pixelSize, pixelSize, pixelSize); | |
} | |
} | |
} | |
} | |
isOffScreen() { | |
return this.x < -this.width; | |
} | |
} | |
// Background layer class for parallax effect | |
class BackgroundLayer { | |
constructor(color, speed, y, height) { | |
this.color = color; | |
this.speed = speed; | |
this.y = y; | |
this.height = height; | |
this.mountains = []; | |
// Generate a mountain range | |
let mountainX = 0; | |
while (mountainX < width + 500) { | |
let mountainHeight = random(30, 80); | |
let mountainWidth = random(50, 150); | |
this.mountains.push({ | |
x: mountainX, | |
width: mountainWidth, | |
height: mountainHeight | |
}); | |
mountainX += mountainWidth - random(20, 40); // Overlap mountains a bit | |
} | |
} | |
update() { | |
// Move mountains | |
for (let mountain of this.mountains) { | |
mountain.x -= this.speed; | |
if (mountain.x < -mountain.width) { | |
// Reset mountain to the right side | |
mountain.x = width; | |
mountain.height = random(30, 80); | |
mountain.width = random(50, 150); | |
} | |
} | |
} | |
draw() { | |
noStroke(); | |
fill(this.color); | |
rect(0, this.y, width, this.height); | |
// Draw pixelated mountains | |
for (let mountain of this.mountains) { | |
this.drawPixelatedMountain(mountain.x, this.y, mountain.width, mountain.height); | |
} | |
} | |
drawPixelatedMountain(x, y, mountainWidth, mountainHeight) { | |
let pixelSize = 5; | |
fill(80, 60, 30); | |
// Create a triangular mountain shape with pixels | |
for (let i = 0; i < mountainWidth / pixelSize; i++) { | |
let h = sin((i / (mountainWidth / pixelSize)) * PI) * mountainHeight; | |
for (let j = 0; j < h / pixelSize; j++) { | |
rect(x + i * pixelSize, y - j * pixelSize, pixelSize, pixelSize); | |
} | |
} | |
// Add snow caps | |
fill(240); | |
for (let i = 0; i < mountainWidth / pixelSize; i++) { | |
let h = sin((i / (mountainWidth / pixelSize)) * PI) * mountainHeight; | |
let snowHeight = random(h * 0.2, h * 0.4); | |
for (let j = 0; j < snowHeight / pixelSize; j++) { | |
if (random() > 0.3) { // Add some randomness to snow | |
rect(x + i * pixelSize, y - h + j * pixelSize, pixelSize, pixelSize); | |
} | |
} | |
} | |
} | |
} | |
// Setup function for p5.js | |
function setup() { | |
createCanvas(800, 400); | |
pixelDensity(1); // Set pixel density for better pixel art rendering | |
// Create the dinosaur | |
dino = new Dinosaur(); | |
// Create initial ground pieces | |
grounds.push(new Ground(0)); | |
grounds.push(new Ground(width)); | |
// Create initial clouds | |
for (let i = 0; i < 3; i++) { | |
clouds.push(new Cloud()); | |
clouds[i].x = random(width); | |
} | |
// Create background layers for parallax effect | |
let skyGradient = color(135, 206, 235); // Sky blue | |
backgroundLayers.push(new BackgroundLayer(skyGradient, gameSpeed * 0.1, 0, height - 50)); | |
let midgroundColor = color(110, 180, 200); | |
backgroundLayers.push(new BackgroundLayer(midgroundColor, gameSpeed * 0.2, height - 200, 150)); | |
} | |
// Draw function for p5.js | |
function draw() { | |
background(100, 150, 255); | |
if (!isGameOver) { | |
// Update game | |
updateGame(); | |
} | |
// Draw everything | |
drawGame(); | |
// Draw UI elements | |
drawUI(); | |
} | |
function updateGame() { | |
// Update dinosaur | |
dino.update(); | |
// Update background layers | |
for (let layer of backgroundLayers) { | |
layer.update(); | |
} | |
// Update grounds | |
for (let ground of grounds) { | |
ground.update(); | |
} | |
// Update obstacles | |
for (let i = obstacles.length - 1; i >= 0; i--) { | |
obstacles[i].update(); | |
// Check for collision | |
if (checkCollision(dino.getHitbox(), obstacles[i].getHitbox())) { | |
gameOver(); | |
} | |
// Remove off-screen obstacles | |
if (obstacles[i].isOffScreen()) { | |
obstacles.splice(i, 1); | |
} | |
} | |
// Update clouds | |
for (let i = clouds.length - 1; i >= 0; i--) { | |
clouds[i].update(); | |
if (clouds[i].isOffScreen()) { | |
clouds.splice(i, 1); | |
} | |
} | |
// Add new obstacles randomly | |
if (frameCount % 60 === 0 && random() > 0.3) { | |
obstacles.push(new Obstacle()); | |
} | |
// Add new clouds randomly | |
if (frameCount % 100 === 0 && random() > 0.5) { | |
clouds.push(new Cloud()); | |
} | |
// Update score | |
if (frameCount % 5 === 0) { | |
score++; | |
// Every 100 points | |
if (score % 100 === 0) { | |
gameSpeed += 0.25; // Increase game speed every 100 points | |
} | |
} | |
} | |
function drawGame() { | |
// Draw background layers | |
for (let layer of backgroundLayers) { | |
layer.draw(); | |
} | |
// Draw clouds | |
for (let cloud of clouds) { | |
cloud.draw(); | |
} | |
// Draw grounds | |
for (let ground of grounds) { | |
ground.draw(); | |
} | |
// Draw obstacles | |
for (let obstacle of obstacles) { | |
obstacle.draw(); | |
} | |
// Draw dinosaur | |
dino.draw(); | |
} | |
function drawUI() { | |
// Draw score | |
fill(0); | |
textSize(20); | |
textAlign(RIGHT); | |
text(`Score: ${score}`, width - 20, 30); | |
// Draw high score | |
textAlign(LEFT); | |
text(`HI: ${highScore}`, 20, 30); | |
// Draw instructions on screen | |
if (isGameOver) { | |
textAlign(CENTER); | |
fill(255, 0, 0); | |
textSize(40); | |
text("GAME OVER", width / 2, height / 2 - 20); | |
fill(0); | |
textSize(20); | |
text("Press SPACE or CLICK to restart", width / 2, height / 2 + 20); | |
} else if (frameCount < 180) { | |
// Show instructions at the beginning | |
textAlign(CENTER); | |
fill(0); | |
textSize(20); | |
text("Press SPACE, UP ARROW, or CLICK to jump", width / 2, height / 2); | |
textSize(16); | |
text("Avoid obstacles and survive as long as possible", width / 2, height / 2 + 30); | |
} | |
} | |
function keyPressed() { | |
if (key === ' ' || keyCode === UP_ARROW) { | |
if (isGameOver) { | |
resetGame(); | |
} else { | |
dino.jump(); | |
} | |
} | |
} | |
function mousePressed() { | |
if (isGameOver) { | |
resetGame(); | |
} else { | |
dino.jump(); | |
} | |
} | |
function checkCollision(rect1, rect2) { | |
return rect1.x < rect2.x + rect2.width && | |
rect1.x + rect1.width > rect2.x && | |
rect1.y < rect2.y + rect2.height && | |
rect1.y + rect1.height > rect2.y; | |
} | |
function gameOver() { | |
isGameOver = true; | |
// Update high score | |
if (score > highScore) { | |
highScore = score; | |
} | |
} | |
function resetGame() { | |
dino = new Dinosaur(); | |
obstacles = []; | |
score = 0; | |
gameSpeed = 5; | |
isGameOver = false; | |
// Reset grounds | |
grounds = []; | |
grounds.push(new Ground(0)); | |
grounds.push(new Ground(width)); | |
// Reset clouds | |
clouds = []; | |
for (let i = 0; i < 3; i++) { | |
clouds.push(new Cloud()); | |
clouds[i].x = random(width); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let dino; | |
let obstacles = []; | |
let score = 0; | |
let highScore = 0; // Added high score | |
let gameOver = false; | |
let gravity; | |
let jumpForce; | |
let groundY; | |
let gameSpeed = 5; | |
let minObstacleDist = 50; | |
let nextObstacleFrame; | |
// --- Pixel Art Data --- | |
const PIXEL_SCALE = 4; | |
let C_TRANS, C_GREEN, C_DGREEN, C_WHITE, C_LGRAY, C_DGRAY, C_BROWN, C_SKY, C_GROUND, C_PTERO; | |
// Dinosaur Frame 1 | |
const dinoPixels1 = [ | |
[0,0,1,1,1,1,0,0,0,0], | |
[0,1,1,1,1,1,1,0,0,0], | |
[0,1,1,1,1,1,1,0,0,0], | |
[0,1,1,1,1,0,0,0,0,0], | |
[1,1,1,1,1,1,1,1,0,0], | |
[1,1,1,1,1,1,1,1,1,0], | |
[0,0,1,1,1,1,1,1,1,0], | |
[0,0,1,1,0,1,1,0,0,0], // Body/Tail | |
[0,0,1,1,0,1,1,0,0,0], | |
[0,0,2,2,0,2,2,0,0,0], // Legs 1 | |
[0,0,2,0,0,2,0,0,0,0], | |
[0,0,2,0,0,2,0,0,0,0] | |
]; | |
// Dinosaur Frame 2 (Legs swapped) | |
const dinoPixels2 = [ | |
[0,0,1,1,1,1,0,0,0,0], | |
[0,1,1,1,1,1,1,0,0,0], | |
[0,1,1,1,1,1,1,0,0,0], | |
[0,1,1,1,1,0,0,0,0,0], | |
[1,1,1,1,1,1,1,1,0,0], | |
[1,1,1,1,1,1,1,1,1,0], | |
[0,0,1,1,1,1,1,1,1,0], | |
[0,0,1,1,0,1,1,0,0,0], // Body/Tail | |
[0,0,1,1,0,1,1,0,0,0], | |
[0,0,2,2,0,2,2,0,0,0], // Legs 2 | |
[0,0,0,2,0,0,2,0,0,0], | |
[0,0,0,2,0,0,2,0,0,0] | |
]; | |
const dinoFrames = [dinoPixels1, dinoPixels2]; | |
const dinoWidth = dinoPixels1[0].length; | |
const dinoHeight = dinoPixels1.length; | |
// Obstacle (Cactus) | |
const cactusPixels = [ | |
[0,1,1,1,0], | |
[0,1,1,1,0], | |
[1,1,1,1,1], | |
[1,1,1,1,1], | |
[0,1,1,1,0], | |
[0,1,1,1,0], | |
[0,1,1,1,0], | |
[0,1,1,1,0], | |
[0,1,1,1,0] | |
]; | |
const cactusWidth = cactusPixels[0].length; | |
const cactusHeight = cactusPixels.length; | |
// Obstacle (Pterodactyl) - 12x8 pixels | |
const pteroPixels1 = [ // Frame 1 (Wings up) | |
[0,0,0,0,3,3,3,3,0,0,0,0], | |
[0,0,0,3,3,3,3,3,3,0,0,0], | |
[0,0,3,3,3,3,3,3,3,3,0,0], | |
[0,3,3,3,3,3,3,3,3,3,3,0], | |
[3,3,3,3,3,3,3,3,3,3,3,3], | |
[0,0,0,3,3,3,3,3,3,0,0,0], | |
[0,0,0,0,3,3,3,3,0,0,0,0], | |
[0,0,0,0,0,3,3,0,0,0,0,0] // Small body part | |
]; | |
const pteroPixels2 = [ // Frame 2 (Wings down) | |
[0,0,0,0,0,0,0,0,0,0,0,0], | |
[0,0,0,0,3,3,3,3,0,0,0,0], | |
[0,0,0,3,3,3,3,3,3,0,0,0], | |
[0,0,3,3,3,3,3,3,3,3,0,0], | |
[0,3,3,3,3,3,3,3,3,3,3,0], | |
[3,3,3,3,3,3,3,3,3,3,3,3], | |
[0,3,3,0,3,3,3,3,0,3,3,0], // Body/Legs visible | |
[0,0,0,0,3,3,3,3,0,0,0,0] | |
]; | |
const pteroFrames = [pteroPixels1, pteroPixels2]; | |
const pteroWidth = pteroPixels1[0].length; | |
const pteroHeight = pteroPixels1.length; | |
// --- Background Elements --- | |
let clouds = []; | |
let hills = []; | |
let groundDetail = []; | |
function setup() { | |
createCanvas(windowWidth, 400); | |
pixelDensity(1); | |
// Define Colors | |
C_TRANS = color(0, 0, 0, 0); | |
C_GREEN = color(50, 180, 50); | |
C_DGREEN = color(30, 120, 30); | |
C_WHITE = color(255); | |
C_LGRAY = color(200); | |
C_DGRAY = color(100); | |
C_BROWN = color(139, 69, 19); | |
C_PTERO = color(180, 160, 190); // Pterodactyl color | |
C_SKY = color(135, 206, 250); | |
C_GROUND = color(210, 180, 140); | |
groundY = height - 50; | |
gravity = createVector(0, 0.6); | |
jumpForce = createVector(0, -12); | |
dino = new Dinosaur(); | |
nextObstacleFrame = floor(random(minObstacleDist * 2, minObstacleDist * 4)); | |
textAlign(CENTER); | |
textSize(20); | |
textFont('monospace'); | |
// Load high score from local storage | |
let storedHighScore = localStorage.getItem('dinoHighScore'); | |
if (storedHighScore) { | |
highScore = parseInt(storedHighScore); | |
} | |
initializeBackground(); | |
} | |
function initializeBackground() { | |
clouds = []; | |
hills = []; | |
groundDetail = []; | |
for (let i = 0; i < 5; i++) { | |
clouds.push({ x: random(width), y: random(50, 150), size: random(40, 80) }); | |
hills.push({ x: random(width), y: groundY - random(20, 60), w: random(100, 300), h: random(40, 80) }); | |
} | |
for (let i = 0; i < 20; i++) { | |
groundDetail.push({x: random(width), y: groundY + random(5, height - groundY - 5), size: random(2, 5)}); | |
} | |
} | |
function drawBackground() { | |
background(C_SKY); | |
// Hills | |
fill(188, 143, 143); | |
noStroke(); | |
for (let i = 0; i < hills.length; i++) { | |
let hill = hills[i]; | |
ellipse(hill.x, hill.y, hill.w, hill.h * 2); | |
hill.x -= gameSpeed * 0.2; | |
if (hill.x + hill.w / 2 < 0) { | |
hill.x = width + hill.w / 2 + random(50, 150); | |
hill.y = groundY - random(20, 60); | |
hill.w = random(100, 300); | |
hill.h = random(40, 80); | |
} | |
} | |
// Clouds | |
fill(C_WHITE); | |
noStroke(); | |
for (let i = 0; i < clouds.length; i++) { | |
let cloud = clouds[i]; | |
ellipse(cloud.x, cloud.y, cloud.size, cloud.size / 2); | |
ellipse(cloud.x + cloud.size * 0.3, cloud.y + cloud.size * 0.1, cloud.size * 0.8, cloud.size / 2.5); | |
ellipse(cloud.x - cloud.size * 0.2, cloud.y + cloud.size * 0.15, cloud.size * 0.7, cloud.size / 3); | |
cloud.x -= gameSpeed * 0.1; | |
if (cloud.x + cloud.size < 0) { | |
cloud.x = width + random(cloud.size, cloud.size * 2); | |
cloud.y = random(50, 150); | |
cloud.size = random(40, 80); | |
} | |
} | |
// Ground | |
fill(C_GROUND); | |
noStroke(); | |
rect(0, groundY, width, height - groundY); | |
// Ground Detail | |
fill(C_BROWN); | |
for(let i = 0; i < groundDetail.length; i++) { | |
let detail = groundDetail[i]; | |
ellipse(detail.x, detail.y, detail.size, detail.size / 1.5); | |
detail.x -= gameSpeed; | |
if (detail.x + detail.size < 0) { | |
detail.x = width + random(10, 50); | |
detail.y = groundY + random(5, height - groundY - 5); | |
detail.size = random(2, 5); | |
} | |
} | |
} | |
function draw() { | |
drawBackground(); | |
if (gameOver) { | |
fill(0, 0, 0, 180); | |
rect(0, 0, width, height); | |
fill(C_WHITE); | |
textSize(32); | |
text("GAME OVER", width / 2, height / 2 - 40); | |
textSize(20); | |
text(`Score: ${score}`, width / 2, height / 2); | |
text(`High Score: ${highScore}`, width / 2, height / 2 + 30); | |
text("Press R to Restart", width / 2, height / 2 + 70); | |
noLoop(); | |
return; | |
} | |
// --- Game Logic --- | |
dino.applyForce(gravity); | |
dino.update(); | |
dino.show(); | |
// Spawn obstacles | |
if (frameCount >= nextObstacleFrame) { | |
// Add chance for pterodactyl after score > 5 | |
if (score > 5 && random() < 0.3) { | |
obstacles.push(new Pterodactyl()); | |
} else { | |
obstacles.push(new Cactus()); | |
} | |
gameSpeed = min(15, gameSpeed + 0.05); | |
let baseDist = width / gameSpeed * 1.5; // Adjust base distance based on speed | |
nextObstacleFrame = frameCount + floor(random(baseDist * 0.7, baseDist * 1.1)); // More variation | |
// Ensure minimum distance scales slightly with speed (less distance at higher speeds) | |
let dynamicMinDist = minObstacleDist * (1 + (15 - gameSpeed) / 15); | |
nextObstacleFrame = max(nextObstacleFrame, frameCount + dynamicMinDist); | |
} | |
// Update and draw obstacles | |
for (let i = obstacles.length - 1; i >= 0; i--) { | |
obstacles[i].update(); | |
obstacles[i].show(); | |
if (dino.hits(obstacles[i])) { | |
gameOver = true; | |
// Update high score if needed | |
if (score > highScore) { | |
highScore = score; | |
localStorage.setItem('dinoHighScore', highScore); // Save high score | |
} | |
} | |
// Remove obstacles that are off-screen | |
if (obstacles[i].isOffscreen()) { | |
obstacles.splice(i, 1); | |
if (!gameOver) { // Only increment score if game isn't over | |
score++; | |
} | |
} | |
} | |
// --- UI --- | |
fill(0); | |
textSize(20); | |
textAlign(LEFT); | |
text(`Score: ${score}`, 20, 30); | |
text(`Hi: ${highScore}`, 20, 55); // Show high score | |
textAlign(CENTER); | |
text("Press SPACE to Jump", width / 2, 30); | |
} | |
// --- Helper Function to Draw Pixel Art --- | |
function drawPixelArt(pixelData, x, y, pixelScale, colorMap) { | |
push(); | |
translate(x, y); | |
noStroke(); | |
for (let r = 0; r < pixelData.length; r++) { | |
for (let c = 0; c < pixelData[r].length; c++) { | |
let colorIndex = pixelData[r][c]; | |
if (colorIndex > 0) { | |
fill(colorMap[colorIndex]); | |
rect(c * pixelScale, r * pixelScale, pixelScale, pixelScale); | |
} | |
} | |
} | |
pop(); | |
} | |
function keyPressed() { | |
if (key === ' ' && !gameOver) { | |
dino.jump(); | |
} | |
if ((key === 'r' || key === 'R') && gameOver) { | |
restartGame(); | |
} | |
} | |
function restartGame() { | |
obstacles = []; | |
score = 0; | |
gameSpeed = 5; | |
dino = new Dinosaur(); | |
nextObstacleFrame = frameCount + floor(random(minObstacleDist * 2, minObstacleDist * 4)); | |
initializeBackground(); // Re-randomize background elements | |
gameOver = false; | |
loop(); | |
} | |
// --- Dinosaur Class --- | |
class Dinosaur { | |
constructor() { | |
this.baseWidth = dinoWidth * PIXEL_SCALE; | |
this.baseHeight = dinoHeight * PIXEL_SCALE; | |
this.width = this.baseWidth; | |
this.height = this.baseHeight; | |
this.pos = createVector(width / 4, groundY - this.height); | |
this.vel = createVector(0, 0); | |
this.acc = createVector(0, 0); | |
this.onGround = true; | |
this.colorMap = { 1: C_GREEN, 2: C_DGREEN }; | |
this.animFrame = 0; | |
this.animTimer = 0; | |
this.animSpeed = 8; // Frames per animation switch | |
} | |
applyForce(force) { | |
this.acc.add(force); | |
} | |
jump() { | |
if (this.onGround) { | |
this.applyForce(jumpForce); | |
this.onGround = false; | |
} | |
} | |
update() { | |
this.vel.add(this.acc); | |
this.pos.add(this.vel); | |
this.acc.mult(0); | |
if (this.pos.y + this.height >= groundY) { | |
this.pos.y = groundY - this.height; | |
this.vel.y = 0; | |
this.onGround = true; | |
} else { | |
this.onGround = false; | |
} | |
this.pos.y = min(this.pos.y, groundY - this.height); | |
// Animation | |
if (this.onGround) { | |
this.animTimer++; | |
if (this.animTimer >= this.animSpeed) { | |
this.animFrame = (this.animFrame + 1) % dinoFrames.length; | |
this.animTimer = 0; | |
} | |
} else { | |
this.animFrame = 0; // Use default frame when jumping | |
this.animTimer = 0; | |
} | |
} | |
show() { | |
drawPixelArt(dinoFrames[this.animFrame], this.pos.x, this.pos.y, PIXEL_SCALE, this.colorMap); | |
} | |
hits(obstacle) { | |
// Use base dimensions for collision, slightly inset | |
let dinoRight = this.pos.x + this.baseWidth * 0.8; | |
let dinoLeft = this.pos.x + this.baseWidth * 0.2; | |
let dinoBottom = this.pos.y + this.baseHeight * 0.9; | |
let dinoTop = this.pos.y + this.baseHeight * 0.1; | |
let obsRight = obstacle.pos.x + obstacle.width * 0.8; | |
let obsLeft = obstacle.pos.x + obstacle.width * 0.2; | |
let obsBottom = obstacle.pos.y + obstacle.height * 0.9; | |
let obsTop = obstacle.pos.y + obstacle.height * 0.1; | |
return ( | |
dinoRight > obsLeft && | |
dinoLeft < obsRight && | |
dinoBottom > obsTop && | |
dinoTop < obsBottom | |
); | |
} | |
} | |
// --- Base Obstacle Class (Can be extended or used directly) --- | |
class Obstacle { | |
constructor(pixelData, colorMap, width, height) { | |
this.pixelData = pixelData; | |
this.colorMap = colorMap; | |
this.width = width * PIXEL_SCALE; | |
this.height = height * PIXEL_SCALE; | |
this.pos = createVector(width, 0); // Initial X, Y set by subclass | |
this.animFrame = 0; | |
this.animTimer = 0; | |
this.animSpeed = 10; // Default animation speed | |
} | |
update() { | |
this.pos.x -= gameSpeed; | |
// Animate if multiple frames exist | |
if (Array.isArray(this.pixelData[0][0])) { // Check if pixelData is an array of frames | |
this.animTimer++; | |
if (this.animTimer >= this.animSpeed) { | |
this.animFrame = (this.animFrame + 1) % this.pixelData.length; | |
this.animTimer = 0; | |
} | |
} | |
} | |
show() { | |
let frameToShow = Array.isArray(this.pixelData[0][0]) ? this.pixelData[this.animFrame] : this.pixelData; | |
drawPixelArt(frameToShow, this.pos.x, this.pos.y, PIXEL_SCALE, this.colorMap); | |
} | |
isOffscreen() { | |
return this.pos.x + this.width < 0; | |
} | |
} | |
// --- Cactus Class --- | |
class Cactus extends Obstacle { | |
constructor() { | |
super(cactusPixels, { 1: C_BROWN }, cactusWidth, cactusHeight); | |
this.pos.y = groundY - this.height; // Place on ground | |
} | |
} | |
// --- Pterodactyl Class --- | |
class Pterodactyl extends Obstacle { | |
constructor() { | |
super(pteroFrames, { 3: C_PTERO }, pteroWidth, pteroHeight); | |
// Spawn at one of two heights | |
let spawnHeightChoice = random([1, 2]); | |
if (spawnHeightChoice === 1) { | |
this.pos.y = groundY - this.height - dino.height * 0.8; // Head height | |
} else { | |
this.pos.y = groundY - this.height * 1.8; // Higher up | |
} | |
this.animSpeed = 12; // Flap a bit slower | |
} | |
} | |
function windowResized() { | |
resizeCanvas(windowWidth, 400); | |
groundY = height - 50; | |
if (!gameOver) { | |
restartGame(); | |
} else { | |
// Redraw game over screen centered | |
drawBackground(); // Redraw background | |
fill(0, 0, 0, 180); | |
rect(0, 0, width, height); | |
fill(C_WHITE); | |
textSize(32); | |
textAlign(CENTER); | |
text("GAME OVER", width / 2, height / 2 - 40); | |
textSize(20); | |
text(`Score: ${score}`, width / 2, height / 2); | |
text(`High Score: ${highScore}`, width / 2, height / 2 + 30); | |
text("Press R to Restart", width / 2, height / 2 + 70); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment