Qt/C++项目Galgame游戏《Luck No Complete》技术点整理

2024-01-02 19:30

本文主要是介绍Qt/C++项目Galgame游戏《Luck No Complete》技术点整理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 项目回顾

    本项目初步尝试Data-Tool-UI三层结构框架,初步尝试使用json文件对程序进行外部数据配置,整理音源播放、存档存读、自定义组件的页面切换和数据传递等功能,同时探索了轮播图、可平滑伸缩角色栏、看板娘动作切换、指令驱动等技术。

  • 项目地址及技术点整理文档放在github上:https://github.com/AuraKaliya/MyGalgame2


  • Json文件读取的几种方式

    通用读取流程:在使用Qt进行Json文件读取时,通常需要引入以下文件:

     #include <QJsonDocument>#include <QJsonObject>#include <QJsonArray>#include <QJsonValue>

    在读取类中应存在一个read()函数、一个setPath(QString)函数。

    标准开头如下,根据实际需求进行增删:

     QFile file(m_filePath);if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {// 打开文件失败return;}​QString jsonStr = file.readAll();file.close();​QJsonParseError parseError;QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &parseError);if (parseError.error != QJsonParseError::NoError) {// 解析json内容失败return;}

    在探索如何进行数据读取的道路上,本人尝试过以下方法:

    ①自定义结构体进行临时数据存储和传递,在相应类中进行处理。

    这种方案其实算“失败”的案例,因为“相应类”通常为Qt的组件,所需要转化的数据为QString、QRect、QSize等,不如一开始就以这些数据结构进行临时数据的读取。

    ②仅载入配置的数据信息,在程序内的UI层进行页面的构建,通过Init()函数确保单例页面的实例化,通过QMap<QString,QWidget*>进行指定配置。

    这种方案还算成功,效果显著,但美中不足的是项目的拓展性不够,仅配置数据能在外部进行更改,初始化方案在程序内写死。此外,读取类需要引入大量的页面类文件,同时也需要把QMap写成Static形式,而基于QMap进行寻找相应页面类的读取函数也必须要以Static形式出现,这种情况对一个工程项目而言并不好。

    此方案用到一个技术点是通过typeof关键字定义函数指针,在读取类的构建时进行字典构建和相应类的初始化。

     typedef void (*FUNC_Read)(UIWidget*,const QJsonObject&);static QMap<QString,FUNC_Read> m_ReadFunction;​//这个想法应当被记录,虽然用在此处并不合适,但不失为一种替代性的反射机制的实现办法​

    ③尝试将单独的页面配置文件合并为整体的配置文件,通过结构化手段进行读取。

    这种方案是一个成功的尝试,并在下一个项目《Mirro And Flowers》中得到良好的应用。整体思路是提取两个配置文件中的共同点,将配置方法进行归类划分,并以过程的方式进行配置。初步实践中采用Name、Rect、Setting、Contains的方式实现读取流程,在Setting中进行读取函数的更详细划分;后续改进为从QWidget中引出一个UIWidget作为项目UI页面的基类,通过调用该BaseClass的虚函数实现Setting中的配置,这样不仅提高了程序的拓展性,也使项目构建过程中更有逻辑,使读取类的结构变得简单易懂。

  • 通过Updater类实现的组件行为/状态监测

    其实,这个技术点应该属于观察者模式的应用,其思路是使用一个观察者类Updater,通过addObject函数添加监控对象,并提供监测通道(如信号传递),在Updater类的内部实现功能分化的槽函数UpdateLabel/UpdateWidget/UpdateImage等,达到通过中间类来调整整个项目的组件状态转化的功能。

    在本项目中,本人应用该方法进行自定义Label的点击状态的切换,在JumpLabel中仅配置需要切换的图片地址和跳转目标指针,鼠标点击事件发生时,由Updater进行捕获并执行图片切换(即Label被按下状态)和跳转至目标页面。

  • 通过指令系统实现的事件触发功能

    这是对反射机制的一个实践,通过预编译实现反射方法并在进入剧情时顺序处理剧情文本、剧情相关角色、剧情后续影响,这是一个成功的尝试,能够让开发者在程序外通过对文本的调整达到影响角色相关属性(如好感度)的功能。

    相关代码:

     //指令类和工厂类的实现基于反射机制,不再赘述。//指令处理类#ifndef INSTRUCTIONSOLVER_H#define INSTRUCTIONSOLVER_H​#include <QObject>#include "instruction.h"#include <QString>#include <QVector>#include <QMap>#include <QMutex>​class InstructionSolver : public QObject{Q_OBJECTpublic:static InstructionSolver* getInstance(QObject* parent = nullptr); //获取单例对象void init(Instruction &instruction);void solution();  //处理指令​​typedef std::function<void(QString name, QString value)> FuncPtr;​signals:void playerGetMoney(QString name,QString value);  //玩家获取金币void playerLoseMoney(QString name,QString value); //玩家失去金币void playerGetAchievement(QString name,QString serialNumber); //达成成就​void storyEnding(QString name,QString serialNumber);  //剧情完成void storyUnlock(QString name,QString serialNumber);  //剧情解锁void storyJump(QString name,QString serialNumber);//剧情跳转​void characterLoveUp(QString name,QString value);   //角色好感度上升void characterLoveDown(QString name,QString value); //角色好感度下降void characterUnlock(QString name,QString serialNumber); //角色解锁​​​private:static InstructionSolver* instance;static QMutex mutex;QMap<QString, FuncPtr> funcTable;Instruction m_instruction;​explicit InstructionSolver(QObject *parent = nullptr);InstructionSolver(const InstructionSolver&);​~InstructionSolver();​void emitPlayerGetMoney(QString name, QString value);void emitPlayerLoseMoney(QString name, QString value);void emitPlayerGetAchievement(QString name, QString serialNumber);void emitStoryEnding(QString name, QString serialNumber);void emitStoryUnlock(QString name, QString serialNumber);void emitCharacterLoveUp(QString name, QString value);void emitCharacterLoveDown(QString name, QString value);void emitCharacterUnlock(QString name, QString serialNumber);void emitStoryJump(QString name, QString serialNumber);};​#endif // INSTRUCTIONSOLVER_H​#include "instructionsolver.h"​InstructionSolver* InstructionSolver::instance = nullptr;QMutex InstructionSolver::mutex;​InstructionSolver::InstructionSolver(QObject *parent) : QObject(parent){// 构造函数​// 初始化指令与函数的映射funcTable["PlayerGetMoney"] = std::bind(&InstructionSolver::emitPlayerGetMoney, this, std::placeholders::_1, std::placeholders::_2);funcTable["PlayerLoseMoney"] = std::bind(&InstructionSolver::emitPlayerLoseMoney, this, std::placeholders::_1, std::placeholders::_2);funcTable["PlayerGetAchievement"] = std::bind(&InstructionSolver::emitPlayerGetAchievement, this, std::placeholders::_1, std::placeholders::_2);funcTable["StoryEnding"] = std::bind(&InstructionSolver::emitStoryEnding, this, std::placeholders::_1, std::placeholders::_2);funcTable["StoryUnlock"] = std::bind(&InstructionSolver::emitStoryUnlock, this, std::placeholders::_1, std::placeholders::_2);funcTable["CharacterLoveUp"] = std::bind(&InstructionSolver::emitCharacterLoveUp, this, std::placeholders::_1, std::placeholders::_2);funcTable["CharacterLoveDown"] = std::bind(&InstructionSolver::emitCharacterLoveDown, this, std::placeholders::_1, std::placeholders::_2);funcTable["CharacterUnlock"] = std::bind(&InstructionSolver::emitCharacterUnlock, this, std::placeholders::_1, std::placeholders::_2);funcTable["StoryJump"] = std::bind(&InstructionSolver::emitStoryJump, this, std::placeholders::_1, std::placeholders::_2);​}​InstructionSolver* InstructionSolver::getInstance(QObject* parent){if (instance == nullptr){QMutexLocker locker(&mutex);if (instance == nullptr){instance = new InstructionSolver(parent);}}return instance;}​InstructionSolver::~InstructionSolver(){// 析构函数}​void InstructionSolver::init(Instruction &instruction){m_instruction = instruction;}​​​void InstructionSolver::emitPlayerGetMoney(QString name, QString value){emit playerGetMoney(name, value);}​void InstructionSolver::emitPlayerLoseMoney(QString name, QString value){emit playerLoseMoney(name, value);}​void InstructionSolver::emitPlayerGetAchievement(QString name, QString serialNumber){emit playerGetAchievement(name, serialNumber);}​void InstructionSolver::emitStoryEnding(QString name, QString serialNumber){emit storyEnding(name, serialNumber);}​void InstructionSolver::emitStoryUnlock(QString name, QString serialNumber){emit storyUnlock(name, serialNumber);}​void InstructionSolver::emitCharacterLoveUp(QString name, QString value){emit characterLoveUp(name, value);}​void InstructionSolver::emitCharacterLoveDown(QString name, QString value){emit characterLoveDown(name, value);}​void InstructionSolver::emitCharacterUnlock(QString name, QString serialNumber){emit characterUnlock(name, serialNumber);}​void InstructionSolver::emitStoryJump(QString name, QString serialNumber){emit storyJump(name, serialNumber);}​void InstructionSolver::solution(){//Story#Player@Player#GetMoney@100// 提取指令QString targetAction = m_instruction.insObject();QString operation = m_instruction.insDoing();​// 根据指令映射到对应的函数并调用QString tmpStr, name, value;if (targetAction.contains("Player")) {tmpStr = "Player";name = targetAction.split("@")[1];} else if (targetAction.contains("Story")) {tmpStr = "Story";name = targetAction.split("@")[1];} else if (targetAction.contains("Character")) {tmpStr = "Character";name = targetAction.split("@")[1];}​QStringList opList = operation.split("@");QString funcName = tmpStr + opList[0]; // 修正funcName的构造方式value = opList[1];​if (funcTable.contains(funcName)){funcTable[funcName](name, value);}}​​//相关应用UI:剧情页面#ifndef STORYWIDGET_H#define STORYWIDGET_H​#include <QWidget>#include <QMap>​#include "uiwidget.h"#include "tachielabel.h"#include "talkshowwidget.h"#include "../DATA/Story/character.h"#include "../DATA/Story/characterhub.h"#include "../DATA/Story/story.h"​#include "../DATA/instructionfactory.h"​typedef void (*FuncP)(QString) ;​class StoryWidget : public UIWidget{Q_OBJECTpublic:static StoryWidget* getInstance(QWidget * parent=nullptr);​void setBackgroundPixUrl(QString) override;void initTalkShowWidget(TalkShowWidget * talkShowWidget,QRect rect) override;void initTachieLabel(TachieLabel* tachieLabel,QRect rect) override;static void setNowCharacter(QString name);static void setNowCharacterGesture(QString gesUrl);static void setStory(Story*);static void startStory();​​public slots:void nextSegment();​private slots:static void solveOption(QString);static void solveTalkText(QString);static void solveBackground(QString);static void solveCharacter(QString);static void solveGesture(QString);static void solvePlusIntroduction(QString);// void showOption();signals:void storyFinished();private:static StoryWidget* m_instance;explicit StoryWidget(QWidget *parent = nullptr);static  QMap<QString,FuncP> m_textTypeGroup;​static Character * m_nowCharacter;static Story * m_nowStory;//QStringListstatic QVector<QString> m_segmentGroup;static TachieLabel* m_talkerLabel;static TalkShowWidget * m_talkShowWidget;static  QPixmap* m_backgroundPix;static  int m_nowSegIdx;​};​#endif // STORYWIDGET_H​#include "storywidget.h"​StoryWidget * StoryWidget::m_instance=nullptr;​QMap<QString,FuncP> StoryWidget::m_textTypeGroup=QMap<QString,FuncP> ();​Character * StoryWidget::m_nowCharacter=nullptr;Story * StoryWidget::m_nowStory=nullptr;//QStringListQVector<QString> StoryWidget::m_segmentGroup= QVector<QString>();TachieLabel* StoryWidget::m_talkerLabel=nullptr;TalkShowWidget * StoryWidget::m_talkShowWidget=nullptr;QPixmap* StoryWidget::m_backgroundPix=nullptr;int StoryWidget::m_nowSegIdx=0;​​​StoryWidget::StoryWidget(QWidget *parent): UIWidget{parent}{m_textTypeGroup.insert(QString("Options"),&StoryWidget::solveOption);m_textTypeGroup.insert(QString("TalkText"),&StoryWidget::solveTalkText);m_textTypeGroup.insert(QString("Background"),&StoryWidget::solveBackground);m_textTypeGroup.insert(QString("Character"),&StoryWidget::solveCharacter);m_textTypeGroup.insert(QString("Gesture"),&StoryWidget::solveGesture);m_textTypeGroup.insert(QString("PlusIntroduction"),&StoryWidget::solvePlusIntroduction);}​StoryWidget *StoryWidget::getInstance(QWidget *parent){if(m_instance==nullptr){m_instance=new StoryWidget(parent);}return m_instance;}​void StoryWidget::setBackgroundPixUrl(QString url){m_backgroundPix=new QPixmap(url);//m_backgroundPix->load(url);}​void StoryWidget::initTalkShowWidget(TalkShowWidget *talkShowWidget, QRect rect){if(m_talkShowWidget!=nullptr) delete m_talkShowWidget;m_talkShowWidget=talkShowWidget;m_talkShowWidget->setGeometry(rect);connect(m_talkShowWidget->getStoryShowLabel(),&storyShowLabel::segmentFinished,StoryWidget::getInstance(),&StoryWidget::nextSegment);}​void StoryWidget::initTachieLabel(TachieLabel *tachieLabel, QRect rect){if(m_talkerLabel!=nullptr) delete m_talkerLabel;m_talkerLabel=tachieLabel;m_talkerLabel->setGeometry(rect);}​void StoryWidget::setNowCharacter(QString name){QString  headUrl=CharacterHub::getInstance()->findCharacter(name)->headPixPath();m_talkShowWidget->setHead(QPixmap(headUrl));}​void StoryWidget::setNowCharacterGesture(QString gesUrl){m_talkerLabel->setGesture(gesUrl);}​void StoryWidget::setStory(Story * s){m_nowStory=s;m_segmentGroup.clear();QStringList tmpSeg = m_nowStory->text().split("|");m_segmentGroup=tmpSeg;m_nowSegIdx=0;qDebug()<<"Story:"<<m_nowStory;for(auto it:m_segmentGroup)qDebug()<<it;}​void StoryWidget::startStory(){qDebug()<<"Now SegmentCount"<<m_segmentGroup.size();m_nowSegIdx=0;StoryWidget::getInstance()->nextSegment();}​void StoryWidget::nextSegment(){//1. 处理文本  分割出 文本类型-背景-人物-表情-文本-后续指令//处理采用策略模式 分割符号为|$  (,-#@)(option)    (因为指令分割为#@)//此处不需要进行反射调用,只有在指令处理时才调用反射机制//eg:    Options:我们一起吧-Story#Character@Luwies#LoveUp@10,没什么兴趣-Story#Character@Luwies#LoveDown@10|//       Background:测试背景1$Character:Luwies$Gesture:正常$TalkText:这是一个测试$PlusIntroduction:Story#Character@Luwies#LoveUp@10,Story#Player@Player#GetMoney@100​if(m_nowSegIdx<m_segmentGroup.size()){QString tmpText= m_segmentGroup[m_nowSegIdx];QStringList slipStr=tmpText.split("$");for(auto it : slipStr){QString tmpKey=it.split(":")[0];QString tmpValue=it.split(":")[1];qDebug()<<tmpKey<<" "<<tmpValue;if(m_textTypeGroup.contains(tmpKey))m_textTypeGroup.value(tmpKey)(tmpValue);}++m_nowSegIdx;}else{emit storyFinished();}}​void StoryWidget::solveOption(QString s){//先用,分割不同选项 用-分割文本和指令 调用处理类对指令进行处理QStringList tmpStrL=s.split(",");QVector<QString> optionGroup;QVector<QString>OptionResultGroup;for(auto it:tmpStrL){QString OptionText=it.split("-")[0];QString Instruction=it.split("-")[1];optionGroup<<OptionText;OptionResultGroup<<Instruction;}//进行Option显示​//处理指令for(auto it :optionGroup)qDebug()<<it;for(auto it:OptionResultGroup)qDebug()<<it;​}​void StoryWidget::solveTalkText(QString s){qDebug()<<"setText "<<s;StoryWidget::getInstance()->m_talkShowWidget->setText(s);}​void StoryWidget::solveBackground(QString s){qDebug()<<"m_backgroundPix "<<s;//StoryWidget::getInstance()->m_backgroundPix=QPixmap(s);}​void StoryWidget::solveCharacter(QString s){​StoryWidget::getInstance()->m_nowCharacter=CharacterHub::getInstance()->findCharacter(s);qDebug()<<CharacterHub::getInstance()->findCharacter(s)->getName()<<" "<<CharacterHub::getInstance()->findCharacter(s)->story();qDebug()<<StoryWidget::getInstance()->m_talkerLabel;​StoryWidget::getInstance()->m_talkerLabel->initCharacter(StoryWidget::getInstance()->m_nowCharacter,StoryWidget::getInstance()->m_talkerLabel->geometry());​StoryWidget::getInstance()->m_talkShowWidget->setHead(*new QPixmap(StoryWidget::getInstance()->m_nowCharacter->headPixPath()));​}​void StoryWidget::solveGesture(QString s){// qDebug()StoryWidget::getInstance()->m_talkerLabel->setGesture(StoryWidget::getInstance()->m_nowCharacter->getGesture(s));}​void StoryWidget::solvePlusIntroduction(QString s){qDebug()<<s;QStringList tmpStrL=s.split(",");for(auto it:tmpStrL){//处理指令QString insType=it.split("#")[2].split("@")[0];Instruction* ins=InstructionFactory::getInstance()->creatInstruction(QString("Instruction_"+insType));if(ins!=nullptr){qDebug()<<"Solute!";ins->soluteInstruction(it);}elseqDebug()<<"No Have This Ins";}}
  • 模拟原神的双状态角色界面UI

    这部分想法出来时先做了一个原型进行尝试,在获得成功后在本项目中应用。

    整体来说,是为一个页面设置两种状态(展开和收缩),规划两种状态的页面大小和位置,通过一个可移动滑块在规定区域内滑动,根据滑块的位置信息或相关反馈选择展开或收缩状态,技术难度并不大。

        相关代码:

//页面代码
#ifndef CHARACTERHUBWIDGET_H
#define CHARACTERHUBWIDGET_H#include <QWidget>
#include <QStackedLayout>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QScrollArea>#include <QMap>
#include <QVector>#include "jumplabel.h"
#include "searchblok.h"
#include "slideblock.h"
#include "../DATA/Story/characterhub.h"
#include "characterinfoshowwidget.h"class CharacterHubWidget : public QWidget
{Q_OBJECT
public:explicit CharacterHubWidget(QWidget *parent = nullptr);void initCharacterHub();void initRect(QRect showRect,QRect hideRect);void initCardSize(QSize cardSize,QSize cardSmallSize);void initAreaRect(QRect showRect,QRect hideRect);void setSpace(int sp);void setbackground();public slots:void hideWidget();void showWidget();void characterChoiced(int);signals:void characterClicked(Character*);void widgetHide();void widgetShow();
private:Searchblok * m_searchBlock;SlideBlock * m_slideBlock;QScrollArea * m_characterShowArea;QWidget * m_characterShowWidget;QVector <JumpLabel*> m_characterLabelGroup;QRect m_showRect;QRect m_hideRect;QSize m_cardSize;QSize m_cardSmallSize;int m_space;QRect m_showAreaRect;QRect m_showWidgetRect;QRect m_hideWidgetRect;QRect m_hideAreaRect;QStackedLayout *m_stackedLayout;QGridLayout *m_showLayout;QGridLayout *m_hideLayout;};#endif // CHARACTERHUBWIDGET_H#include "characterhubwidget.h"CharacterHubWidget::CharacterHubWidget(QWidget *parent): QWidget{parent}
{resize(450,900);setWindowFlags(Qt::FramelessWindowHint);//setAttribute(Qt::WA_TranslucentBackground,true);m_searchBlock=new Searchblok();m_searchBlock->setParent(this);m_searchBlock->setGeometry(25,50,400,30);m_characterShowArea=new QScrollArea(this);m_characterShowWidget=new QWidget(m_characterShowArea);m_characterShowArea->setGeometry(25,100,400,600);m_characterShowArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);m_characterShowArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);m_characterShowWidget->setWindowFlags(Qt::FramelessWindowHint);m_characterShowArea->setWidget(m_characterShowWidget);m_showAreaRect=m_characterShowArea->geometry();m_characterShowWidget->setGeometry(m_showAreaRect);// m_stackedLayout=new QStackedLayout();m_showLayout=new QGridLayout();// m_stackedLayout->
//    for(int i=1;i<5;i++)
//    {
//        JumpLabel * tmpJ=new JumpLabel(m_characterShowWidget);
//        m_characterLabelGroup<<tmpJ;
//        tmpJ->setPixmapPathGroup(":/UI/RESOURCE/test_CharacterCardPix"+QString::number(i)+".png",":/UI/RESOURCE/test_CharacterCardSmallPix"+QString::number(i)+".png");
//        tmpJ->setFixedSize(90,120);
//        m_showLayout->addWidget(tmpJ,(i-1)/3,(i-1)%3,Qt::AlignCenter);
//        m_characterShowWidget->resize(m_characterShowWidget->width(),((i-1)/3+1)*150);
//    }// m_characterShowWidget->setLayout(m_showLayout);m_showLayout->setSpacing(10);m_showLayout->setContentsMargins(0,5,0,5);m_showWidgetRect=m_characterShowWidget->geometry();//m_hideAreaRect=QRect(25,100,85,600);//m_hideWidgetRect=QRect(0,0,85,m_characterLabelGroup.size()*70);m_slideBlock=new SlideBlock();m_slideBlock->setParent(this);m_slideBlock->setRect(QRect(25,775,400,85));m_slideBlock->setGeometry(340,775,85,85);m_slideBlock->setFixPos(m_slideBlock->pos());connect(m_slideBlock,&SlideBlock::valueMax,this,&CharacterHubWidget::hideWidget);connect(m_slideBlock,&SlideBlock::reset,this,&CharacterHubWidget::showWidget);setStyleSheet("border-image:url(:/UI/RESOURCE/test_transparency3.png);");//hideWidget();}void CharacterHubWidget::initCharacterHub()
{int i=0;for(auto it:CharacterHub::getInstance()->getCharaList()){JumpLabel * tmpJ=new JumpLabel(m_characterShowWidget);m_characterLabelGroup<<tmpJ;tmpJ->setCardID(it->getID());//tmpJ->setPixmapPathGroup(":/UI/RESOURCE/test_CharacterCardPix"+QString::number(i)+".png",":/UI/RESOURCE/test_CharacterCardSmallPix"+QString::number(i)+".png");tmpJ->setPixmapPathGroup(it->cardPixPath(),it->cardSmallPixPath());tmpJ->setFixedSize(m_cardSize);
//        tmpJ->setFixedSize(m_cardSize);
//        m_showLayout->addWidget(tmpJ,(i)/3,(i)%3,Qt::AlignCenter);m_characterShowWidget->resize(m_characterShowWidget->width(),((i-1)/3+1)*150);m_showLayout->addWidget(tmpJ,(i)/3,(i)%3,Qt::AlignCenter);m_characterShowWidget->setLayout(m_showLayout);connect(tmpJ,&JumpLabel::choiceCard,this,&CharacterHubWidget::characterChoiced);++i;}m_hideWidgetRect=QRect(0,0,85,m_characterLabelGroup.size()*70);CharacterInfoShowWidget::getInstance()->initCharacterInfomation(CharacterHub::getInstance()->findCharacter(m_characterLabelGroup[0]->getCardID()));emit characterClicked(CharacterHub::getInstance()->findCharacter(m_characterLabelGroup[0]->getCardID()));
}void CharacterHubWidget::initRect(QRect showRect, QRect hideRect)
{m_showRect=showRect;m_hideRect=hideRect;
}void CharacterHubWidget::initCardSize(QSize cardSize, QSize cardSmallSize)
{m_cardSize=cardSize;m_cardSmallSize=cardSmallSize;
}void CharacterHubWidget::initAreaRect(QRect showRect, QRect hideRect)
{m_showAreaRect=showRect;m_hideAreaRect=hideRect;
}void CharacterHubWidget::setSpace(int sp)
{m_space=sp;
}void CharacterHubWidget::hideWidget()
{delete m_showLayout;m_showLayout=new QGridLayout();int i=0;for (auto it:m_characterLabelGroup){it->changePix();//it->setFixedSize(65,65);it->setFixedSize(m_cardSmallSize);m_showLayout->addWidget(it,i++,0,Qt::AlignCenter);}//m_characterShowArea->setGeometry(25,100,85,600);m_characterShowArea->setGeometry(m_hideAreaRect);m_characterShowWidget->setGeometry(0,0,m_hideAreaRect.width(),m_characterLabelGroup.size()*70);m_characterShowWidget->setLayout(m_showLayout);m_searchBlock->setVisible(false);resize(m_hideRect.size());emit widgetHide();}void CharacterHubWidget::showWidget()
{delete m_showLayout;m_showLayout=new QGridLayout();int i=0;for (auto it:m_characterLabelGroup){it->changePix();//it->setFixedSize(90,120);it->setFixedSize(m_cardSize);m_showLayout->addWidget(it,i/3,i%3,Qt::AlignCenter);++i;}m_characterShowArea->setGeometry(m_showAreaRect);m_characterShowWidget->setGeometry(0,0,m_characterShowArea->width(),((i-1)/3+1)*150);m_characterShowWidget->setLayout(m_showLayout);m_searchBlock->setVisible(true);resize(m_showRect.size());emit widgetShow();
}void CharacterHubWidget::characterChoiced(int id)
{Character* tmp=CharacterHub::getInstance()->findCharacter(id);CharacterInfoShowWidget::getInstance()->initCharacterInfomation(tmp);emit characterClicked(tmp); //  不行 需要新建一个用于paint立绘的类
}//滑块代码
#ifndef SLIDEBLOCK_H
#define SLIDEBLOCK_H#include <QPushButton>
#include <QObject>
#include <QRect>
#include <QMouseEvent>class SlideBlock : public QPushButton
{Q_OBJECT
public:SlideBlock();void setRect(QRect rect);void mousePressEvent(QMouseEvent * e)override;void mouseMoveEvent(QMouseEvent *e)override;void mouseReleaseEvent(QMouseEvent * e) override;void initPos();void resetPos();void setMouseStartPos(QPoint newMouseStartPos);void setFixPos(QPoint newFixPos);signals:void valueMax();void reset();
private:QRect m_abRect;QPoint m_mouseStartPos;QPoint fixPos;QPoint m_btnStarPos;int values;bool m_isPressed;};#endif // SLIDEBLOCK_H
#include "slideblock.h"SlideBlock::SlideBlock()
{m_isPressed=false;values=0;}void SlideBlock::setRect(QRect rect)
{m_abRect=rect;
}void SlideBlock::mousePressEvent(QMouseEvent *e)
{m_isPressed=true;m_mouseStartPos=e->pos();qDebug()<<m_mouseStartPos;m_btnStarPos=this->frameGeometry().topLeft();}void SlideBlock::mouseMoveEvent(QMouseEvent *e)
{if((m_isPressed&&(this->x()>=m_abRect.x()))&&(values==0)){QPoint distance=e->pos()-m_mouseStartPos;//this->move((m_btnStarPos+distance).x(),m_btnStarPos.y());this->move(distance.x()+this->x(),m_btnStarPos.y());//this->move(e->pos().x()+this->x()-e->pos().x(),m_btnStarPos.y());}
}void SlideBlock::mouseReleaseEvent(QMouseEvent *e)
{m_isPressed=false;if((this->x()<m_abRect.x()+10)&&(values==0)){this->move(m_abRect.x(),m_abRect.y());values=1;emit valueMax();}else{resetPos();if(values==1){emit reset();values=0;}}
}void SlideBlock::initPos()
{}void SlideBlock::resetPos()
{move(fixPos);
}void SlideBlock::setMouseStartPos(QPoint newMouseStartPos)
{// m_mouseStartPos = newMouseStartPos;//m_mouseStartPos=QPoint(0,0);qDebug()<<m_mouseStartPos;
}void SlideBlock::setFixPos(QPoint newFixPos)
{fixPos = newFixPos;
}
  • 轮播图实现

    参考网上的一些方案后,本人根据自己的理解进行了一些实践,相关实践结果已有相应的原型。

    实现思路为:确定当前图的Size和其余图的Size,通过容器对组件进行管理,设置整体前移和后移的方法并传递当前图片的下标,根据实际容器大小进行移动或不动,动画方面用到了<QPropertyAnimation>和<QParallelAnimationGroup>。

    该技术点有以下需要注意的地方:①两类Size的大小应该适当,组件间距设置应该适当。②对于临界点的判断。③切换当前组件时,若间距设置导致部分重叠,应该在动画开始时使用raise()函数将下一个当前组件进行置顶。

    代码有些长就不放在此处了,有兴趣的可以去Qt_Practice中看原型的代码。

        贴个地址:AuraKaliya/QT_practice: 这是在QT学习的过程中想到的一些好点子,由于学习性的目的只制作了半成品,不过觉得有趣便保留下来慢慢更新。 (github.com)

  • 鼠标点击水波纹效果

    这部分实现思路非常清晰,注意点是路径的构建,绘制应在效果类中的paintEvent里进行,配置应该在主窗体中进行触发。

    相关代码:
    #ifndef RIPPLE_H
    #define RIPPLE_H#include <QWidget>
    #include <QVariantAnimation>
    #include <QPainter>
    #include <QPainterPath>
    #include <QPoint>
    #include <QCursor>#define RIPPLE_RADIUS 15class Ripple : public QWidget
    {Q_OBJECT
    public:// 构造函数explicit Ripple(QWidget *parent = nullptr);// 重写QWidget的show方法void show();// 设置水滴颜色void setColor(QColor color);// 移动水滴到指定位置void move(const QPoint &point);signals:
    private slots:// 当水滴半径变化时,更新水滴的绘制void onRadiusChanged(QVariant value);private:// 绘制水滴void paintEvent(QPaintEvent *event) override;// 水滴动画QVariantAnimation *m_rippleAnimation;// 水滴当前半径int m_radius;// 水滴中心位置QPointF m_point;// 水滴颜色QColor m_color;// 显示水滴的路径QPainterPath m_showRip;// 隐藏水滴的路径QPainterPath m_hideRip;
    };#endif // RIPPLE_H#include "ripple.h"
    #include "qpainter.h"Ripple::Ripple(QWidget *parent): QWidget{parent}, m_rippleAnimation(nullptr), m_radius(0), m_point(QCursor::pos()), m_color(QColor(255,120,0,255))
    {// 初始化水滴大小和窗口属性setFixedSize(RIPPLE_RADIUS*2.5, RIPPLE_RADIUS*2.5);setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::CustomizeWindowHint | Qt::Window);// 设置窗口背景为透明setAttribute(Qt::WA_TranslucentBackground);// 关闭窗口时自动删除对象setAttribute(Qt::WA_DeleteOnClose, true);m_rippleAnimation=new QVariantAnimation(this);
    }void Ripple::show()
    {__super::show();// 启动水滴动画m_rippleAnimation->setStartValue(0);m_rippleAnimation->setEndValue(RIPPLE_RADIUS);m_rippleAnimation->setDuration(350);connect(m_rippleAnimation, &QVariantAnimation::valueChanged, this, &Ripple::onRadiusChanged);connect(m_rippleAnimation, &QVariantAnimation::finished, this, &Ripple::close);// 将水滴颜色的alpha通道设置为250m_color.setAlpha(250);m_rippleAnimation->start();
    }void Ripple::setColor(QColor color)
    {m_color=color;
    }void Ripple::move(const QPoint &point)
    {QPoint tp=point-QPoint(RIPPLE_RADIUS,RIPPLE_RADIUS);__super::move(tp);
    }void Ripple::onRadiusChanged(QVariant value)
    {if (m_radius < RIPPLE_RADIUS){// 水滴半径增加2像素m_radius += 2;// 设置颜色的alpha通道值,使水滴渐渐消失m_color.setAlpha(m_color.alpha() - 30);// 更新窗口绘制update();}
    }void Ripple::paintEvent(QPaintEvent *event)
    {// 清空路径m_showRip.clear();m_hideRip.clear();QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing);painter.setPen(Qt::NoPen);painter.setBrush(QBrush(m_color));// 计算水滴显示和隐藏的路径m_showRip.addEllipse(QPoint(RIPPLE_RADIUS + 2, RIPPLE_RADIUS + 2), m_radius + 2, m_radius + 2);m_hideRip.addEllipse(QPoint(RIPPLE_RADIUS + 2, RIPPLE_RADIUS + 2), m_radius, m_radius);m_showRip -= m_hideRip;// 绘制水滴painter.drawPath(m_showRip);
    }

这篇关于Qt/C++项目Galgame游戏《Luck No Complete》技术点整理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/563432

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定