Snake Game 使用 ReactJS 项目实现功能组件并相应地管理状态。开发的游戏允许用户使用箭头键控制蛇或触摸屏幕上显示的按钮来收集食物并增长长度。游戏的目标是在不与墙壁或蛇自己的身体碰撞的情况下吃尽可能多的食物。
最终输出预览: 让我们看看我们的最终项目会是什么样子。
给定的代码代表使用 ReactJS 的贪吃蛇游戏项目。它涉及设置蛇、食物、按钮和菜单的组件。游戏以初始状态初始化,处理蛇运动的用户输入,检测碰撞,并相应地更新游戏板。渲染和用户界面的实现是为了显示游戏元素。游戏流程包括菜单和游戏玩法的过渡。
步骤 1: 在 VSCode IDE 中使用以下命令设置 React 项目。
npx create-react-app snack_game
步骤 2: 执行以下命令导航到新创建的项目文件夹。
cd snack_game
步骤 3: 创建一个名为 Components 的文件夹。我们将在此组件文件夹中创建各种组件及其样式文件,例如 Button.js、Food.js、Menu.js、Snake.js、Menu.css 和 Button.css。
package.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
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 */
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
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
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
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
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 */
.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 */
.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;
}
npm start
http://localhost:3000/
http://localhost:3000/