最终效果
github下载地址
前话
最近在学习Qt绘图,看了很多文章,Qt的图形视图框架,最核心的三个类为:QGraphicsScene、QGraphicsItem与QGraphicsView。 关于QGraphicsScene、QGraphicsItem与QGraphicsView的详细介绍可参考Qt图形视图框架详解
个人理解:QGraphicsScene提供一个场景, 上面放置QGraphicsItem图元, 而QGraphicsView则是选择场景中的一块区域进行展示。
平时也经常玩游戏, 就想着用这个绘图做一款游戏练习, 复杂的又没能力做, 思来想去, 最终选择了台球斯诺克,至于为什么不选择中式黑8, 因为它的球不是纯色的, 需要做滚动效果, 难以实现。
项目分析
图元部分
需要自定义图元分别绘制:球台、球、球杆。
计算部分
计算主要为:击球、球与球的碰撞、球与球台的碰撞、球自身的运动。
主要流程
进入游戏—>鼠标左键在半圆形区域放置白球—>再次点击鼠标左键进入瞄准状态—>释放鼠标进入出杆状态—>随后球杆击中白球进入台球移动状态(进行相应碰撞检测计算处理及进球判断)—>待球全部静止能再次进行瞄准操作。
主要代码
ItemBase
此项目所有图元继承此类, 由于此项目的图元效果难以用qt提供的基本图元实现,需要自定义图元, 继承QGraphicsItem类, 重写boundingRect、paint函数即可。
itembase.h
#ifndef ITEMBASE_H
#define ITEMBASE_H
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QStyleOption>
#include <QList>
#include <QtMath>
class ItemBase :public QGraphicsItem
{
public:
enum itemType
{
ball = 1, //球
cushion = 2, //桌子
club = 3 //球杆
};
ItemBase();
itemType itemtype();
public:
virtual QRectF boundingRect() const override = 0;
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override = 0;
protected:
itemType m_type;
};
#endif // ITEMBASE_H
itembase.cpp
#include "itembase.h"
ItemBase::ItemBase()
{
}
ItemBase::itemType ItemBase::itemtype()
{
return m_type;
}
Cushion
球台图元, 根据斯诺克球台进行缩放的, 所以看到有些奇怪的数字, 多数是斯诺克标准尺寸, 大量出现的2.6是因为项目中的台球尺寸给的是20, 实际尺寸54, 存在2.6倍关系, 导致桌面等尺寸需要除2.6。
cushion.h
#ifndef CUSHION_H
#define CUSHION_H
#include <QDebug>
#include "itembase.h"
#include "ball.h"
class Cushion: public ItemBase
{
public:
Cushion();
~Cushion();
//与球的碰撞处理(包括进球)返回true表示进球
bool collisionWithBall(Ball *ball, float fps);
public:
QRectF boundingRect() const override;
QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
public:
QPointF m_outRect = QPointF(3820, 2035); //球桌外延尺寸
QPointF m_inRect = QPoint(3569, 1778); //球桌内沿尺寸
float m_pocket_r = 45; //袋口半径
private:
QPainterPath m_ellipsePath; //库边圆角
QPainterPath m_cushionPath; //库边
QPainterPath m_pointPath; //辅助点、线
QPainterPath m_pocketPath; //袋口
QVector<QPointF> m_pocketPoints; //袋口原点, 做碰撞检测时使用
QVector<QPointF> m_filletedCornerPoints; //库边圆角, 做碰撞检测时使用
};
#endif // CUSHION_H
cushion.cpp
#include "cushion.h"
Cushion::Cushion()
{
m_type = cushion;
m_ellipsePath.addEllipse((-m_inRect.x())/2.6/2 + 8, (-m_inRect.y()-2*m_pocket_r*2)/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((-m_pocket_r*2*3.0/2)/2.6, (-m_inRect.y()-2*m_pocket_r*2)/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((m_pocket_r*2/2)/2.6, (-m_inRect.y()-2*m_pocket_r*2)/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((m_inRect.x() - m_pocket_r*2*2)/2.6/2 - 8, (-m_inRect.y()-2*m_pocket_r*2)/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((-m_inRect.x() - m_pocket_r*2*2)/2.6/2, (-m_inRect.y())/2.6/2 + 8, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((m_inRect.x())/2.6/2, (-m_inRect.y())/2.6/2 + 8, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((-m_inRect.x())/2.6/2 + 8, (m_inRect.y())/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((-m_pocket_r*2*3.0/2)/2.6, (m_inRect.y())/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((m_pocket_r*2/2)/2.6, (m_inRect.y())/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((m_inRect.x() - m_pocket_r*2*2)/2.6/2 - 8, (m_inRect.y())/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((-m_inRect.x() - m_pocket_r*2*2)/2.6/2, (m_inRect.y() - m_pocket_r*2*2)/2.6/2 - 8, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_ellipsePath.addEllipse((m_inRect.x())/2.6/2, (m_inRect.y() - m_pocket_r*2*2)/2.6/2 - 8, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_cushionPath.addRect((-m_inRect.x()+m_pocket_r*2)/2.6/2 + 8, (-m_inRect.y()-m_pocket_r*2)/2.6/2, (m_inRect.x() - m_pocket_r*2*3)/2.6/2 - 8, m_pocket_r*2/2/2.6);
m_cushionPath.addRect(m_pocket_r*2/2.6, (-m_inRect.y()-m_pocket_r*2)/2.6/2, (m_inRect.x() - m_pocket_r*2*3)/2.6/2 - 8, m_pocket_r*2/2/2.6);
m_cushionPath.addRect((-m_inRect.x()+m_pocket_r*2)/2.6/2 + 8, (m_inRect.y())/2.6/2, (m_inRect.x() - m_pocket_r*2*3)/2.6/2 - 8, m_pocket_r*2/2/2.6);
m_cushionPath.addRect(m_pocket_r*2/2.6, (m_inRect.y())/2.6/2, (m_inRect.x() - m_pocket_r*2*3)/2.6/2 - 8, m_pocket_r*2/2/2.6);
m_cushionPath.addRect((-m_inRect.x()-m_pocket_r*2)/2.6/2, (-m_inRect.y()+m_pocket_r*2)/2.6/2 + 8, m_pocket_r*2/2/2.6, (m_inRect.y() - m_pocket_r*2)/2.6 - 16);
m_cushionPath.addRect((m_inRect.x())/2.6/2, (-m_inRect.y()+m_pocket_r*2)/2.6/2 + 8, m_pocket_r*2/2/2.6, (m_inRect.y() - m_pocket_r*2)/2.6 - 16);
m_pointPath.addEllipse(-m_inRect.x()/2.6/2 + 324/2.6 - 2, 0 - 2, 4, 4);
m_pointPath.addEllipse(-m_inRect.x()/2.6/2/2 - 2, 0 - 2, 4, 4);
m_pointPath.addEllipse(m_inRect.x()/2/2.6 - m_inRect.x()*737/3569/2.6 - 2, -m_inRect.x()*292/3569/2.6 - 2, 4, 4);
m_pointPath.addEllipse(m_inRect.x()/2/2.6 - m_inRect.x()*737/3569/2.6 - 2, 0 - 2, 4, 4);
m_pointPath.addEllipse(m_inRect.x()/2/2.6 - m_inRect.x()*737/3569/2.6 - 2, m_inRect.x()*292/3569/2.6 - 2, 4, 4);
m_pocketPath.addEllipse(-m_inRect.x()/2.6/2 - m_pocket_r*2/2.6 + 10, -m_inRect.y()/2.6/2 - m_pocket_r*2/2.6 + 10, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_pocketPath.addEllipse(-m_pocket_r*2/2/2.6, -m_inRect.y()/2.6/2 - m_pocket_r*2/2.6, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_pocketPath.addEllipse(m_inRect.x()/2.6/2 - 10, -m_inRect.y()/2.6/2 - m_pocket_r*2/2.6 + 10, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_pocketPath.addEllipse(-m_inRect.x()/2.6/2 - m_pocket_r*2/2.6 + 10, m_inRect.y()/2.6/2 - 10, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_pocketPath.addEllipse(-m_pocket_r*2/2/2.6, m_inRect.y()/2.6/2, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_pocketPath.addEllipse(m_inRect.x()/2.6/2 - 10, m_inRect.y()/2.6/2 - 10, m_pocket_r*2/2.6, m_pocket_r*2/2.6);
m_pocketPoints<< QPointF(-m_inRect.x()/2.6/2 - m_pocket_r*2/2.6 + 10, -m_inRect.y()/2.6/2 - m_pocket_r*2/2.6 + 10) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF(-m_pocket_r*2/2/2.6, -m_inRect.y()/2.6/2 - m_pocket_r*2/2.6) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF(m_inRect.x()/2.6/2 - 10, -m_inRect.y()/2.6/2 - m_pocket_r*2/2.6 + 10) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF(-m_inRect.x()/2.6/2 - m_pocket_r*2/2.6 + 10, m_inRect.y()/2.6/2 - 10) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF(-m_pocket_r*2/2/2.6, m_inRect.y()/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF(m_inRect.x()/2.6/2 - 10, m_inRect.y()/2.6/2 - 10) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2;
m_filletedCornerPoints<< QPointF((-m_inRect.x())/2.6/2 + 8, (-m_inRect.y()-2*m_pocket_r*2)/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((-m_pocket_r*2*3.0/2)/2.6, (-m_inRect.y()-2*m_pocket_r*2)/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((m_pocket_r*2/2)/2.6, (-m_inRect.y()-2*m_pocket_r*2)/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((m_inRect.x() - m_pocket_r*2*2)/2.6/2 - 8, (-m_inRect.y()-2*m_pocket_r*2)/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((-m_inRect.x() - m_pocket_r*2*2)/2.6/2, (-m_inRect.y())/2.6/2 + 8) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((m_inRect.x())/2.6/2, (-m_inRect.y())/2.6/2 + 8) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((-m_inRect.x())/2.6/2 + 8, (m_inRect.y())/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((-m_pocket_r*2*3.0/2)/2.6, (m_inRect.y())/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((m_pocket_r*2/2)/2.6, (m_inRect.y())/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((m_inRect.x() - m_pocket_r*2*2)/2.6/2 - 8, (m_inRect.y())/2.6/2) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((-m_inRect.x() - m_pocket_r*2*2)/2.6/2, (m_inRect.y() - m_pocket_r*2*2)/2.6/2 - 8) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2
<< QPointF((m_inRect.x())/2.6/2, (m_inRect.y() - m_pocket_r*2*2)/2.6/2 - 8) + QPointF(m_pocket_r*2/2.6, m_pocket_r*2/2.6)/2;
}
Cushion::~Cushion()
{
}
bool Cushion::collisionWithBall(Ball *ball, float fps)
{
if(ball == nullptr)
{
return false;
}
QPointF ballPos = mapFromItem(ball, 0, 0);
//先与6个球袋进行判断
for(auto point:m_pocketPoints)
{
//根据两球心距离与半径判断进球,若进球直接返回true, +1是在微调进球范围,与下面的+2效果相同
if(QVector2D(ballPos - point).length() < m_pocket_r*2/2.6/2 - ball->r() + 1)
{
return true;
}
//若还未进球但球心已经进入球袋, 则改变速度及方向,使朝着球袋中心移动
else if(QVector2D(ballPos - point).length() < m_pocket_r*2/2.6/2 + 2)
{
ball->setMoveDir(QVector2D(point - ballPos).normalized());
ball->setSpeed(ball->speed() + 20);
}
}
//若速度为0,则无需考虑碰撞
if(ball->speed() < 1e-6)
{
return false;
}
//与库边圆角做碰撞检测(将库边圆角当作圆形处理, 依旧根据球心距离与半径判断)
for(auto point:m_filletedCornerPoints)
{
if(QVector2D(ballPos - point).length() <= m_pocket_r*2/2.6/2 + ball->r() + 1e-6)
{
//计算出球沿着库边圆角中心的速度分量, 这个分量(矢量)便是球的损失速度
//球心到库边圆角中心的方向
QVector2D pos_dif = QVector2D(point - ballPos);
QVector2D temp1 = (ball->moveDir()*pos_dif);
//向量积
float vectorValue = (temp1.x() + temp1.y());
//(向量积/模之积)极为余弦值
float pos_difCosBallDir = vectorValue/(pos_dif.length()*ball->moveDir().length());
//pos_dif方向的速度损失量
float lossSpeed = pos_difCosBallDir*ball->speed();
//小于0即代表夹角大于90度, 不构成碰撞
if(pos_difCosBallDir > 0)
{
//速度减去pos_dif方向的损失量,0.8为碰撞损耗
ball->addSpeedVector(-0.8*lossSpeed*pos_dif.normalized());
return false;
}
}
}
//分别与除库边圆角外的直边进行碰撞检测0.8为碰撞损耗(与水平边碰撞:x方向不变, y反向, 与竖直边碰撞:x反向, y不变)
if(ballPos.x() < -m_inRect.x()/2.6/2 + ball->r()
&& ballPos.y() >= (-m_inRect.y()+m_pocket_r*2)/2.6/2 + 8
&& ballPos.y() <= (-m_inRect.y()+m_pocket_r*2)/2.6/2 + 8 + (m_inRect.y() - m_pocket_r*2)/2.6 - 16)
{
ball->setPos(-m_inRect.x()/2.6/2 + ball->r(),ball->scenePos().y());
ball->setMoveDir(QVector2D(-ball->moveDir().x(), ball->moveDir().y()));
ball->setSpeed(ball->speed()*0.8);
return false;
}
else if(ballPos.x() > m_inRect.x()/2.6/2 - ball->r()
&& ballPos.y() >= (-m_inRect.y()+m_pocket_r*2)/2.6/2 + 8
&& ballPos.y() <= (-m_inRect.y()+m_pocket_r*2)/2.6/2 + 8 + (m_inRect.y() - m_pocket_r*2)/2.6 - 16)
{
ball->setPos(m_inRect.x()/2.6/2 - ball->r(),ball->scenePos().y());
ball->setMoveDir(QVector2D(-ball->moveDir().x(), ball->moveDir().y()));
ball->setSpeed(ball->speed()*0.6);
return false;
}
else if(ballPos.y() < -m_inRect.y()/2.6/2 + ball->r()
&& ((ballPos.x() >= (-m_inRect.x()+m_pocket_r*2)/2.6/2 + 8
&& ballPos.x() <= (-m_inRect.x()+m_pocket_r*2)/2.6/2 + 8 + (m_inRect.x() - m_pocket_r*2*3)/2.6/2 - 8)
|| (ballPos.x() >= m_pocket_r*2/2.6
&& ballPos.x() <= m_pocket_r*2/2.6 + (m_inRect.x() - m_pocket_r*2*3)/2.6/2 - 8)))
{
ball->setPos(ball->scenePos().x(), -m_inRect.y()/2.6/2 + ball->r());
ball->setMoveDir(QVector2D(ball->moveDir().x(), -ball->moveDir().y()));
ball->setSpeed(ball->speed()*0.8);
return false;
}
else if(ballPos.y() > m_inRect.y()/2.6/2 - ball->r()
&& ((ballPos.x() >= (-m_inRect.x()+m_pocket_r*2)/2.6/2 + 8
&& ballPos.x() <= (-m_inRect.x()+m_pocket_r*2)/2.6/2 + 8 + (m_inRect.x() - m_pocket_r*2*3)/2.6/2 - 8)
|| (ballPos.x() >= m_pocket_r*2/2.6
&& ballPos.x() <= m_pocket_r*2/2.6 + (m_inRect.x() - m_pocket_r*2*3)/2.6/2 - 8)))
{
ball->setPos(ball->scenePos().x(), m_inRect.y()/2.6/2 - ball->r());
ball->setMoveDir(QVector2D(ball->moveDir().x(), -ball->moveDir().y()));
ball->setSpeed(ball->speed()*0.8);
return false;
}
return false;
}
QRectF Cushion::boundingRect() const
{
return QRectF(-m_outRect.x()/2.6/2, -m_outRect.y()/2.6/2, m_outRect.x()/2.6, m_outRect.y()/2.6);
}
QPainterPath Cushion::shape() const
{
QPainterPath path;
path.addRect(-m_outRect.x()/2.6/2, -m_outRect.y()/2.6/2, m_outRect.x()/2.6, m_outRect.y()/2.6);
return path;
}
void Cushion::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setRenderHint(QPainter::Antialiasing,true);
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(Qt::green).light(60));
//绘制中间绿色运动区域
painter->drawRect((-m_inRect.x()-m_pocket_r*2)/2.6/2, (-m_inRect.y()-m_pocket_r*2)/2.6/2, (m_inRect.x()+m_pocket_r*2)/2.6, (m_inRect.y()+m_pocket_r*2)/2.6);
painter->setBrush(QColor(Qt::white).light(70));
//绘制中间区域的点与线
painter->drawPath(m_pointPath);
painter->setPen(Qt::black);
painter->setBrush(QColor(Qt::green).light(50));
//绘制库边矩形与圆角
painter->drawPath(m_cushionPath + m_ellipsePath);
painter->setBrush(QColor(150, 75, 0).light(70));
QPainterPath outFramePath;
outFramePath.addRoundRect(-m_outRect.x()/2.6/2, -m_outRect.y()/2.6/2, m_outRect.x()/2.6, m_outRect.y()/2.6, 3, 5);
QPainterPath inFramePath;
inFramePath.addRect((-m_inRect.x()-m_pocket_r*2)/2.6/2, (-m_inRect.y()-m_pocket_r*2)/2.6/2, (m_inRect.x()+m_pocket_r*2)/2.6, (m_inRect.y()+m_pocket_r*2)/2.6);
// painter->drawRoundRect(-m_outRect.x()/2.6/2, -m_outRect.y()/2.6/2, m_outRect.x()/2.6, m_outRect.y()/2.6, 3, 5);
QLinearGradient linear(QPointF(-m_outRect.x()/2.6/2, -m_outRect.y()/2.6/2)*0.2,
QPointF(m_outRect.x()/2.6/2, m_outRect.y()/2.6/2)*0.2);
linear.setColorAt(0, QColor(150, 75, 0).light(70));
linear.setColorAt(0.3, QColor(150, 75, 0).light(120));
linear.setColorAt(1,QColor(150, 75, 0).light(70));
// linear.setSpread(QGradient::ReflectSpread);
painter->setBrush(linear);
//绘制外边框(一个大的圆角矩形减去中间一个矩形)
painter->drawPath(outFramePath - inFramePath);
painter->setPen(QColor(Qt::white).light(70));
painter->drawLine(QPointF(m_inRect.x()/2/2.6 - m_inRect.x()*737/3569/2.6, -m_inRect.y()/2.6/2), QPointF(m_inRect.x()/2/2.6 - m_inRect.x()*737/3569/2.6, m_inRect.y()/2.6/2));
painter->drawArc(m_inRect.x()/2/2.6 - m_inRect.x()*737/3569/2.6 - m_inRect.x()*292/3569/2.6 , -m_inRect.x()*292/3569/2.6, m_inRect.x()*292/3569/2.6*2, m_inRect.x()*292/3569/2.6*2, -m_pocket_r*2*16, 180*16);
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(Qt::black));
//最后绘制球袋
painter->drawPath(m_pocketPath);
}
Ball
球类图元, 需要表现出光照、阴影等。
ball.h
#ifndef BALL_H
#define BALL_H
#include <QVector2D>
#include "itembase.h"
class Ball: public ItemBase
{
public:
//将球按颜色分类
enum BallType
{
red,
green,
coffee,
yellow,
blue,
pink,
black,
white
};
public:
Ball(BallType ballType = red);
//设置球的类型
void setBallType(BallType ballType, bool isChangeColor = true);
BallType ballType();
//设置颜色
void setColor(QColor color);
//设置移动速度(标量)
void setSpeed(const float &speed);
float speed();
//增加速度(矢量, 因碰撞会改变移动方向)
void addSpeedVector(const QVector2D &speed);
//设置移动方向
void setMoveDir(const QVector2D &dir);
QVector2D moveDir();
//设置球体半径
void setR(const float &r);
float r();
//每帧根据帧率做减速直线运动计算
void move(float fps);
QRectF boundingRect() const override;
QPainterPath shape() const override;
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
private:
float m_r = 10; //球体半径
float m_speed = 0; //移动速度
float m_lossSpeed = 0; //每帧速度减少量
QColor m_color = QColor(Qt::red); //球体颜色
QVector2D m_moveDir = QVector2D(0, 1); //移动方向
BallType m_ballType = red; //所属类型
};
#endif // BALL_H
ball.cpp
#include "ball.h"
#include <QDebug>
Ball::Ball(BallType ballType)
{
setBallType(ballType);
m_type = ball;
}
void Ball::setBallType(Ball::BallType ballType, bool isChangeColor)
{
m_ballType = ballType;
if(isChangeColor)
{
switch (m_ballType) {
case green:
m_color = Qt::green;
break;
case coffee:
m_color = QColor(150, 75, 0);
break;
case yellow:
m_color = QColor(Qt::yellow).light(80);
break;
case blue:
m_color = Qt::blue;
break;
case pink:
m_color = QColor(255, 105, 180);
break;
case black:
m_color = Qt::black;
break;
case white:
m_color = QColor(Qt::white).light(80);
break;
default:
m_color = Qt::red;
break;
}
}
}
Ball::BallType Ball::ballType()
{
return m_ballType;
}
void Ball::setColor(QColor color)
{
m_color = color;
}
void Ball::setSpeed(const float &speed)
{
m_speed = speed;
}
void Ball::addSpeedVector(const QVector2D &speed)
{
QVector2D temp = m_moveDir*m_speed + speed;
m_speed = temp.length();
m_moveDir = temp.normalized();
}
void Ball::setMoveDir(const QVector2D &dir)
{
m_moveDir = dir.normalized();
}
void Ball::setR(const float &r)
{
m_r = r;
}
float Ball::r()
{
return m_r;
}
QVector2D Ball::moveDir()
{
return m_moveDir;
}
float Ball::speed()
{
return m_speed;
}
QRectF Ball::boundingRect() const
{
qreal adjust = 2;
return QRectF( -m_r - adjust, -m_r - adjust, 2*m_r + adjust, 2*m_r + adjust);
}
QPainterPath Ball::shape() const
{
QPainterPath path;
path.addEllipse(-m_r, -m_r, 2*m_r, 2*m_r);
return path;
}
void Ball::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
//抗锯齿
painter->setRenderHint(QPainter::Antialiasing,true);
//不绘制轮廓线
painter->setPen(Qt::NoPen);
QColor color(Qt::black);
color.setAlpha(90);
painter->setBrush(color);
//先绘制阴影部分
painter->drawEllipse(-7, -7, 2*m_r, 2*m_r);
//此操作可以使绘制效果为圆形渐变(表现出光照效果)
QRadialGradient gradient(-3.0/10*m_r, -3.0/10*m_r, m_r/2);
gradient.setColorAt(0, QColor(Qt::white).light(200));
gradient.setColorAt(1, m_color);
painter->setBrush(gradient);
//绘制球体
painter->drawEllipse(-m_r, -m_r, 2*m_r, 2*m_r);
}
void Ball::move(float fps)
{
//fps有时会计算错误则跳过
if(isnan(fps))
{
return;
}
if(m_speed < 1e-6)
{
m_speed = 0;
}
//速度>0, 则移动
if(m_speed > 0)
{
//所谓移动,就是根据:速度、方向、帧率不断设置球的位置
setPos(pos() + m_speed/fps*m_moveDir.normalized().toPointF());
//随意写的一个减速规则
m_lossSpeed = (m_speed > 200)?0.4*m_speed:40;
m_speed -= m_lossSpeed/fps;
}
}
GCanvasView
由于需要用到鼠标等事件且考虑代码复用性, 实现GraphicsView类, 继承QGraphicsView;将事件分离出去用BaseOperator类实现;
grahpicsview.h
#ifndef GRAPHICSVIEW_H
#define GRAPHICSVIEW_H
#include <QObject>
#include <QGraphicsView>
#include <QTimer>
class BaseOperator;
class GraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit GraphicsView(QWidget *parent = nullptr);
~GraphicsView();
void createScene();
void setOperatorObj(QSharedPointer<BaseOperator> op);
QSharedPointer<BaseOperator> operatorObj();
protected:
void initialize();
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void timerEvent(QTimerEvent *event) override;
bool event(QEvent *event) override;
private:
QSharedPointer<BaseOperator> m_pOperator;
bool m_isPressed = false;
bool m_isDoubleClick = false;
};
#endif // GRAPHICSVIEW_H
grahpicsview.cpp
#include "graphicsview.h"
#include <QMouseEvent>
#include <QDebug>
#include <QVector2D>
#include "baseoperator.h"
#include "defualtoperator.h"
GraphicsView::GraphicsView(QWidget *parent)
: QGraphicsView{parent}
{
initialize();
}
GraphicsView::~GraphicsView()
{
}
void GraphicsView::createScene()
{
if(this->scene() != nullptr){
return;
}
auto pScene = new QGraphicsScene(this);
this->setScene(pScene);
}
void GraphicsView::setOperatorObj(QSharedPointer<BaseOperator> op)
{
m_pOperator = op;
}
QSharedPointer<BaseOperator> GraphicsView::operatorObj()
{
return m_pOperator;
}
void GraphicsView::initialize()
{
this->setMouseTracking(true);
this->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
auto op = QSharedPointer<DefualtOperator>(new DefualtOperator(this));
this->setOperatorObj(op);
startTimer(0);
}
void GraphicsView::mousePressEvent(QMouseEvent *event)
{
m_isPressed = true;
QPoint pos = event->pos();
m_pOperator->mousePressEvent(event,this->mapToScene(pos));
}
void GraphicsView::mouseMoveEvent(QMouseEvent *event)
{
QPoint pos = event->pos();
if(m_isPressed){
m_pOperator->mouseMoveEvent(event,this->mapToScene(pos));
}else{
m_pOperator->mouseHoverEvent(event,this->mapToScene(pos));
}
}
void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
QPoint pos = event->pos();
m_isPressed = false;
m_pOperator->mouseReleaseEvent(event,this->mapToScene(pos));
}
void GraphicsView::mouseDoubleClickEvent(QMouseEvent *event)
{
QPoint pos = event->pos();
m_pOperator->mouseDoubleClickEvent(event,this->mapToScene(pos));
}
void GraphicsView::wheelEvent(QWheelEvent *event)
{
QPointF pos = this->mapToScene(event->pos());
m_pOperator->wheelEvent(event,pos);
}
void GraphicsView::keyPressEvent(QKeyEvent *event)
{
m_pOperator->keyPressEvent(event);
}
void GraphicsView::keyReleaseEvent(QKeyEvent *event)
{
m_pOperator->keyReleaseEvent(event);
}
void GraphicsView::resizeEvent(QResizeEvent *event)
{
// moveScene(QPoint(0,0));
m_pOperator->resizeEvent(event);
QGraphicsView::resizeEvent(event);
}
void GraphicsView::timerEvent(QTimerEvent *event)
{
m_pOperator->timerEvent(event);
}
bool GraphicsView::event(QEvent *event)
{
switch (event->type()) {
case QEvent::Leave:
{
if(!m_pOperator.isNull()){
m_pOperator->graphicsViewLeaveEvent();
}
}
break;
default:
break;
}
return QGraphicsView::event(event);
}
DefualtOperator
此类继承BaseOperator, 实现GrahpicsView中的事件(此项目主要表现为鼠标按下、释放, 窗口大小变换),实现整个游戏的逻辑。
defualtoperator.h
#ifndef DEFUALTOPERATOR_H
#define DEFUALTOPERATOR_H
#include "baseoperator.h"
#include <QObject>
#include <QUndoStack>
#include <QTime>
#include "cushion.h"
#include "ball.h"
#include "club.h"
#include "undocommands.h"
class DefualtOperator : public BaseOperator
{
Q_OBJECT
public:
explicit DefualtOperator(GraphicsView *parent);
~DefualtOperator();
virtual void mousePressEvent(QMouseEvent *event, QPointF scenePoint) override;
virtual void mouseMoveEvent(QMouseEvent *event, QPointF scenePoint) override;
virtual void mouseHoverEvent(QMouseEvent *event, QPointF scenePoint) override;
virtual void mouseReleaseEvent(QMouseEvent *event, QPointF scenePoint) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event, QPointF scenePoint) override;
virtual void wheelEvent(QWheelEvent *event, QPointF scenePoint) override;
virtual void keyPressEvent(QKeyEvent *event) override;
virtual void keyReleaseEvent(QKeyEvent *event) override;
virtual void resizeEvent(QResizeEvent *event) override;
void timerEvent(QTimerEvent *event) override;
//单独传入白球、球杆、球台方便直接操作
void setWhiteBall(Ball *ball);
void setClub(Club *club);
void setCushion(Cushion *cushion);
//开始
void start();
public slots:
//撤销
void onUndo();
//恢复
void onRedo();
protected:
void initialize();
//计算帧率
void calculateFps();
protected slots:
//每帧刷新,做相应运动计算
void updateView();
private:
bool m_isBallsMove = false; //球处于移动状态
bool m_isClubMove = false; //球杆处于移动状态
QUndoStack *m_pUndoStack = nullptr; //回退处理
float m_fps; //帧率
Ball *m_pWhiteBall = nullptr; //白球
Cushion *m_pCushion = nullptr; //球桌
Club *m_pClub = nullptr; //球杆
QGraphicsLineItem *m_pLine = nullptr; //辅助线
QTimer m_timer; //定时器
QMap<Ball *, QPointF> m_oldBallPos; //保存上一次球的位置
};
#endif // DEFUALTOPERATOR_H
defualtoperator.cpp
#include "defualtoperator.h"
DefualtOperator::DefualtOperator(GraphicsView *parent)
: BaseOperator{parent}
{
initialize();
}
DefualtOperator::~DefualtOperator()
{
}
void DefualtOperator::mousePressEvent(QMouseEvent *event, QPointF scenePoint)
{
//此项目只考虑鼠标左键
if(event->button() != Qt::LeftButton)
{
return;
}
//球处于移动状态 或者 在出杆状态,不允许操作
if(m_isBallsMove || m_isClubMove)
{
return;
}
//先得到场景中的球及球台
Cushion *pCushion;
QList<Ball*> balls;
for(auto item:scene()->items())
{
if(static_cast<ItemBase*>(item)->itemtype() == ItemBase::ball)
{
balls.append(static_cast<Ball*>(item));
}
else if(static_cast<ItemBase*>(item)->itemtype() == ItemBase::cushion)
{
pCushion = static_cast<Cushion*>(item);
}
}
//若场景中没有白球,则需根据当前点击点放置白球
if(!scene()->items().contains(m_pWhiteBall))
{
//放置白球必须在半圆形区域(游戏规则)
if(!QGraphicsEllipseItem(pCushion->m_inRect.x()/2/2.6 - pCushion->m_inRect.x()*737/3569/2.6 - pCushion->m_inRect.x()*292/3569/2.6,
-pCushion->m_inRect.x()*292/3569/2.6,
pCushion->m_inRect.x()*292/3569/2.6*2,
pCushion->m_inRect.x()*292/3569/2.6*2).shape().toFillPolygon().containsPoint(scenePoint,Qt::WindingFill)
|| scenePoint.x() < pCushion->m_inRect.x()/2/2.6 - pCushion->m_inRect.x()*737/3569/2.6)
{
return;
}
//不能在已有球的位置放置白球
for(auto ball:balls)
{
if(QVector2D(ball->pos() - scenePoint).length() < 2*ball->r())
{
return;
}
}
m_pWhiteBall->setPos(scenePoint);
if(!scene()->items().contains(m_pWhiteBall))
{
scene()->addItem(m_pWhiteBall);
}
return;
}
//若点击到白球, 不做瞄准处理,直接退出
if(QVector2D(m_pWhiteBall->pos() - scenePoint).length() < m_pWhiteBall->r() + 1e-6)
{
return;
}
//以下为进入瞄准状态
QPointF pos = scenePoint;
//限制球杆拉伸的距离(最大击球速度)
if(QVector2D(pos - m_pWhiteBall->pos()).length() > 500)
{
pos = QVector2D(QVector2D(m_pWhiteBall->pos()) + QVector2D(pos - m_pWhiteBall->pos()).normalized() * 500).toPointF();
}
QLineF line = QLineF(pos,m_pWhiteBall->pos());
line.setLength(line.length() + 50);
//设置辅助线
m_pLine->setLine(line);
if(!scene()->items().contains(m_pLine))
{
scene()->addItem(m_pLine);
}
//设置球杆的位置与方向
m_pClub->setPos(pos);
m_pClub->setDir(QVector2D(m_pWhiteBall->pos() - pos));
}
void DefualtOperator::mouseMoveEvent(QMouseEvent *event, QPointF scenePoint)
{
//此事件为鼠标按下移动
Q_UNUSED(event)
if(!scene()->items().contains(m_pLine))
{
return;
}
//若鼠标移动到白球, 退出瞄准状态
if(QVector2D(m_pWhiteBall->pos() - scenePoint).length() < m_pWhiteBall->r() + 1e-6)
{
//移除辅助线
if(scene()->items().contains(m_pLine))
{
scene()->removeItem(m_pLine);
}
//球杆放置原位
m_pClub->setDir(QVector2D(-1, 0));
m_pClub->setPos(-m_pClub->length()/2.6/2, m_pCushion->m_outRect.y()/2.6/2 + 100/2.6);
return;
}
//根据鼠标位置改变瞄准方向与力度
QPointF pos = scenePoint;
if(QVector2D(pos - m_pWhiteBall->pos()).length() > 500)
{
pos = QVector2D(QVector2D(m_pWhiteBall->pos()) + QVector2D(pos - m_pWhiteBall->pos()).normalized() * 500).toPointF();
}
QLineF line = QLineF(pos,m_pWhiteBall->pos());
line.setLength(line.length() + 50);
m_pLine->setLine(line);
m_pClub->setPos(pos);
m_pClub->setDir(QVector2D(m_pWhiteBall->pos() - scenePoint));
}
void DefualtOperator::mouseHoverEvent(QMouseEvent *event, QPointF scenePoint)
{
Q_UNUSED(event)
Q_UNUSED(scenePoint)
}
void DefualtOperator::mouseReleaseEvent(QMouseEvent *event, QPointF scenePoint)
{
Q_UNUSED(event)
Q_UNUSED(scenePoint)
//判断是否处于瞄准状态(是否有辅助线)
if(!scene()->items().contains(m_pLine))
{
return;
}
//退出瞄准状态, 进入出杆状态
if(scene()->items().contains(m_pLine))
{
scene()->removeItem(m_pLine);
}
m_isClubMove = true;
}
void DefualtOperator::mouseDoubleClickEvent(QMouseEvent *event, QPointF scenePoint)
{
Q_UNUSED(event)
Q_UNUSED(scenePoint)
}
void DefualtOperator::wheelEvent(QWheelEvent *event, QPointF scenePoint)
{
Q_UNUSED(event)
Q_UNUSED(scenePoint)
}
void DefualtOperator::keyPressEvent(QKeyEvent *event)
{
Q_UNUSED(event)
}
void DefualtOperator::keyReleaseEvent(QKeyEvent *event)
{
Q_UNUSED(event)
}
void DefualtOperator::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
updateView();
}
void DefualtOperator::timerEvent(QTimerEvent *event)
{
Q_UNUSED(event)
}
void DefualtOperator::setWhiteBall(Ball *ball)
{
m_pWhiteBall = ball;
}
void DefualtOperator::setClub(Club *club)
{
m_pClub = club;
}
void DefualtOperator::setCushion(Cushion *cushion)
{
m_pCushion = cushion;
}
void DefualtOperator::start()
{
m_pUndoStack->clear();
m_oldBallPos.clear();
for(auto item:scene()->items())
{
if(static_cast<ItemBase*>(item)->itemtype() == ItemBase::ball)
{
m_oldBallPos.insert(static_cast<Ball*>(item), item->pos());
}
}
// m_oldBallPos.insert(m_pWhiteBall, m_pWhiteBall->pos());
connect(&m_timer, SIGNAL(timeout()), this, SLOT(updateView()));
m_timer.start(1000/240.0);
}
void DefualtOperator::onUndo()
{
if(m_isBallsMove || m_isClubMove)
{
return;
}
m_pUndoStack->undo();
}
void DefualtOperator::onRedo()
{
if(m_isBallsMove || m_isClubMove)
{
return;
}
m_pUndoStack->redo();
}
void DefualtOperator::initialize()
{
m_pUndoStack = new QUndoStack(this);
m_pLine = new QGraphicsLineItem;
m_pGView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_pGView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
void DefualtOperator::calculateFps()
{
static QTime time(QTime::currentTime());//
double key = time.elapsed()/1000.0;
//this->replot();
static double lastFpsKey = 0;
static int frameCount;
++frameCount;
if(key - lastFpsKey>1){
lastFpsKey = key;
frameCount = 0;
}
m_fps = frameCount/(key-lastFpsKey);
}
void DefualtOperator::updateView()
{
//先计算帧率
calculateFps();
//判断是否所有球是否静止
if(m_isBallsMove)
{
bool isStatic = true;
QMap<Ball *, QPointF> newBallPos;
for(auto item:scene()->items())
{
if(static_cast<ItemBase*>(item)->itemtype() == ItemBase::ball)
{
if(qAbs(static_cast<Ball*>(item)->speed()) >= 1e-6)
{
isStatic = false;
break;
}
newBallPos.insert(static_cast<Ball*>(item), item->pos());
}
}
//若静止则保存上一次位置到撤销栈,并记录这次位置
if(isStatic)
{
m_pUndoStack->push(new UndoCommandMoveItems(m_oldBallPos, newBallPos, scene(), "balls move"));
m_oldBallPos = newBallPos;
m_isBallsMove = false;
}
}
if(isnan(m_fps))
{
return;
}
//若处于出杆状态
if(m_isClubMove)
{
//沿着辅助线计算球杆运动到的位置
QPointF pos = m_pClub->pos() + QVector2D(m_pLine->line().p2() - m_pLine->line().p1()).toPointF()/m_fps*5;
//判断与白球碰撞
if(QVector2D(m_pLine->line().p1() - pos).length() >= m_pLine->line().length() - 50)
{
//可根据辅助线的拉伸长度决定速度大小
float speed = m_pLine->line().length()/50*200;
//给白球一个沿着击球方向的速度
m_pWhiteBall->setMoveDir(QVector2D(m_pLine->line().p2() - m_pLine->line().p1()).normalized());
m_pWhiteBall->setSpeed(speed);
m_pClub->setDir(QVector2D(-1, 0));
m_pClub->setPos(-m_pClub->length()/2.6/2, m_pCushion->m_outRect.y()/2.6/2 + 100/2.6);
//出杆状态结束,球将处于运动状态
m_isClubMove = false;
m_isBallsMove = true;
return;
}
//球杆移动
m_pClub->setPos(pos);
return;
}
//若球都处于静止状态, 则无需下列运动计算
if(!m_isBallsMove)
{
return;
}
//先得到场景上的所有球及球台
QList<Ball *> balls;
Cushion* pCushion = nullptr;
foreach (QGraphicsItem *item, scene()->items()) {
if (static_cast<ItemBase*>(item)->itemtype() == ItemBase::ball)
{
balls << static_cast<Ball*>(item);
}
else if (static_cast<ItemBase*>(item)->itemtype() == ItemBase::cushion)
{
pCushion = static_cast<Cushion*>(item);
}
}
//保存副本
auto tempballs = balls;
foreach (Ball *ball, tempballs)
{
//与球台进行碰撞检测并判断进球,若进球, 则移除场景
if(pCushion->collisionWithBall(ball, m_fps))
{
ball->setSpeed(0);
balls.removeOne(ball);
if(scene()->items().contains(ball))
{
scene()->removeItem(ball);
}
}
}
//计算球与球之间的碰撞
foreach (Ball *ball, balls)
{
foreach (Ball *ball_temp, balls)
{
if(ball != ball_temp)
{
//计算出球沿着击中球中心的速度分量, 这个分量(矢量)便是球的损失速度
//两球的相对方向
QVector2D pos_dif = QVector2D(ball_temp->scenePos() - ball->scenePos());
//根据距离与半径判断碰撞
if(pos_dif.length() < ball->r() + ball_temp->r())
{
QVector2D temp1 = (ball->moveDir()*pos_dif);
//向量积
float vectorValue = (temp1.x() + temp1.y());
//(向量积/模之积)极为余弦值
float pos_difCosBallDir = vectorValue/(pos_dif.length()*ball->moveDir().length());
//pos_dif方向的速度损失量(被击中球的增加量)
float lossSpeed = pos_difCosBallDir*ball->speed();
if(pos_difCosBallDir > 0)
{
//被击中球增加这个方向的速度
ball_temp->addSpeedVector(0.8*lossSpeed*pos_dif.normalized());
//减少这个方向的速度
ball->addSpeedVector(-0.8*lossSpeed*pos_dif.normalized());
}
}
}
}
}
foreach (Ball *ball, balls)
{
//根据球到一个偏x方向45度的斜线‘/’的距离, 设置ZValue值,保证右下方的球始终在左上方的球上层, 避免左上方球的阴影会遮挡右下方的球体
ball->setZValue(QVector2D(ball->pos()).distanceToLine(QVector2D(-10000, -10000), QVector2D(1, 1)));
//球体运动
ball->move(m_fps);
}
}
MainWin
主窗口,初始化游戏,摆放按钮。
mainwin.h
#ifndef MAINWIN_H
#define MAINWIN_H
#include <QWidget>
#include <QPushButton>
#include "ui_mainwin.h"
#include "ball.h"
#include "cushion.h"
#include "club.h"
#ifndef ADDITEM
#define ADDITEM(item) if(!m_pGraphicsView->scene()->items().contains(item)){m_pGraphicsView->scene()->addItem(item);}
#endif
class MainWin : public QWidget, public Ui_MainWin
{
Q_OBJECT
public:
MainWin(QWidget *parent = nullptr);
~MainWin();
void initialzed();
protected:
bool event(QEvent *event);
protected slots:
void rst();
private:
QList<Ball*> m_redBalls;
Cushion *m_pCushion = nullptr;
Ball *m_pWhite = nullptr;
Ball *m_pBlack = nullptr;
Ball *m_pPink = nullptr;
Ball *m_pBlue = nullptr;
Ball *m_pYellow = nullptr;
Ball *m_pCoffee = nullptr;
Ball *m_pGreen = nullptr;
Club *m_pClub = nullptr;
QPushButton *m_pBtnRst = nullptr;
QPushButton *m_pBtnUndo = nullptr;
QPushButton *m_pBtnRedo = nullptr;
};
#endif // MAINWIN_H
mainwin.cpp
#include "mainwin.h"
#include "ui_mainwin.h"
#include <QtMath>
#include "defualtoperator.h"
#include "cushion.h"
#include "club.h"
MainWin::MainWin(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
initialzed();
}
MainWin::~MainWin()
{
}
void MainWin::initialzed()
{
this->setWindowTitle("Billiards");
m_pCushion = new Cushion();
m_pGraphicsView->createScene();
ADDITEM(m_pCushion);
m_pClub = new Club();
m_pWhite = new Ball(Ball::white);
m_pBlack = new Ball(Ball::black);
m_pPink = new Ball(Ball::pink);
m_pBlue = new Ball(Ball::blue);
m_pYellow = new Ball(Ball::yellow);
m_pCoffee = new Ball(Ball::coffee);
m_pGreen = new Ball(Ball::green);
for(int i = 0; i < 15; ++i)
{
m_redBalls.append(new Ball(Ball::red));
}
static_cast<DefualtOperator*>(m_pGraphicsView->operatorObj().get())->setWhiteBall(m_pWhite);
static_cast<DefualtOperator*>(m_pGraphicsView->operatorObj().get())->setCushion(m_pCushion);
static_cast<DefualtOperator*>(m_pGraphicsView->operatorObj().get())->setClub(m_pClub);
m_pBtnRst = new QPushButton("重置", m_pGraphicsView);
m_pBtnRst->setFixedSize(100, 30);
connect(m_pBtnRst, SIGNAL(clicked(bool)), this, SLOT(rst()));
m_pBtnUndo = new QPushButton("回退", m_pGraphicsView);
m_pBtnUndo->setFixedSize(100, 30);
m_pBtnUndo->move(m_pBtnRst->pos() + QPoint(m_pBtnRst->width(), 0));
connect(m_pBtnUndo, SIGNAL(clicked(bool)), static_cast<DefualtOperator*>(m_pGraphicsView->operatorObj().get()), SLOT(onUndo()));
m_pBtnRedo = new QPushButton("恢复", m_pGraphicsView);
m_pBtnRedo->setFixedSize(100, 30);
m_pBtnRedo->move(m_pBtnUndo->pos() + QPoint(m_pBtnUndo->width(), 0));
connect(m_pBtnRedo, SIGNAL(clicked(bool)), static_cast<DefualtOperator*>(m_pGraphicsView->operatorObj().get()), SLOT(onRedo()));
rst();
}
bool MainWin::event(QEvent *event)
{
//根据窗口大小变化调整缩放, 使图元始终保持在窗口中
static float scale = 1;
if(event->type() == QEvent::Resize)
{
m_pGraphicsView->scale(1/scale, 1/scale);
if(m_pGraphicsView->width()/m_pGraphicsView->height() >
m_pCushion->boundingRect().width()/m_pCushion->boundingRect().height())
{
scale = m_pGraphicsView->height()/m_pCushion->boundingRect().height()/1.0;
}
else
{
scale = m_pGraphicsView->width()/m_pCushion->boundingRect().width()/1.0;
}
scale *= 0.8f;
m_pGraphicsView->scale(scale, scale);
m_pGraphicsView->scene()->setSceneRect(m_pCushion->boundingRect());
}
return QWidget::event(event);
}
void MainWin::rst()
{
//规则:初始状态没有白球, 需要自己放置在半圆形区域
if(m_pGraphicsView->scene()->items().contains(m_pWhite))
{
m_pGraphicsView->scene()->removeItem(m_pWhite);
}
//根据规则设置彩球摆放位置
m_pBlack->setPos(-m_pCushion->m_inRect.x()/2.6/2 + 324/2.6, 0);
m_pBlack->setSpeed(0);
m_pPink->setPos(-m_pCushion->m_inRect.x()/2.6/2/2, 0);
m_pPink->setSpeed(0);
m_pBlue->setPos(0, 0);
m_pBlue->setSpeed(0);
m_pYellow->setPos(m_pCushion->m_inRect.x()/2/2.6 - 737/2.6, 292/2.6);
m_pYellow->setSpeed(0);
m_pCoffee->setPos(m_pCushion->m_inRect.x()/2/2.6 - 737/2.6, 0);
m_pCoffee->setSpeed(0);
m_pGreen->setPos(m_pCushion->m_inRect.x()/2/2.6 - 737/2.6, -292/2.6);
m_pGreen->setSpeed(0);
m_pWhite->setSpeed(0);
//15个红球可以先确定第一个球(粉球旁边)的位置, 然后通过计算得出其余球位置
float baseX = -m_pCushion->m_inRect.x()/2.6/2/2 - m_pBlack->r()*2 - 20/2.6;
float d = m_pBlack->r()*2;
for(int i = 0; i < 15; ++i)
{
float x = 0;
float y = 0;
if(i < 1)
{
x = baseX;
y = 0;
}
else if(i < 3)
{
x = baseX - m_pBlack->r()*qPow(3, 0.5);
y = 0 + (i - 1.5)*d;
}
else if(i < 6)
{
x = baseX - m_pBlack->r()*qPow(3, 0.5)*2;
y = 0 + (i - 4)*d;
}
else if(i < 10)
{
x = baseX - m_pBlack->r()*qPow(3, 0.5)*3;
y = 0 + (i - 7.5)*d;
}
else
{
x = baseX - m_pBlack->r()*qPow(3, 0.5)*4;
y = 0 + (i - 12)*d;
}
m_redBalls[i]->setPos(x, y);
m_redBalls[i]->setSpeed(0);
m_redBalls[i]->setZValue(QVector2D(m_redBalls[i]->pos()).distanceToLine(QVector2D(-10000, -10000), QVector2D(1, 1)));
ADDITEM(m_redBalls[i]);
}
//ZValue值大的, 绘制在上层
m_pClub->setZValue(20000);
m_pClub->setDir(QVector2D(-1, 0));
m_pClub->setPos(-m_pClub->length()/2.6/2, m_pCushion->m_outRect.y()/2.6/2 + 100/2.6);
ADDITEM(m_pBlack);
ADDITEM(m_pPink);
ADDITEM(m_pBlue);
ADDITEM(m_pYellow);
ADDITEM(m_pCoffee);
ADDITEM(m_pGreen);
ADDITEM(m_pClub);
static_cast<DefualtOperator*>(m_pGraphicsView->operatorObj().get())->start();
}
结语
代码中已经敲了大致注释, 欢迎提问。