【taro react】(游戏) ---- 贪吃蛇

1. 预览

输入图片说明

2. 实现思路

  1. 实现食物类,食物坐标和刷新食物的位置,以及获取食物的坐标点;
  2. 实现计分面板类,实现吃食物每次的计分以及积累一定程度的等级,实现等级和分数的增加;
  3. 实现蛇类,蛇类分为蛇头和蛇身,蛇头有方向,蛇身每一节跟着前一节移动;
  4. 实现控制器类,初始化上边实现的各个类,同时绑定键盘事件,实现方向的修改;
  5. 使用 requestAnimationFrame 实现页面刷新,蛇移动,坐标点的更新。

3. 食物类

  1. 接口 Point 的实现,用于返回食物的坐标点;
  2. maxX 和 maxY 用于记录横向和纵向的最大坐标值;
  3. 实现 change 实现食物的刷新;
    3.1 传入当前蛇的坐标点,用于判断刷新的点是否是蛇中间的坐标点;
    3.2 使用 Math.round 和 Math.random 生成一个食物坐标点 (x,y);
    3.3 过滤 snakes 蛇坐标点,看看该点是否存在蛇中;
    3.4 如果存在,continue 跳出该次循环,继续生成新的坐标,走3.2,3.3流程;
    3.5 不存在,就保存该坐标点,跳出循环,完成此次坐标的生成。
  4. 实现 getPoints,获取最新的食物坐标。
interface Point{x: number,y: number,
}class Food{// 食物坐标x: number;y: number;// 格子的最大数量maxX: number;maxY: number;constructor(maxX: number = 35, maxY: number = 35){this.maxX = maxX;this.maxY = maxY;}// 修改食物的坐标change(snakes: Point[] = []){while(true){let x = Math.round(Math.random() * (this.maxX - 1));let y = Math.round(Math.random() * (this.maxY - 1));let filters = snakes.filter(item => {if(item.x === x && item.y === y){return item;}})if(filters.length){continue}this.x = x;this.y = y;break}}// 获取食物坐标getPoints(): Point{return {x: this.x,y: this.y}}
}export default Food;

4. 计分面板类

  1. 初始化分数、等级、最大等级、每次升级所需要的分数;
  2. 分数增长 addScore 实现:
    2.1 分数自增 ++this.score;
    2.2 判断自增后的分数是否满足升级条件 this.score % this.upLevelScore === 0;
    2.3 满足升级条件,调用升级方法 addLevel;
  3. 等级增加 addLevel 实现:
    3.1 判断当前等级是否小于最高等级;
    3.2 满足条件,进行等级升级;
  4. 实现当前等级和分数的获取 getScoreAndLevel 方法实现。
class ScorePanel{score: number = 0;level: number = 1;// 最大等级maxLevel: number;// 多少分升一级upLevelScore: number;constructor(maxLevel: number = 10, upLevelScore: number = 10){this.maxLevel = maxLevel;this.upLevelScore = upLevelScore;}// 分数增加addScore(){++this.score// 满足升级条件,升级if(this.score % this.upLevelScore === 0){this.addLevel()}}// 等级增加addLevel(){if(this.level < this.maxLevel){++this.level}}// 获取当前分数和等级getScoreAndLevel(){return {score: this.score,level: this.level}}
}
export default ScorePanel;

5. 蛇类

5.1 全局变量

  1. 由于蛇是一个列表,因此需要一个key的id,因此 generateId 作为id生成器;
  2. DIRECTION_RIGHT、DIRECTION_DOWN、DIRECTION_LEFT、DIRECTION_UP常量定义;
  3. 接口 Point 实现,id参数在食物时不存在,因此可以不存在。
import { generateId } from './utils';
export const DIRECTION_RIGHT = 0;
export const DIRECTION_DOWN = 1;
export const DIRECTION_LEFT = 2;
export const DIRECTION_UP = 3;interface Point{x: number,y: number,id?: string,
}

5.2 蛇头类

  1. 初始化蛇头坐标(x,y),方向 direction,唯一标识key的id;
  2. 蛇头移动函数move的实现:
    2.1 方向向右【DIRECTION_RIGHT】,x坐标自增1;
    2.2 方向向下【DIRECTION_DOWN】,y坐标自增1;
    2.3 方向向左【DIRECTION_LEFT】,x坐标自减1;
    2.4 方向向上【DIRECTION_UP】,y坐标自减1。
  3. 修改方向更新 setDirection
    3.1 传入方向和当前方向,相同,对方向不做处理;
    3.2 方向相反不做处理,比如当前方向向右,传入方向向左,此次方向不允许改变。
