目录
摘要:俄罗斯方块网页版游戏
本项目是一款使用 p5.js 开发的俄罗斯方块网页版小游戏,提供完整的游戏功能与良好用户体验。玩家可通过浏览器直接运行游戏,无需安装插件。
主要特性包括:
🕹️ 经典操作:支持方向键移动、旋转、快速下落
🧭 菜单系统:带有开始菜单与暂停菜单,支持调整游戏速度
💡 得分统计:游戏过程中实时显示得分
🎨 炫酷视觉:渐变背景+高亮方块+阴影效果
💻 跨平台支持:HTML + JS 实现,可直接在浏览器中运行
适合用于娱乐、教学演示或 Web 游戏开发学习项目。
一、页面效果
1.1 开始界面
滑竿:调节下落速度
开始游戏按钮:弹窗隐藏,游戏开始
1.2 游戏界面
“←”键:向左移动;
“→”键:向右移动;
“↑”键:旋转方块;
“↓”:方块直接下落到最底部;
“空格”键:暂停弹窗(继续,重新开始,退出)
1.3 游戏结束界面
重新开始:回到游戏开始页面
退出:关闭窗口
二、怎么运行
1、新建一个txt文本;
2、代码复制进文本;
3、txt文件 重命名为html格式即可,如index.html
4、点击即可在浏览器中开始游戏。
三、执行代码
<!DOCTYPE html>
<html>
<head>
<title>俄罗斯方块</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: linear-gradient(135deg, #1e3c72, #2a5298);
font-family: Arial, sans-serif;
}
canvas {
border: 3px solid #fff;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
display: none;
}
#scoreDisplay {
margin-top: 20px;
background: rgba(255, 255, 255, 0.1);
padding: 15px;
border-radius: 10px;
color: white;
font-size: 1.2em;
font-weight: bold;
}
#startModal, #pauseModal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: #fff;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.modal-content h2 {
margin: 0 0 20px;
color: #333;
}
.modal-content button {
margin: 10px;
padding: 10px 20px;
font-size: 1em;
cursor: pointer;
border: none;
border-radius: 5px;
background: #ff6b6b;
color: white;
transition: background 0.3s;
}
.modal-content button:hover {
background: #ff8787;
}
.modal-content input[type="range"] {
width: 200px;
margin: 20px 0;
accent-color: #ff6b6b;
}
</style>
</head>
<body>
<div id="startModal">
<div class="modal-content">
<h2>俄罗斯方块</h2>
<p>调整速度:</p>
<input type="range" id="startSpeedSlider" min="5" max="30" value="10">
<button id="startButton">开始游戏</button>
</div>
</div>
<div id="pauseModal" style="display: none;">
<div class="modal-content">
<h2 id="pauseModalTitle">暂停</h2>
<p>调整速度:</p>
<input type="range" id="pauseSpeedSlider" min="5" max="30" value="10">
<button id="resumeButton">继续</button>
<button id="restartButton">重新开始</button>
<button id="exitButton">退出</button>
</div>
</div>
<div id="scoreDisplay" style="display: none;">得分:0</div>
<script>
let board;
let currentPiece;
let score = 0;
let gameOver = false;
let paused = false;
let frameRateValue = 10;
let gameStarted = false;
function setup() {
let canvas = createCanvas(300, 600);
canvas.elt.style.display = 'none';
board = new Board(10, 20);
currentPiece = new Piece();
frameRate(frameRateValue);
document.getElementById('startButton').addEventListener('click', () => {
document.getElementById('startModal').style.display = 'none';
canvas.elt.style.display = 'block';
document.getElementById('scoreDisplay').style.display = 'block';
gameStarted = true;
frameRateValue = parseInt(document.getElementById('startSpeedSlider').value);
});
document.getElementById('resumeButton').addEventListener('click', () => {
if (!gameOver) {
document.getElementById('pauseModal').style.display = 'none';
paused = false;
frameRateValue = parseInt(document.getElementById('pauseSpeedSlider').value);
}
});
document.getElementById('restartButton').addEventListener('click', () => {
window.location.reload();
});
document.getElementById('exitButton').addEventListener('click', () => {
window.close();
});
document.getElementById('startSpeedSlider').addEventListener('input', () => {
frameRateValue = parseInt(document.getElementById('startSpeedSlider').value);
document.getElementById('pauseSpeedSlider').value = frameRateValue;
});
document.getElementById('pauseSpeedSlider').addEventListener('input', () => {
frameRateValue = parseInt(document.getElementById('pauseSpeedSlider').value);
document.getElementById('startSpeedSlider').value = frameRateValue;
});
}
function draw() {
let gradient = drawingContext.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, '#4facfe');
gradient.addColorStop(1, '#00f2fe');
drawingContext.fillStyle = gradient;
rect(0, 0, width, height);
if (gameStarted && !paused && !gameOver) {
frameRate(frameRateValue);
board.show();
currentPiece.show();
currentPiece.update();
} else if (gameStarted) {
board.show();
currentPiece.show();
}
document.getElementById('scoreDisplay').innerText = `得分:${score}`;
if (gameOver) {
textSize(32);
textAlign(CENTER);
fill(255, 50, 50);
text("游戏结束", width/2, height/2);
drawingContext.shadowBlur = 20;
drawingContext.shadowColor = 'rgba(255, 0, 0, 0.5)';
} else {
drawingContext.shadowBlur = 0;
}
}
function keyPressed() {
if (keyCode === 32) {
if (gameStarted && !gameOver) {
paused = !paused;
document.getElementById('pauseModal').style.display = paused ? 'flex' : 'none';
document.getElementById('pauseModalTitle').innerText = '暂停';
document.getElementById('resumeButton').style.display = 'inline-block';
}
return;
}
if (gameOver || paused || !gameStarted) return;
if (keyCode === LEFT_ARROW) {
currentPiece.move(-1);
} else if (keyCode === RIGHT_ARROW) {
currentPiece.move(1);
} else if (keyCode === DOWN_ARROW) {
currentPiece.dropToBottom();
} else if (keyCode === UP_ARROW) {
currentPiece.rotate();
}
}
class Board {
constructor(w, h) {
this.width = w;
this.height = h;
this.grid = [];
for (let i = 0; i < h; i++) {
this.grid[i] = [];
for (let j = 0; j < w; j++) {
this.grid[i][j] = 0;
}
}
}
show() {
let cellWidth = width / this.width;
let cellHeight = height / this.height;
for (let i = 0; i < this.height; i++) {
for (let j = 0; j < this.width; j++) {
if (this.grid[i][j] === 1) {
fill(0, 128, 255);
stroke(255);
strokeWeight(2);
drawingContext.shadowBlur = 10;
drawingContext.shadowColor = 'rgba(0, 128, 255, 0.5)';
rect(j * cellWidth, i * cellHeight, cellWidth, cellHeight, 5);
drawingContext.shadowBlur = 0;
}
}
}
}
addPiece(piece) {
for (let i = 0; i < piece.shape.length; i++) {
for (let j = 0; j < piece.shape[i].length; j++) {
if (piece.shape[i][j] === 1) {
let boardX = piece.x + j;
let boardY = piece.y + i;
if (boardY >= 0 && boardY < this.height && boardX >= 0 && boardX < this.width) {
this.grid[boardY][boardX] = 1;
}
}
}
}
this.clearLines();
}
checkCollision(piece) {
for (let i = 0; i < piece.shape.length; i++) {
for (let j = 0; j < piece.shape[i].length; j++) {
if (piece.shape[i][j] === 1) {
let boardX = piece.x + j;
let boardY = piece.y + i;
if (boardY >= this.height) return true;
if (boardX < 0 || boardX >= this.width) return true;
if (boardY >= 0 && this.grid[boardY][boardX] === 1) return true;
}
}
}
return false;
}
clearLines() {
let linesCleared = 0;
for (let i = this.height - 1; i >= 0; i--) {
let full = true;
for (let j = 0; j < this.width; j++) {
if (this.grid[i][j] === 0) {
full = false;
break;
}
}
if (full) {
this.grid.splice(i, 1);
this.grid.unshift(new Array(this.width).fill(0));
linesCleared++;
i++;
}
}
score += linesCleared * 100;
}
}
class Piece {
constructor() {
this.shapes = [
[[1, 1, 1, 1]], // I
[[1, 1], [1, 1]], // O
[[1, 1, 1], [0, 1, 0]], // T
[[1, 1, 1], [1, 0, 0]], // L
[[1, 1, 1], [0, 0, 1]], // J
[[1, 1, 0], [0, 1, 1]], // S
[[0, 1, 1], [1, 1, 0]] // Z
];
this.shape = random(this.shapes);
this.x = floor(board.width / 2) - floor(this.shape[0].length / 2);
this.y = -this.shape.length;
this.colors = [
[0, 255, 255], // I: 青色
[255, 255, 0], // O: 黄色
[128, 0, 128], // T: 紫色
[255, 165, 0], // L: 橙色
[0, 0, 255], // J: 蓝色
[0, 255, 0], // S: 绿色
[255, 0, 0] // Z: 红色
];
this.color = this.colors[this.shapes.indexOf(this.shape)];
}
show() {
let cellWidth = width / board.width;
let cellHeight = height / board.height;
for (let i = 0; i < this.shape.length; i++) {
for (let j = 0; j < this.shape[i].length; j++) {
if (this.shape[i][j] === 1) {
fill(this.color[0], this.color[1], this.color[2]);
stroke(255);
strokeWeight(2);
drawingContext.shadowBlur = 10;
drawingContext.shadowColor = `rgba(${this.color[0]}, ${this.color[1]}, ${this.color[2]}, 0.5)`;
rect((this.x + j) * cellWidth, (this.y + i) * cellHeight, cellWidth, cellHeight, 5);
drawingContext.shadowBlur = 0;
}
}
}
}
update() {
let nextPiece = new Piece();
Object.assign(nextPiece, this);
nextPiece.y++;
if (board.checkCollision(nextPiece)) {
if (this.y < 0) {
gameOver = true;
document.getElementById('pauseModal').style.display = 'flex';
document.getElementById('pauseModalTitle').innerText = '游戏结束';
document.getElementById('resumeButton').style.display = 'none';
} else {
board.addPiece(this);
currentPiece = new Piece();
}
} else {
this.y++;
}
}
move(dir) {
let nextPiece = new Piece();
Object.assign(nextPiece, this);
nextPiece.x += dir;
if (!board.checkCollision(nextPiece)) {
this.x = nextPiece.x;
}
}
rotate() {
let nextPiece = new Piece();
Object.assign(nextPiece, this);
let newShape = [];
for (let i = 0; i < this.shape[0].length; i++) {
newShape[i] = [];
for (let j = 0; j < this.shape.length; j++) {
newShape[i][j] = this.shape[this.shape.length - 1 - j][i];
}
}
nextPiece.shape = newShape;
if (!board.checkCollision(nextPiece)) {
this.shape = newShape;
}
}
dropToBottom() {
let nextPiece = new Piece();
Object.assign(nextPiece, this);
while (!board.checkCollision(nextPiece)) {
this.y = nextPiece.y;
nextPiece.y++;
}
if (this.y < 0 && board.checkCollision(nextPiece)) {
gameOver = true;
document.getElementById('pauseModal').style.display = 'flex';
document.getElementById('pauseModalTitle').innerText = '游戏结束';
document.getElementById('resumeButton').style.display = 'none';
} else {
board.addPiece(this);
currentPiece = new Piece();
}
}
}
</script>
</body>
</html>
四、游戏主要功能
4.1 开始游戏前设置
初始弹出「开始菜单」,可通过滑块选择游戏速度(帧率 5~30)。
点击 “开始游戏” 按钮进入游戏界面。
4.2 游戏界面与内容
主界面居中展示游戏区域(10 列 × 20 行)。
每个方块都有高亮颜色 + 阴影效果,视觉体验良好。
屏幕上方显示当前 得分。
4.3 操作方式
⬅️ 左方向键:方块左移
➡️ 右方向键:方块右移
⬆️ 上方向键:旋转当前方块
⬇️ 下方向键:直接下落到底部
空格键(Space):暂停/恢复游戏
暂停时弹出「暂停菜单」,可继续游戏、调整速度、重新开始、退出窗口
4.4 游戏结束
当新方块生成时碰到已有方块(顶部被填满)→ 游戏结束
弹出“游戏结束”提示框,提供:
🔄 重新开始
❌ 退出页面
五、操作说明表
功能 | 操作方式 |
---|---|
移动方块(左右) | ← → 键盘方向键 |
旋转方块 | ↑ 键盘方向键 |
快速下落到底 | ↓ 键盘方向键 |
暂停 / 恢复游戏 | 空格键(Space) |
调整游戏速度 | 菜单滑块 |
重新开始游戏 | 暂停菜单 → 重新开始按钮 |
退出游戏 | 暂停菜单 → 退出按钮 |
六、技术栈
🔸 HTML + CSS + JavaScript
🔸 p5.js 图形库:用于渲染游戏画面
🔸 DOM 控制:实现游戏菜单交互与速度控制
📌 附加建议
若你希望继续扩展功能,可以考虑加入:
⏱️ 倒计时模式 / 挑战模式
🌐 排行榜或最高分记录
🔊 音效与背景音乐
🖱️ 支持鼠标 / 触控操作(移动端支持)