这篇文章将会做弹小球游戏,弹小球游戏大家小时候都玩过,玩家需要在小球到达游戏区域底部时候控制砖块去承接小球,并不断的将小球弹出去。
首先看一下实现的效果。
玩家需要通过控制鼠标来实现砖块的移动,保证在小球下落到底部时接到小球。
游戏区域包括小球和砖块2个部分,小球在来回移动,砖块在底部移动。
import {useEffect, useState} from "react";
const BounceBall = () => {
// 游戏区域配置
const gameArea = {
width: 500,
height: 400
}
// 小球对象
const initBall = {
width: 20, // 小球宽度
height: 20,// 小球高度
posX: 240, // 240 ~ 260
posY: 190, // 190 ~ 210
speedX: 0, // 小球移动速度
speedY: 2 // 小球移动速度
}
const [ball, setBall] = useState(initBall)
// 砖块对象
const initPaddle = {
width: 80, // 砖块宽度
height: 20,// 砖块高度
posX: 210, // 210 ~ 290
posY: 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 (<>
{/*小球*/}
{/*砖块*/}
>)
}
小球移动通过定时器实现,定时器会在小球发生碰撞、游戏失败后清楚。
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 speed
const collisionPoint = (ball.posX + ball.width/2 ) - (paddle.posX + paddle.width/2) ; // 计算碰撞点距离挡板中心点的距离
ball.speedX = collisionPoint * 0.1
ball.speedY = -1 * ball.speedY
setBall({...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 ~ 260
posY: 190, // 190 ~ 210
speedX: 0, // 小球移动速度
speedY: 2 // 小球移动速度
}
const [ball, setBall] = useState(initBall)
// 砖块对象
const initPaddle = {
width: 80, // 砖块宽度
height: 20,// 砖块高度
posX: 210, // 210 ~ 290
posY: 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 speed
const collisionPoint = (ball.posX + ball.width/2 ) - (paddle.posX + paddle.width/2) ; // 计算碰撞点距离挡板中心点的距离
ball.speedX = collisionPoint * 0.1
ball.speedY = -1 * ball.speedY
setBall({...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 (<>
{/*小球*/}
{/*砖块*/}
>)
}
export default BounceBall