// 蛇头
export class SnakeHead{x: number;y: number;direction: number;id: string;constructor(direction:number, x:number, y:number){this.direction = direction || DIRECTION_RIGHT;this.x = x;this.y = y;this.id = generateId()}move(){if(this.direction == DIRECTION_RIGHT){// 向右this.x += 1} else if(this.direction == DIRECTION_DOWN){// 向下this.y += 1} else if(this.direction == DIRECTION_LEFT){// 向左this.x -= 1} else if(this.direction == DIRECTION_UP){// 向上this.y -= 1}}// 修改移动方向setDirection(direction:number){if(this.direction === direction){return;} else {if(this.direction === DIRECTION_RIGHT && direction !== DIRECTION_LEFT ||this.direction === DIRECTION_DOWN && direction !== DIRECTION_UP ||this.direction === DIRECTION_LEFT && direction !== DIRECTION_RIGHT ||this.direction === DIRECTION_UP && direction !== DIRECTION_DOWN){this.direction = direction}}}
}

5.3 蛇身类

  1. 初始化每节蛇身的坐标点和唯一标识;
  2. 实现蛇身的移动,当前坐标点是前移节的坐标。
// 蛇身
export class SnakeBody{x: number;y: number;id: string;constructor(x:number, y:number){this.x = x;this.y = y;this.id = generateId()}// 将当前位置的模块移动到前一个模块的位置move(prevItem: (SnakeHead | SnakeBody)){if(prevItem){this.x = prevItem.xthis.y = prevItem.y}}
}

5.4 蛇类

