Qt的 Graphics View的框架非常强大,框架由Scene Item 和View组成,场景管理所有的Item,View用来展示。详细的介绍请看Qt自带的帮助文档。为了学习这个框架,博主决定自己写个坦克大战试试。首先我们来完成坦克的移动。
玩家控制的坦克作为场景中的一个元素必然是继承QGraphicsItem类,我们来看下这个类,发现里面有:
virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0) = 0
virtual QRectF boundingRect() const = 0
这两个纯虚函数是必须我们自己定义的。
BoudingRect指的是用来绘制元素的那个矩形范围,这有别于元素的形状(shape),shape()用来作碰撞检测,shape()默认返回的是BoudingRect,这需要注意一下。
paint()函数用来绘制元素,我们需要用这个函数来绘制坦克的图片。
class userTank :public QGraphicsItem
{
public:
userTank(QGraphicsItem * parent = 0);
QRectF boundingRect() const; //pure virtual
void paint(QPainter * painter,
const QStyleOptionGraphicsItem * option,
QWidget * widget = 0) ; //virtual
enum{up,down,left,right}movedir; //坦克移动方向
enum{moving,stop}state; //移动状态
QSize size;
protected:
void keyPressEvent(QKeyEvent * event) ;
void keyReleaseEvent(QKeyEvent * event);
private:
QPixmap *picLeft,
*picRight,
*picUp,
*picDown; //玩家坦克的4个运动方向的图片
static int objectType; //目标类型为玩家坦克
};
int userTank::objectType = 0;
userTank::userTank(QGraphicsItem *parent):QGraphicsItem(parent)
{
picLeft = new QPixmap(":/image/usertank/img/p1tankL.gif");
picRight = new QPixmap(":/image/usertank/img/p1tankR.gif");
picUp = new QPixmap(":/image/usertank/img/p1tankU.gif");
picDown = new QPixmap(":/image/usertank/img/p1tankD.gif");
size = picDown->size();
movedir = up;
state = stop;
QTimer timer;
timer.setInterval(100);
}
QRectF userTank::boundingRect() const
{
return QRectF(QPoint(0,0),size);
}
void userTank::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPixmap* pic;
switch(movedir){
case up:
pic = picUp;
break;
case down:
pic = picDown;
break;
case left:
pic = picLeft;
break;
default:
pic = picRight;
}
painter->drawPixmap(0,0,*pic);
}
void userTank::keyPressEvent(QKeyEvent *event)
{
switch(event->key()){
case Qt::Key_Up:
movedir = up;
state = moving;
break;
case Qt::Key_Down:
movedir = down;
state = moving;
break;
case Qt::Key_Left:
movedir = left;
state = moving;
break;
case Qt::Key_Right:
movedir= right;
state = moving;
break;
default: ;
}
}
void userTank::keyReleaseEvent(QKeyEvent *event)
{
if( state == moving){
switch(event->key()){
case Qt::Key_Up:
if(movedir != up) return;
break;
case Qt::Key_Down:
if(movedir != down) return;
break;
case Qt::Key_Left:
if(movedir != left) return;
break;
case Qt::Key_Right:
if(movedir != right) return;
break;
default:
return;
}
state = stop;
}
}
原理比较简单,就是获取按键,然后确定移动方向 和移动状态。有个地方要注意一下,就是按键释放的时候可能我们已经按下了别的方向键,然后松开了原来那个键,所以要检测一下当前释放的键是否是移动方向,不然会出现调转方向卡顿的情况。
一个游戏通常都有一个核心控制类,为了测试我们的坦克移动,我们先简单的写一下gamecontroller
class QGraphicsView;
class QGraphicsScene;
class userTank;
class gameController :public QObject
{
Q_OBJECT
public:
gameController(QObject* parent =0,QWidget* widget=0);
private:
QGraphicsView* view;
QGraphicsScene* scene;
userTank* tank;
private slots:
void userTankMove();
};
gameController::gameController(QObject *parent, QWidget *widget) :QObject(parent)
{
scene = new QGraphicsScene(QRectF(0,0,300,300));
tank = new userTank;
scene->addItem(tank);
tank->grabKeyboard();
view = new QGraphicsView(scene,widget);
QTimer* timer = new QTimer;
timer->setInterval(100);
connect(timer,SIGNAL(timeout()),SLOT(userTankMove()));
timer->start();
}
void gameController::userTankMove()
{
static int speed = 10;
if(tank->state == userTank::moving){
QPointF srcPos = tank->pos();
switch(tank->movedir){
case userTank::down:
srcPos.setY(qMin(srcPos.y()+speed,scene->sceneRect().height()-tank->size.rheight()));
break;
case userTank::up:
srcPos.setY(qMax(srcPos.y()-speed,0.0));
break;
case userTank::right:
srcPos.setX(qMin(srcPos.x()+speed,scene->sceneRect().width()-tank->size.rwidth()));
break;
default:
srcPos.setX(qMax(srcPos.x()-speed,0.0));
}
tank->setPos(srcPos);
}
}
有个地方要注意一下:我们的tank必须开启grabkeybord才能接收到按键事件。坦克移动由定时器完成,可以控制speed来控制坦克的移动速度。
以下是运行结果: