用Qt实现一个桌面弹幕程序(三)--实现一个弹幕③



  • 不同种类弹幕的实现

    在上一篇文章中,杰洛君带大家实现了从屏幕右方飞行到左方的弹幕。\(^o^)/

    但是弹幕并不止这一种,有很多很多不同的弹幕种类,包括但不限于:

    • 顶部弹幕 -- -- 弹幕悬停于屏幕顶部中央一段时间
    • 底部弹幕 -- -- 弹幕悬停在屏幕底部中央一段时间
    • 逆向弹幕 -- -- 弹幕从屏幕的右方飞行到屏幕左方

    实现起来也是非常简单的~

    继续使用QPropertyAnimation作为弹幕的动画实现方式

    顶部和底部弹幕:

    开始位置和结束位置设置为屏幕中间即可
    设置停留时间
    动画结束时销毁自身

    代码实现:

    QPropertyAnimation * anim2=new QPropertyAnimation(this, "pos");
    anim2->setDuration(7000);
    anim2->setEasingCurve(QEasingCurve::Linear);
    this->setWindowOpacity(this->getTransparency());
            connect(anim2,SIGNAL(finished()),this,SLOT(deleteLater()));
    

    问题又来了,顶部弹幕和底部弹幕的坐标如何确定呢?

    这两种弹幕的显示是位于屏幕中央,所以很自然可以想到 用 屏幕的宽度 - 弹幕的宽度除以2 得到弹幕的横坐标位置。

    但是纵坐标呢?

    纵坐标位置应该和上一个弹幕位置相关,也就是说,纵坐标的位置应该为字体高度的整数倍,这样这两种弹幕的文字才能不重叠。

    所以记得用两个变量来记录顶部弹幕数量和底部弹幕数量哦~

    至于逆向弹幕的话就更简单了,把正向弹幕的起始位置和结束位置互换就可以了~这里就不用代码写出了~

    杰洛君 怒偷一懒233O(∩_∩)O哈哈~

    (p.s. Qt的动画效果有很多,感兴趣的同学可以了解一下 QEasingCurve 类 , 恩恩,就是上面代码中经常出现的 QEasingCurve::Linear 部分,在这里是用了线性,你可以切换不同的类型来体验体验他们的动画效果哦~O(∩_∩)O嗯! )

    来试试动画弹幕吧~

    杰洛君的弹幕是继承QLabel实现的~

    而QLabel是可以播放GIF的哦~

    于是乎,杰洛君 脑洞大开地想到了一个很好玩的东西~

    不如让一个GIF动画飞过屏幕吧~

    好吧,那就决定是你了,上吧!!! 小doge!!!

    doge

    杰洛君创建了一个Doge类

    基本设置和弹幕类似,但是不需要重载 paintEvent函数了~

    Doge类用到了QMovie

    在Doge类中杰洛君添加了一个 QMovie * movie 这个私有变量。

    令他的构造函数看起来是这样子的

    Doge(
    QWidget *parent, 
    QMovie * movie2,
    QRect rect,
    int type = 4,
    double Transparency = 1.00,
    int runTime=15000
    );
    

    初始化后

    调用QLabel的 QLabel::setMovie(QMovie * movie); 函数就可以在Label上显示GIF了~

    所以在这里movie2的创建是在Doge类外部的。

    在构造函数中movie2的值会赋值给私有变量movie,自然在析构函数中要加上delete这个movie指针的代码,不然就内存泄露了。

    QMovie 类用法

    QMovie * movie = new QMovie(":/project/rundoge");
    movie->start(); //启动动画
    movie->setPaused(true); //暂停动画
    movie->stop(); //停止动画

    那上面代码中的":/project/rundoge" 是什么呢?

    这个是Qt中用资源文件进行管理文件时 文件的引用方式

    右键点击项目可以新建资源文件,在里面可以添加项目要用到的资源文件

    比如 杰洛君的项目中

    0_1457010603276_20160217221427169.jpg

    我给这个gif文件 定义了前缀为 /project 别名为 rundoge

    所以访问这个文件就是用 ":/project/rundoge" 进行访问啦~

    还不清楚的话,可以用搜索引擎搜索一下 Qt 资源文件 学习更加详细的用法~

    来试试图片弹幕吧~

    除了GIF 有时一些好玩的图片也是你希望能够放上屏幕让它飞过的,比如一些图片表情(手动滑稽)

    这时就可以用 QPixmap来加载图片了,与上文中的 QMovie类似,QPixmap在构造函数中传入图片资源即可。

    初始化后再用 QLabel中的setPixmap(const QPixmap & )函数即可

    为了让图片显示完全,设置弹幕窗体大小和图片大小一致即可,如果需要缩放 QPixmap 也有提供scaled 函数给你使用。这个函数参数比较多,快快选中它 按下F1 进行学习吧~

    小 D : 感觉从上到下 说明越来越水了。。。(#‵′)凸

    杰洛君 : 冤枉冤枉!重点在下面呢~

    来试试图片弹幕吧~(重点)

    制作一个南小鸟动画~

    相信看过LoveLive的同学都知道一个非常经典的表情

    0_1457010642644_LLleft.png

    它一般从画面的左下角缓慢/快速探出头,停留在左下角一段时间,再缓慢/快速收回去

    这个如何实现呢?

    没错,还是用Qt 的动画类

    只不过这次不同,这里应该有三个动画 探出 停留 收回

    而且这三个动画是顺序执行的,于是这里 杰洛君 就用了

    QSequentialAnimationGroup类

    这个类名中我们看到了Sequential 说明动画组中 动画的执行顺序是线性的,也就是有顺序,和加入组中的顺序有关,只要动画顺序加入这个组就可以了~

            group = new QSequentialAnimationGroup(this);
      
            anim1=new QPropertyAnimation(this, "pos");
            anim1->setDuration(100);
            anim1->setStartValue(QPoint(this->getPosX(),this->getPosY()));
            anim1->setEndValue(QPoint(this->getPosX(),this->getPosY()));
            anim1->setEasingCurve(QEasingCurve::Linear);
      
            anim2=new QPropertyAnimation(this, "pos");
            anim2->setDuration(200);
            anim2->setStartValue(QPoint(this->getPosX(),this->getPosY()));
            anim2->setEndValue(QPoint(0, rect.height()-pic.size().rheight()));
            anim2->setEasingCurve(QEasingCurve::Linear);
      
            anim3=new QPropertyAnimation(this, "pos");
            anim3->setDuration(500);
            anim3->setStartValue(QPoint(0, rect.height()-pic.size().rheight()));
            anim3->setEndValue(QPoint(0, rect.height()-pic.size().rheight()));
            anim3->setEasingCurve(QEasingCurve::Linear);
      
            anim4=new QPropertyAnimation(this, "pos");
            anim4->setDuration(200);
            anim4->setEndValue(QPoint(this->getPosX(),this->getPosY()));
            anim4->setStartValue(QPoint(0, rect.height()-pic.size().rheight()));
            anim4->setEasingCurve(QEasingCurve::Linear);
      
            group->addAnimation(anim1);
            group->addAnimation(anim2);
            group->addAnimation(anim3);
            group->addAnimation(anim4);
    

    上面的代码也是非常耿直的~

    (p.s. 既然有SequentialGroup 自然也会有 QParallelAnimationGroup ,感兴趣的小伙伴可以了解一下哦~)

    后续

    今天带大家实现了各种各样奇葩弹幕呢~

    那么弹幕的介绍大概就到这里,接下来会讲解主窗体的建立和设置。

    看到未来道阻且长,杰洛君不禁一声长叹~(╯3╰) 哎~

    不过我不会就这样轻易地狗带的!加油,记录自己做过的每一个程序~


  • 网站研运

    @杰洛飞 谢谢分享!你的博客让我找到了如何在论坛中播放gif的方法!真是帮助太大了。


  • 网站研运

    @杰洛飞 说:

    QPropertyAnimation

    这个是以前用过的类,我以前还用它来制作角色移动的二维动画呢。
    链接在这里



  • @jcy 嗯嗯,我发现直接发gif是不能播放的,只能用外链的形式~谢谢你给的链接分享啦~


Log in to reply
 

走马观花

最近的回复

  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more
  • 843143141.jpg
    闲下来了,我又开始大规模地学习了。
    最近开始学习内存模型和无锁结构。因为这个是和操作系统密切相关的,懂得这些对于编写C++服务端应用程序
    有着非常好的帮助。之前我对内存模型以及无锁结构几乎没有什么了解,我就询问群里的大佬看看有没有可以参考的资料。
    大佬很高兴,并且推荐了我一本名为《Memory Model》的电子书。这本电子书虽然页数不多,但是从起源到发展,
    从源码到汇编,都给我们详细地介绍了。看了一遍,不是非常理解,但是依然尝试将自己的理解写下来,以便日后翻阅。
    首先因为多核处理器成为主流,多线程的程序已经非常常见,因此我们不可避免地要处理多线程程序的同步问题。
    然后,因为编译器默认都对源码进行了优化,在单核处理器中这通常不是什么问题,但是在多核处理器中,就会因为编译器
    对其进行了乱序处理而导致程序出现问题。由此深入地探讨内存模型。
    内存模型主要分为:
    载-载 顺序(load-load order)
    载-存 顺序(load-store order)
    存-载 顺序(store-load order)
    存-存 顺序(store-store order)
    依赖载入顺序(dependent loads order)

    通过内存栅栏(memory barrier)能够避免编译器对指令的乱序。Linux中有

    READ_ONCE( x, value ) WRITE_ONCE( x )

    避免这些读写被编译器乱序或者是优化掉。

    这里谈到volatile关键字。在另外一篇博客上说,volatile具有“易变性、不可优化性、顺序性”。简单说,由于
    被volatile声明的变量,指令须从内存读取,并且不能被编译器乱序以及优化。在Java(语言扩展)和MSVC(系统兼容)上,
    还附带了Accquire()和Release()语义,因此可部分用于多线程环境。但多数情况下,还是慎用volatile,
    因为不同架构的处理器,它的内存模型是千变万化的,不能一而概之。

    至于C++11,它提供了std::atomic<T>这个模板类,相当于提供了很多方式来实现不同内存模型的原子操作。
    它的load()和store()方法,第二个参数有以下几个选项:

    std::memory_order_relaxed std::memory_order_seq_cst std::memory_order_acq_rel std::memory_order_acquire std::memory_order_release std::memory_order_consume

    我们最常用来实现RCpc(Release Consistency、Processor Consistency)是使用

    std::memory_order_acquire std::memory_order_release

    这两对。

    作为例子,在实现自旋锁时使用std::atomic<T>是这样的:

    struct SpinLock2 { void lock( ) { for ( ; ; ) { while ( lock_.load( std::memory_order_relaxed ) ); if ( !lock_.exchange( true, std::memory_order_acquire ) ) break; } } void unlock( ) { lock_.store( false, std::memory_order_release ); } std::atomic<bool> lock_ = { false }; };

    read more

关注我们

微博
QQ群