这篇文章将会做弹小球游戏,弹小球游戏大家小时候都玩过,玩家需要在小球到达游戏区域底部时候控制砖块去承接小球,并不断的将小球弹出去。
首先看一下实现的效果。
效果演示
玩家需要通过控制鼠标来实现砖块的移动,保证在小球下落到底部时接到小球。
技术实现
html布局
游戏区域包括小球和砖块2个部分,小球在来回移动,砖块在底部移动。
import {useEffect, useState} from "react";const BounceBall = () => {// 游戏区域配置const gameArea = {width: 500,height: 400}// 小球对象const initBall = {width: 20, // 小球宽度height: 20,// 小球高度posX: 240, // 240 ~ 260posY: 190, // 190 ~ 210speedX: 0, // 小球移动速度speedY: 2 // 小球移动速度}const [ball, setBall] = useState(initBall)// 砖块对象const initPaddle = {width: 80, // 砖块宽度height: 20,// 砖块高度posX: 210, // 210 ~ 290posY: 380 // 380 ~ 400}const [paddle, setPaddle] = useState(initPaddle)// 游戏区域样式const gameAreaStyle = {position: "relative", /*相对定位*/width: `${gameArea.width}px`, /*区域宽度*/height: `${gameArea.height}px`, /*区域高度*/backgroundColor: "#333"}// 小球样式const ballStyle = {position: "absolute",/* 绝对定位 */top: `${ball.posY}px`, /*小球位置*/left: `${ball.posX}px`, /*小球位置*/width: `${ball.width}px`,/* 设置小球的宽度 */height: `${ball.height}px`, /* 设置小球的高度 */borderRadius: "50%", /* 设置小球为圆形 */backgroundColor: "red" /* 设置小球的背景颜色 */}// 砖块样式const paddleStyle = {position: 'absolute', /* 绝对定位 */top: `${paddle.posY}px`, /*板块位置*/left: `${paddle.posX}px`, /*板块位置*/width: `${paddle.width}px`, /* 设置砖块的宽度 */height: `${paddle.height}px`, /* 设置砖块的高度 */backgroundColor: "blue", /* 设置砖块的背景颜色 */border: "1px solid black" /* 设置砖块的边框 */}return (<><div style={gameAreaStyle}>{/*小球*/}<div style={ballStyle}></div>{/*砖块*/}<div style={paddleStyle}></div></div></>)
}
游戏控制处理
小球移动处理
小球移动通过定时器实现,定时器会在小球发生碰撞、游戏失败后清楚。
const [failCnt, setFailCnt] = useState(0)
// 定时器控制小球移动
useEffect(()=>{const intervalId = setInterval(()=>{// 小球移動ball.posX += ball.speedX;ball.posY += ball.speedY;setBall({...ball})}, 20)return ()=> {clearInterval(intervalId)} // 返回是一个函数,并非结果},[ball.speedX, ball.speedY, failCnt])
砖块移动处理
砖块移动通过控制鼠标移动实现
// 砖块移动
useEffect(()=>{document.addEventListener('mousemove',(ev => {const mouseX = ev.clientX;let paddlePosX = mouseX - paddle.width/2;if (paddlePosX < 0){paddlePosX = 0;}else if (paddlePosX >= gameArea.width - paddle.width){paddlePosX = gameArea.width - paddle.width;}setPaddle({...paddle,posX: paddlePosX})}))
},[])
小球位置判断
需要考虑边界碰撞、砖块碰撞等场景
// 小球位置判断
useEffect(()=>{// 边界判断if (ball.posX <= 0 || ball.posX >= gameArea.width - ball.width) {ball.speedX = -1 * ball.speedX;setBall({...ball});return;}if (ball.posY <= 0) {ball.speedY = -1 * ball.speedY;setBall({...ball});return;}// 判断是否碰撞到砖块if (ball.posY >= gameArea.height - ball.height - paddle.height) {// 未碰撞砖块if (ball.posX + ball.width <= paddle.posX || ball.posX >= paddle.posX + paddle.width) {alert('游戏失败')setBall({...initBall})setPaddle({...initPaddle})setFailCnt(failCnt + 1)return;}console.log(ball)// 计算小球x speedconst collisionPoint = (ball.posX + ball.width/2 ) - (paddle.posX + paddle.width/2) ; // 计算碰撞点距离挡板中心点的距离ball.speedX = collisionPoint * 0.1ball.speedY = -1 * ball.speedYsetBall({...ball})}
}, [ball.posX, ball.posY])
整体代码
import {useEffect, useState} from "react";const BounceBall = () => {// 游戏区域配置const gameArea = {width: 500,height: 400}// 小球对象const initBall = {width: 20, // 小球宽度height: 20,// 小球高度posX: 240, // 240 ~ 260posY: 190, // 190 ~ 210speedX: 0, // 小球移动速度speedY: 2 // 小球移动速度}const [ball, setBall] = useState(initBall)// 砖块对象const initPaddle = {width: 80, // 砖块宽度height: 20,// 砖块高度posX: 210, // 210 ~ 290posY: 380 // 380 ~ 400}const [paddle, setPaddle] = useState(initPaddle)const [failCnt, setFailCnt] = useState(0)// 定时器控制小球移动useEffect(()=>{const intervalId = setInterval(()=>{// 小球移動ball.posX += ball.speedX;ball.posY += ball.speedY;setBall({...ball})}, 20)return ()=> {clearInterval(intervalId)} // 返回是一个函数,并非结果},[ball.speedX, ball.speedY, failCnt])// 小球位置判断useEffect(()=>{// 边界判断if (ball.posX <= 0 || ball.posX >= gameArea.width - ball.width) {ball.speedX = -1 * ball.speedX;setBall({...ball});return;}if (ball.posY <= 0) {ball.speedY = -1 * ball.speedY;setBall({...ball});return;}// 判断是否碰撞到砖块if (ball.posY >= gameArea.height - ball.height - paddle.height) {// 未碰撞砖块if (ball.posX + ball.width <= paddle.posX || ball.posX >= paddle.posX + paddle.width) {alert('游戏失败')setBall({...initBall})setPaddle({...initPaddle})setFailCnt(failCnt + 1)return;}console.log(ball)// 计算小球x speedconst collisionPoint = (ball.posX + ball.width/2 ) - (paddle.posX + paddle.width/2) ; // 计算碰撞点距离挡板中心点的距离ball.speedX = collisionPoint * 0.1ball.speedY = -1 * ball.speedYsetBall({...ball})}}, [ball.posX, ball.posY])// 砖块移动useEffect(()=>{document.addEventListener('mousemove',(ev => {const mouseX = ev.clientX;let paddlePosX = mouseX - paddle.width/2;if (paddlePosX < 0){paddlePosX = 0;}else if (paddlePosX >= gameArea.width - paddle.width){paddlePosX = gameArea.width - paddle.width;}setPaddle({...paddle,posX: paddlePosX})}))},[])// 游戏区域样式const gameAreaStyle = {position: "relative", /*相对定位*/width: `${gameArea.width}px`, /*区域宽度*/height: `${gameArea.height}px`, /*区域高度*/backgroundColor: "#333"}// 小球样式const ballStyle = {position: "absolute",/* 绝对定位 */top: `${ball.posY}px`, /*小球位置*/left: `${ball.posX}px`, /*小球位置*/width: `${ball.width}px`,/* 设置小球的宽度 */height: `${ball.height}px`, /* 设置小球的高度 */borderRadius: "50%", /* 设置小球为圆形 */backgroundColor: "red" /* 设置小球的背景颜色 */}// 砖块样式const paddleStyle = {position: 'absolute', /* 绝对定位 */top: `${paddle.posY}px`, /*板块位置*/left: `${paddle.posX}px`, /*板块位置*/width: `${paddle.width}px`, /* 设置砖块的宽度 */height: `${paddle.height}px`, /* 设置砖块的高度 */backgroundColor: "blue", /* 设置砖块的背景颜色 */border: "1px solid black" /* 设置砖块的边框 */}return (<><div style={gameAreaStyle}>{/*小球*/}<div style={ballStyle}></div>{/*砖块*/}<div style={paddleStyle}></div></div></>)
}export default BounceBall