【鸣潮,原神PC端启动器】仿二次元手游PC端游戏启动器,以鸣潮为例。 ...

打印 上一主题 下一主题

主题 563|帖子 563|积分 1689

二游GAMELauncher启动器

1.前言


  • 许多二次元手游(原神,鸣潮,少女前线)的PC端启动器都是利用Qt做的,正好最近正在玩鸣潮,心血来潮,便仿鸣潮启动器,从头写一个。先下载一个官方版的PC启动器,找到图标,背景图等素材,然后对着界面写代码就行。
  • 结果如下


2. 划分模块


  • 游戏启动器大致可以分为六部门

    • 主体窗口
    • 顶部标题栏
    • 公告栏
    • 轮播图
    • 游戏下载模块
    • 设置对话框

  • 模块划分后,要做的事就很清楚了,对每一个模块,都新建一个带ui(方便结构)的类,然后根据各模块功能分别实现,末了组装在一起就行。
3. 主体窗口


  • 主体窗口是一个无边框窗口,然后有动态的背景图,有logo,版本号,版本标题。

    • Qt中设置无边框窗口很简单,只要一行代码即可
      1.   this->setWindowFlags(Qt::FramelessWindowHint | windowFlags());
      复制代码
      这会导致窗口原本的移动事件和缩放事件无效,移动事件我们留在标题栏部门实现,缩放事件我们则不需要。
    • 动态背景图,其实是通过定时切换图片实现,这时我们很容易想到利用定时器实现,到时间就就加载下一张图片。
      如许做会有一个标题,加载图片是需要时间的,如许做界面会有卡顿感。我们可以先把所有图片加载到内存,然后就不需要加载了,可以解决卡顿感。
      但是这又会导致另一个标题,图片许多,全部加载会占用许多内存空间,不敷优雅。这里就需要用到线程,我们可以利用线程加载图片,然后通过信号把加载好的图片发送给主窗口就行绘制,如许既不卡顿也不占用许多内存。
    • 新建一个加载图片的类(LoadImage),继承QThread,实现run方法。
      1.    while (!stop)
      2.    {
      3.         if (!imgNameList.empty())
      4.         {
      5.             QPixmap pix = QPixmap(imgNameList[curIndex]);
      6.             curIndex = (curIndex + 1) % imgNameList.size();
      7.             emit sendPixmap(pix);
      8.         }
      9.         QThread::msleep(fps);
      10.     }
      复制代码
      我们可以设定帧数,让背景图实现指定帧率革新。
    • 如许我们就实现了背景图的切换

    • 然后我们把那些logo,版本号,标题,根据对应位置绘制上去就行了。logo 和 slogan都是图片来的。
      1. //绘制logo 和 slogan
      2.     p.drawImage(0,this->height()-slogan.height(),this->slogan);
      3.     p.drawImage(50,120,this->logo
      4.    //绘制版本号
      5.     QPen pen;
      6.     pen.setWidth(1);
      7.     pen.setColor(Qt::white);
      8.     p.setPen(pen);
      9.     QFont font("Arial", 12, QFont::Bold);
      10.     p.setFont(font);
      11.     p.drawText(10,this->height()-10,versionNumber);
      复制代码
    • 如许就得到了主体视觉图


  • 主体窗口还需要接收来自标题栏的移动,最小化,关闭的信号。
    1.     connect(ui->topBar,&TopBar::miniumWindow,[this]()
    2.     {
    3.         this->showMinimized();
    4.     });
    5.     connect(ui->topBar,&TopBar::closeWindow,[this]()
    6.     {
    7.         this->close();
    8.     });
    9.     connect(ui->topBar,&TopBar::moveWindow,[this](QPoint pos)
    10.     {
    11.         this->move(pos+this->pos());
    12.     });
    复制代码
4. 顶部标题栏


  • 标题栏用一个QWidget,然后把背景颜色设置成rgba(0,0,0,80) 就可以实现透明的样式。其它的控件就是很通例的,里面有些按钮是有渐变的背景色和底部有白线,我们可以用一个QWidget加一个QPushButton作为一个组件实现,利用QWidget控件方便绘制白线。还有鼠标悬浮时显示的类似气泡的对话框,这个对话框需要自己实现。

    • 气泡框可以通过把QWidget设置为无边框和透明窗口,然后里面绘制一个圆角矩形,然后再画一个三角形箭头即可。
      1. setWindowFlags(Qt::FramelessWindowHint);
      2. setAttribute(Qt::WA_TranslucentBackground);
      3. void BubbleWidget::paintEvent(QPaintEvent*event)
      4. {
      5.     QPainter painter(this);
      6.     painter.setRenderHint(QPainter::Antialiasing);
      7.     // 设置背景颜色和边框
      8.     painter.setBrush(Qt::white);
      9.     painter.setPen(QPen(Qt::gray, 1));
      10.     // 创建圆角矩形路径
      11.     QPainterPath path;
      12.     QRectF rect = this->rect().adjusted(1, 10, -1,1); // 为箭头留出空间
      13.     path.addRoundedRect(rect, 6, 6);
      14.     painter.drawPath(path);
      15.     path.clear();
      16.     // 添加三角形箭头
      17.     int arrowWidth = 15;
      18.     int arrowHeight = 6;
      19.     QVector<QPointF>points =
      20.     {
      21.         QPointF(rect.center().x() - arrowWidth / 2, rect.top()),
      22.         QPointF(rect.center().x() + arrowWidth / 2, rect.top()),
      23.         QPointF(rect.center().x(), rect.top() - arrowHeight)
      24.     };
      25.     path.addPolygon(QPolygonF(points));
      26.     // 绘制路径
      27.     painter.drawPath(path);
      复制代码
    • 然后我们可以根据需要往这个气泡框设置不同的Layout,就可以实现不同的结构结果了。

    • 把其他控件放上就可以得到下面的标题栏,我们在这个类里面把移动,关闭,最小化的信号发给父窗口即可。

    • 然后得到一个带标题栏的窗口。


5. 公告栏


  • 公告栏可以用QFrame和QStackedWidget组合实现,每条公告需要自定义一个QWiget来表示,处置惩罚好气泡框提示以及绘制左则的竖线。剩下就是对样式的设置,需要慢慢调一下。

6. 轮播图


  • 轮播图利用QWiget和两个QPushButton实现,按钮固定在中间的左右两则,鼠标进入轮播图时显示。QWiget负责绘制轮播图片,图片切换是带一定动画结果的,不能直接切换图片。

    • 轮播图上利用缓出动画结果,使得切换图片时更平滑。我们可以利用Qt的属性动画QPropertyAnimation,让图片位置属性offset 按缓和曲线举行变动,然后根据属性变革绘制当前图片和下一张图片即可。
      1.   animation = new QPropertyAnimation(this, "offset");
      2.   animation->setStartValue(0.0);
      3.   animation->setEndValue(1.0);
      4.   animation->setDuration(400);
      5.   animation->setEasingCurve(QEasingCurve::OutCubic);   //缓出效果
      6.   void Carousel::paintEvent(QPaintEvent*e)
      7.   {
      8.        QPainter p(this);
      9.     if(!left)
      10.     {
      11.         p.drawImage(QRect(-width() * offset, 0, width(), height()), imgArr.at(curIndex).scaled(width(),height(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
      12.         // 绘制下一张图片
      13.         p.drawImage(QRect(width() * (1 - offset), 0, width(), height()), imgArr.at(nextIndex).scaled(width(),height(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
      14.     }
      15.     else
      16.     {
      17.         p.drawImage(QRect(width() * offset, 0, width(), height()), imgArr.at(curIndex).scaled(width(),height(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
      18.         // 绘制下一张图片
      19.         p.drawImage(QRect(-width() * (1 - offset), 0, width(), height()), imgArr.at(nextIndex).scaled(width(),height(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
      20.     }
      21.   }
      复制代码
    • 结果如下


  • 图片举行缩放时要利用Qt::SmoothTransformation ,不然图片会很模糊。
7. 下载模块


  • 这个模块就比较简单,利用QWidget+QStackedWidget,实现下载界面和进入游戏界面的切换。

    • 有一些细节注意,QLineEdit要实现有个图标在最右侧可以利用addAction函数,添加一个图标。当QLineEdit的笔墨内容过长时,要让光标位于最开始位置,可以设置
      setCursorPosition(0) 。还需要把QLineEdit自动获取鼠标焦点功能禁用,设置setFocusPolicy(Qt::NoFocus)


8. 设置

<ul>设置界面比较贫苦,里面的QCheckBox和QRadioButton的结果无法通过QSS实现,需要重写,里面的漏斗形图形需要比较多步骤去绘制。

  • 重写QCheckBox
    1. void paintEvent(QPaintEvent *event) override
    2.     {
    3.         QCheckBox::paintEvent(event);
    4.         QPainter painter(this);
    5.         // 绘制复选框
    6.         QStyleOptionButton opt;
    7.         initStyleOption(&opt);
    8.         QRect checkBoxRect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt, this);
    9.         painter.setRenderHint(QPainter::Antialiasing);
    10.         if (isChecked())
    11.         {
    12.             // 绘制选中时的圆角背景
    13.             painter.setBrush(QColor("#BB9F5E"));  // 设置选中时的背景颜色
    14.             painter.setPen(Qt::NoPen);  // 去除边框
    15.         }
    16.         else
    17.         {
    18.             // 绘制未选中时的圆角边框
    19.             painter.setBrush(Qt::NoBrush);  // 不填充背景
    20.             painter.setPen(QPen(QColor("#8C8C8C"), 2));  // 使用灰色边框,线宽为2
    21.         }
    22.         // 绘制圆角矩形,圆角半径为3
    23.         painter.drawRoundedRect(checkBoxRect.adjusted(1, 1, -1, -1), 3, 3);
    24.         // 如果复选框被选中,绘制白色的勾
    25.         if (isChecked())
    26.         {
    27.             painter.setPen(QPen(Qt::white, 2));  // 设置勾的颜色为白色,线宽为2
    28.             // 使用 QPainterPath 绘制勾的形状
    29.             QPainterPath checkMarkPath;
    30.             checkMarkPath.moveTo(checkBoxRect.left() + checkBoxRect.width() * 0.3, checkBoxRect.center().y());
    31.             checkMarkPath.lineTo(checkBoxRect.center().x()-2, checkBoxRect.bottom() - checkBoxRect.height() * 0.3);
    32.             checkMarkPath.lineTo(checkBoxRect.right() - checkBoxRect.width() * 0.3, checkBoxRect.top() + checkBoxRect.height() * 0.35);
    33.             painter.drawPath(checkMarkPath);  // 绘制勾
    34.         }
    35.     }
    复制代码
重写QRadioButton
  1.   void paintEvent(QPaintEvent* event) override
  2.     {
  3.           QPainter painter(this);
  4.           painter.setRenderHint(QPainter::Antialiasing);
  5.           QStyleOptionButton option;
  6.           initStyleOption(&option);
  7.           painter.save();
  8.   
  9.   
  10.   
  11.           // 获取单选框的矩形区域
  12.           QRect radioButtonRect = style()->subElementRect(QStyle::SE_RadioButtonIndicator, &option, this);
  13.   
  14.           // 增大单选框的尺寸
  15.           int enlargedSize = 24;  // 自定义单选框的大小(增大后的大小)
  16.           radioButtonRect.setWidth(enlargedSize);
  17.           radioButtonRect.setHeight(enlargedSize);
  18.   
  19.   
  20.   
  21.           painter.setBrush(Qt::NoBrush);  // 不填充背景
  22.           painter.setPen(QPen(QColor("#8C8C8C"), 2));  // 使用灰色边框,线宽为2
  23.   
  24.           // 绘制增大的圆形的单选框
  25.           QRect circleRect = radioButtonRect.adjusted(2, 2, -2, -2); // 调整绘制圆形的位置
  26.           painter.drawEllipse(circleRect);
  27.   
  28.           // 如果当前单选框被选中,则填充中心
  29.           if (isChecked())
  30.           {   
  31.   
  32.               painter.setPen(QPen(QColor("#BB9F5E"), 2));
  33.               painter.drawEllipse(circleRect);
  34.   
  35.   
  36.               painter.setBrush(QColor("#BB9F5E"));   // 设置选中时的填充颜色为白色
  37.               painter.drawEllipse(circleRect.adjusted(5,5, -5, -5));  // 绘制小圆圈,表示选中
  38.           }
  39.   
  40.           // 绘制文本,确保文本位置对齐
  41.           QRect textRect = option.rect;
  42.   
  43.           // 将文本左移,使其与增大的单选框右边对齐
  44.           textRect.setLeft(radioButtonRect.right() + 5);  // 将文本移到单选框右侧
  45.   
  46.           // 使文本垂直居中
  47.           textRect.moveTop(radioButtonRect.top() + (radioButtonRect.height() - textRect.height()) / 2);
  48.           painter.restore();
  49.   
  50.           // 使用默认的文本颜色(由样式表和控件状态决定)
  51.           style()->drawItemText(&painter, textRect, Qt::AlignVCenter, option.palette, isEnabled(), option.text);
  52.       }
复制代码
绘制设置框的线条和图形。
[code]void Setting::paintEvent(QPaintEvent*event){         QPainter p(this);      QPen pen;      QPainterPath path;      pen.setColor(QColor("#CFCFCF"));//CFCFCF      pen.setWidth(2);      p.setPen(pen);        //画顶部线条      int x = ui->labelSetting->pos().x();      int y = ui->labelSetting->pos().y()+ui->labelSetting->height()+10;      p.drawLine(x,y,x+this->width()-40,y);              //画圆弧      int aw = 20;      int endx = x+this->width()-35;      int endy = y;        int cx = endx-aw;      int cy = endy-aw;        int tx = cx-aw;      int ty = cy-aw;        //int bx = cx+aw;      int by = cy+aw;          int startAngle = 270;      int spanAngle = 80;      double rr = aw;        int cx1 = tx+aw;      int cy1 = ty+aw;      double ex1 = cx1 + rr * cos((startAngle + spanAngle) * 3.14 / 180);      double ey1 = cy1 - rr * sin((startAngle + spanAngle) * 3.14 / 180);        startAngle = 90;      spanAngle  = -80;      int cx2 = tx+aw;      int cy2 = by+aw;      double ex2 = cx2 + rr * cos((startAngle + spanAngle) * 3.14 / 180);      double ey2 = cy2 - rr * sin((startAngle + spanAngle) * 3.14 / 180);            p.setBrush(QColor("#333333"));      p.setPen(Qt::white);      path.moveTo(cx,by);      path.lineTo(ex1,ey1);      path.lineTo(ex2,ey2);      path.lineTo(cx,by);      p.drawPath(path);            path.clear();      path.moveTo(cx,by);      QRect r (tx,ty,aw*2,aw*2);  //x,y,width,height      QRect r2(tx,by,aw*2,aw*2);        pen.setWidth(1);      p.setPen(pen);      p.setRenderHint(QPainter::Antialiasing);      path.arcTo(r,270,80);      path.moveTo(cx,by);      path.arcTo(r2,90,-80);      p.fillPath(path,Qt::white);            //画顶部线条      pen.setColor(QColor("#464646"));      pen.setWidth(2);      p.setPen(pen);      p.drawLine(x+this->width()-35,0,x+this->width()-35,this->height());          //画右边线条      int s1x = ui->btnCancel->pos().x();      int s1y = ui->btnCancel->pos().y()-20;      int s2x = ui->btnOk->pos().x()+ui->btnOk->width();        pen.setColor(QColor("#CBCBCB"));      pen.setWidthF(1.5);      p.setPen(pen);      p.drawLine(s1x,s1y,s2x,s1y);          //画右下角三角形      QPoint p1(x+this->width()-35,this->height()-10);      QPoint p2(x+this->width()-35,this->height());      QPoint p3(x+this->width()-45,this->height());          QPolygon cons;      cons

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

星球的眼睛

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表