1.介绍
这是一个简单的受超级马里奥启发的平台游戏演示!这个基于网络的游戏包括:
- 角色移动:使用箭头键让马里奥向左和向右移动,空格键或向上箭头键跳跃。
- 跳跃平台:游戏中有多个可以跳跃的平台,包括经典的问题方块,从下方击中会释放金币。
- 敌人:有类似栗子怪的敌人来回移动。你可以通过踩在它们头上来打败它们。
- 可收集物品:在整个关卡中收集金币以增加你的得分。
- 目标:到达关卡末尾的旗帜以获胜。
游戏包括:
- 得分和金币追踪
- 游戏结束和获胜屏幕,并重新开始选项
- 跟随马里奥的横向滚动摄像机
- 基本的物理效果,包括重力和碰撞检测
- 简单的敌人人工智能,在碰到障碍物或边缘时改变方向。
游戏包括:
- 角色移动:使用箭头键让马里奥向左和向右移动,空格键或向上箭头键跳跃。
- 跳跃平台:游戏中有多个可以跳跃的平台,包括经典的问题方块,从下方击中会释放金币。
- 敌人:有类似栗子怪的敌人来回移动。你可以通过踩在它们头上来打败它们。
- 可收集物品:在整个关卡中收集金币以增加你的得分。
- 目标:到达关卡末尾的旗帜以获胜。
游戏包括:
得分和金币追踪
游戏结束和获胜屏幕,并重新开始选项
跟随马里奥的横向滚动摄像机
基本的物理效果,包括重力和碰撞检测
简单的敌人人工智能,在碰到障碍物或边缘时改变方向。
2.效果展示图
3.代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Mario Demo</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #87CEEB;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
}
#game-container {
position: relative;
width: 800px;
height: 400px;
background: #87CEEB;
overflow: hidden;
border: 4px solid #333;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
#game-canvas {
position: absolute;
top: 0;
left: 0;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 20px;
font-weight: bold;
text-shadow: 2px 2px 0 #000;
}
#controls {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-size: 14px;
text-shadow: 1px 1px 0 #000;
}
#game-over, #win-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
font-size: 36px;
font-weight: bold;
display: none;
}
button {
margin-top: 20px;
padding: 10px 20px;
font-size: 18px;
background-color: #e52521;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #ff4040;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas" width="800" height="400"></canvas>
<div id="ui">Score: <span id="score">0</span> | Coins: <span id="coins">0</span></div>
<div id="controls">
Controls: Arrow keys to move, Space to jump
</div>
<div id="game-over">
Game Over!
<button id="restart-button">Restart</button>
</div>
<div id="win-screen">
You Win!
<button id="restart-button-win">Play Again</button>
</div>
</div>
<script>
// Game setup
const canvas = document.getElementById("game-canvas");
const ctx = canvas.getContext("2d");
const scoreElement = document.getElementById("score");
const coinsElement = document.getElementById("coins");
const gameOverScreen = document.getElementById("game-over");
const winScreen = document.getElementById("win-screen");
const restartButton = document.getElementById("restart-button");
const restartButtonWin = document.getElementById("restart-button-win");
// Game state
let gameRunning = true;
let score = 0;
let coins = 0;
// Sprites (simplified placeholders)
const sprites = {
mario: {
width: 40,
height: 60,
color: "red",
jumpingColor: "#e52521"
},
ground: {
color: "#8b4513"
},
platform: {
color: "#5d94fb"
},
brick: {
color: "#c86e0c"
},
pipe: {
color: "#05c34c"
},
questionBlock: {
color: "#e39d25",
hitColor: "#c86e0c"
},
goomba: {
width: 40,
height: 40,
color: "#a05000"
},
coin: {
width: 20,
height: 30,
color: "gold"
},
flag: {
color: "green",
poleColor: "#ffffff"
}
};
// Player
const player = {
x: 100,
y: 300,
width: sprites.mario.width,
height: sprites.mario.height,
velocityX: 0,
velocityY: 0,
speed: 5,
jumpForce: 12,
gravity: 0.5,
isJumping: false,
facingRight: true,
isAlive: true
};
// Camera offset (for scrolling)
let cameraOffsetX = 0;
// Level design (coordinates are in game space, not screen space)
const platforms = [
// Ground
{ x: 0, y: 360, width: 1000, height: 40, type: "ground" },
{ x: 1100, y: 360, width: 600, height: 40, type: "ground" },
{ x: 1800, y: 360, width: 800, height: 40, type: "ground" },
// Platforms
{ x: 400, y: 280, width: 120, height: 30, type: "platform" },
{ x: 600, y: 220, width: 120, height: 30, type: "platform" },
{ x: 1300, y: 260, width: 100, height: 30, type: "platform" },
{ x: 1500, y: 200, width: 80, height: 30, type: "platform" },
// Bricks and question blocks
{ x: 300, y: 200, width: 40, height: 40, type: "brick" },
{ x: 340, y: 200, width: 40, height: 40, type: "question", hit: false },
{ x: 380, y: 200, width: 40, height: 40, type: "brick" },
{ x: 800, y: 240, width: 40, height: 40, type: "question", hit: false },
{ x: 1200, y: 200, width: 40, height: 40, type: "question", hit: false },
// Pipes
{ x: 500, y: 320, width: 60, height: 40, type: "pipe" },
{ x: 1400, y: 320, width: 60, height: 40, type: "pipe" },
{ x: 1700, y: 320, width: 60, height: 40, type: "pipe" }
];
// Goal
const goal = {
x: 2400,
y: 160,
width: 10,
height: 200,
flagHeight: 40,
flagWidth: 30
};
// Enemies
let enemies = [
{ x: 500, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true },
{ x: 900, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true },
{ x: 1300, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true },
{ x: 1600, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true },
{ x: 2000, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true }
];
// Coins
let coins_collection = [
{ x: 340, y: 150, width: sprites.coin.width, height: sprites.coin.height, collected: false },
{ x: 800, y: 190, width: sprites.coin.width, height: sprites.coin.height, collected: false },
{ x: 450, y: 230, width: sprites.coin.width, height: sprites.coin.height, collected: false },
{ x: 650, y: 170, width: sprites.coin.width, height: sprites.coin.height, collected: false },
{ x: 1200, y: 150, width: sprites.coin.width, height: sprites.coin.height, collected: false },
{ x: 1350, y: 210, width: sprites.coin.width, height: sprites.coin.height, collected: false },
{ x: 1550, y: 150, width: sprites.coin.width, height: sprites.coin.height, collected: false },
{ x: 2100, y: 310, width: sprites.coin.width, height: sprites.coin.height, collected: false }
];
// Controls
const keys = {
right: false,
left: false,
up: false
};
// Event listeners for keyboard controls
window.addEventListener('keydown', function(e) {
if (e.key === 'ArrowRight') keys.right = true;
if (e.key === 'ArrowLeft') keys.left = true;
if (e.key === 'ArrowUp' || e.key === ' ') keys.up = true;
// Prevent scrolling when pressing space
if (e.key === ' ') e.preventDefault();
});
window.addEventListener('keyup', function(e) {
if (e.key === 'ArrowRight') keys.right = false;
if (e.key === 'ArrowLeft') keys.left = false;
if (e.key === 'ArrowUp' || e.key === ' ') keys.up = false;
});
// Game restart
restartButton.addEventListener('click', resetGame);
restartButtonWin.addEventListener('click', resetGame);
function resetGame() {
// Reset player
player.x = 100;
player.y = 300;
player.velocityX = 0;
player.velocityY = 0;
player.isAlive = true;
// Reset camera
cameraOffsetX = 0;
// Reset score and coins
score = 0;
coins = 0;
scoreElement.textContent = score;
coinsElement.textContent = coins;
// Reset enemies
enemies = [
{ x: 500, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true },
{ x: 900, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true },
{ x: 1300, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true },
{ x: 1600, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true },
{ x: 2000, y: 320, width: sprites.goomba.width, height: sprites.goomba.height, velocityX: -1, isAlive: true }
];
// Reset coins
coins_collection.forEach(coin => coin.collected = false);
// Reset question blocks
platforms.forEach(platform => {
if (platform.type === "question") {
platform.hit = false;
}
});
// Hide screens
gameOverScreen.style.display = "none";
winScreen.style.display = "none";
// Restart game
gameRunning = true;
requestAnimationFrame(gameLoop);
}
// Collision detection
function isColliding(obj1, obj2) {
return obj1.x < obj2.x + obj2.width &&
obj1.x + obj1.width > obj2.x &&
obj1.y < obj2.y + obj2.height &&
obj1.y + obj1.height > obj2.y;
}
// Game loop
function gameLoop() {
if (!gameRunning) return;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Calculate camera offset (follow player)
if (player.x > canvas.width / 2) {
cameraOffsetX = player.x - canvas.width / 2;
}
// Background (simple blue sky)
ctx.fillStyle = '#87CEEB';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw ground and platforms
platforms.forEach(platform => {
ctx.fillStyle = getPlatformColor(platform);
ctx.fillRect(platform.x - cameraOffsetX, platform.y, platform.width, platform.height);
// Add details for question blocks
if (platform.type === "question" && !platform.hit) {
ctx.fillStyle = 'white';
ctx.font = '20px Arial';
ctx.fillText('?', platform.x - cameraOffsetX + 15, platform.y + 25);
}
});
// Draw goal (flag)
ctx.fillStyle = sprites.flag.poleColor;
ctx.fillRect(goal.x - cameraOffsetX, goal.y, goal.width, goal.height);
ctx.fillStyle = sprites.flag.color;
ctx.fillRect(goal.x - cameraOffsetX, goal.y, goal.flagWidth, goal.flagHeight);
// Draw coins
coins_collection.forEach(coin => {
if (!coin.collected) {
ctx.fillStyle = sprites.coin.color;
ctx.beginPath();
ctx.arc(coin.x - cameraOffsetX + coin.width/2, coin.y + coin.height/2, coin.width/2, 0, Math.PI * 2);
ctx.fill();
}
});
// Draw enemies
enemies.forEach(enemy => {
if (enemy.isAlive) {
ctx.fillStyle = sprites.goomba.color;
ctx.fillRect(enemy.x - cameraOffsetX, enemy.y, enemy.width, enemy.height);
// Simple face for the goomba
ctx.fillStyle = 'black';
ctx.fillRect(enemy.x - cameraOffsetX + 10, enemy.y + 15, 5, 5);
ctx.fillRect(enemy.x - cameraOffsetX + 25, enemy.y + 15, 5, 5);
ctx.beginPath();
ctx.arc(enemy.x - cameraOffsetX + 20, enemy.y + 30, 10, 0, Math.PI, false);
ctx.stroke();
}
});
// Draw player (Mario)
if (player.isAlive) {
ctx.fillStyle = player.isJumping ? sprites.mario.jumpingColor : sprites.mario.color;
ctx.fillRect(player.x - cameraOffsetX, player.y, player.width, player.height);
// Simple face for mario
ctx.fillStyle = 'black';
ctx.fillRect(player.facingRight ? player.x - cameraOffsetX + 25 : player.x - cameraOffsetX + 10, player.y + 15, 5, 5);
// Mario's cap and overalls (simplified)
ctx.fillStyle = 'red';
ctx.fillRect(player.x - cameraOffsetX, player.y, player.width, 15);
ctx.fillStyle = 'blue';
ctx.fillRect(player.x - cameraOffsetX + 5, player.y + 35, player.width - 10, 25);
}
// Update player
if (gameRunning && player.isAlive) {
// Handle movement
if (keys.right) {
player.velocityX = player.speed;
player.facingRight = true;
} else if (keys.left) {
player.velocityX = -player.speed;
player.facingRight = false;
} else {
player.velocityX = 0;
}
// Handle jumping
if (keys.up && !player.isJumping) {
player.velocityY = -player.jumpForce;
player.isJumping = true;
}
// Apply gravity
player.velocityY += player.gravity;
// Update position
player.x += player.velocityX;
player.y += player.velocityY;
// Don't go off the left edge
if (player.x < 0) player.x = 0;
// Check for platform collisions
player.isJumping = true; // Assume we're in the air unless we find a platform below
platforms.forEach(platform => {
if (isColliding(player, platform)) {
// Collision with bottom of platform
if (player.y < platform.y + platform.height &&
player.y > platform.y &&
player.velocityY < 0) {
player.y = platform.y + platform.height;
player.velocityY = 0;
// Hit question block
if (platform.type === "question" && !platform.hit) {
platform.hit = true;
spawnCoin(platform.x + platform.width/2, platform.y - 40);
}
}
// Collision with top of platform
else if (player.y + player.height > platform.y &&
player.y + player.height < platform.y + platform.height &&
player.velocityY > 0) {
player.y = platform.y - player.height;
player.velocityY = 0;
player.isJumping = false;
}
// Collision with right side of platform
else if (player.x < platform.x + platform.width &&
player.x + player.width > platform.x + platform.width) {
player.x = platform.x + platform.width;
}
// Collision with left side of platform
else if (player.x + player.width > platform.x &&
player.x < platform.x) {
player.x = platform.x - player.width;
}
}
});
// Check for enemy collisions
enemies.forEach(enemy => {
if (enemy.isAlive && isColliding(player, enemy)) {
// Jumping on enemy
if (player.velocityY > 0 && player.y + player.height < enemy.y + enemy.height/2) {
enemy.isAlive = false;
player.velocityY = -player.jumpForce/1.5;
score += 100;
scoreElement.textContent = score;
}
// Hit by enemy
else {
gameOver();
}
}
});
// Check for coin collisions
coins_collection.forEach(coin => {
if (!coin.collected && isColliding(player, coin)) {
coin.collected = true;
coins++;
score += 50;
scoreElement.textContent = score;
coinsElement.textContent = coins;
}
});
// Check for goal collision
if (isColliding(player, goal)) {
win();
}
// Check for falling off the world
if (player.y > canvas.height) {
gameOver();
}
}
// Update enemies
enemies.forEach(enemy => {
if (enemy.isAlive) {
enemy.x += enemy.velocityX;
// Simple AI: Change direction when hitting obstacles
let onGround = false;
let hitWall = false;
platforms.forEach(platform => {
if (isColliding(enemy, platform)) {
// Detect if enemy is on ground
if (enemy.y + enemy.height >= platform.y &&
enemy.y + enemy.height <= platform.y + 10) {
onGround = true;
}
// Detect if enemy hit wall
else if (enemy.x + enemy.width >= platform.x &&
enemy.x <= platform.x) {
hitWall = true;
}
else if (enemy.x <= platform.x + platform.width &&
enemy.x + enemy.width >= platform.x + platform.width) {
hitWall = true;
}
}
});
// Check if there's ground ahead
let groundAhead = false;
platforms.forEach(platform => {
if (enemy.velocityX < 0) { // Moving left
if (enemy.x - 5 >= platform.x &&
enemy.x - 5 <= platform.x + platform.width &&
enemy.y + enemy.height >= platform.y &&
enemy.y + enemy.height <= platform.y + 10) {
groundAhead = true;
}
} else { // Moving right
if (enemy.x + enemy.width + 5 >= platform.x &&
enemy.x + enemy.width + 5 <= platform.x + platform.width &&
enemy.y + enemy.height >= platform.y &&
enemy.y + enemy.height <= platform.y + 10) {
groundAhead = true;
}
}
});
// Change direction if hit wall or about to fall
if (hitWall || (!groundAhead && onGround)) {
enemy.velocityX = -enemy.velocityX;
}
}
});
// Continue the loop
if (gameRunning) {
requestAnimationFrame(gameLoop);
}
}
// Spawn a coin (when hitting question blocks)
function spawnCoin(x, y) {
const newCoin = {
x: x - sprites.coin.width/2,
y: y,
width: sprites.coin.width,
height: sprites.coin.height,
collected: false
};
coins_collection.push(newCoin);
// Animation to collect the coin automatically
setTimeout(() => {
if (!newCoin.collected) {
newCoin.collected = true;
coins++;
score += 50;
scoreElement.textContent = score;
coinsElement.textContent = coins;
}
}, 1000);
}
// Get color based on platform type
function getPlatformColor(platform) {
switch(platform.type) {
case "ground":
return sprites.ground.color;
case "platform":
return sprites.platform.color;
case "brick":
return sprites.brick.color;
case "pipe":
return sprites.pipe.color;
case "question":
return platform.hit ? sprites.questionBlock.hitColor : sprites.questionBlock.color;
default:
return "gray";
}
}
// Game over
function gameOver() {
player.isAlive = false;
gameRunning = false;
gameOverScreen.style.display = "flex";
}
// Win
function win() {
gameRunning = false;
winScreen.style.display = "flex";
}
// Start the game
gameLoop();
</script>
</body>
</html>