纯代码绘制艾尔登法环,并将其点燃🔥

2,234 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 14 天,点击查看活动详情


📒博客首页:何名取 的个人主页 - 文章 - 掘金 (juejin.cn)
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❤️期待一起交流!
🙏作者水平很有限,如果发现错误,求告知,多谢!
🌺有问题可私信交流!!!


QML Canvas类型

前言

前面在学习使用QML粒子系统时制作了一个动态的艾尔登法环,将法环上加上了火焰粒子。QML粒子系统,点燃🔥 艾尔登法环 - 掘金 (juejin.cn)在文章中我使用了遮罩的方式来发射粒子,遮罩使用的是一张处理过的透明背景图片。当时我观察到法环的形状并不复杂,主要部分可以分解为三个小圆和一个大圆。

            //! [0]
            anchors.fill: parent
            shape: MaskShape {
                source: "qrc:/ring.png"
            }
            //! [0]
elden.pngimage.png

Canvas2D可以让我们像画画一样绘制图形图案。因此,我们可以使用今天的主角QML Canvas来绘制出法环图形。

QML Canvas简介

Canvas项允许绘制直线和曲线、简单和复杂的形状、图形和参考的图形图像。它还可以添加文本、颜色、阴影、渐变和模式,并进行低级别像素操作。Canvas输出可以保存为图像文件或序列化到URL。

QML Canvas类型与HTML5大致相同,移植了现有的HTML5 Canvas API,不同之处在于一些事件的处理方式和增加了一些Qt特有的信号。比如将HTML中的事件处理转为了鼠标事件MouseArea item。

属性列表

属性类型描述
availablebool该属性用于设置Canvs是否可用,只有为true时后续的操作才有效。
canvasSizesize画布大小。默认情况下,画布大小与当前Canvas的width和height相同.
contextobject保存活动的绘图上下文。如果画布已经准备好,并且成功调用了getContext(),或者contextType属性已经设置了支持的上下文类型,那么这个属性将包含当前的绘图上下文,否则为空。
contextTypestring保存绘图上下文的类型,对于Context2D,该值将为"2d"
renderStrategyenumeration保存当前画布渲染策略,取值有以下几种:
Canvas.Immediate - 在UI主线程中立即执行图形命令(默认)
Canvas.Threaded - 当前要绘制的图形延迟到专用的线程里执行(不在UI主线程中立即执行)
Canvas.Cooperative - 当前要绘制的图形延迟到应用程序的全局渲染线程
renderTargetenumeration渲染目标.取值有以下几种:
Canvas.Image -渲染到内存中的图像缓冲区(默认)
Canvas.FramebufferObject -渲染到OpenGL帧缓冲区

信号列表

信号描述
imageLoaded()加载图像时发出此信号。
paint(rect region)当需要渲染区域时,会发出此信号。我们可以在该信号接收器里写入要绘制的内容。
painted()在执行所有上下文绘制命令并渲染Canvas(onPaint之后)后发出。

我们在进行绘制时主要使用的就是与paint相应的处理函数onPaint来实现图形图案。

函数列表

函数描述
cancelRequestAnimationFrame(handle)取消请求动画框架(句柄)
object getContext(contextId, ... args)返回图形上下文,如果没有可用的上下文,则返回null。在第一次调用getContext之后,后续再次调用时都将返回相同的上下文对象
isImageError(image)如果图像加载失败,则返回true,否则返回false。
isImageLoaded(image)如果图像已成功加载并准备好使用,则返回true。
isImageLoading(image)如果图像当前正在加载,则返回true。
loadImage(image)异步加载图像。一旦图像准备好,将发出imageLoaded()信号。加载的图像可以使用unloadImage()方法卸载。
markDirty(area)将给定区域标记为等待刷新的区域(只会刷新一次),只要该此区域为可见区,画布渲染器将重新绘制它。这将触发requestPaint信号。
int requestAnimationFrame(callback)动画计时器,它会自动根据电脑配置来计算刷新帧率,一般与屏幕刷新频率一致,它的返回值是一个计数值,可以获取当前调用了callback多少次
requestPaint()请求重新绘制整个可见区域,将会重新发出paint信号
bool save(filename)将当前画布内容保存到图像文件文件名中。保存的图像格式由文件名的后缀自动决定。成功时返回true,比如save("1.png")
string toDataURL(mimeType)返回画布中图像的数据URL。mimeType参数默认为“image/png”。
unloadImage(image)卸载图像后,除非再次加载图像(loadImage()、Context2D::createImageData()和Context2D::drawImage()),否则画布上下文将无法绘制图像

