Qt:击球游戏
近期做的一个小项目:Qt击球游戏,相当于二次开发增加附加功能。
已有功能:
-
JSON文件读取;
-
球类生成;
-
球桌生成;
-
碰撞规则;
新增功能:
-
JSON文件新增节点读取;
-
球袋功能;
-
击球功能;
-
动能功能;
-
设计模式:必须 组合模式+其他模式(适配器);
-
母球分裂功能;
开发环境:
Ubuntu+Qt5.10.1+QtCreator4.6.0
功能实现:
球袋功能:
-
"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}
球袋采用组合模式:
-
struct pocket
-
{
-
double x;
-
double y;
-
double radius;
-
}; //球袋属性结构体
组合模式:抽象类,采用未保护模式
-
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;
-
};
组合模式:叶子类 实现单一元素操作
-
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);
-
}
-
};
组合模式:枝节点类 实现组合容器操作
-
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;
击球功能: 适配器模式/实现比较简单,强行用设计者模式
-
/*需适配者*/
-
class ballarm
-
{
-
public:
-
virtual void render(QPainter& painter ,QVector2D m_startpos,QVector2D m_endpos) = 0;
-
};
-
-
/*适配元素*/
-
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()); //画线
-
}
-
};
-
-
/*适配器*/
-
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;
-
};
设计者模式这一块也为初次接触,公司项目比较单一,很少接触这些,熬了一晚上,才看懂,稍微理解了一点组合模式和适配者模式。
-
组合模式:抽象类,叶子类,枝节点类。
-
-
抽象类只提供虚函数接口,不作具体实现。
-
-
叶子类,实现单一元素的操作,设置属性,元素的具现之类的。
-
-
枝节点类,元素集合体,可作为根节点与枝节点。实现对集合的操作,元素的增删改。元素容器一般在私有类里边,作为对元素的保护。对外提供接口时,尽量使用变量,少使用指针,作为安全保护。
-
-
我没有采用保护者模式,保护者模式,抽象类,只抽像单一元素操作,不提供枝节点类的操作接口。
-
适配者模式:我目前的理解,就拿电脑显示接口举例,电脑只提供了对外输出的VGA视频接口,但是目前需要HDMI接口的电脑,将电脑和HDMI转接头差分为两个元素。
-
两个适配器组成的类,电脑作为基础类,HDMI作为适配元素,将两个元素组合在一起,将变成了一个新的类,提供HDMI接口的电脑。
JSON元素读取:
提供的JSON数据格式:
-
"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}
-
]
-
"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数据的读取:
-
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;
-
}
-
// 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);
-
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数据的数据层级,位置为每个球所在等级的位置。
为此建立了两个容器,一个容器,放置,当前所有显示球类元素的容器,一个容器作为记录第二层及以后球类的元素。
球类元素的改造前与改造后。
-
StageOneBall(QColor colour, QVector2D position,
-
QVector2D velocity, double mass, int radius,double strength) : Ball(colour, position, velocity, mass, radius,strength) {}
-
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) {}
容器:
-
struct ParentStr
-
{
-
int balltype ;
-
int ballindex;
-
};
-
extern int p_balltype; //层级
-
extern ParentStr *m_Partent; //父类索引属性
-
extern QMap<ParentStr *,Ball *> childball; //第二级以后球的集合
std::vector<Ball*>* m_balls;//当前显示球类容器
第二级以后球类集合,为Map容器,Key,记录对应球类元素的所对应它的父类的等级与位置。当一个父类球被删除掉的时候,提取出当前删除球所对应的等级与位置,作为key,遍历第二级及以后球的集合的容器,将对应的球类元素添加到当前球类元素显示容器。
口袋吃球函数:
-
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;
-
}
球类元素删除函数:
-
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);
-
}
子球显示函数:
-
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();
-
}
-
}
-
}
-
}
动能功能:
-
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;
-
}
文章来源: blog.csdn.net,作者:何其不顾四月天,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/u011218356/article/details/80396571
- 点赞
- 收藏
- 关注作者
评论(0)