  1. 初始化蛇列表、蛇头、蛇身最后一节、每次吃食物蛇身的增长长度、记录当前次吃食物身体增长的长度、蛇能存在的最大坐标;
  2. 初始化蛇:
    2.1 创建蛇的坐标点列表;
    2.2 创建蛇头函数执行;
    2.3 创建蛇身函数执行。
  3. 修改蛇的行进方向改变实现 setDirection,直接调用蛇头的修改方向方法;
  4. 获取两个数之间的随机值 random 方法实现;
  5. 创建蛇头函数 createHead 实现:
    5.1 获取坐标随机值的最大值maxX的一半;
    5.2 由于初始化蛇身最少三节,因此对最小值处理必须大于0;
    5.3 创建蛇头 snakeHead;
    5.4 将蛇头对象存放到蛇的列表中。
  6. 蛇身 createBodies 实现:
    6.1 蛇身最少三节,因此循环产生每一节蛇身;
    6.2 创建每一节蛇身,对y坐标,不做处理,使用蛇头的x坐标一次递减。
  7. 蛇移动函数 move 实现:
    7.1 获取移动前左后一节蛇身对象;
    7.2 记录最后一节蛇身对象的坐标用于吃食物后身体的增长;
    7.3 由于移动防止脱节,因此从最后一节一次往前一节循环移动;
    7.4 由于蛇身的移动需要获取前一节蛇身的坐标,因此移动时,传入前一节蛇身对象;
    7.5 移动完成,检测当前移动后蛇头是否碰撞墙或者撞到自身。
  8. 撞墙检测函数 hasOver 实现:
    8.1 获取蛇头坐标,看看是否超出盒子的x,y的范围;
  9. 撞自身检测函数 hasSelf实现:
    9.1 因为蛇头不可能撞到自己蛇头,因此去掉蛇头,从1开始遍历蛇身坐标;
    9.2 如果存在有蛇身坐标和蛇头坐标相同,说明撞到自己,结束游戏。
  10. 吃掉食物判断函数 eatFood:
    10.1 传入当前食物的坐标值;
    10.2 判断食物坐标和蛇头坐标是否重合;
    10.3 更新增长身体增长变量;
    10.4 返回吃到食物未 true;
    10.5 否则说明没有吃到食物,返回 false。
  11. 获取最新蛇的全部坐标点函数 getPoints:
    11.1 返回蛇的坐标点和唯一标识id。
  12. 设置每次吃食物蛇的增长长度 setUpdateBodyNumber 函数实现;
  13. 实现蛇的增长函数 updateBody:
    13.1 判断 currentUpdateBody > 0 是否满足;
    13.2 添加一个最新的蛇身对象,坐标是移动前最后一节的坐标;
    13.3 添加完成,增长记录变量自减1。
class Snake{// 完整蛇列表snakes: (SnakeHead | SnakeBody)[]// 蛇头snakeHead: SnakeHead;// 蛇身最后一节snakeLastBody: Point;// 每次食物身体增长长度updateBodyNumber: number;// 当前次身体增长的值currentUpdateBody: number;// 格子的最大数量maxX: number;maxY: number;constructor(maxX: number = 35, maxY: number = 35, updateBodyNumber: number = 3){this.maxX = maxX;this.maxY = maxY;this.updateBodyNumber = updateBodyNumber;this.currentUpdateBody = 0;// 初始化蛇this.init()}// 初始化蛇init(){this.snakes = []// 创建蛇头this.createHead()// 创建蛇身this.createBodies()}setDirection(direction: number){this.snakeHead.setDirection(direction)}random(n:number,m:number):number{return Math.round(Math.random() * (m - n) + n)}// 创建蛇头createHead(){let max = Math.round(this.maxX / 2);let min = max - 5 > 0 ? max - 5 : 0;this.snakeHead = new SnakeHead(DIRECTION_RIGHT, this.random(min,max), this.random(min,max))this.snakes.push(this.snakeHead)}// 创建蛇身createBodies(){for(let i = 1; i <= 3; i++){this.snakes.push(new SnakeBody(this.snakeHead.x - i, this.snakeHead.y))}}// 移动函数move(){// 移动前记录蛇身最后一节的坐标let last = this.snakes.at(-1) as SnakeBodythis.snakeLastBody = { x: last.x, y: last.y }let len:number = this.snakes.length;for(let i = len - 1; i >= 0; i--){this.snakes[i].move(this.snakes[i - 1])}// 移动完成,检测是否撞到墙和自身this.hasOver()this.hasSelf()}// 判断是否撞墙或者撞到自身hasOver(){// 获取蛇头let head:SnakeHead = this.snakeHeadif(head.x < 0 || head.x >= this.maxX || head.y < 0 || head.y >= this.maxY){throw('撞墙了!')}}// 判断是否撞到蛇自身hasSelf(){// 获取蛇头let head:SnakeHead = this.snakeHeadlet len = this.snakes.lengthfor(let i = 1; i < len; i++){let item = this.snakes[i]if(head.x === item.x && head.y === item.y){throw('撞到自己了!')}}}// 是否吃掉食物eatFood(food: Point): boolean{// 获取蛇头let head:SnakeHead = this.snakeHeadif(head.x === food.x && head.y === food.y){this.currentUpdateBody = this.updateBodyNumber;return true}return false}// 获取最新坐标点列表getPoints(): Point[]{let snakes: Point[] = this.snakes.map(item => {return {x: item.x,y: item.y,id: item.id}})return snakes}// 设置身体增长的长度setUpdateBodyNumber(bodyNumber:number){this.updateBodyNumber = bodyNumber;}// 身体增长updateBody(){if(this.currentUpdateBody > 0){this.snakes.push(new SnakeBody(this.snakeLastBody.x, this.snakeLastBody.y))this.currentUpdateBody--}}
}export default Snake;

5.5 完整蛇类代码

import { generateId } from './utils';
export const DIRECTION_RIGHT = 0;
export const DIRECTION_DOWN = 1;
export const DIRECTION_LEFT = 2;
export const DIRECTION_UP = 3;interface Point{x: number,y: number,id?: string,
}// 蛇头
export class SnakeHead{x: number;y: number;direction: number;id: string;constructor(direction:number, x:number, y:number){this.direction = direction || DIRECTION_RIGHT;this.x = x;this.y = y;this.id = generateId()}move(){if(this.direction == DIRECTION_RIGHT){// 向右this.x += 1} else if(this.direction == DIRECTION_DOWN){// 向下this.y += 1} else if(this.direction == DIRECTION_LEFT){// 向左this.x -= 1} else if(this.direction == DIRECTION_UP){// 向上this.y -= 1}}// 修改移动方向setDirection(direction:number){if(this.direction === direction){return;} else {if(this.direction === DIRECTION_RIGHT && direction !== DIRECTION_LEFT ||this.direction === DIRECTION_DOWN && direction !== DIRECTION_UP ||this.direction === DIRECTION_LEFT && direction !== DIRECTION_RIGHT ||this.direction === DIRECTION_UP && direction !== DIRECTION_DOWN){this.direction = direction}}}
}
// 蛇身
export class SnakeBody{x: number;y: number;id: string;constructor(x:number, y:number){this.x = x;this.y = y;this.id = generateId()}// 将当前位置的模块移动到前一个模块的位置move(prevItem: (SnakeHead | SnakeBody)){if(prevItem){this.x = prevItem.xthis.y = prevItem.y}}
}class Snake{// 完整蛇列表snakes: (SnakeHead | SnakeBody)[]// 蛇头snakeHead: SnakeHead;// 蛇身最后一节snakeLastBody: Point;// 每次食物身体增长长度updateBodyNumber: number;// 当前次身体增长的值currentUpdateBody: number;// 格子的最大数量maxX: number;maxY: number;constructor(maxX: number = 35, maxY: number = 35, updateBodyNumber: number = 3){this.maxX = maxX;this.maxY = maxY;this.updateBodyNumber = updateBodyNumber;this.currentUpdateBody = 0;// 初始化蛇this.init()}// 初始化蛇init(){this.snakes = []// 创建蛇头this.createHead()// 创建蛇身this.createBodies()}setDirection(direction: number){this.snakeHead.setDirection(direction)}random(n:number,m:number):number{return Math.round(Math.random() * (m - n) + n)}// 创建蛇头createHead(){let max = Math.round(this.maxX / 2);let min = max - 5 > 0 ? max - 5 : 0;this.snakeHead = new SnakeHead(DIRECTION_RIGHT, this.random(min,max), this.random(min,max))this.snakes.push(this.snakeHead)}// 创建蛇身createBodies(){for(let i = 1; i <= 3; i++){this.snakes.push(new SnakeBody(this.snakeHead.x - i, this.snakeHead.y))}}// 移动函数move(){// 移动前记录蛇身最后一节的坐标let last = this.snakes.at(-1) as SnakeBodythis.snakeLastBody = { x: last.x, y: last.y }let len:number = this.snakes.length;for(let i = len - 1; i >= 0; i--){this.snakes[i].move(this.snakes[i - 1])}// 移动完成,检测是否撞到墙和自身this.hasOver()this.hasSelf()}// 判断是否撞墙或者撞到自身hasOver(){// 获取蛇头let head:SnakeHead = this.snakeHeadif(head.x < 0 || head.x >= this.maxX || head.y < 0 || head.y >= this.maxY){throw('撞墙了!')}}// 判断是否撞到蛇自身hasSelf(){// 获取蛇头let head:SnakeHead = this.snakeHeadlet len = this.snakes.lengthfor(let i = 1; i < len; i++){let item = this.snakes[i]if(head.x === item.x && head.y === item.y){throw('撞到自己了!')}}}// 是否吃掉食物eatFood(food: Point): boolean{// 获取蛇头let head:SnakeHead = this.snakeHeadif(head.x === food.x && head.y === food.y){this.currentUpdateBody = this.updateBodyNumber;return true}return false}// 获取最新坐标点列表getPoints(): Point[]{let snakes: Point[] = this.snakes.map(item => {return {x: item.x,y: item.y,id: item.id}})return snakes}// 设置身体增长的长度setUpdateBodyNumber(bodyNumber:number){this.updateBodyNumber = bodyNumber;}// 身体增长updateBody(){if(this.currentUpdateBody > 0){this.snakes.push(new SnakeBody(this.snakeLastBody.x, this.snakeLastBody.y))this.currentUpdateBody--}}
}export default Snake;

6. 控制器类

