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

JS简单贪吃蛇逻辑

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

 

实现步骤

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-2024 ClickSun All Rights Reserved