前言
国庆阅兵的时候,战斗机组成各种编队一起飞过天安门的场景很是壮观,让国人振奋。我们使用软件把编队的方式绘制出来,更加直观的展现给大家。
在一个编队中一般会有一架长机和多架僚机,长机一般处于编队居中或者靠前的位置,而僚机在会在长机的周围,编队的图形一般是对称。
软件实现了以下功能:
- 一架长机多架僚机;
- 鼠标点击选中飞机,具有高亮选中效果;
- 长机或者僚机可以自由的拖动;
- 长机由红色的五角星表示,僚机由不同的数据来表示;
- 按住Ctrl键同时点击鼠标左键选中长机然后拖动可以复制一架僚机;
- 可以通过右键菜单删除僚机;
- 可以设置僚机与长机的方位距离(每隔方格代表1000米*1000米);
- 最多可以展示100架飞机,还可以自由的扩展(修改代码)。
先看一下实现的效果:
开发环境
VS2010 + QT4.8.6 + x64
设计
以上功能使用Qt的GraphicsView和GraphicsScene、GraphicsItem绘制完成,GraphicsScene用来绘制场景,GraphicsView用来展示。每种类型的飞机对应的时候一种类型的Item,所有类型的Item都是通过统一的实体管理器进行管理的。
代码
宏定义 TeamSceneDef.h
#pragma once
//场景的起始像素位置
#define SCENE_START_X (-300)
#define SCENE_START_Y (-300)
//场景的大小
#define SCENE_WIDTH 600
#define SCENE_HEIGHT 600
//格子的长和宽
#define GRID_WIDTH 50
#define GRID_HEIGHT 50
//格子的起始像素位置
#define GRID_START_X (-250)
#define GRID_START_Y (-250)
//格子的终止像素位置
#define GRID_END_X 250
#define GRID_END_Y 250
//格子的行数和列数
#define GRID_ROW_COUNT 10
#define GRID_COL_COUNT 10
//长机初始位置位于格子的行列位置
#define LEADAIRCRAF_ORG_ROW ((GRID_ROW_COUNT - 1)/2)
#define LEADAIRCRAF_ORG_COL ((GRID_COL_COUNT - 1)/2)
//实体图片在每一个格子中的偏移量
#define ITEM_PIAMAP_OFFSET_X 10
#define ITEM_PIAMAP_OFFSET_Y 10
飞机实体定义 EntityPixmapItem.h
#ifndef ENTITYPIXMAPITEM_H
#define ENTITYPIXMAPITEM_H
#include <QGraphicsPixmapItem>
class EntityPixmapItem : public QGraphicsPixmapItem{
public:
EntityPixmapItem();
~EntityPixmapItem();
virtual void setGridPos(int r, int c);
void getGridPos(int&r, int&c){
r = _gridRow;
c = _gridCol;
}
int&getGridRow(){
return _gridRow;
}
int&getGridCol(){
return _gridCol;
}
void setLeadAircraft(bool b = false){
_isLeadAircraft = b;
}
bool&getLeadAircraft(){
return _isLeadAircraft;
}
void setSelected(bool b);
bool&getSelected(){
return _isSelected;
}
QRect&getRectRange(){
return _rectRange;
}
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)override;
protected:
//当前Item在10*10格子中的位置
int _gridRow, _gridCol;
//在Item中绘制图片的偏移量
int _offsetX,_offsetY;
//Item所在的矩形范围
QRect _rectRange;
//长机
bool _isLeadAircraft;
//是否被选中
bool _isSelected;
};
#endif // ENTITYPIXMAPITEM_H
#include <QPainter>
#include <QPen>
#include "EntityPixmapItem.h"
#include "TeamSceneDef.h"
EntityPixmapItem::EntityPixmapItem()
: QGraphicsPixmapItem(){
_gridRow = -1;
_gridCol = -1;
_offsetX = ITEM_PIAMAP_OFFSET_X;
_offsetY = ITEM_PIAMAP_OFFSET_Y;
_isLeadAircraft = false;
_isSelected = false;
}
EntityPixmapItem::~EntityPixmapItem(){
}
void EntityPixmapItem::setGridPos(int r, int c){
_gridRow = r;
_gridCol = c;
_rectRange = QRect(GRID_START_X + _gridRow * GRID_WIDTH, GRID_START_Y +_gridCol * GRID_HEIGHT , GRID_WIDTH, GRID_HEIGHT);
setPos(_rectRange.left(),_rectRange.top());
setOffset(_offsetX,_offsetY);
}
void EntityPixmapItem::setSelected(bool b){
_isSelected = b;
update();
}
void EntityPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
//1.绘制军标
QGraphicsPixmapItem::paint(painter,option,widget);
QPixmap&pmp = pixmap();
//绘制选中效果
if (_isSelected){
painter->setPen(Qt::NoPen);
QBrush brush(QColor(255,255,0,100));
painter->setBrush(brush);
painter->drawEllipse(_offsetX, _offsetY, pmp.width(), pmp.height());
}
}
长机定义 LeadAircraftEntityItem.h
#ifndef LEADAIRCRAFTENTITYITEM_H
#define LEADAIRCRAFTENTITYITEM_H
#include "EntityPixmapItem.h"
class QSvgRenderer;
class QPixmap;
//长机实体Item
class LeadAircraftEntityItem: public EntityPixmapItem{
public:
LeadAircraftEntityItem();
~LeadAircraftEntityItem();
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)override;
private:
//SVG图片
QSvgRenderer* _svgRender;
QPixmap*_svgPixmap;
};
#endif // LEADAIRCRAFTENTITYITEM_H
#include <QPainter>
#include <QSvgRenderer>
#include "LeadAircraftEntityItem.h"
LeadAircraftEntityItem::LeadAircraftEntityItem(){
_svgRender = nullptr;
_svgPixmap = nullptr;
}
LeadAircraftEntityItem::~LeadAircraftEntityItem(){
}
void LeadAircraftEntityItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
EntityPixmapItem::paint(painter, option, widget);
//长(zhang)机
if (_svgPixmap == nullptr){
_svgRender = new QSvgRenderer;
_svgRender->load(QString("lead.svg"));
_svgPixmap = new QPixmap(15,15);
_svgPixmap->fill(Qt::transparent);
QPainter p(_svgPixmap);
_svgRender->render(&p);
}
//长机绘制图片
painter->drawPixmap(_offsetX , _offsetY , 15, 15, *_svgPixmap);
}
僚机定义:LeadAircraftEntityItem
#ifndef LEADAIRCRAFTENTITYITEM_H
#define LEADAIRCRAFTENTITYITEM_H
#include "EntityPixmapItem.h"
class QSvgRenderer;
class QPixmap;
//长机实体Item
class LeadAircraftEntityItem: public EntityPixmapItem{
public:
LeadAircraftEntityItem();
~LeadAircraftEntityItem();
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)override;
private:
//SVG图片
QSvgRenderer* _svgRender;
QPixmap*_svgPixmap;
};
#endif // LEADAIRCRAFTENTITYITEM_H
#include <QPainter>
#include <QSvgRenderer>
#include "LeadAircraftEntityItem.h"
LeadAircraftEntityItem::LeadAircraftEntityItem(){
_svgRender = nullptr;
_svgPixmap = nullptr;
}
LeadAircraftEntityItem::~LeadAircraftEntityItem(){
}
void LeadAircraftEntityItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
EntityPixmapItem::paint(painter, option, widget);
//长(zhang)机
if (_svgPixmap == nullptr){
_svgRender = new QSvgRenderer;
_svgRender->load(QString("lead.svg"));
_svgPixmap = new QPixmap(15,15);
_svgPixmap->fill(Qt::transparent);
QPainter p(_svgPixmap);
_svgRender->render(&p);
}
//长机绘制图片
painter->drawPixmap(_offsetX , _offsetY , 15, 15, *_svgPixmap);
}
场景定义 TeamGraphicsScene
#ifndef TEAMGRAPHICSSCENE_H
#define TEAMGRAPHICSSCENE_H
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QVector>
class QGraphicsSceneMouseEvent;
class MyDefinePlatformEntity;
class EntityPixmapItem;
class QMenu;
class QAction;
class GuiItemSetting;
/*————————————————————————————————————— 编队场景 ———————————————————————————————————————————————————*/
class TeamGraphicsScene : public QGraphicsScene{
Q_OBJECT
public:
TeamGraphicsScene(QObject *parent = nullptr);
~TeamGraphicsScene();
void setEntity();
private:
void init();
void mousePressEvent(QGraphicsSceneMouseEvent *event)override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)override;
void moveEntityItem(QPointF movePt);
void moveVirtualItem(QPointF movePt);
private slots:
void slotDelete();
void slotSetting();
private:
int _x, _y, _width, _hight;
//保存所有表格的位置信息
QRect _gridTeam[10][10];
bool _leftBtnDown ;
QPointF _leftBtnDownPos;//鼠标按下时的坐标
QPointF _mouseMovePt;//鼠标滑动时的坐标
//鼠标左键按下时选中的实体项
EntityPixmapItem* _selectEntityItem;
//拖动时虚拟的军标图形
QGraphicsPixmapItem* _virtualPixmap;
//菜单
QMenu* _menu;
QAction* _actionDelete; //删除
QAction* _actionSetting; //设置
GuiItemSetting* _guiItemSetting;
};
#endif // TEAMGRAPHICSSCENE_H
#include <QApplication>
#include <QDebug>
#include <QMenu>
#include <QAction>
#include <QMessageBox>
#include <QGraphicsEllipseItem>
#include <QGraphicsSceneDragDropEvent>
#include "TeamGraphicsScene.h"
#include "EntityPixmapItem.h"
#include "LeadAircraftEntityItem.h"
#include "WingmanEntityItem.h"
#include "EntityItemManager.h"
#include "TeamSceneDef.h"
#include "GuiItemSetting.h"
TeamGraphicsScene::TeamGraphicsScene(QObject *parent)
: QGraphicsScene(parent){
//设置场景的大小
_x = SCENE_START_X;
_y = SCENE_START_Y;
_width = SCENE_WIDTH;
_hight = SCENE_HEIGHT;
_leftBtnDown = false;
_selectEntityItem = nullptr;
_virtualPixmap = nullptr;
_menu = nullptr;
_actionDelete = nullptr; //删除
_actionSetting = nullptr; //设置
_guiItemSetting = nullptr;
init();
}
/*
* 定义坐标系:
* X轴:向右为正(东) Y轴:向上为正(北)
* 绘制场景的范围 (-250,-250) 到 (250,250),每个小格子的大小是 50*50
*/
TeamGraphicsScene::~TeamGraphicsScene(){
}
void TeamGraphicsScene::setEntity(){
QPixmap* pixmap = new QPixmap(32,32);
pixmap->load("001.png");
QPixmap pmp = pixmap->scaled(32,32);
LeadAircraftEntityItem* jbItem = new LeadAircraftEntityItem;
jbItem->setPixmap(pmp);
jbItem->setGridPos(LEADAIRCRAF_ORG_ROW/*4*/, LEADAIRCRAF_ORG_COL/*4*/);
jbItem->setLeadAircraft(true);//设为长机
EntityItemMgr()->setLeadAircraftItem(jbItem);
addItem(jbItem);
//创建出一个虚拟的军标图像Item
_virtualPixmap = new QGraphicsPixmapItem;
_virtualPixmap->setPixmap(pmp);
_virtualPixmap->setVisible(false);//首先隐藏掉
addItem(_virtualPixmap);
}
void TeamGraphicsScene::init(){
setSceneRect(_x, _y, _width, _hight);
QPen pen;
pen.setColor(Qt::lightGray);
pen.setWidth(1);
//中心点
QGraphicsEllipseItem* ellipCenter = new QGraphicsEllipseItem(-2, -2, 4, 4);
ellipCenter->setPen(pen);
addItem(ellipCenter);
/*————————————————————————————————————— 绘制坐标系 ———————————————————————————————————————————————————*/
//绘制横轴
QGraphicsLineItem* lineX = new QGraphicsLineItem(GRID_START_X/*-250*/, 0, GRID_END_X/*250*/, 0);
lineX->setPen(pen);
addItem(lineX);
//绘制横轴箭头
QGraphicsLineItem* lineXArrows1 = new QGraphicsLineItem(-GRID_START_X-10/*240*/, 5, GRID_END_X/*250*/, 0);
lineXArrows1->setPen(pen);
addItem(lineXArrows1);
QGraphicsLineItem* lineXArrows2 = new QGraphicsLineItem(-GRID_START_X-10, -5, GRID_END_X/*250*/, 0);
lineXArrows2->setPen(pen);
addItem(lineXArrows2);
QFont font;
font.setFamily(QString::fromUtf8("微软雅黑"));
font.setPointSize(12);
QGraphicsTextItem* tX = new QGraphicsTextItem(QString::fromLocal8Bit("X(东)"));
tX->setDefaultTextColor(Qt::yellow);
tX->setFont(font);
tX->setPos(GRID_END_X + 3, 0);
addItem(tX);
//绘制纵轴
QGraphicsLineItem* lineY = new QGraphicsLineItem(0, GRID_START_Y/*-250*/, 0, GRID_END_Y/*250*/);
lineY->setPen(pen);
addItem(lineY);
/*
* Y轴的方向与实际屏幕的绘制方向正好相反
*/
//绘制纵轴箭头
QGraphicsLineItem* lineYArrows1 = new QGraphicsLineItem(0, GRID_START_Y/*-250*/, 5, -GRID_END_Y + 10/*-240*/);
lineYArrows1->setPen(pen);
addItem(lineYArrows1);
QGraphicsLineItem* lineYArrows2 = new QGraphicsLineItem(0, GRID_START_Y/*-250*/, -5, -GRID_END_Y + 10/*-240*/);
lineYArrows2->setPen(pen);
addItem(lineYArrows2);
QGraphicsTextItem* tY = new QGraphicsTextItem(QString::fromLocal8Bit("Y(北)"));
tY->setDefaultTextColor(Qt::yellow);
tY->setFont(font);
tY->setPos(3, GRID_START_Y - 3);
addItem(tY);
/*————————————————————————————————————— 绘制网格 —————————————————————————————————————————*/
//绘制行
pen.setStyle(Qt::DashLine);
for (int row = 0; row < 11; row ++){
QGraphicsLineItem* lineRow = new QGraphicsLineItem(GRID_START_X/*-250*/, GRID_START_Y/*-250*/ + row * GRID_HEIGHT/*50*/, GRID_END_X, GRID_START_Y/*-250*/ + row * GRID_HEIGHT/*50*/);
lineRow->setPen(pen);
addItem(lineRow);
}
//绘制列
for (int col = 0; col < 11; col ++){
QGraphicsLineItem* lineRow = new QGraphicsLineItem(GRID_START_Y/*-250*/ + col * GRID_WIDTH/*50*/, GRID_START_Y/*-250*/, GRID_START_Y/*-250*/ + col*GRID_WIDTH/*50*/ , GRID_END_Y/*250*/);
lineRow->setPen(pen);
addItem(lineRow);
}
//计算格子
for (int row = 0; row < 10; row ++){
for (int col = 0; col < 10; col ++){
_gridTeam[row][col] = QRect( -250 + row * 50 , -250 + 50 * col , 50, 50);
#if 0
//显示格子
QGraphicsRectItem* rect = new QGraphicsRectItem(_gridTeam[row][col]);
pen.setColor(Qt::yellow);
rect->setPen(pen);
addItem(rect);
QGraphicsTextItem* t = new QGraphicsTextItem(QString("(%1,%2)").arg(row).arg(col));
t->setDefaultTextColor(Qt::yellow);
t->setFont(font);
t->setPos(_gridTeam[row][col].left() , _gridTeam[row][col].top());
addItem(t);
#endif
}
}
}
void TeamGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event){
if (event->button() & Qt::LeftButton) {
_leftBtnDown = true;
QPointF point = event->scenePos();
_selectEntityItem = EntityItemMgr()->clickItem(point);
if (_selectEntityItem!= nullptr){
if (_selectEntityItem->getLeadAircraft() && QApplication::keyboardModifiers() == Qt::ControlModifier){//选中长机并且按下Ctrl键
//1.根据长机数据创建一个僚机
WingmanEntityItem* copyItem = new WingmanEntityItem;
int copyItemRow = -1, copyItemCol = -1;
_selectEntityItem->getGridPos(copyItemRow, copyItemCol);
copyItem->setGridPos(copyItemRow, copyItemCol);//设置在方格中的位置
copyItem->setLeadAircraft(false);//设置僚机
copyItem->setPixmap(_selectEntityItem->pixmap());//设置图片
//2.保存僚机
copyItem->setRoleID(EntityItemMgr()->createRoleID());//设置僚机的RoleID
EntityItemMgr()->addWingmanItem(copyItem);
addItem(copyItem);
_selectEntityItem = copyItem;
}
//把虚图标移动到正确的位置,但是不需要显示出来
moveVirtualItem(point);
}
}
}
void TeamGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
QPointF movePt = event->scenePos();
if (_leftBtnDown && _selectEntityItem != nullptr){//鼠标左键按下
//移动实体军表项
moveEntityItem(movePt);
//显示出来并且移动虚拟的军标项
_virtualPixmap->setVisible(true);
moveVirtualItem(movePt);
}
}
void TeamGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
if (event->button() & Qt::LeftButton) {
_leftBtnDown = false;
_virtualPixmap->setVisible(false);
}else if (event->button() & Qt::RightButton) {//右键弹出菜单
if (_selectEntityItem != nullptr && !_selectEntityItem->getLeadAircraft()){//僚机
QMenu popMenu;
QAction* actionDelete = popMenu.addAction(QString::fromLocal8Bit("删除"), this, SLOT(slotDelete()));
QAction* actionSetting = popMenu.addAction(QString::fromLocal8Bit("设置"), this, SLOT(slotSetting()));
popMenu.exec(QCursor::pos());
}
}
}
/*
* 鼠标选中移动实体
* 鼠标首先移动到他周边的8个方格内
*
* +--------+
* |1 |2 |3 |
* -----------
* |4 |E |6 |
* ----------
* |7 |8 |9 |
* +--------+
*/
void TeamGraphicsScene::moveEntityItem(QPointF movePt){
int currentRow = -1, currentCol = -1;
_selectEntityItem->getGridPos(currentRow, currentCol);
for (int col = currentCol - 1; col <= currentCol + 1; col ++){
for (int row = currentRow - 1; row <= currentRow + 1; row ++){
if (row < 0 || row > GRID_ROW_COUNT - 1){//超出行边界
continue;
}
if (col < 0 || col > GRID_COL_COUNT - 1){//超出列边界
continue;
}
if (row == currentRow && col == currentCol){//排除当前位置
continue;
}
if (_gridTeam[row][col].contains(movePt.toPoint())) {
_selectEntityItem->setGridPos(row, col);
_selectEntityItem->update();
//计算长机和僚机之间的距离
EntityItemMgr()->resetGridLength(_selectEntityItem);
break;
}
}
}
}
void TeamGraphicsScene::moveVirtualItem(QPointF movePt){
QPixmap&pmp = _virtualPixmap->pixmap();
int w = pmp.width();
int h = pmp.height();
_virtualPixmap->setPos(movePt.x() - w/2, movePt.y() - h/2);
}
void TeamGraphicsScene::slotDelete(){
if (0 == QMessageBox::question(nullptr, QString::fromLocal8Bit("删除"), QString::fromLocal8Bit("确定要删除?"), QString::fromLocal8Bit("确定"), QString::fromLocal8Bit("取消"), QString::fromLocal8Bit(""), 1)) {
//1.在管理器中移除掉
EntityItemMgr()->deleteWingmanItem(dynamic_cast<WingmanEntityItem*>(_selectEntityItem));
//2.在场景中移除
removeItem(_selectEntityItem);
_selectEntityItem = nullptr;
}
}
void TeamGraphicsScene::slotSetting(){
if (_guiItemSetting == nullptr){
_guiItemSetting = new GuiItemSetting;
}
_guiItemSetting->initEntityPixmapItem(dynamic_cast<WingmanEntityItem*>(_selectEntityItem));
_guiItemSetting->setAttribute(Qt::WA_ShowModal);
_guiItemSetting->show();
}
调用场景
void QtGuiTeam::setEntity(MyDefinePlatformEntity* myDefinePlatformEntity){
_myDefinePlatformEntity = myDefinePlatformEntity;
_teamGraphicsScene->setEntity();
}
void QtGuiTeam::init(){
ui.graphicsView->setBackgroundBrush(QBrush(QColor(64, 128, 128)));
ui.graphicsView->setRenderHint(QPainter::Antialiasing);//反锯齿设置
ui.graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui.graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_teamGraphicsScene = new TeamGraphicsScene;
ui.graphicsView->setScene(_teamGraphicsScene);
}
设置僚机和长机的距离方位
void GuiItemSetting::slotOK(){
LeadAircraftEntityItem*leadAircraft = EntityItemMgr()->getLeadAircraftItem();
double x = ui.lineEditX->text().toDouble();
double y = ui.lineEditY->text().toDouble();
double z = ui.lineEditZ->text().toDouble();
_wingmanEntityItem->setOffsetLeadAircraftX(x);
int xOffset = (int)x/(int)EntityItemMgr()->getGridWidth() + ((int)x % (int)EntityItemMgr()->getGridWidth() == 0 ? 0 : 1);
_wingmanEntityItem->setOffsetLeadAircraftY(y);
int yOffset = (int)y/(int)EntityItemMgr()->getGridHeight() + ((int)y % (int)EntityItemMgr()->getGridHeight() == 0 ? 0 : 1);
_wingmanEntityItem->setGridPos(leadAircraft->getGridRow() + xOffset, leadAircraft->getGridCol() - yOffset);
_wingmanEntityItem->setOffsetLeadAircraftZ(z);
_wingmanEntityItem->update();
close();
}
aaa.