LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

JS简单贪吃蛇逻辑

freeflydom
2024年8月15日 9:26 本文热度 1856

 

实现步骤

1、屏幕刷新

利用浏览器的 requestAnimationFrame 方法进行刷新重置

2、对象封装

//位置类 

class Rect

//身体类 

class Body

// 食物类 

class Food

// 蛇类 

class Snake

// 摇杆控制类 

class EleOption class BallOption

// 启动 

let snake = new Snake() 

snake.begin()

3、源码

css

body {
position: relative;
width: 100vw;
height: 100vh;
}


.snake_body {
position: absolute;
background-color: rgb(234, 12, 101);
border-radius: 50%;
}


.food_body_1 {
position: absolute;
background-color: rgb(170, 192, 170);
border-radius: 50%;
}


.food_body_2 {
position: absolute;
background-color: rgb(169, 214, 169);
border-radius: 50%;
}


.food_body_3 {
position: absolute;
background-color: rgb(18, 139, 18);
border-radius: 50%;
}


.food_body_4 {
position: absolute;
background-color: rgb(207, 15, 197);
border-radius: 50%;
}


.food_body_5 {
position: absolute;
background-color: rgb(222, 27, 53);
border-radius: 50%;
}


#optionDiv{
width: 100px;
height: 100px;
position: absolute;
right: 40px;
bottom: 40px;
  z-index: 99;
}


#resultWarningDiv{
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}


.hideResultWarningDiv{
visibility: hidden !important;
}


.warningContent{
width: 40%;
height: 40%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: white;
border-radius: 10px;
}


#warningContentDiv{
margin-bottom: 20px;
}


#resumeGameDiv{
background-color: rgb(209, 126, 17);
color: white;
padding: 10px 40px;
border-radius: 4px;
}

js

class Rect {


x = 0
y = 0
width = 0
height = 0


constructor({ x, y, width = 10, height = 10 }){
this.x = x
this.y = y
this.width = width
this.height = height
}
}


class Body {


rect = null
div = null
constructor(rect){
this.render(rect)
}


render(rect){
this.createBody()
this.setRect(rect)
}


// 创建舍身
createBody(){
this.div = document.createElement('div');
this.div.setAttribute('class', 'snake_body');
document.body.appendChild(this.div);
}


// 位置设置
setRect(rect){
this.rect = rect
this.div.style.top = rect.y + 'px'
this.div.style.left = rect.x + 'px'
this.div.style.width = rect.width + 'px'
this.div.style.height = rect.height + 'px'
}
}


class Food {


rect = null
div = null
size = null


constructor(){
this.render()
}


// 类方法,创建随机食物
static scatterFoods(){
let foods = []
let count = 0;
while(count < 100) {
// 要执行的操作
foods.push(new Food())
count++;
}
return foods;
}


// 渲染
render(){
this.size = (Math.floor(Math.random() * 5) + 1);
this.createFood(this.size)
this.setRect(this.size)
}


// 创建食物
createFood(size){
this.div = document.createElement('div');
this.div.setAttribute('class', 'food_body_' + size);
document.body.appendChild(this.div);
}


// 位置设定
setRect(size){


let screenWidth = document.body.offsetWidth
let screenHeight = document.body.offsetHeight


let width = size * 5
let height = size * 5


let x = Math.floor(Math.random() * (screenWidth - 4 * width)) + width
let y = Math.floor(Math.random() * (screenHeight - 4 * height)) + height


this.rect = new Rect({ x, y, width, height })
this.div.style.top = this.rect.y + 'px'
this.div.style.left = this.rect.x + 'px'
this.div.style.width = this.rect.width + 'px'
this.div.style.height = this.rect.height + 'px'
}


// 销毁
destroy(){
this.div.remove()
}
}


class Snake {


bodys = []
foods = []
ballOption = null
angle = null
isPause = false


constructor(){


}


// 开始
begin(){
        this.clearAll()
        this.createHeader()
this.foods = Food.scatterFoods()
this.createBall()
        return this
}