  1. 初始化食物、蛇、计分面板;
  2. 获取初始化时蛇的坐标列表;
  3. 调用食物刷新方法,刷新食物坐标;
  4. 绑定键盘事件:
    4.1 key 是 ArrowDown,调用 this.snake.setDirection(DIRECTION_DOWN);
    4.2 key 是 ArrowLeft,调用 this.snake.setDirection(DIRECTION_LEFT);
    4.3 key 是 ArrowUp,调用 this.snake.setDirection(DIRECTION_UP);
    4.4 key 是 ArrowRight,调用 this.snake.setDirection(DIRECTION_RIGHT)。
import Food from "./Food";
import Snake, {DIRECTION_RIGHT,DIRECTION_LEFT,DIRECTION_UP,DIRECTION_DOWN,
} from "./Snake";
import ScorePanel from './ScorePanel';interface Point{x: number,y: number
}class GameContral{// 食物food: Food;// 蛇snake: Snake;// 计分面板scorePanel: ScorePanel;// 格子数cell: number;constructor(cell: number = 15){this.cell = cell;// 初始化this.init()}// 初始化init(){// 初始化蛇this.snake = new Snake(this.cell, this.cell);// 初始化食物this.food = new Food(this.cell, this.cell);// 初始化食物位置let points: Point[] = this.snake.getPoints()this.food.change(points)// 初始化计分面板this.scorePanel = new ScorePanel();// 绑定键盘事件document.addEventListener('keydown', this.keyboardHandler.bind(this))}// 键盘操作keyboardHandler(event: KeyboardEvent){let key:string = event.key;switch(key){case 'ArrowDown':this.snake.setDirection(DIRECTION_DOWN)break;case 'ArrowLeft':this.snake.setDirection(DIRECTION_LEFT)break;case 'ArrowUp':this.snake.setDirection(DIRECTION_UP)break;case 'ArrowRight':this.snake.setDirection(DIRECTION_RIGHT)break;default:this.snake.setDirection(DIRECTION_RIGHT)break;}}
}export default GameContral;

7. 界面实现

7.1 接口实现

