canvas在小程序里写小游戏

news2024/9/25 11:17:26

最近接了个小需求需要写个小游戏,由简单的帧动画加上碰撞相关的处理,组成。具体页面信息如下图

具体的游戏步骤,是通过长按按钮蓄力,松开时卡通人物跳起,卡通人物跳起碰撞到上面的元宝等元素的得分,这里我们需要关注的主要在于以下几点:

1.图片的缓存加载问题
2.金币,元宝的移动问题,从左向右,或者从右向左
3.卡通人物的跳起加速度问题,蓄力人物压缩问题(模拟起跳)
4.人物和元素的碰撞问题

初始化canvas对象和ctx对象

 initCanvas() {(uni.createSelectorQuery().select('#canvas') as any).fields({ node: true, context:true, size: true }).exec((res:any)=>{console.log(uni.getSystemInfoSync());const dpr = (uni.getSystemInfoSync() as any).devicePixelRatio || 1const canvas = res[0].node// 获取canvas对象 this.canvas = canvasthis.setCanvasSize(canvas, dpr)this.loadImgs(this.imgArr);// 获取上下文ctx对象this.ctx = canvas.getContext('2d')this.ctx.scale(dpr,dpr)})} // canvas默认初始化尺寸为300*150,如果通过css设置的话将会把canvas拉伸,导致绘制的时候出现图形扭曲,通过dom对象设置width和height可以达到真是的尺寸setCanvasSize(canvas:any,dpr:any) {// 乘上dpr 会使画出来的图片没有那么糊,更清晰canvas.width = this.screenWidth * dprcanvas.height = this.screenHeight * dpr} 

canvas的图片加载和缓存问题

// 缓存几种金币,元宝图片,避免canvas绘制时还需要异步读取图片loadImgs(arr:any) {return new Promise<void>((resolve) => {let count = 0;// 循环图片数组,每张图片都生成一个新的图片对象const len = arr.length;for (let i = 0; i < len; i++) {if(typeof arr[i].img === 'object' ){count++if (count == len) {console.log(arr)arr.forEach((ele:any) => {this.loadImgObj[ele.key] = ele});// 加载好,清空定时器,设置加载进度为100%(this.$refs.GameLoadref as any).loadnum = 100;// 隔200ms 去除加载页面const timerOut =setTimeout(() => {(this.$refs.GameLoadref as any).show = false;this.showcount = truethis.initPageelement()clearTimeout(timerOut)resolve();}, 600);}}else {const image = this.canvas.createImage()// 成功的异步回调image.onload = () => {count++;arr.splice(i, 1, {// 加载完的图片对象都缓存在这里了,canvas可以直接绘制img: image,width: arr[i].width,height: arr[i].height,x: arr[i].x,y: arr[i].y,key:arr[i].key,speed:arr[i].speed,prepareSpeed:arr[i].prepareSpeed || 0// 这里可以直接生成并缓存离屏canvas,用于优化性能,但本次不用,只是举个例子// offScreenCanvas: this.createOffScreenCanvas(image)});// 这里说明 整个图片数组arr里面的图片全都加载好了if (count == len) {// this.preloaded = true;arr.forEach((ele:any) => {this.loadImgObj[ele.key] = ele});// 加载好,清空定时器,设置加载进度为100%(this.$refs.GameLoadref as any).loadnum = 100;// 隔200ms 去除加载页面const timerOut =setTimeout(() => {(this.$refs.GameLoadref as any).show = false;this.showcount = truethis.initPageelement() clearTimeout(timerOut)resolve();}, 600);}}image.src = arr[i].img;}}});} 

金币,元宝的移动问题

核心思想就是改变x轴的坐标,类似我们以前写的红包雨动画 差不多意思

 //绘制金币元宝对象drawCoins() {// 遍历这个金币对象数组this.coinArr.forEach((coin:any, index:any) => {if(!coin) returnconst result =this.checkCollision(coin,index,this.loadImgObj['uni']) if(result) returnconst newCoin = {// 运动的关键每次只有x不一样x: coin.x + coin.speed,y: coin.y ,width:coin.width,height:coin.height,key:coin.key,img: coin.img,speed: coin.speed};// 绘制某个金币对象时,也同时生成一个新的金币对象,替换掉原来的它,唯一的区别就是它的x变了,下一帧绘制这个金币时,就运动了一点点距离this.coinArr.splice(index, 1, newCoin);this.ctx.drawImage(coin.img,this.calculatePos(coin.x),this.calculateHeight(coin.y) ,this.calculatePos(coin.width),// coin.height/coin.width * this.calculatePos(coin.width)this.calculateHeight(coin.height));});} 

生成金币元宝

 pushCoins() {if(!this.addCoinsTimer) return// 每次随机生成3~5个金币或者元宝等const random = this.randomRound(3,5);let arr:any = [];for (let i = 0; i < random; i++) {const randomNum = this.randomRound(0, 4)// 创建新的金币对象let newCoin = {x:0 - this.calculatePos(Math.random() * 250),y:this.randomRound(this.calculateHeight(100),this.calculateHeight(450)),width:this.imgArr[randomNum].width,key:this.imgArr[randomNum].key,height:this.imgArr[randomNum].height,img: this.imgArr[randomNum].img, // 随机取一个金币图片对象,这几个图片对象在页面初始化时就已经缓存好了speed: this.calculatePos(Math.random() * 7 + 5) // 移动速度 随机};// 控制页面中的爆竹的数量,我们还有减分项,就是🧨,所以需要控制多少,不能完全随机出,不然会出现满屏的爆竹const hasBomb =this.coinArr.find((ele:any,index:any)=>{returnele &&ele.key === 'bomb'})if(hasBomb && newCoin.key === 'bomb') {// 取反太烦 直接这么写,你们别学我}else {arr.push(newCoin as never);}}// 每次都插入一批新金币对象arr到运动的金币数组this.coinArrthis.coinArr = [...this.coinArr, ...arr];// 定时删除数组中跑到屏幕外面的数据for (let i = 2; i >=0 ; i--) {if(!this.coinArr[i] || (this.coinArr[i] && this.calculatePos(this.coinArr[i].x) > this.screenWidth)){this.coinArr.splice(i,1)}}// 间隔多久生成一批金币this.addCoinsTimer = setTimeout(() => {this.pushCoins();}, 1000);} 

移动金币元宝,需要一个api requestAnimationFrame,通过这个来绘制帧动画,在h5中是直接挂载在window上的,小程序中,是挂载在canvas对象上的,所以这就是为什么我们初始化的时候,要获取一个canvas对象

 moveCoins() {// 清空canvasthis.ctx.clearRect(0, 0, this.screenWidth, this.screenHeight);//绘制背景this.drewBg()// 绘制新的一帧动画this.drawCoins(); this.drawblessBagDelay&&this.drawblessBag()// 不断执行绘制,形成动画this.moveCoinAnimation = this.canvas.requestAnimationFrame(this.moveCoins); // 绘制指示器的动画 this.drawIndicator()  // 画碰撞的分数 // 把opacity为0的全部清除this.bubbleArr.forEach((ele:any, index:any) => {if (ele.opacity < 0) {this.bubbleArr.splice(index, 1);}});// 碰撞的分数动画 this.drawPoint(); // 画uni this.drawUni() // 画按钮 if(this.drawBtnFlag) {this.drawBtnUni([this.loadImgObj['btnLongpress']]) }else {this.drawBtnUni([this.loadImgObj['btnReleasejump']]) } } 

卡通人物的跳起加速度问题,蓄力人物压缩问题(模拟起跳)

蓄力压缩,类似于我们起跳前的蹲下蓄力的动作
 // 蓄力压缩if(!this.drawUniFlag && this.longpressflag ) { // const prepareSpeed = this.loadImgObj['uni'].prepareSpeedthis.loadImgObj['uni'].y = this.loadImgObj['uni'].y + prepareSpeedthis.loadImgObj['uni'].height = this.loadImgObj['uni'].height - prepareSpeedthis.loadImgObj['uni'].x = this.loadImgObj['uni'].x - prepareSpeed/4this.loadImgObj['uni'].width = this.loadImgObj['uni'].width + prepareSpeed/2// this.longpressflagif(this.loadImgObj['uni'].y > 864){this.loadImgObj['uni'].y = 864this.loadImgObj['uni'].height= 227this.loadImgObj['uni'].x = 244this.loadImgObj['uni'].width= 247}return} 
放开的时候 先是回到正常大小,然后再起跳
 // 反弹回正常大小if(this.loadImgObj['uni'].y > 824 && !this.drawUniFlag){ this.loadImgObj['uni'].y = this.loadImgObj['uni'].y - 8* prepareSpeedthis.loadImgObj['uni'].height = this.loadImgObj['uni'].height + 8 * prepareSpeedthis.loadImgObj['uni'].x = this.loadImgObj['uni'].x + 2* prepareSpeedthis.loadImgObj['uni'].width = this.loadImgObj['uni'].width + 4 * prepareSpeedif(this.loadImgObj['uni'].y <= 824 ||this.loadImgObj['uni'].x >= 254){this.loadImgObj['uni'].y = 824this.loadImgObj['uni'].height= 267this.loadImgObj['uni'].x = 254this.loadImgObj['uni'].width= 227this.drawUniFlag = true}return} 
关于加速度的问题,起跳时速度最快,到达最大高度时,速度最小,然后做类似于自由落体的反向加速度下落
 // uni加速度const easing = 0.05const vy = (this.loadImgObj['uni'].y - this.uniJumpY) * easing this.loadImgObj['uni'].y = this.loadImgObj['uni'].y + this.loadImgObj['uni'].speed* vy+ this.loadImgObj['uni'].speed *3if(this.loadImgObj['uni'].y > 824){// 停止uni动画this.stopMoveUniFlag = true}else if(this.loadImgObj['uni'].y <= this.uniJumpY && this.loadImgObj['uni'].speed < 0){//到顶了,反向this.loadImgObj['uni'].speed = - this.loadImgObj['uni'].speed} 

人物和元素的碰撞问题

碰撞其实是比较简单的,就是检查 人物和元素直接的坐标有没有重叠的部分

// 检查是否碰撞checkCollision(coinItem:any,index:any,uniItem:any){if(coinItem.key !== 'blessingbag' && uniItem.y > 450){returnfalse}if(coinItem.x > uniItem.x && coinItem.x < (uniItem.x + uniItem.width) ||((coinItem.x + coinItem.width ) > uniItem.x&& (coinItem.x +coinItem.width ) <(uniItem.x + uniItem.width))){if(uniItem.y > coinItem.y && uniItem.y < (coinItem.y+ coinItem.height) || ((uniItem.y+uniItem.height )>coinItem.y && (uniItem.y+uniItem.height ) < (coinItem.y +coinItem.height) )){// 碰撞const partx =coinItem.xconst party =coinItem.y// 删掉当前 金币this.coinArr.splice(index, 1, undefined);// 加分的动画冒泡,加入数组const bubble = {x: partx + coinItem.width/2,y: party,key:coinItem.key,opacity:1}this.bubbleArr.push(bubble)// 积分this.totalPoints = this.totalPoints + (this.enumkey[coinItem.key] as any).scorereturn true}}} 

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

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

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

相关文章

笔试题之编写SQL分析门店销售情况

销售员、客户、产品 文章目录前言一、SQL题目二、解答方法&#xff08;一&#xff09;建表插入测试数据&#xff08;二&#xff09;第一题解答&#xff08;三&#xff09;第二题解答&#xff08;四&#xff09;第三题解答总结前言 分享本人遇到的笔试真题与解法&#xff0c;并…

MATLAB算法实战应用案例精讲-【人工智能】语义分割(附实战应用案例及代码)

前言 语义分割是一种典型的计算机视觉问题,其涉及将一些原始数据(例如,平面图像)作为输入并将它们转换为具有突出显示的感兴趣区域的掩模。许多人使用术语全像素语义分割(full-pixel semantic segmentation),其中图像中的每个像素根据其所属的感兴趣对象被分配类别ID。…

[ XSS-labs通关宝典 ] xss-labs 通关宝典之 less1 - less5

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

前端常见问题汇总(十)

一、HTTP1.0和HTTP2.0的区别 http1.0&#xff1a;每次请求都需要重新建立tcp连接&#xff0c;请求完后立即断开与服务器连接&#xff0c;这很大程度造成了性能上的缺陷&#xff0c;http1.0被抱怨最多的就是连接无法复用。 http1.1&#xff1a;引入了长连接&#xff08;keep-al…

麒麟系统虚拟机安装教程

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 1.首先得安装VM Ware软件。 2.打开VM Ware&#xff0c;点击“文件”->“新建虚拟机”。 3.进入新建虚拟机向导&#xff0c;点击下一步。如下图&…

API管理神器:Apifox

前言 代码未动&#xff0c;文档先行 其实大家都知道 API 文档先行的重要性&#xff0c;但是在实践过程中往往会遇到很多困难。 程序员最讨厌的两件事&#xff1a;1. 写文档&#xff0c;2. 别人不写文档。大多数开发人员不愿意写 API 文档的原因是写文档短期收益远低于付出的…

2023—静待“雨中的海棠”发芽

2023—静待“雨中的海棠”发芽认真负责、全身心的投入工作减少抱怨勤思考、多总结—>高效工作保持7*24小时在线全身心BKGWY坚持不懈多运动骑车车、练哑铃、慢跑多看书看自己喜欢的书环青海湖准备环青海湖的攻略身体上的准备内心信念的支撑最后就静待“雨中的海棠”发芽吧&am…

kali - 扫描

数据来源 Whatweb WhateWhatweb是一个基于Ruby语言的开源网站指纹识别软件&#xff0c;正如它的名字一样,&#xff0c;whate能够识别各种关于网站的详细信息&#xff0c;包括&#xff1a;CMS类型、博客平台、中间件、web框架模块、网站服务器、脚本类型、 Javascript库、lP、 …

Apollo 配置中心

Apollo 配置中心目录概述需求&#xff1a;设计思路实现思路分析1.Apollo 配置中心2.Client端配置中心3.爬虫调度器5.Server端配置中心参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardne…

(免费分享)基于jsp,ssm甜点网站

开发工具&#xff1a;eclipse&#xff0c;jdk1.8 数据库&#xff1a;mysql5.7&#xff0c;Tomcat8.0 package com.softeem.controller;import java.util.HashMap; import java.util.Map;import javax.annotation.Resource;import org.springframework.stereotype.Controller; …

labelImag安装及使用教程

在做目标检测任务时&#xff0c;需要进行标注&#xff0c;选择了LabelImg作为标注工具&#xff0c;下面是安装及使用过程。 我们使用Anconda的虚拟环境进行安装&#xff0c;激活环境后&#xff0c;执行&#xff1a; pip install labelimg -i https://pypi.tuna.tsinghua.edu.c…

WebSocket 协议详述( java在线聊天室_上篇)

文章目录1、 WebSocket 协议1.1、 何为WebSocket&#xff1f;1.2、 websocket 和 http&#xff08;应用层的俩个协议&#xff09;1.3、 websocket协议的具体过程1.4、websocket好处2、 WebSocket实现2.1、 客户端实现2.1.1、 websocket对象2.1.2、 websocket事件2.1.3、 websoc…

【linux】linux中vim/vi (linux基本开发工具)

本期主题&#xff1a;linux中vim/vi的使用和介绍。博客主页&#xff1a;小峰同学分享小编的在Linux中学习到的知识和遇到的问题小编的能力有限&#xff0c;出现错误希望大家不吝赐 目录 &#x1f341;vim键盘图 &#x1f341;vim基本概念 &#x1f341;vim的基本操作 &#x1…

Python使用库(二)

Python使用库&#xff08;二&#xff09; 第三方库 认识第三方库 第三方库就是别人已经实现好了的库, 我们可以拿过来直接使用. 虽然标准库已经很强大了, 但是终究是有限的. 而第三方库可以视为是集合了全世界 Python 程序猿的智慧, 可以说是几乎无穷无尽. 问题来了, 当我们…

Linux驱动入门-最简单字符设备驱动(基于pc ubuntu)

一.字符设备驱动概念 字符设备是 Linux 驱动中最基本的一类设备驱动&#xff0c;字符设备就是一个一个字节&#xff0c;按照字节流进行读写操作的设备&#xff0c;读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI&#xff0c;LCD 等等都是字符设备&#xff0…

公共管理老师赴英国G5名校-伦敦大学学院CSC公派访学

CSC青年骨干教师项目的实施院校一般都要求申请人提前上报邀请函等申请材料&#xff0c;以进行校内遴选。为提升竞争优势&#xff0c;A老师希望能获得英国名校的邀请函。最终我们为其申请到英国G5名校之一的伦敦大学学院&#xff0c;凭借该邀请函&#xff0c;A老师顺利通过了本校…

【2022年终总结】勇敢追梦,去和人生博弈

目录序言刚开始的1月松懈的2月忙碌的3月迷茫的4月开源项目的5月入职汇报的6月7月8月9月假期过后的10月至关重要的11月最后冲刺的12月2022年的总结2023年的目标往年回顾序言 在刚刚过完的平安夜和圣诞节之际&#xff0c;同时意味着2022年要画上一个句号。这一周算是比较煎熬的几…

高效的事件处理模式——Reactor、Proactor

IO模型 从理论上说&#xff0c;阻塞IO、IO复用和信号驱动IO都是同步IO模型。因为在这三种IO模型中&#xff0c;IO的读写操作&#xff0c;都是在IO事件发生之后&#xff0c;由应用程序来完成的。而POSIX规范所定义的异步IO模型则不同。对异步IO而言&#xff0c;用户可以直接对I…

ubuntu18.04安装docker和nvidia-docker2

ubuntu18.04安装docker和nvidia-docker 1、卸载旧版本的docker 旧版本的 Docker 被称为 docker、docker.io 或 docker-engine。 如果安装了这些&#xff0c;需要卸载它们&#xff1a; sudo apt-get remove docker docker-engine docker.io containerd runc2、 使用存储库安装…

聊一聊 SQLSERVER 的行不能跨页

一&#xff1a;背景 1. 讲故事 相信有很多朋友在学习 SQLSERVER 的时候都听说过这句话&#xff0c;但大多都是记忆为主&#xff0c;最近在研究 SQLSERVER&#xff0c;所以我们从 底层存储 的角度来深入理解下。 二&#xff1a;理解数据页 1. 数据页的组织 在前面的文章中我…