本节中我们主要使用toDataURL函数来将绘制出的法环导入到粒子系统中作为发射器的遮罩使用。

绘制法环

设计思路

前言中已经提到,法环的形状是由三个小圆和一个大圆构成。将大圆放置在画布正中,以大圆的圆心作为坐标原点。找出大圆的内心三角形的位置,它的三个边的中点就是三个小圆的圆心。小圆的直径则为这个三角形的边长。三个小圆呈“品”字护持在大圆上。

绘制大圆

                 ctx.beginPath();
                 ctx.arc(ox0, oy0, r, 0, 2 * Math.PI);
                 ctx.stroke();

将大圆的圆心设置为坐标原点,也就是画布的中心ox0和oy0,半径为r。

绘制小圆

绘制小圆时需要根据大圆的位置和半径来计算小圆的位置和半径。

上方小圆

                 var oy2 = r * Math.cos(Math.PI/3);
                 var r2 = r * Math.sin(Math.PI/3);
                 ctx.beginPath();
                 ctx.arc(ox0, oy0 - oy2, r2, 0, 2 * Math.PI);
                 ctx.stroke();

右侧小圆

                 var o1o3 = r * Math.sin(Math.PI/6);
                 ox3 = o1o3 * Math.cos(Math.PI/6);
                 oy3 = o1o3 * Math.sin(Math.PI/6);
                 ctx.beginPath();
                 ctx.arc(ox0 + ox3, oy0 + oy3, r2, 0, 2 * Math.PI);
                 ctx.stroke();

左侧小圆

                 ctx.beginPath();
                 ctx.arc(ox0 - ox3, oy0 + oy3, r2, 0, 2 * Math.PI);
                 ctx.stroke();

绘制上下圆弧

绘制完成圆环后,则需要将上下圆弧部分进行绘制。圆弧的绘制比较简单,上面是一段小圆弧,中间一条直线连接,下面一段大圆弧。

                     ctx.beginPath();
                     ctx.arc(ox0, oy0 - 4*r, 2*r, Math.PI/3, Math.PI - Math.PI/3);
                     ctx.moveTo(ox0, oy0 - 2*r);
                     ctx.lineTo(ox0, oy0 + 1.5*r);
                     ctx.stroke();
                     ctx.beginPath();
                     ctx.arc(ox0, oy0 - 2*r, 3.5*r, Math.PI/3, Math.PI - Math.PI/3);
                     ctx.stroke();

效果展示

image.png

点燃法环

加上粒子效果

既然法环已经绘制好了,那么就赶紧加入粒子系统来点燃它吧。

在这里为了显示出与之前的图片遮罩不同,加入了鼠标点击后再发射火焰的处理。在点击鼠标之前,canvas只对法环进行绘制,点击鼠标后,toDataURL()函数将绘制的图形的URL导入发射器中,开启粒子系统发射粒子。

鼠标处理

    MouseArea {
        anchors.fill: parent
        onClicked: {
            ringImage = canvas.toDataURL()
            particles.running = true
        }
    }

在此处使用了toDataURL()函数,将绘制的法环以URL进行返回。

发射器遮罩

            //! [0]
            anchors.fill: parent
            shape: MaskShape {
                source: ringImage
            }
            //! [0]
        }

这里的遮罩不再使用之前找到的图片素材,而是以上面canva绘制出法环图形作为遮罩。

效果展示

canvas.gif

点击下面的链接可以获取完整的代码。有兴趣的小伙伴可以持续关注。

感谢❤️💛💚💙💜💗

gitee.com/heyufeng471…