  1. 坐标点接口 Point、计分和等级接口 Score、页面渲染数据接口 State、逻辑处理接口 RefData 实现;
interface Point{x: number,y: number,id?: string,
}
interface Score{score: number,level: number
}interface State{isStart: boolean,snakes: Point[],food: Point,cells: number,scorePanel: Score
}
interface RefData{gameContral: GameContral,food: Food,snake: Snake,isFirst: boolean
}

7.2 逻辑变量和渲染变量初始化

  1. foodAndSnake 存储食物类对象、蛇对象、控制器对象,是否第一次执行等做逻辑处理,不需要界面渲染;
  2. data 存储是否开始,蛇身坐标列表、食物坐标、计分面板,用于页面渲染,数据都是重新筛选后,不存在多于数据。
  let foodAndSnake = useRef<RefData>({gameContral: new GameContral(),food: new Food(),snake: new Snake(),isFirst: true})// 格子数量let [data, setData] = useSetState<State>({isStart: false,snakes: [],food: {x: 0, y: 0},cells: 30,scorePanel: {score: 0, level: 1}})

7.3 初始化

  1. useEffect 监听 onmount 的时候,初始化页面;
  2. init 初始化各个类对象:
    2.1 初始化 GameContral 游戏控制器类;
    2.2 赋值食物类对象;
    2.3 赋值蛇类对象;
  3. 更新食物坐标、蛇坐标、计分面板
    3.1 获取最新的蛇坐标列表;
    3.2 获取最新的食物坐标;
    3.3 获取最新的计分面板信息。
  // 初始化食物和蛇得数据useEffect(() => {init()},[])// 初始化function init(){let gameContral = new GameContral(30)foodAndSnake.current.gameContral = gameContral;foodAndSnake.current.food = gameContral.food;foodAndSnake.current.snake = gameContral.snake;setPoints()}// 获取食物和蛇得坐标function setPoints(){// 获取蛇的最新坐标let snakes: Point[] = foodAndSnake.current.snake.getPoints()// 获取食物的最新坐标let food: Point = foodAndSnake.current.food.getPoints()// 获取计分面板的最新分数和等级let scorePanel: Score = foodAndSnake.current.gameContral.scorePanel.getScoreAndLevel()setData({ snakes, food, scorePanel })}

7.4 监听动画实现

  1. 对蛇进行移动,调用蛇对象的移动函数;
  2. 调用蛇对象的 eatFood 判断是否吃食物;
  3. 吃了食物,进行刷新;
  4. 如果吃了食物,进行加分;
  5. 更新身体长度;
  6. 更新蛇坐标列表,食物坐标,计分面板信息;
  7. 如果上边流程出现异常报错,直接结束游戏,游戏结束的逻辑在异常处处理;
  8. 随着等级的提升,速度越来越快。【300 - (data.scorePanel.level - 1) * 30】
  // 监听刷新界面useAnimationFrame(() => {try {// 对蛇进行移动foodAndSnake.current.snake.move()// 判断是否吃食物let isEating: boolean = foodAndSnake.current.snake.eatFood(data.food)if(isEating){// 吃了食物,进行刷新foodAndSnake.current.food.change(foodAndSnake.current.snake.getPoints())// 如果吃了食物,进行加分foodAndSnake.current.gameContral.scorePanel.addScore()}// 更新身体长度foodAndSnake.current.snake.updateBody()setPoints()} catch (error) {setData({isStart: false})console.log('error',error)}},data.isStart,{delay: 300 - (data.scorePanel.level - 1) * 30})

7.5 useAnimationFrame 实现

import { useMemoizedFn } from '../useMemoizedFn'
import { useRef, useCallback, useEffect } from 'react'
import { isObject, isNumber } from '../utils'interface Options {immediate?: boolean,delay: number
}export function useAnimationFrame(fn: () => void, running?: boolean, options: Options = {delay: 0}){const timerCallback = useMemoizedFn(fn)const requestId = useRef<number>(0)const handleTime = useRef<number>(Date.now())const clear = useCallback(() => {if (requestId.current) {cancelAnimationFrame(requestId.current)}}, []);const tick = useCallback(() => {if(isObject(options) && isNumber(options.delay) && options.delay){let current = Date.now()if(current - handleTime.current > options.delay){handleTime.current = currenttimerCallback()}} else {timerCallback()}if(running){requestId.current = requestAnimationFrame(tick)}},[running, options.delay])useEffect(() => {if (!running) {return}handleTime.current = Date.now()requestId.current = requestAnimationFrame(tick)return clear}, [running, options.delay])return clear
}

7.6 蛇和食物的绘制

