Qt:击球游戏

2023-03-10 13:43:31 浏览数 (1)

近期做的一个小项目:Qt击球游戏,相当于二次开发增加附加功能。

已有功能:

代码语言:javascript复制
JSON文件读取;
球类生成;
球桌生成;
碰撞规则;

新增功能:

代码语言:javascript复制
代码语言:javascript复制
JSON文件新增节点读取;
球袋功能;
击球功能;
动能功能;
设计模式:必须 组合模式 其他模式(适配器); 
母球分裂功能;

开发环境:

代码语言:javascript复制
Ubuntu Qt5.10.1 QtCreator4.6.0

功能实现:

                球袋功能:

代码语言:javascript复制
        "pockets": [
            {"position":{"x":50,"y":50}},
            {"position":{"x":195,"y":195}},            
            {"position":{"x":5,"y":295},"radius":20},
            {"position":{"x":100,"y":100},"radius":25}

               球袋采用组合模式:

代码语言:javascript复制
struct pocket
{
    double x;
    double y;
    double radius;
};  //球袋属性结构体

组合模式:抽象类,采用未保护模式

代码语言:javascript复制
class CompontPocket
{
public:
    CompontPocket() {}
    virtual void Addpocket(CompontPocket *) = 0;
    virtual void Removepocket(CompontPocket *) = 0;
    virtual pocket  Output() = 0;
    virtual int getVecCount() = 0;
    virtual QVector <CompontPocket *> getvetor() = 0;
    virtual void render(QPainter& painter) = 0;
  /*  virtual CompontPocket * getItem(QVector<CompontPocket *>::iterator it) =0;*/
    virtual void PocketSetValue(double x,double y ,int rad)=0;
protected:
    pocket  p_pocket;
};

组合模式:叶子类 实现单一元素操作

代码语言:javascript复制
class LeafPocket : public CompontPocket
{
public:
    LeafPocket():CompontPocket() {}
    void Addpocket(CompontPocket *) {return ;}
    void Removepocket(CompontPocket *) {return ;}
    pocket  Output() {  return p_pocket;}
    int getVecCount() {return 0;}
    QVector <CompontPocket *> getvetor() {  QVector<CompontPocket * > a;     return  a; }
  //  CompontPocket * getItem(QVector<CompontPocket *>::iterator it) { CompontPocket * a; a = NULL; return a; }
    void PocketSetValue(double x,double y ,int rad)
    {
        p_pocket.x = x;
        p_pocket.y = y;
        p_pocket.radius = rad;
    }

    void render(QPainter& painter)
    {
        QVector2D m_point;
        m_point.setX(p_pocket.x);
        m_point.setY(p_pocket.y);
        // use our colour
        painter.setBrush(Qt::black);
        // circle centered
        painter.drawEllipse(m_point.toPointF(), p_pocket.radius, p_pocket.radius);
    }
};

组合模式:枝节点类 实现组合容器操作

代码语言:javascript复制
class CompositePockets: public CompontPocket
{    
public:    
//  CompositePockets():CompontPocket() {}
    void Addpocket(CompontPocket* m_Pocket)
    {
        m_Pockets.push_back(m_Pocket);
    }
    void Removepocket(CompontPocket * p_Pocket)
    {
        for(QVector<CompontPocket *>::iterator it=m_Pockets.begin();it!=m_Pockets.end();  it)
        {
            if(p_Pocket == *it)
            {
                m_Pockets.erase(it);
                break;
            }
        }
    }
    pocket  Output()
    {
      /*  for(QVector<CompontPocket *>::iterator it = m_Pockets.begin();it<m_Pockets.end();  it )
        {
            CompontPocket * poc = *it;
            poc->Output();
        }*/
        return p_pocket;
    }
    int getVecCount()
    {
        int Count = m_Pockets.size();
        return Count;
    }
    QVector<CompontPocket *> getvetor()
    {
        return m_Pockets;
    }
 /*   CompontPocket * getItem(QVector<CompontPocket *>::iterator it)
    {
        CompontPocket * Buf =*it;
        return Buf;
    }*/
    void PocketSetValue(double x,double y ,int rad)
    {
        p_pocket.x = x;
        p_pocket.y = y;
        p_pocket.radius = rad;
    }

    void render(QPainter& painter)
    {
        for(auto it = m_Pockets.begin();it!= m_Pockets.end();  it)
        {
            CompontPocket * pockets = *it;
            pockets->render(painter);
        }
    }

private:
    QVector <CompontPocket *> m_Pockets;  //球袋的集合
};

extern CompositePockets  p_CompositePockets;

击球功能: 适配器模式/实现比较简单,强行用设计者模式

代码语言:javascript复制
/*需适配者*/
class ballarm
{
public:
    virtual void render(QPainter& painter ,QVector2D m_startpos,QVector2D m_endpos) = 0;
};
代码语言:javascript复制
/*适配元素*/
class ColorBallarm
{
public:
    void DrawBallArm(QPainter& painter ,QVector2D m_startpos,QVector2D m_endpos)
    {
        QPen pen;
        pen.setColor(Qt::white);   //颜色:白色
        pen.setWidth(5);     //宽度:5
        painter.setPen(pen);
        painter.drawLine(m_startpos.toPointF(),m_endpos.toPointF());  //画线
    }
};
代码语言:javascript复制
/*适配器*/
class UseBallarm: public ballarm
{
public:
    UseBallarm():m_ColorBallarm(new ColorBallarm) {}
    ~UseBallarm();
    void render(QPainter &painter, QVector2D m_startpos, QVector2D m_endpos)
    {
        m_ColorBallarm->DrawBallArm(painter,m_startpos,m_endpos);
    }
private:
    ColorBallarm * m_ColorBallarm;
};

      设计者模式这一块也为初次接触,公司项目比较单一,很少接触这些,熬了一晚上,才看懂,稍微理解了一点组合模式和适配者模式。

代码语言:javascript复制
组合模式:抽象类,叶子类,枝节点类。

抽象类只提供虚函数接口,不作具体实现。

叶子类,实现单一元素的操作,设置属性,元素的具现之类的。

枝节点类,元素集合体,可作为根节点与枝节点。实现对集合的操作,元素的增删改。元素容器一般在私有类里边,作为对元素的保护。对外提供接口时,尽量使用变量,少使用指针,作为安全保护。

我没有采用保护者模式,保护者模式,抽象类,只抽像单一元素操作,不提供枝节点类的操作接口。
代码语言:javascript复制
适配者模式:我目前的理解,就拿电脑显示接口举例,电脑只提供了对外输出的VGA视频接口,但是目前需要HDMI接口的电脑,将电脑和HDMI转接头差分为两个元素。
两个适配器组成的类,电脑作为基础类,HDMI作为适配元素,将两个元素组合在一起,将变成了一个新的类,提供HDMI接口的电脑。 

JSON元素读取:

提供的JSON数据格式:

代码语言:javascript复制
    "balls" : [
        {"colour":"white","position":{"x":50,"y":50},"velocity":{"x":20,"y":50},"mass":2,"radius":30,"strength":1e5},
        {"colour":"red","position":{"x":150,"y":60},"velocity":{"x":-20,"y":20},"mass":2,"radius":100,"strength":1e5},
        {"colour":"blue","position":{"x":550,"y":320},"velocity":{"x":-150,"y":80},"mass":2,"radius":15,"strength":1e5},
        {"colour":"yellow","position":{"x":450,"y":200},"velocity":{"x":100,"y":-80},"mass":1,"radius":10,"strength":1e4},
        {"colour":"#123456","position":{"x":250,"y":70},"mass":1,"radius":20,"strength":1e5},
        {"colour":"#123456","position":{"x":250,"y":70},"mass":1,"radius":20,"strength":1e5}
            ]

新的JSON数据格式。数据格式有单一层级,变为了多层数据元素。

代码语言:javascript复制
    "balls" : [
        {"colour":"white","position":{"x":50,"y":50},"velocity":{"x":20,"y":50},"mass":2,"radius":30,"strength":1e5},
        {"colour":"red","position":{"x":150,"y":60},"velocity":{"x":-20,"y":20},"mass":2,"radius":100,"strength":1e5},
        {"colour":"blue","position":{"x":550,"y":320},"velocity":{"x":-150,"y":80},"mass":2,"radius":15,"strength":1e5},
        {"colour":"yellow","position":{"x":450,"y":200},"velocity":{"x":100,"y":-80},"mass":1,"radius":10,"strength":1e4},
        {"colour":"#123456","position":{"x":250,"y":70},"mass":1,"radius":20,"strength":1e5,
         "balls":[
             {"colour":"red","position":{"x":-10,"y":0},"velocity":{"x":100,"y":10},"mass":1,"radius":10},
             {"colour":"red","position":{"x":20,"y":0},"velocity":{"x":100,"y":10},"mass":1,"radius":30},
             {"colour":"red","position":{"x":0,"y":-10}},
             {"position":{"x":0,"y":10}, "mass":2,"strength":1e4,
                 "balls":[
                             {"colour":"blue","strength":1e3,
                                 "balls":[
                                            {"colour":"red", "strength":1e4}
                                         ]
                             }
                          ]
             }
                ]
        },
        {"colour":"#123456","position":{"x":250,"y":70},"mass":1,"radius":20,"strength":1e5,
         "balls":[
             {"colour":"red","position":{"x":-10,"y":40},"velocity":{"x":100,"y":10},"mass":1,"radius":10},
             {"colour":"red","position":{"x":60,"y":0}},
             {"colour":"red","position":{"x":70,"y":-10}}
                ]
        }
   ]

  JSON数据的读取:

代码语言:javascript复制
QJsonObject loadConfig() {
    // load json from config file
    QFile conf_file(config_path);
    conf_file.open(QIODevice::ReadOnly | QIODevice::Text);
    QString content = conf_file.readAll();
    conf_file.close();
    QJsonObject config = QJsonDocument::fromJson(content.toUtf8()).object();
    return config;
}
代码语言:javascript复制
 // for each of our balls, construct them
    QJsonArray ballData = m_conf->value("balls").toArray();
    if(!stageFlag)
    {
        for (const auto& item : ballData)
        {
            QJsonObject t = item.toObject();
            m_builder->addBall(t);
        }
    }else  CreatBall(ballData);
代码语言:javascript复制
void GameDirector::CreatBall(QJsonArray ballData)  //球的创建
{
       for (const auto& item : ballData)
           {
            //   PosIndex   ;
               m_BallPos_int[p_balltype] = PosIndex;  //同一级别中,上一次所记录的最大值
               QJsonObject t = item.toObject();
               QJsonArray  t_Arry =t.value("balls").toArray();
               if(!t_Arry.count())   { m_builder->addBall(t);}
              else
               {
                   m_BallPos_int[p_balltype] = PosIndex;
                   m_builder->addBall(t);
                   p_balltype   ;
                   PosIndex =m_BallPos_int[p_balltype];
                   CreatBall(t_Arry);  //递归寻找每一级的球
               }
                PosIndex   ;
                BallNum   ;
                m_BallPos_int[p_balltype] = PosIndex;
           }

       if(p_balltype>0)    p_balltype --;    else ;  //每一次退出时,等级减一,回到上一级别
        PosIndex = m_BallPos_int[p_balltype];  //得到上一级上一次最后一个球的位置
}

JSON数据的读取没什么可说的,在这比较有点难度的地方是,JSON数据作为提供球类的数据元素,球有一个属性为包含属性,每个球 被球袋吃掉或者碰撞破碎的时候,将所包含的子球都show出来。而子球的个数不限。但在整体框架中,尽量维持原来的框架属性不变,只能增加功能作为嵌套实现。实现时给球类增加了两个属性,等级与位置。等级每一个球所在的等级,也就对应JSON数据的数据层级,位置为每个球所在等级的位置。

为此建立了两个容器,一个容器,放置,当前所有显示球类元素的容器,一个容器作为记录第二层及以后球类的元素。

球类元素的改造前与改造后。

代码语言:javascript复制
    StageOneBall(QColor colour, QVector2D position,
                 QVector2D velocity, double mass, int radius,double strength) : Ball(colour, position, velocity, mass, radius,strength) {}
代码语言:javascript复制
    StageOneBall(QColor colour, QVector2D position,
                 QVector2D velocity, double mass, int radius,double strength,int Balltype,int TypeIndex) :
        Ball(colour, position, velocity, mass, radius,strength,Balltype,TypeIndex) {}

容器:

代码语言:javascript复制
struct  ParentStr
{
   int  balltype ;
   int  ballindex;
};
extern int p_balltype;   //层级
extern ParentStr *m_Partent;  //父类索引属性
extern QMap<ParentStr *,Ball *>  childball;  //第二级以后球的集合
代码语言:javascript复制
std::vector<Ball*>* m_balls;//当前显示球类容器

第二级以后球类集合,为Map容器,Key,记录对应球类元素的所对应它的父类的等级与位置。当一个父类球被删除掉的时候,提取出当前删除球所对应的等级与位置,作为key,遍历第二级及以后球的集合的容器,将对应的球类元素添加到当前球类元素显示容器。

口袋吃球函数:

代码语言:javascript复制
bool  PocketEatingBall(QVector2D pos,double ballrad)
{
   bool status;
   QVector<CompontPocket *> Buf_m_pockets = p_CompositePockets.getvetor();
   for (auto it =Buf_m_pockets.begin();it!= Buf_m_pockets.end();it  )
         {
           CompontPocket * Buf_Compont = *it;
           pocket  a_pocket =  Buf_Compont->Output();
           double Log2 = a_pocket.radius*a_pocket.radius-((a_pocket.x-pos.x())*(a_pocket.x-pos.x()) (a_pocket.y-pos.y())*(a_pocket.y-pos.y()));
           double rad2 = ballrad*ballrad;
           double value = Log2-rad2;
           if(value<0) {status = 0;}
           else  { status = 1; break;}
        }
    return status;
}

球类元素删除函数:

代码语言:javascript复制
void Game::ballsDelItem()
{   
    int i =0,a=0;
    if(j == 0) return;
    for(a = 0;a<j;  a)
    {
        for (std::vector<Ball *>::iterator it = m_balls->begin();it!=m_balls->end();)
            {
                 Ball * DelBall = *it;
                if(i== buf[a])
                {
                    DelBallbuf[a][0] = DelBall->getBalltype();
                    DelBallbuf[a][1] = DelBall->getBalltypeIndex();
                    it =  m_balls->erase(it);
                 // qDebug()<<"Del A Ball"<<m_balls->size()<<DelBallbuf[a][0]<<DelBallbuf[a][1];
                }
                else      it;
                  i;
            }
    }
    NextBallsShow(j);
}

子球显示函数:

代码语言:javascript复制
void Game::NextBallsShow(int Num)
{
    for(int a = 0;a<Num;  a)
    {
        for (QMap<ParentStr *,Ball *>::iterator it = childball.begin();it!=childball.end();  it)
        {
            if(it.key()->balltype==DelBallbuf[a][0]&&it.key()->ballindex==DelBallbuf[a][1])
            {
               // qDebug()<<"No instert"<<m_balls->size();
                m_balls->push_back(it.value());
              //  qDebug()<<"Instert "<<m_balls->size();
            }
        }
    }
}

动能功能:

代码语言:javascript复制
bool Game::EnergyCalculation(Ball* ballA)  //碰撞能量计算
{
    bool status;  //状态值
    float ballMass  = ballA->getMass();
    float ballStrength = ballA->getstrength();
    float ballRadius  = ballA->getRadius();
    QVector2D preCollisionVelocity  = ballA->getVelocity() =changeVec;
    QVector2D deltaV = changeVec;
    float energyOfCollision = ballMass*deltaV.lengthSquared();  //能量计算公式
//    qDebug()<<energyOfCollision << deltaV.lengthSquared()<<changeVec.x()<<changeVec.y();
    int balltype = ballA->getBalltype();
    int ballIndex = ballA->getBalltypeIndex();
    int numComponentBalls = 0;
    for(QMap<ParentStr *,Ball *>::iterator it = childball.begin();it!=childball.end();  it)  //得到当前球里边包含子球的个数
    {
        Ball * componentBall = *it;
        if(componentBall->getBalltypeIndex() == balltype && componentBall->getBalltypeIndex() == ballIndex)
        {
            numComponentBalls  ;
        }
    }
    if(ballStrength<energyOfCollision)  //承受质量小于碰撞能量
    {
        float energyPerBall = energyOfCollision/numComponentBalls;    //子球的能量
        deltaV.normalize();
        QVector2D pointOfCollision((-deltaV)*ballRadius);
        //for each component ball
        for(QMap<ParentStr *,Ball *>::iterator it = childball.begin();it!=childball.end();  it)
        {
            Ball * componentBall = *it;
            if(componentBall->getBalltypeIndex() == balltype && componentBall->getBalltypeIndex() == ballIndex)
            {
                QVector2D buf = componentBall->getPosition()-pointOfCollision;
                buf.normalize();
                QVector2D componentBallVelocity = preCollisionVelocity   sqrt(energyPerBall/componentBall->getMass())*buf; //子球速度的计算
                componentBall->changeVelocity(componentBallVelocity);  //子球速度的设置
            }
        }
        status = 1;
    }else status =0;
    return status;
}

0 人点赞