    // 清除全部元素、再次绘制
    clearAll(){
        this.angle = null
        this.ballOption = null
        this.bodys = []
        this.foods = []
        this.isPause = false
        document.body.innerHTML = `
            <div id="optionDiv"></div>
            <div id="resultWarningDiv" class="hideResultWarningDiv">
                <div class="warningContent">
                    <span id="warningContentDiv">22</span>
                    <span id="resumeGameDiv"> 继续 </span>
                </div>
            </div>
        `
    }


// 创建摇杆
createBall(){
this.ballOption = new BallOption('optionDiv',(angle)=>{
angle = 270 + angle
if(angle > 360){
angle = angle - 360
}
this.angle = angle * (Math.PI / 180)
})
this.ballOption.createOptionView()
}


// 创建蛇头
createHeader(){
let x = document.body.offsetWidth / 2.0
let y = document.body.offsetHeight / 2.0
let header = new Body(new Rect({ x, y }))
this.bodys.push(header)
}


// 吃
eat(foodItem){
let lastBody = this.getTailer()
let body = new Body(lastBody)
this.bodys.push(body)
// 移除食物
foodItem.destroy()
}


// 移动
move(){
requestAnimationFrame(() => {
            if(!this.isPause){
              this.setNewHeaderDirection()
              this.collisionDetection()
              this.checkIsSucceeded()
            }
            this.move()
});
}


// 转向
setNewHeaderDirection(){
for(let i = this.bodys.length - 1; i >= 0;i--){
let item = this.bodys[i]
if(i == 0){
item.setRect(this.nextStepRect())
} else {
item.setRect(this.bodys[i - 1].rect)
}
}
}


// 获取蛇头
getHeader(){
return this.bodys[0]
}


// 获取蛇尾
getTailer(){
return this.bodys[this.bodys.length - 1]
}


// 蛇头下一步的位置计算
nextStepRect(){
let header = this.getHeader()
let step = 1.5
let addX = 0
let addY = 0
addX = this.angle ? step * Math.cos(this.angle) : step
addY = this.angle ? step * Math.sin(this.angle) : 0
let x = header.rect.x + addX
let y = header.rect.y + addY
return new Rect({x, y })
}


// 吃到食物检测
collisionDetection(){
let headerRect = this.getHeader().rect
this.foods = this.foods.filter((foodItem)=>{
let foodRect = foodItem.rect
let isDetection = this.checkRectOverlap(headerRect,foodRect)
if(isDetection){
//根据size大小成长
for(let i = 0; i < foodItem.size; i ++){
this.eat(foodItem)
}
}
return !isDetection
})
}


// 蛇头与食物区域是否重叠判断
checkRectOverlap(rect1, rect2) {
// 提取矩形的坐标和尺寸
let x1 = rect1.x;
let y1 = rect1.y;
let width1 = rect1.width;
let height1 = rect1.height;
  
let x2 = rect2.x;
let y2 = rect2.y;
let width2 = rect2.width;
let height2 = rect2.height;
  
// 检查是否有重叠
return (x1 < x2 + width2 &&
x1 + width1 > x2 &&
y1 < y2 + height2 &&
y1 + height1 > y2)
}


// 游戏结果计算
checkIsSucceeded(){
let screenWidth = document.body.offsetWidth
let screenHeight = document.body.offsetHeight
let { x, y } = this.getHeader().rect
if(x >= screenWidth || x <= 0 || y >= screenHeight || y <= 0){
this.isPause = true
            this.resultWarning('游戏结束')
}
if(this.foods.length == 0){
this.isPause = true
this.resultWarning('厉害!')
}
}


    // 结果提醒
    resultWarning(content = '游戏结束'){
        document.getElementById('resultWarningDiv').classList.remove('hideResultWarningDiv')
        document.getElementById('warningContentDiv').innerText = content
        document.getElementById('resumeGameDiv').onclick = ()=>{
            document.body.innerHTML = ''
            this.begin()
        }
    }
}


class EleOption{
    //添加操作dom ID
    eleId


    constructor(eleId){
        this.eleId = eleId
    }


    //获取当前关联的el
    getCurrentEle(){
        return document.getElementById(this.eleId)
    }


    //获取el宽度
    getEleWidth(el){
        return el.offsetWidth
    }


    //获取el高度
    getEleHeight(el){
        return el.offsetHeight
    }


    //设置背景颜色
    setBackgroundColor(el,color){
        el.style.backgroundColor = color
    }


