当前位置: 首页 > 科技观察

Vue3+Canvas实现坦克大战

时间:2023-03-12 01:31:32 科技观察

前言记得几年前做前端开发的时候,跟着大师用纯es5实现了这个坦克大战。可以说我是从javaScript小游戏开始做前端这行的,时间过得飞快几年,前端开发日新月异,各种新框架、新概念层出不穷。在各种新技术的盲目学习和应用中很容易迷失方向。什么是真正的编程?值得思考的问题。我准备用vue3重新实现这个游戏,顺便回顾整理一下自己的知识体系。W/UpS/DownA/LeftD/RightF/Shoot让我们开始吧!架构搭建项目技术选择为vue3、vite、less、pnpm、ts,新建项目按照vue3官网文档创建。注:虽然我用的是vue3,但其实只是一个强行尝鲜,主要内容仅限于js使用的框架特性。$pnpmcreatevite----templatevue$cd$pnpminstall$pnpmadd-Dless$pnpmdevCanvasconstructor游戏的核心是canvas画布和tank元素,我们定义两个ConstructorcanvasConstructor定义参数和方法:dom、维度大小、renderTo渲染函数、drawText文字绘制函数、drawImageSlice图片绘制函数由不同的画布层表示,每次更新只需要重新绘制动态元素,我抽象了一个渲染函数//renderingthis.renderTo=functionrenderTo(container_id){if(!is_rendered){letcontainer=document.getElementById(container_id)//画布起始坐标dom=document.createElement('canvas')//创建画布画布dom.setAttribute('class','canvas')ctx=dom.getContext('2d')dom.setAttribute('width',container.clientWidth)dom.setAttribute('height',container.clientHeight)//画布大小dimension={x:container.clientWidth,y:container.clientHeight,}container.insertBefore(dom,container.firstChild)//插入cantainer容器}}文本渲染如果想知道canvas中的具体位置坐标,可以定义一个函数,当鼠标滑动绘制当前位置坐标时执行该函数this.drawText=functionndrawText(text,offset_left,offset_top,font){ctx.font=字体||'25pxCalibri'ctx.fillStyle='#fff'ctx.fillText(text,offset_left,offset_top)}每次画布重绘前清空重绘前需要先擦除整个画布。this.clear=functionclear(){ctx.clearRect(0,0,dimension.x,dimension.y)}核心:绘制坦克,子弹,建筑等函数,就是通过这个函数完成的,距离使用精灵图实现,通过坐标抓取特定位置的图片元素,获取各种坦克等元素的UI;通过rotate旋转元件实现坦克的转向;this.drawImageSlice=functiondrawImage(img_ele,sx,sy,sWidth,sHeight,x,y,rotation){ctx.保存()ctx。translate((2*x+sWidth)/2,(2*y+sHeight)/2)//改变起点坐标ctx.rotate((Math.PI/180)*rotation)//旋转x=x||0y=y||0ctx.drawImage(img_ele,sx,sy,sWidth,sHeight,-sWidth/2,-sHeight/2,sWidth,sHeight)ctx.restore()//Restore}BattleCity构造函数BattleCity构造函数定义了坦克的各种配置信息,以及方法函数letTankConfig=function(cfg){this.explosion_count=cfg.explosion_countthis.width=cfg.type.dimension[0]this.height=cfg.type.dimension[1]this.missle_type=cfg.导弹类型||MISSILE_TYPE.NORMALthis.x=cfg.x||0这个.y=cfg.y||0this.direction=cfg.direction||DIRECTION.UPthis.is_player=cfg.is_player||0this.movi??ng=cfg.movi??ng||0this.alive=cfg.alive||1this.border_x=cfg.border_x||0this.border_y=cfg.border_y||0this.speed=cfg.speed||TANK_SPEEDthis.direction=cfg.direction||DIRECTION.UP这个.type=cfg.type||TANK_TYPE.PLAYER0}实现坦克的移动使用键盘的W、S、A、D代表上下左右方向键,按下键盘触发对应坦克实例的移动功能用于计算移动后的位置坐标信息。注意:边界条件的判断不能让它超出战场的边界CanvasSprite.prototype.move=function(d,obstacle_sprites){this.direction=dswitch(d){caseDIRECTION.UP:if((obstacle_sprites&&!this.checkRangeOverlap(obstacle_sprites))||!obstacle_sprites){this.y-=this.speedif(this.y<=5){if(!this.out_of_border_die){this.y=0}else{//this.alive=0;this.explode()document.getElementById('steelhit').play()}}}中断案例DIRECTION.DOWN:if((obstacle_sprites&&!this.checkRangeOverlap(obstacle_sprites))||!obstacle_sprites){this.y+=this.speedif(this.y+this.height>=this.border_y-10){if(!this.out_of_border_die){this.y=this.border_y-this.height}else{//this.alive=0;this.explode()文档。getElementById('steelhit').play()}}}中断案例x<=5){if(!this.out_of_border_die){this.x=0}else{//this.alive=0;this.explode()document.getElementById('steelhit').play()}}}中断案例DIRECTION.RIGHT:if((obstacle_sprites&&!this.checkRangeOverlap(obstacle_sprites))||!obstacle_sprites){this.x+=this.speedif(this.x+this.width>=this.border_x-10){if(!this.out_of_border_die){this.x=this.border_x-this.width}else{//this.alive=0;this.explode()document.getElementById('steelhit').play()}}}break}}坦克发射子弹的逻辑首先需要定义子弹配置信息和构造函数;letMissileConfig=function(cfg){this.x=cfg.xthis.y=cfg.ythis.type=cfg.type||MISSILE_TYPE.NORMAL这个.width=cfg.width||this.type.dimension[0]this.height=cfg.height||this.type.dimension[1]this.direction=cfg.direction||DIRECTION.UPthis.is_from_player=cfg.is_from_playerthis.out_of_border_die=cfg.out_of_border_die||1//判断边界类型this.border_x=cfg.border_x||0this.border_y=cfg.border_y||0this.speed=cfg.speed||TANK_SPEEDthis.alive=cfg.alive||1}varMissile=function(MissileConfig){varx=MissileConfig.xvary=MissileConfig.yvarwidth=MissileConfig.widthvarheight=MissileConfig.widthvardirection=MissileConfig.directionthis.type=MissileConfig.typethis.is_from_player=MissileConfig.is_from_player||0var爆炸计数=0CanvasSprite.apply(this,[{alive:1,out_of_border_die:1,border_y:HEIGHT,border_x:WIDTH,speed:MISSILE_SPEED,direction:direction,x:x,y:y,width:width,height:height,},])this.isDestroied=function(){returnexplosion_count>0}this.explode=function(){if(explosion_count++===5){this.alive=0}}this.getImg=function(){if(explosion_count>0){返回{宽度:TANK_EXPLOSION_FRAME[explosion_count].dimension[0],高度:TANK_EXPLOSION_FRAME[explosion_count].dimension[1],offset_x:TANK_EXPLOSION_FRAME[explosion_count].image_coordinates[0],offset_y:TANK_EXPLOSION_FRAME[explosion_count].image_coordinates[1],}}else{return{width:宽度,h八:高度,offset_x:this.type.image_coordinates[0],offset_y:this.type.image_coordinates[1],}}}this.getHeadCoordinates=function(){varh_x,h_yswitch(this.direction){caseDIRECTION.UP:h_x=this.x+this.width/2-this.type.dimension[0]/2h_y=this.y-this.type.dimension[1]/2breakcaseDIRECTION.DOWN:h_x=this.x+this.width/2-this.type.dimension[0]/2h_y=this.y+this.height-this.type.dimension[1]/2breakcaseDIRECTION.LEFT:h_x=this.xh_y=this.y+this.width/2-this.type.dimension[0]/2breakcaseDIRECTION.RIGHT:h_x=this.x+this.heighth_y=this.y+this.width/2-这个.type.dimension[0]/2}console.log({x:h_x,y:h_y,})rreturn{x:h_x,y:h_y,}}this._generateId=function(){returnuuidv4()}sprites[this._generateId()]=this}然后定义一个fire开发函数,firingwindow之后会用到.requestAnimationFrame()实现循环效果,每次重绘最新的位置信息this.fire=function(boolean_type){if(!this.missle||!this.missle.alive){varcoor=this.getCannonCoordinates()this.missle=newMissile(newMissileConfig({x:coor.x,y:coor.y,direction:this.direction,type:this.miss_type,is_from_player:boolean_type,}))if(boolean_type){文档.getElementById('shoot').play()}}}总结使用requestAnimationFrame循环刷新画布,在下一次画布重绘时通过修改每个元素的位置坐标来更新视图。这是舞台互动的基本逻辑;到这里坦克移动和发射子弹的效果就实现了。下篇将介绍最关键的子弹和物体碰撞实现逻辑。