  1. 不同屏幕的计算函数 handleSize;
  2. 食物绘制函数 drawFood 实现,通过食物坐标进行定位;
  3. 蛇列表绘制函数 drawSnake 实现,根据蛇列表循环计算坐标点。
  // 计算对应屏幕的尺寸function handleSize(size: number):number{let windowWidth = window.innerWidth > 750 ? 750 : window.innerWidth;return (windowWidth / 750) * size;}// 绘制食物function drawFood():JSX.Element{return <View style={`top:${handleSize(data.food.y * 20)}px;left:${handleSize(data.food.x * 20)}px;`}className='rui-snake-food'></View>}// 绘制蛇function drawSnake():JSX.Element[]{return data.snakes.map(item => <View key={item.id}id={item.id}style={`top:${handleSize(item.y * 20)}px;left:${handleSize(item.x * 20)}px;`}className='rui-snake-body'></View>)}

7.7 界面布局

    <View className='rui-snake-container'><View className='rui-stage-content'>{/* 食物 */}{drawFood()}{/* 蛇 */}{drawSnake()}</View><View className='rui-score-panel'><View>SCORE: <Text>{data.scorePanel.score}</Text></View><View>LEVEL: <Text>{data.scorePanel.level}</Text></View></View><View className='rui-handle-panel'><View onClick={resetStart}>开始</View></View></View>

7.8 界面完整代码

import { View, Text } from '@tarojs/components';
import React, { useEffect, useRef } from "react";
import { useAnimationFrame, useSetState } from '../../utils/hooks'
import Food from './modules/Food'
import Snake from './modules/Snake'
import GameContral from './modules/GameContral'
import './index.scss';interface Point{x: number,y: number,id?: string,
}
interface Score{score: number,level: number
}interface State{isStart: boolean,snakes: Point[],food: Point,cells: number,scorePanel: Score
}
interface RefData{gameContral: GameContral,food: Food,snake: Snake,isFirst: boolean
}const SnakeGame = () => {let foodAndSnake = useRef<RefData>({gameContral: new GameContral(),food: new Food(),snake: new Snake(),isFirst: true})// 格子数量let [data, setData] = useSetState<State>({isStart: false,snakes: [],food: {x: 0, y: 0},cells: 30,scorePanel: {score: 0, level: 1}})// 初始化食物和蛇得数据useEffect(() => {init()},[])// 监听刷新界面useAnimationFrame(() => {try {// 对蛇进行移动foodAndSnake.current.snake.move()// 判断是否吃食物let isEating: boolean = foodAndSnake.current.snake.eatFood(data.food)if(isEating){// 吃了食物,进行刷新foodAndSnake.current.food.change(foodAndSnake.current.snake.getPoints())// 如果吃了食物,进行加分foodAndSnake.current.gameContral.scorePanel.addScore()}// 更新身体长度foodAndSnake.current.snake.updateBody()setPoints()} catch (error) {setData({isStart: false})console.log('error',error)}},data.isStart,{delay: 300 - (data.scorePanel.level - 1) * 30})// 重新开始function resetStart(){// 是否是第一次点击开始,是就不进行初始化,否则初始化面板if(foodAndSnake.current.isFirst){foodAndSnake.current.isFirst = false} else {init()}setData({isStart: true})}// 初始化function init(){let gameContral = new GameContral(30)foodAndSnake.current.gameContral = gameContral;foodAndSnake.current.food = gameContral.food;foodAndSnake.current.snake = gameContral.snake;setPoints()}// 获取食物和蛇得坐标function setPoints(){// 获取蛇的最新坐标let snakes: Point[] = foodAndSnake.current.snake.getPoints()// 获取食物的最新坐标let food: Point = foodAndSnake.current.food.getPoints()// 获取计分面板的最新分数和等级let scorePanel: Score = foodAndSnake.current.gameContral.scorePanel.getScoreAndLevel()setData({ snakes, food, scorePanel })}// 计算对应屏幕的尺寸function handleSize(size: number):number{let windowWidth = window.innerWidth > 750 ? 750 : window.innerWidth;return (windowWidth / 750) * size;}// 绘制食物function drawFood():JSX.Element{return <View style={`top:${handleSize(data.food.y * 20)}px;left:${handleSize(data.food.x * 20)}px;`}className='rui-snake-food'></View>}// 绘制蛇function drawSnake():JSX.Element[]{return data.snakes.map(item => <View key={item.id}id={item.id}style={`top:${handleSize(item.y * 20)}px;left:${handleSize(item.x * 20)}px;`}className='rui-snake-body'></View>)}return (<View className='rui-snake-container'><View className='rui-stage-content'>{/* 食物 */}{drawFood()}{/* 蛇 */}{drawSnake()}</View><View className='rui-score-panel'><View>SCORE: <Text>{data.scorePanel.score}</Text></View><View>LEVEL: <Text>{data.scorePanel.level}</Text></View></View><View className='rui-handle-panel'><View onClick={resetStart}>开始</View></View></View>)
}
export default SnakeGame;

8. SCSS 样式实现

// 基础颜色变量
$bg-color: #b7d4a8;
$border-color: #000000;
$head-color: lightgreen;
$body-color: red;
$food-color: red;*{margin: 0;padding: 0;box-sizing: border-box;
}.rui-snake-container{box-sizing: border-box;width: 100vw;height: 100vh;background-color: $bg-color;border: 10px solid $border-color;display: flex;flex-direction: column;justify-content: space-around;align-items: center;.rui-stage-content{width: 704px;height: 704px;border: 2px solid $border-color;position: relative;.rui-snake-body{width: 20px;height: 20px;border: 1px solid $bg-color;background-color: $border-color;position: absolute;}.rui-snake-food{width: 20px;height: 20px;overflow: hidden;position: absolute;// transform: rotate(45deg);border: 1px solid $bg-color;background-color: $food-color;display: flex;flex-wrap: wrap;justify-content: space-between;align-content: space-between;.rui-food-li{width: 8px;height: 8px;background-color: $border-color;}}.rui-snake-row{display: flex;align-items: center;.rui-snake-cell{width: 20px;height: 20px;flex: none;}}}.rui-score-panel{width: 700px;font: 30px '微软雅黑';display: flex;justify-content: space-between;align-items: center;}.rui-handle-panel{width: 600px;font: 30px '微软雅黑';text-align: center;}
}

9. 总结

  1. 最开准备使用 30 * 30 个格子,进行判断渲染,渲染数据每次更改太多,渲染时间很久,因此不建议使用;
  2. 为什么每次设置渲染数据都要筛选一次,因为直接将类渲染,数据随着蛇成长,会越来越大,很卡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/62094.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OB Cloud上新,4.1版本现已全面开放

2022 年 8 月 10 日&#xff0c;OceanBase 宣布 OceanBase 公有云服务全球开服&#xff0c;帮助不同规模客户&#xff0c;在全球不同区域&#xff0c;享受同样优质的企业级数据库产品与服务。 经过近一年的发展&#xff0c;公有云业务取得了长足的发展&#xff0c;去年对客收入…

三步完成echers展示离线地图

1.首先要去阿里云提供的地图选择器网站选择你需要下载的地图矢量数据。链接 以湖北省为例&#xff1a; 2.复制上图中的JSON API&#xff0c;在浏览器输入json api链接&#xff0c;可以看到数据格式是很规整的json数据&#xff0c;在浏览器中右键保存为json格式数据&#xff0c…

Bytebase 2.7.0 - ​新增分支(Branching)功能

&#x1f680; 新功能 新增支持与 Git 类似的分支&#xff08;Branching&#xff09;功能来管理 schema 变更。支持搜索所有历史工单。支持导出审计日志。 &#x1f384; 改进 变更数据库工单详情页面全新改版。优化工单搜索体验。SQL 审核规则支持针对不同数据库进行独立配…

癌症预测新利器:弹性逻辑回归让健康更可控!

一、引言 癌症是全球范围内健康领域的重大挑战&#xff0c;早期预测和诊断对于提高治疗效果和生存率至关重要。在过去的几十年里&#xff0c;随着医学和数据科学的快速发展&#xff0c;基于机器学习和统计方法的癌症风险预测成为研究的热点。其中&#xff0c;弹性逻辑回归作为一…

热烈祝贺蜀益表面处理成功入选航天系统采购平台

经过航天系统采购平台的严审&#xff0c;眉山市蜀益表面处理科技有限公司成功入选中国航天系统采购供应商库。航天系统采购平台是航天系统内企业采购专用平台&#xff0c;服务航天全球范围千亿采购需求&#xff0c;目前&#xff0c;已有华为、三一重工、格力电器、科大讯飞等企…

使用php实现微信登录其实并不难,可以简单地分为三步进行

使用php实现微信登录其实并不难&#xff0c;可以简单地分为三步进行。 第一步&#xff1a;用户同意授权&#xff0c;获取code //微信登录public function wxlogin(){$appid "";$secret "";$str"http://***.***.com/getToken";$redirect_uriu…

Linux文件管理知识:查找文件(第二篇)

上篇文章详细介绍了linux系统中查找文件的工具或者命令程序locate和find命令的基本操作。那么&#xff0c;今天这篇文章紧接着查找文件相关操作内容介绍。 Find命令所属操作列表中的条目&#xff0c;有助于我们想要的结果输出。上篇文章已讲到find 命令是基于搜索结果来执行操作…

Hadoop HA模式切换

Hadoop HA模式下 主从的切换&#xff08;操作命令&#xff09; YARN HA 获取所有RM节点的状态 yarn rmadmin -getAllServiceState获取 rm1 节点的状态 yarn rmadmin -getServiceState rm1手动将 rm1 的状态切换到STANDBY yarn rmadmin -transitionToStandby rm1 ##或者 y…

【LeetCode-中等题】236. 二叉树的最近公共祖先

文章目录 题目方法一&#xff1a;后序遍历 回溯 题目 方法一&#xff1a;后序遍历 回溯 解题的核心就是&#xff1a;采用后序遍历 讨论p&#xff0c;q是否在当前的root的两边&#xff0c;如在两边则返回当前节点root 如何不在两边&#xff0c;只要出现一个节点等于p或者q就…

OpenCV

文章目录 OpenCV学习报告读取图片和网络摄像头1.1 图片读取1.2 视频读取1.1.1 读取视频文件1.1.2读取网络摄像头 OpenCV基础功能调整、裁剪图像3.1 调整图像大小3.2 裁剪图像 图像上绘制形状和文本4.1 图像上绘制形状4.2图像上写文字 透视变换图像拼接颜色检测轮廓检测人脸检测…

微信小程序发布一个npm包

参考:https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html 同npm一样流程 npm install weixin_heath_apis

MySQL 8.1.0 推出 InnoDB Cluster 只读副本

全面了解 8.1.0 版本新功能&#xff1a;InnoDB Cluster 只读副本的相关操作。 作者&#xff1a;Miguel Arajo 高级软件工程师 / Kenny Gryp MySQL 产品总监 本文来源&#xff1a;Oracle MySQL 官网博客 * 爱可生开源社区出品。 前言 MySQL 的第一个 Innovation 版本 8.1.0 已…

基于JAVA SpringBoot和HTML婴幼儿商品商城设计

摘要 随着网络技术的发展与普遍,人们的生活发生了日新月异的变化,特别是计算机的应用已经普及到经济和社会的各个领域.为了让消费者网上购物过程变得简单,方便,安全,快捷,网上商城购物成了一种新型而热门的购物方式。网上商城在商品销售的发展中占据了重要的地位,已成为商家展示…

【大数据】数据湖:下一代大数据的发展趋势

数据湖&#xff1a;下一代大数据的发展趋势 1.数据湖技术产生的背景1.1 离线大数据平台&#xff08;第一代&#xff09;1.2 Lambda 架构1.3 Lambda 架构的痛点1.4 Kappa 架构1.5 Kappa 架构的痛点1.6 大数据架构痛点总结1.7 实时数仓建设需求 2.数据湖助力于解决数据仓库痛点问…

5 大虚拟数字人工具:视频内容创作的未来

人工智能&#xff08;AI&#xff09;给视频内容创作领域带来了一场革命。这一领域的显着进步之一是人工智能生成的会说话的化身的出现&#xff0c;它已经成为制作高质量视频的游戏规则改变者&#xff0c;而无需专业演员或昂贵的视频编辑软件。在这篇博文中&#xff0c;我们将深…

java八股文面试[多线程]——一个线程两次调用start()方法会出现什么情况

典型回答&#xff1a; Java 的线程是不允许启动两次的&#xff0c;第二次调用必然会抛出 IllegalThreadStateException&#xff0c;这是一种运行时异常&#xff0c;多次调用 start 被认为是编程错误。 通过线程的状态图&#xff0c;在第二次调用 start() 方法的时候&#xff…

发表于《自然》杂志:语音转文本BCI的新突破实现62字/分钟的速度

语音脑机接口&#xff08;BCI&#xff09;是一项创新技术&#xff0c;通过用户的大脑信号在用户和某些设备之间建立通信通道&#xff0c;它们在恢复残疾患者的言语和通信能力方面具有巨大潜力。 早期的研究虽然很有希望&#xff0c;但尚未达到足够高的精度来解码大脑活动&…

【斗罗Ⅱ】演员阵容曝光,张予曦披发惊艳,奥斯卡选角出新变革

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫&#xff01; 由周翊然和张予曦主演的《斗罗大陆2》电视剧&#xff0c;目前还在拍摄中。和第一部相比&#xff0c;主演阵容来了个全员大换血。服化道有参考动漫&#xff0c;但是做出来的质感&#xff0c;却引起了很多…

Mycat之前世今生

如果我有一个32核心的服务器&#xff0c;我就可以实现1个亿的数据分片&#xff0c;我有32核心的服务器么&#xff1f;没有&#xff0c;所以我至今无法实现1个亿的数据分片。——MyCAT ‘s Plan 话说“每一个成功的男人背后都有一个女人”&#xff0c;自然MyCAT也逃脱不了这个诅…

Flutter的未来与趋势,23年还学吗?

随着移动应用市场的不断扩大&#xff0c;跨平台开发框架的需求也越来越大。Flutter框架可以帮助开发者在不同平台上快速开发高质量的移动应用程序&#xff0c;这种趋势将进一步推动Flutter的发展和普及。 作为一名前端开发工程师&#xff0c;学习Flutter框架是非常有必要的。因…