    //设置宽度
    setWidth(el,w){
        el.style.width = w + 'px'
    }


    //设置高度
    setHeight(el,h){
        el.style.height = h + 'px'
    }


    //设置圆角
    setCircle(el){
        el.style.borderRadius = (this.getEleWidth(el) / 2.0 )+ 'px'
    }


    //设置绝对定位
    setAbsolutePosition(el){
        el.style.position = 'absolute'
    }


    //设置透明度
    setTransparency(el,alpha){
        el.style.opacity = alpha / 100
    }


    //设置为父el中心位置
    setSupCenter(el){
        if(el.style.position != 'absolute'){
            this.setAbsolutePosition(el)
            let superElWidth = this.getEleWidth(this.getSuperEl(el))
            let superElHeight = this.getEleHeight(this.getSuperEl(el))


            let width = this.getEleWidth(el)
            let height = this.getEleHeight(el)


            el.style.left = ((superElWidth - width) / 2.0) + 'px'
            el.style.top = ((superElHeight - height) / 2.0) + 'px'
        }
    }


    //设置中心位置
    setCenter(el,point){
        if(el.style.position != 'absolute'){
            this.setAbsolutePosition(el)
        }
        el.style.left = point.x + 'px'
        el.style.top = point.y + 'px'
    }


    //获取父类el
    getSuperEl(el){
        return el.parentNode
    }


    //获取el
    getElById(elId){
        return document.getElementById(elId)
    }


    //创建el
    createEl(elId){
        let el = document.createElement('div')
        if(elId){
            el.setAttribute('id',elId)
        }
        return el
    }


    //添加子el
    addSubEl(superEl,subEl){
        superEl.appendChild(subEl);
    }


    //取消交互
    cancleUserInreface(el){
        el.style.pointerEvents = 'none'
    }




    //添加move事件
    addMoveEvent(el,ballOption,mcb,emcb){
        el.onmousemove = (event)=>{
            mcb(this.getMoveEventPoint(event,el),ballOption)
        }
        el.onmouseout = (_)=>{
            emcb(ballOption)
        }
    }


    //move事件监听
    getMoveEventPoint(event,el){
        let x = event.clientX - this.getSuperEl(el).offsetLeft
        let y = event.clientY - this.getSuperEl(el).offsetTop
        return {x,y}
    }


    //获取中心点
    getCenterPoint(off){
        let x = this.getSuperEl(this.getCurrentEle()).offsetLeft + (this.getEleWidth(this.getCurrentEle()) / 2.0) - off.offX
        let y = this.getSuperEl(this.getCurrentEle()).offsetTop + (this.getEleHeight(this.getCurrentEle()) / 2.0) - off.offY
        return {x,y}
    }
}


class BallOption{
    //添加操作dom ID
    eleId
    //el操作对象
    eleOption


    //控制球对象
    ball
    //控制球尺寸
    ballWidth
    ballHeight
    ballOffX
    ballOffY
    //是否触碰过控制球
    isTouchedBall = false
    
    //控制区域
    optionRangeView
    optionRangeViewCenterPoint


    //上一次角度
    lastDeg


    //角度回调
    angleCallBack


    constructor(eleId,angleCallBack){
        this.eleId = eleId
        this.angleCallBack = angleCallBack
        this.eleOption = new EleOption(eleId)
    }


    //创建操作框
    createOptionView(){
        if(this.eleId != undefined){
            this.createOptionRangeView()
            this.createOptionBallView()
        }
    }


    //绘制操作范围
    createOptionRangeView(){
        let width = this.eleOption.getEleWidth(this.eleOption.getCurrentEle())
        let height = this.eleOption.getEleHeight(this.eleOption.getCurrentEle())
        this.optionRangeView = this.eleOption.createEl('optionRangeViewEl')
        this.eleOption.addSubEl(this.eleOption.getCurrentEle(),this.optionRangeView)
        this.eleOption.setBackgroundColor(this.optionRangeView,'rgb(248,248,248)')
        this.eleOption.setWidth(this.optionRangeView,width)
        this.eleOption.setHeight(this.optionRangeView,height)
        this.eleOption.setCircle(this.optionRangeView)
        //添加拖拽事件
        this.eleOption.addMoveEvent(optionRangeViewEl,this,this.makeBallFollowScroll,this.resetBall)
    }


