近期做的一个小项目: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;
}