在 React 中创建贪吃蛇游戏
Snake Game 使用 ReactJS 项目实现功能组件并相应地管理状态。开发的游戏允许用户使用箭头键控制蛇或触摸屏幕上显示的按钮来收集食物并增长长度。游戏的目标是在不与墙壁或蛇自己的身体碰撞的情况下吃尽可能多的食物。
最终输出预览: 让我们看看我们的最终项目会是什么样子。
创建贪吃蛇游戏的方法:
给定的代码代表使用 ReactJS
的贪吃蛇游戏项目。它涉及设置蛇、食物、按钮和菜单的组件。游戏以初始状态初始化,处理蛇运动的用户输入,检测碰撞,并相应地更新游戏板。渲染和用户界面的实现是为了显示游戏元素。游戏流程包括菜单和游戏玩法的过渡。
创建贪吃蛇游戏的步骤:
步骤 1: 在 VSCode IDE 中使用以下命令设置 React 项目。
lua
npx create-react-app snack_game
步骤 2: 执行以下命令导航到新创建的项目文件夹。
bash
cd snack_game
步骤 3: 创建一个名为 Components 的文件夹。我们将在此组件文件夹中创建各种组件及其样式文件,例如 Button.js、Food.js、Menu.js、Snake.js、Menu.css 和 Button.css。
贪吃蛇游戏的项目结构:
package.json中更新后的依赖项将如下所示:
json
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
示例: 在上述目录结构中提到的App.js
和 index.css
文件中插入以下代码
App.js
js
// App.js
import React, { Component } from "react";
import Snake from "./Components/Snake";
import Food from "./Components/Food";
import Button from "./Components/Button";
import Menu from "./Components/Menu";
import "./App.css";
const getRandomFood = () => {
let min = 1;
let max = 98;
let x = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
let y = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
return [x, y];
};
const initialState = {
food: getRandomFood(),
direction: "RIGHT",
speed: 100,
route: "menu",
snakeDots: [
[0, 0],
[0, 2],
],
};
class App extends Component {
constructor() {
super();
this.state = initialState;
}
componentDidMount() {
setInterval(this.moveSnake, this.state.speed);
document.onkeydown = this.onKeyDown;
}
componentDidUpdate() {
this.onSnakeOutOfBounds();
this.onSnakeCollapsed();
this.onSnakeEats();
}
onKeyDown = (e) => {
e.preventDefault();
e = e || window.event;
switch (e.keyCode) {
case 37:
this.setState({ direction: "LEFT" });
break;
case 38:
this.setState({ direction: "UP" });
break;
case 39:
this.setState({ direction: "RIGHT" });
break;
case 40:
this.setState({ direction: "DOWN" });
break;
}
};
moveSnake = () => {
let dots = [...this.state.snakeDots];
let head = dots[dots.length - 1];
if (this.state.route === "game") {
switch (this.state.direction) {
case "RIGHT":
head = [head[0] + 2, head[1]];
break;
case "LEFT":
head = [head[0] - 2, head[1]];
break;
case "DOWN":
head = [head[0], head[1] + 2];
break;
case "UP":
head = [head[0], head[1] - 2];
break;
}
dots.push(head);
dots.shift();
this.setState({
snakeDots: dots,
});
}
};
onSnakeOutOfBounds() {
let head = this.state.snakeDots[this.state.snakeDots.length - 1];
if (this.state.route === "game") {
if (
head[0] >= 100 ||
head[1] >= 100 ||
head[0] < 0 ||
head[1] < 0
) {
this.gameOver();
}
}
}
onSnakeCollapsed() {
let snake = [...this.state.snakeDots];
let head = snake[snake.length - 1];
snake.pop();
snake.forEach((dot) => {
if (head[0] == dot[0] && head[1] == dot[1]) {
this.gameOver();
}
});
}
onSnakeEats() {
let head = this.state.snakeDots[this.state.snakeDots.length - 1];
let food = this.state.food;
if (head[0] == food[0] && head[1] == food[1]) {
this.setState({
food: getRandomFood(),
});
this.increaseSnake();
this.increaseSpeed();
}
}
increaseSnake() {
let newSnake = [...this.state.snakeDots];
newSnake.unshift([]);
this.setState({
snakeDots: newSnake,
});
}
increaseSpeed() {
if (this.state.speed > 10) {
this.setState({
speed: this.state.speed - 20,
});
}
}
onRouteChange = () => {
this.setState({
route: "game",
});
};
gameOver() {
alert(`GAME OVER, your score is ${this.state.snakeDots.length - 2}`);
this.setState(initialState);
}
onDown = () => {
let dots = [...this.state.snakeDots];
let head = dots[dots.length - 1];
head = [head[0], head[1] + 2];
dots.push(head);
dots.shift();
this.setState({
direction: "DOWN",
snakeDots: dots,
});
};
onUp = () => {
let dots = [...this.state.snakeDots];
let head = dots[dots.length - 1];
head = [head[0], head[1] - 2];
dots.push(head);
dots.shift();
this.setState({
direction: "UP",
snakeDots: dots,
});
};
onRight = () => {
let dots = [...this.state.snakeDots];
let head = dots[dots.length - 1];
head = [head[0] + 2, head[1]];
dots.push(head);
dots.shift();
this.setState({
direction: "RIGHT",
snakeDots: dots,
});
};
onLeft = () => {
let dots = [...this.state.snakeDots];
let head = dots[dots.length - 1];
head = [head[0] - 2, head[1]];
dots.push(head);
dots.shift();
this.setState({
direction: "LEFT",
snakeDots: dots,
});
};
render() {
const { route, snakeDots, food } = this.state;
return (
<div>
{route === "menu" ? (
<div>
<Menu onRouteChange={this.onRouteChange} />
</div>
) : (
<div>
<div className="game-area">
<Snake snakeDots={snakeDots} />
<Food dot={food} />
</div>
<Button
onDown={this.onDown}
onLeft={this.onLeft}
onRight={this.onRight}
onUp={this.onUp}
/>
</div>
)}
</div>
);
}
}
export default App;
index.css
css
/* index.css */
body {
background-color: #1e1e1e;
}
.game-area {
position: relative;
width: 600px;
height: 500px;
border: 2px solid #dc042c;
border-radius: 10px;
margin: 50px auto;
display: flex;
flex-wrap: wrap;
box-shadow: 0 0 10px #abbfc0;
}
@media only screen and (max-width: 800px) {
.game-area {
position: relative;
width: 350px;
height: 300px;
}
.snake {
width: 12px;
height: 12px;
}
}
.snake {
position: absolute;
width: 2%;
height: 2%;
background-color: #dc042c;
border: 1px solid white;
z-index: 2;
}
.food {
position: absolute;
width: 12px;
height: 12px;
background-color: white;
border-radius: 20px;
z-index: 1;
}
在不同的文件中编写以下提到的代码(每个代码块的第一行都提到了文件名)
- Button.js: Button.js表示React功能组件,用于在snake游戏中渲染控制蛇移动的按钮。
- Menu.js: Menu.js文件代码为Snake Game呈现菜单。它显示一个"开始游戏"按钮,并在单击时触发onRouteChange功能。菜单的样式使用"menu.CSS"文件中的CSS
- Food.js 是一个React组件,它根据提供的坐标在游戏中呈现食物。
- Snake.js:Snake.js文件代码是一个React组件,它在游戏中基于表示蛇点的坐标数组来渲染蛇。
//Button.js
js
//Button.js
import React from "react";
import "./Button.css";
const Button = ({ onUp, onDown, onLeft, onRight }) => {
return (
<div className="buttons">
<div className="upwards">
<input className="up" onClick={onUp} type="button" value="UP" />
</div>
<div className="sideways">
<input
className="left"
onClick={onLeft}
type="button"
value="LEFT"
/>
<input
className="right"
onClick={onRight}
type="button"
value="RIGHT"
/>
</div>
<div className="downwards">
<input
className="down"
onClick={onDown}
type="button"
value="DOWN"
/>
</div>
</div>
);
};
export default Button;
Food.js
js
//Food.js
import React from "react";
const Food = (props) => {
const style = {
left: `${props.dot[0]}%`,
top: `${props.dot[1]}%`,
};
return <div className="food" style={style} />;
};
export default Food;
Menu.js
js
//Menu.js
import React from "react";
import "./Menu.css";
const Menu = ({ onRouteChange }) => {
return (
<div className="wrapper">
<div>
<input
onClick={onRouteChange}
className="start"
type="button"
value="start game"
/>
</div>
</div>
);
};
export default Menu;
Snake.js
js
//Snake.js
import React from "react";
const Snake = (props) => {
return (
<div>
{props.snakeDots.map((dot, i) => {
const style = {
left: `${dot[0]}%`,
top: `${dot[1]}%`,
};
return <div className="snake" key={i} style={style} />;
})}
</div>
);
};
export default Snake;
Button.css
css
/* Button.css */
.upwards,
.downwards {
display: flex;
justify-content: center;
}
.sideways {
display: flex;
justify-content: center;
margin: 10px;
}
.left,
.right {
margin: 50px;
padding: 20px;
border: 0px solid;
border-radius: 20px;
border: 1px solid white;
color: #dc042c;
background: #1e1e1e;
box-shadow: 5px -5px 10px rgba(0, 0, 0, 0.6);
}
.up,
.down {
padding: 20px;
border: 0px solid;
border-radius: 20px;
border: 1px solid white;
color: #dc042c;
background: #1e1e1e;
box-shadow: 5px -5px 10px rgba(0, 0, 0, 0.6);
}
Menu.css
css
/* Menu.css */
.wrapper {
position: relative;
width: 200px;
height: 250px;
border: 2px solid #dc042c;
/* border-radius: 10px; */
margin: 50px auto;
margin-top: 200px;
display: flex;
flex-wrap: wrap;
justify-content: center;
/* box-shadow: 0 0 10px #abbfc0; */
}
.start {
margin: 100px;
background: #1e1e1e;
color: white;
border-radius: 7px;
border: 0px;
padding: 10px;
font-size: 1.2em;
box-shadow: 0 0 70px #abbfc0;
font-family: "Courier New", Courier, monospace;
}
启动我们的程序:
Step 1: 在命令行执行一下命令启动项目程序
shell
npm start
Step 2: 在浏览器地址中打开一下地址 http://localhost:3000/
shell
http://localhost:3000/