    //控制球随鼠标滚
    makeBallFollowScroll(point,ballOption){
        let x = (point.x - ballOption.ballOffX)
        let y = (point.y - ballOption.ballOffY)
        let currentPoint = {x,y}
        if(ballOption.checkIsTouchControlBall(point)){
            ballOption.eleOption.setCenter(ballOption.ball,currentPoint)
            ballOption.getCurrentAngle(point)
        }
    }


    //检测是否碰触过控制球
    checkIsTouchControlBall(point){
        if(!this.isTouchedBall){
            let isTouchBall = (
                point.x > this.optionRangeViewCenterPoint.x - this.ballWidth && 
                point.x < this.optionRangeViewCenterPoint.x + this.ballWidth &&
                point.y > this.optionRangeViewCenterPoint.y - this.ballHeight && 
                point.y < this.optionRangeViewCenterPoint.y + this.ballHeight
            )
    
            if(isTouchBall){
                this.isTouchedBall = true
                this.eleOption.setTransparency(this.ball,100)
            }
        }
        return this.isTouchedBall
    }


    //鼠标移出事件
    resetBall(ballOption){
        ballOption.isTouchedBall = false
        ballOption.eleOption.setCenter(ballOption.ball,ballOption.optionRangeViewCenterPoint)
        ballOption.eleOption.setTransparency(ballOption.ball,40)
        if(ballOption.angleCallBack){
            ballOption.lastDeg = 0
            ballOption.angleCallBack(ballOption.lastDeg)
        }
    }


    //计算角度
    getCurrentAngle(point){
        let addX = (point.x - this.eleOption.getEleWidth(this.optionRangeView) / 2.0)
        let addY = (point.y - this.eleOption.getEleHeight(this.optionRangeView) / 2.0)
        if(addY != 0){
            let tan = addX / addY
            let angle = Math.atan(tan)
            this.lastDeg = (angle / Math.PI) * 180
            if(addX <= 0 && addY < 0){
                this.lastDeg = this.lastDeg
            } else if(addX <= 0 && addY > 0){
                this.lastDeg = (180 - Math.abs(this.lastDeg))
            } else if(addX >= 0 && addY > 0){
                this.lastDeg = 180 + Math.abs(this.lastDeg)
            } else if(addX >= 0 && addY < 0){
                this.lastDeg = (360 - Math.abs(this.lastDeg))
            }
        }
        if(this.angleCallBack){
            this.angleCallBack(360 - this.lastDeg)
        }
    }


    //绘制球滚动
    createOptionBallView(){
        let scale = 3.2
        this.ballWidth = this.eleOption.getEleWidth(this.eleOption.getCurrentEle()) / scale
        this.ballHeight = this.eleOption.getEleHeight(this.eleOption.getCurrentEle()) / scale
        this.ballOffX = this.ballWidth / 2.0
        this.ballOffY = this.ballHeight / 2.0
        this.ball = this.eleOption.createEl('optionBallViewEl')
        this.eleOption.addSubEl(this.eleOption.getCurrentEle(),this.ball)
        this.eleOption.setBackgroundColor(this.ball,'black')
        this.eleOption.setWidth(this.ball,this.ballWidth)
        this.eleOption.setHeight(this.ball,this.ballHeight)
        this.eleOption.setCircle(this.ball)
        this.eleOption.setSupCenter(this.ball)
        this.eleOption.cancleUserInreface(this.ball)
        this.eleOption.setTransparency(this.ball,40)
        //保存中心点坐标
        this.optionRangeViewCenterPoint = this.eleOption.getCenterPoint({offX:this.ballOffX,offY:this.ballOffY})
    }
}


let snake = new Snake()
snake.begin().move()

总结

将功能划分为 “类”,让各自的对象去处理各自的任务,实现起来逻辑就会清晰。主要是用“摇杆”控制方向,利用浏览器的 requestAnimationFrame 方法进行刷新。元素都是 div 标签。


作者:头疼脑胀的代码搬运工
链接:https://juejin.cn/post/7402548056284839947
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



该文章在 2024/8/16 10:10:15 编辑过
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved