• Canvas 从 0 到 1 -- 开发 2D 游戏《保卫家园》-- 【上篇】


    初识 Canvas


    什么是 Canvas?

    Canvas 是 HTML5 提供的一种新的标签形式 - 画布标签

    通过名字就不难理解这个标签的意思,就和一张白纸一样,我们想呈现什么样的画面都可以(前提你需要一根画笔 – JavaScript)

    Canvas 是一个矩形画布,可以使用 JavaScript 在画布上进行作画,控制每一个像素,也就是说他本身不具备绘画功能,是需要 JavaScript 来进行作画的,Canvas 非常的丰富多彩,拥有多种的绘制路径,矩形,图形,字符,图像等等方法,使得 Web 网页更加的美丽

    主题图


    学会了 Canvas 我可以做什么?

    1. 游戏开发:比如我们将要制作的 2D 游戏《保卫家园》就是使用 canvas 开发的一款 2D 游戏,不过说回来 canvas 开发游戏在基于 Web 图像显示方面是比之前的 Flash 更加立体的,更加细节,在流畅度以及跨平台方面比 Flash 更加的友好,当然我自己并不是一个游戏开发者,这只是一个爱好,我本身是一名前端开发者,我感觉游戏开发又是另一个领域了,我们需要学习一些数学知识,物理知识,几何学等等,当然《保卫家园》并没有使用到很多的专业基础知识,仅仅涉及到了一些三角函数,勾股定理这些高中数学知识 😀
    2. 开发可视化数据图表:这个不需要我过多的阐述了,百度开源的 Echart 就是使用 canvas 进行绘制的
    3. banner 广告:我本身也经常的去浏览网站,所以提到广告字眼我就很讨厌,说到 banner 广告,我们不得不又一次提到 Flash ,那个时候还没有智能手机,HTML5 技术可以在 banner 广告上发挥不可一世的作用,所以用 Canvas 实现动态广告是很合适的
    4. 未来走向预测:模拟器(WebGL),远程计算机,图形编辑器,纯 Canvas 移动应用

    开始学习 Canvas 🚀

    首先我们说 Canvas 是 HTML5 的标签,那么我们新建画布的第一步就是创建一个 Cnavas 标签:

    DOCTYPE html>
    <html lang="en">
    	<head>
    		<meta charset="UTF-8" />
    		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
    		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
    		<title>Canvastitle>
    	head>
    	<body>
    		
    		<canvas>canvas>
    	body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    那么接下来就是 canvas 标签的一些常用属性

    • width
    • hright

    不建议使用 css 设置宽高,会被拉伸

    <canvas class="canvas" width="800" height="600">canvas>
    
    • 1

    JavaScript 创建 canvas 环境上下文 😀

    // 开启 canvas 环境(其实没有很大的必要 - 至少可以拥有一些 API 智能提示)
    /** @type {HTMLCanvasElement} */
    
    // 拿到 canvas节点
    const canvas = document.querySelector(".canvas")
    
    // 拿到 canvas 上下文(开启 2d 环境)
    const ctx = canvas.getContext("2d")
    // 如果开启 3D 环境的话,我们可以写入 webgl
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Canvas 坐标轴是怎样的? 🤔

    这里我就不做图示了,很简单啊,就是直角坐标系的第四象限嘛,Y 轴向下为正,X 轴向右为正,这很合理,和浏览器渲染方向是一样的(从左上角开始渲染)

    小试牛刀 - 画俩条连接线段

    // ctx 就是canvas上下文嘛,moveTo - 移动到 --(直角坐标系第四象限x:100,y::100)的位置
    ctx.moveTo(100, 100)
    // 现在我们已经有了一个点了(页面无显示)
    
    // 以上一个点拉长线条
    ctx.lineTo(200, 100)
    
    // 继续拉长当前线条到指定位置
    ctx.lineTo(100, 200)
    
    // 此时页面还无显示(我们只是把路径绘制出来了,没有描线)
    
    // 所以我们这一步填充线条
    ctx.stroke()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    线条

    闭合路径

    上面我们已经画出了俩条相连接的线段,接下来我们想画出第三条线以形成一个三角形,有俩种方案

    • 我们可以继续使用 lineTo 进行一个拉伸
    • 我们可以使用 closePath 进行一个闭合路径
    // 闭合路径
    ctx.closePath()
    
    • 1
    • 2

    线条颜色

    我们可以在描边之前给到这些线条一些自定义颜色 strokeStyle

    ctx.strokeStyle = "yellow"
    // or
    ctx.strokeStyle = "rgba(98,98,98,0.5)"
    
    • 1
    • 2
    • 3

    线的宽度

    依旧是在描边之前定义 lineWidth

    ctx.lineWidth = 10
    
    • 1

    线的宽度是以初始坐标点为基准进行内外扩展的,也就是说,我们给到的 10 像素宽,以 100 ~ 105 为一半宽度,100 ~ 95 为一半宽度,共同组合为了 10 像素的宽度

    填充

    填充在描边之后进行,对上面刚创建好的闭合图形进行一个填充 fill

    默认颜色为黑色,我们可以自定义颜色

    ctx.fill()
    
    • 1

    自定义填充颜色的话,我们需要在填充之前进行定义 fillStyle

    ctx.fillStyle = "green"
    
    • 1

    Canvas 基础实践应用(1) - 绘制表格

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const lineFunc = function () {
    	// 每条横线的间隔:
    	const restHeight = 10
    	const restWidth = 20
    
    	ctx.lineWidth = 0.5
    
    	// 绘制横线
    	for (let h = 0; h < canvas.height / restHeight; h++) {
    		ctx.moveTo(0, restHeight * h)
    		ctx.lineTo(canvas.width, restHeight * h)
    	}
    	// 竖线
    	for (let w = 0; w < canvas.width / restWidth; w++) {
    		ctx.moveTo(restWidth * w, 0)
    		ctx.lineTo(restWidth * w, canvas.height)
    	}
    	ctx.stroke()
    }
    
    lineFunc()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    表格


    Canvas - 什么是基于状态绘图

    假如现在我们有一个需求,需要画出三条颜色不同的线段 🤔

    乍一想好像很简单,这样不就可以实现了吗?

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const lineFunc = function () {
    	// first line
    	ctx.strokeStyle = "#D36118"
    	ctx.lineWidth = 16
    
    	ctx.moveTo(100, 100)
    	ctx.lineTo(300, 100)
    
    	ctx.stroke()
    
    	// second line
    	ctx.strokeStyle = "#8B95C6"
    	ctx.moveTo(100, 200)
    	ctx.lineTo(300, 200)
    	ctx.stroke()
    }
    
    lineFunc()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    看似没有错误,我们打开页面看一下发现俩条线的颜色都变成了最后我们赋值的样式颜色,这可不妙呀,我们想要画的是三条颜色不同的线段

    颜色一样的线条

    其实这个问题就出自于 Canvas 的基于状态绘图这么一个理念,那么我们在创建一个新状态不就可以了吗?正好我们拥有这个 API – beginPath

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const lineFunc = function () {
    	// first line
    	ctx.strokeStyle = "#D36118"
    	ctx.lineWidth = 16
    
    	ctx.moveTo(100, 100)
    	ctx.lineTo(300, 100)
    
    	ctx.stroke()
    
    	// second line
    	ctx.beginPath()
    	ctx.strokeStyle = "#8B95C6"
    	ctx.moveTo(100, 200)
    	ctx.lineTo(300, 200)
    	ctx.stroke()
    }
    
    lineFunc()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    对于这个状态的理解非常重要,也就是相当于开启了一个新的样式环境,我们绘制的第一条线其实是有一个默认状态的,如果之后绘图不开启新的状态,那么样式依旧会使用默认状态的样式,如果我们使用了 beginPath 开启了一个新的状态,那么默认状态依旧会进行继承,但是这个继承不是强制继承,如果之后的状态中的样式没有做出相应的修改才会进行继承,之后的状态样式一旦修改了,也就是与默认状态样式冲突了,那么基于状态这个理念,样式会进行一个覆盖,当前的状态样式覆盖点默认状态样式

    所以我们继续完成这个需求:

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const lineFunc = function () {
    	// first line
    	ctx.strokeStyle = "#D36118"
    	ctx.lineWidth = 16
    
    	ctx.moveTo(100, 100)
    	ctx.lineTo(300, 100)
    
    	ctx.stroke()
    
    	// second line
    	ctx.beginPath()
    	ctx.strokeStyle = "#8B95C6"
    	ctx.moveTo(100, 200)
    	ctx.lineTo(300, 200)
    	ctx.stroke()
    
    	// third line
    	ctx.beginPath()
    	ctx.strokeStyle = "#F0F64E"
    	ctx.moveTo(100, 300)
    	ctx.lineTo(300, 300)
    	ctx.stroke()
    }
    
    lineFunc()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    三条线


    闭合矩形的快速绘制

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const lineFunc = function () {
    	// rect语法:rect(X,Y,width,height)
    
    	ctx.rect(0, 0, 200, 100)
    	ctx.stroke()
    }
    
    lineFunc()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    或者我们还可以直接绘制一个矩形:

    // 描边矩形
    ctx.strokeRect(0, 0, 200, 100)
    
    • 1
    • 2
    // 填充矩形
    ctx.strokeRect(0, 0, 200, 100)
    
    • 1
    • 2

    清楚矩形:

    ctx.clearRect(0, 0, 200, 100)
    
    • 1

    圆形的快速绘制

    ctx.arc(x, y, 半径, 角度起始位置, 角度结束位置, 逆时针绘制)
    
    • 1

    角度

    实践出真知,我们画一个圆弧:

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const arcFunc = function () {
    	// 弧度转角度:deg  * Math.PI / 180
    	ctx.arc(500, 500, 100, (0 * Math.PI) / 180, (360 * Math.PI) / 180, false)
    	ctx.stroke()
    
    	ctx.beginPath()
    	// 从圆心点绘制圆弧
    	ctx.moveTo(300, 300)
    
    	ctx.arc(300, 300, 50, (0 * Math.PI) / 180, (37 * Math.PI) / 180)
    	ctx.closePath()
    	ctx.stroke()
    
    	// 逆时针绘制
    	ctx.beginPath()
    	// 从圆心点绘制圆弧
    	ctx.moveTo(150, 150)
    
    	ctx.arc(150, 150, 50, (0 * Math.PI) / 180, (37 * Math.PI) / 180, true)
    	ctx.closePath()
    	// 填充
    	ctx.fill()
    
    	ctx.stroke()
    }
    
    arcFunc()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    绘制圆形


    Canvas 基础实践应用(2) - 绘制扇形图

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    // 假设从后端请求来的数据
    const data = [
    	{
    		match: 1,
    		color: "#EFC544",
    		title: "完成事项",
    	},
    	{
    		match: 2,
    		color: "#290D48",
    		title: "完成事项",
    	},
    	{
    		match: 1,
    		color: "#A05AF9",
    		title: "完成事项",
    	},
    	{
    		match: 6,
    		color: "#43BDE5",
    		title: "完成事项",
    	},
    ]
    
    const arcFunc = function (x, y, radius, data) {
    	let deg = -90
    
    	data.forEach((item) => {
    		ctx.beginPath()
    		ctx.moveTo(x, y)
    
    		let angle = item.match * 36
    
    		ctx.fillStyle = item.color
    
    		let startAngle = (deg * Math.PI) / 180
    		let endAngle = ((deg + angle) * Math.PI) / 180
    		ctx.arc(x, y, radius, startAngle, endAngle)
    
    		ctx.fill()
    		deg += angle
    
    		ctx.closePath()
    		ctx.stroke()
    	})
    }
    
    arcFunc(300, 300, 100, data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    扇形图

    COOL !! 🤓 – 太有成就感了


    文字填充 - 角度求坐标

    像 EaChars 这样的图表库,当鼠标悬停在上方时可以显示对应标题以及百分比数据,那么问题来了,这些文字该怎么实现呢? 🤔

    怎么把文字放在一个合适的位置?假如我们的需求是将文字放置在每一个扇形的夹角中间位置,这个我们应该怎么去实现

    坐标画文字

    这就不得不谈论到我们的三角函数了 🤓

    就以上面的图来说

    x = 300 + Math.cos((deg * Math.PI) / 180) * (R + 20)
    
    y = 300 + Math.sin((deg * Math.PI) / 180) * (r + 20)
    
    • 1
    • 2
    • 3

    文字填充 - 绘制方法

    // 空心文字(无填充)
    ctx.strokeText("我想绘制这样一段文字", 500, 400)
    
    // 返回包含指定文本宽度的对象
    // ctx.measureText('Hello-Canvas')
    
    /******************************/
    ctx.moveTo(500, 500)
    ctx.fillStyle = "#290D48"
    // 字体样式
    ctx.font = "20px 幼圆"
    // 字体底线对齐绘制基线
    ctx.textBaseline = "bottom"
    // 字体对齐方式
    ctx.textAlign = "center"
    // 填充文字
    ctx.fillText("你好 画板 -- Hello Canvas", 300, 500)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    基线


    Canvas 基础实践应用(3) - 绘制文字 + 扇形图完结

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    // 假设从后端请求来的数据
    const data = [
    	{
    		match: 1,
    		color: "#EFC544",
    		title: "待完成事项",
    	},
    	{
    		match: 2,
    		color: "#290D48",
    		title: "以完成事项",
    	},
    	{
    		match: 1,
    		color: "#A05AF9",
    		title: "可选完成事项",
    	},
    	{
    		match: 6,
    		color: "#43BDE5",
    		title: "必要完成事项",
    	},
    ]
    
    const arcFunc = function (x, y, radius, data) {
    	let deg = -90
    
    	data.forEach((item) => {
    		ctx.beginPath()
    		ctx.moveTo(x, y)
    
    		let angle = item.match * 36
    
    		ctx.fillStyle = item.color
    
    		let startAngle = (deg * Math.PI) / 180
    		let endAngle = ((deg + angle) * Math.PI) / 180
    		ctx.arc(x, y, radius, startAngle, endAngle)
    
    		// 绘制文字
    		let text = `${item.title}:${item.match * 10}%`
    		let textAngle = deg + (1 / 2) * angle
    		let x0 = x + Math.cos((textAngle * Math.PI) / 180) * (radius + 30)
    		let y0 = y + Math.sin((textAngle * Math.PI) / 180) * (radius + 30)
    
    		// 在左半边的文字正常显示
    		if (textAngle > 90 && textAngle < 270) ctx.textAlign = "end"
    
    		ctx.font = "16px 幼圆"
    		ctx.fillText(text, x0, y0)
    
    		ctx.fill()
    		deg += angle
    
    		ctx.closePath()
    		ctx.stroke()
    	})
    }
    
    arcFunc(300, 300, 100, data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    完整扇形图


    图片的绘制

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1000
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const imgFunc = function () {
    	// 创建图片实例(或者说是获取到了图片的DOM对象)
    	const img_01 = new Image()
    	img_01.src = "./wallhaven-2879mg.png"
    
    	// 当我们的图片加载完毕之后再将图片绘制到 canvas 中
    	img_01.addEventListener("load", () => {
    		// 参数:图片,X轴位置,Y轴位置,宽度,高度
    		ctx.drawImage(img_01, 100, 100, 500, 260)
    		// 或许我们可以进行一个等比拉伸
    		ctx.drawImage(img_01, 300, 300, 500, 500 * (img_01.height / img_01.width))
    	})
    }
    
    imgFunc()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    美女图片

    😍😍😍😍😍😍😍😍 哈哈哈,请允许我这样,我对美女没有抵抗力


    帧动画

    其实对于图片的绘制还有很多的参数:

    ctx.drawImage(
    	图片,
    	裁剪图片的左上角X坐标,
    	裁剪图片的左上角Y坐标,
    	裁剪图片的宽度,
    	裁剪图片的高度,
    	图片所绘制的X坐标,
    	图片所绘制的Y坐标,
    	图片的宽度,
    	图片的高度
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    额…好像确实真的有点多了,不过为了帧动画我非常愿意学,帧动画可以说是游戏必备,其实帧动画就是一个精灵图,对于精灵图大家应该都不会陌生,话不多说,我们就拿下面这张图来做一个例子:

    人物示例

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1200
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const imgFunc = function () {}
    const img = new Image()
    img.src = "./怪兽.png"
    img.addEventListener("load", () => {
    	let frameIndex = 0
    	setInterval(() => {
    		ctx.clearRect(0, 0, canvas.width, canvas.height) // 清除上一步的墨迹(从0,0位置开始清除canvas的宽高)
    		ctx.drawImage(img, frameIndex * 900, 0, 900, 900, 300, 300, 200, 200)
    
    		frameIndex++
    		if (frameIndex === 8) frameIndex = 0
    	}, 100)
    })
    imgFunc()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    怪兽GIF

    让我们再来完善一下小人物的动作,比如 jump – 跳

    /** @type {HTMLCanvasElement} */
    
    const canvas = document.querySelector(".canvas")
    const ctx = canvas.getContext("2d")
    
    canvas.width = 1200
    canvas.height = 800
    canvas.style.border = "3px solid #000"
    
    const imgFunc = function () {}
    const img = new Image()
    img.src = "./怪兽.png"
    
    // 定义高度
    let peopleHeight = 300
    
    img.addEventListener("load", () => {
    	let frameIndex = 0
    	setInterval(() => {
    		ctx.clearRect(0, 0, canvas.width, canvas.height) // 清除上一步的墨迹(从0,0位置开始清除canvas的宽高)
    		ctx.drawImage(
    			img,
    			frameIndex * 900,
    			0,
    			900,
    			900,
    			300,
    			peopleHeight,
    			200,
    			200
    		)
    
    		frameIndex++
    		if (frameIndex === 8) frameIndex = 0
    	}, 100)
    })
    imgFunc()
    
    // jump
    const btn = document.querySelector(".button")
    
    btn.addEventListener("click", () => {
    	if (peopleHeight === 300) {
    		const moveInterval = setInterval(() => {
    			peopleHeight--
    			if (peopleHeight === 100) {
    				clearInterval(moveInterval)
    				const backInterval = setInterval(() => {
    					peopleHeight++
    					if (peopleHeight === 300) clearInterval(backInterval)
    				}, 4)
    			}
    		}, 2)
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    好吧,我承认自己写了一个回调地狱,不过功能却简单的实现了 😁

    jump

    这是上篇,因为作者本人写的慢,所以先将上篇写出来并发布了,之后我们会完整的做一个 2d游戏,并且我会将源代码以及资料免费上传到CSDN资源库 🚀

  • 相关阅读:
    生成对抗网络 GAN——Generative Adversarial Nets
    yyds,Elasticsearch Template自动化管理新索引创建
    面向需求,面向系统,物联网安全体系你知道多少?
    Android修行手册 - 模板匹配函数matchTemplate详解,从N张图片中找到是否包含五星
    信号完整性(SI)电源完整性(PI)学习笔记(三十)电源分配网路(二)
    Chat GPT 使用教学,文字创作、学习
    Java中时间工具类
    图解LeetCode——1704. 判断字符串的两半是否相似(难度:简单)
    MySql数据库应该这样优化
    【MySql】mysql之连接查询和存储过程
  • 原文地址:https://blog.csdn.net/weixin_63836026/article/details/126322472