[Qt] 我的一款射击游戏及设计模式 - Hori Miona
2017-08-01 11:41
363 查看
【更新】我的新博客:www.ryuzhihao.cc,当然这个csdn博客也会更新
本文在新博客中的链接:点击打开链接
时间:2017年7月28日-7月30日
耗时:2天
此前一直在玩OpenGL,做的都是三维世界的东西,已经好久没有写过2D类的小游戏了。上学期,玩了《charles》之后,就一直想要写一款类似的射击类小游戏,只是前段时间有很多跟图形学有关的程序还要填坑,而且还要忙着参加夏令营。到现在终于闲了下来,就开始着手做一下这款小游戏《Hori Miona》吧。(在家一周也没见体重长回去多少)
这篇博文,可能更多的涉及到“游戏开发架构”。这俩天对于我自己来讲,也算是是摸索出了比较不错的架构。不管是3D还是2D游戏,render和modeling虽然不同,但是架构应该是个可以通用的模式。
在正文开始之前,还是惯例上一下我的实现结果。
射击游戏《Hori Miona》
一、下载链接:
http://download.csdn.net/detail/mahabharata_/9916762
二、游戏截图:
本游戏的部分设计细节
一、类图
程序的可视化界面为GameWidget类,并进行游戏循环,利用计时器Timer更新游戏状态。玩家Player类,用于记录玩家的状态信息;同时,玩家的所有操作由状态控制,每次游戏循环中,根据玩家当前所处的行为状态(攻击、移动?),更新玩家的信息。在Player类中,还有一个攻击管理器AttackManager,用于对玩家的攻击进行处理,包括:攻击的渲染、攻击模式的转化、攻击的碰撞检测等等。此外,在GameWidget中,还有一个成员EnemyManager,用于对游戏中的AI单位进行控制,根据玩家的当前位置,更新敌人的状态。
如下是简化的类图(在进行大致设计时采用):
二、基于四叉树的场景管理。
在游戏中的敌人的数目,这里采取了基于四叉树的场景管理的方法。虽然前面的类图在设计阶段是list<Enemy>来代替的。但是为了提高检索效率还是必须要引入四叉树进行场景对象的管理。
四叉树并不仅仅针对二维的游戏场景上,这里所指的二维是因为四叉树本身仅仅考虑了两个维度。在三维的游戏场景中,我们也可以使用四叉树,因为在四叉树应用最广泛的室外地形场景中,z轴往往只代表地形高度信息,或者说它的信息相比起xy轴而言,并没有特别重要。这是我们在场景管理中可以适当忽略的部分。
四叉树有很多变种,先谈一个简单的情况,就是假设所有物体是一个点,这样比较容易理解。在后面可以利用AABB包围盒进行碰撞检测。
把每个点放到正方形空间里,若该正方形内含有超过1个点(2个或2个以上的点),就把该正方形分割,直至每个小正方形(叶节点)仅含有一个点,就可以得出以下的分割结果:
这个做法是可以调整的,也就是说当空间分割到一定大小就不能继续分割(如最多只能对一个结点分割4层,因为层数太多也会影响检索效率)。关于四叉树节点的数据结构可按如下进行定义:
这里推荐我的另外一篇文章《对Obj模型的AABB包围盒加载器》:http://blog.csdn.net/mahabharata_/article/details/72593925
三、状态图在游戏中的应用:
相比起一般的软件开发,游戏开发对状态图的依赖应该是比较强的了。因为游戏中通常会涉及很多复杂的状态转换过程。比如:实现飞机移动的状态转换。这里也简单拿这个例子来进行一下说明:在程序中,飞机的非战斗状态主要包括以下几种行为(用枚举类型表示):
这是玩家类Player的简化代码,预设了6种非战斗的状态。为了能理顺完整的状态转换,因此设计了如下的状态图(为了方便绘制,没有体现左转的情况,但是可以对称得到):
在有了上面的状态图的情况下,便可以非常轻松地写出相应的状态转换代码:
四、火焰碰撞的矩阵计算:
在2D游戏的开发中,其实会发现和3D游戏有很多相似之处。用到的主要数学方法有:
变换矩阵、 插值、图像遮罩等等。
这里介绍其中一个处理方法:火焰跟随飞机旋转的效果——借助变换矩阵去实现。
这个和OpenGL中三维变换矩阵的计算类似,只是相应的转换成了2维,但是为了方便计算,我们还是要使用齐次坐标。火焰虽然在视觉上看到的是不规则且不断变化的图形,但是在游戏中,我是用的是一个凸多边形作为一个检测区域,如下图:
已知玩家的坐标pos(Vec3),和倾斜角度dir(float)。那么我们可以首先确定一个原始的菱形(顶点坐标pi已知,为齐次坐标x,y,w),再计算相应的三维变换矩阵matrix(3*3),将顶点坐标pi乘以三维变换矩阵matrix便可求出火焰的范围。
计算的数学过程如下:
(1) 已知火焰顶点pi(x,y,w),和变换角度dir,
(2) 计算pi到火焰中心位置的平移矩阵mat1;
计算绕火焰中心位置旋转dir角度的旋转矩阵mat2;
计算从火焰中心到原位置pi的平移助阵Mat3;
(3) 得到三维变换矩阵matrix = mat3* mat2*mat1;
(4) 由点pi* matrix即可得出变换后的坐标pi'。
可以用如下代码得到:
结语:
整体来说,这个项目的代码量要在2天时间内完成,还算是比较多的。想要在博客里面完全阐述所有的设计细节,真的是不太可能,但是欢迎交流。本程序是个娱乐工作,所以代码在整理完成后,会抽时间全部上传。
本文在新博客中的链接:点击打开链接
时间:2017年7月28日-7月30日
耗时:2天
此前一直在玩OpenGL,做的都是三维世界的东西,已经好久没有写过2D类的小游戏了。上学期,玩了《charles》之后,就一直想要写一款类似的射击类小游戏,只是前段时间有很多跟图形学有关的程序还要填坑,而且还要忙着参加夏令营。到现在终于闲了下来,就开始着手做一下这款小游戏《Hori Miona》吧。(在家一周也没见体重长回去多少)
这篇博文,可能更多的涉及到“游戏开发架构”。这俩天对于我自己来讲,也算是是摸索出了比较不错的架构。不管是3D还是2D游戏,render和modeling虽然不同,但是架构应该是个可以通用的模式。
在正文开始之前,还是惯例上一下我的实现结果。
射击游戏《Hori Miona》
一、下载链接:
http://download.csdn.net/detail/mahabharata_/9916762
二、游戏截图:
本游戏的部分设计细节
一、类图
程序的可视化界面为GameWidget类,并进行游戏循环,利用计时器Timer更新游戏状态。玩家Player类,用于记录玩家的状态信息;同时,玩家的所有操作由状态控制,每次游戏循环中,根据玩家当前所处的行为状态(攻击、移动?),更新玩家的信息。在Player类中,还有一个攻击管理器AttackManager,用于对玩家的攻击进行处理,包括:攻击的渲染、攻击模式的转化、攻击的碰撞检测等等。此外,在GameWidget中,还有一个成员EnemyManager,用于对游戏中的AI单位进行控制,根据玩家的当前位置,更新敌人的状态。
如下是简化的类图(在进行大致设计时采用):
二、基于四叉树的场景管理。
在游戏中的敌人的数目,这里采取了基于四叉树的场景管理的方法。虽然前面的类图在设计阶段是list<Enemy>来代替的。但是为了提高检索效率还是必须要引入四叉树进行场景对象的管理。
四叉树并不仅仅针对二维的游戏场景上,这里所指的二维是因为四叉树本身仅仅考虑了两个维度。在三维的游戏场景中,我们也可以使用四叉树,因为在四叉树应用最广泛的室外地形场景中,z轴往往只代表地形高度信息,或者说它的信息相比起xy轴而言,并没有特别重要。这是我们在场景管理中可以适当忽略的部分。
四叉树有很多变种,先谈一个简单的情况,就是假设所有物体是一个点,这样比较容易理解。在后面可以利用AABB包围盒进行碰撞检测。
把每个点放到正方形空间里,若该正方形内含有超过1个点(2个或2个以上的点),就把该正方形分割,直至每个小正方形(叶节点)仅含有一个点,就可以得出以下的分割结果:
这个做法是可以调整的,也就是说当空间分割到一定大小就不能继续分割(如最多只能对一个结点分割4层,因为层数太多也会影响检索效率)。关于四叉树节点的数据结构可按如下进行定义:
struct treeNode { bool isLeaf; //是否是叶节点 // 四个子树 treeNode* upLeft; treeNode* upRight; treeNode* downLeft; treeNode* downRight; int objectNum; //包含对象个数 List<Object> objects; //包含的对象 AABB box; //所在的AABB包围盒 }了解了最基本四叉树后,可把问题从质点扩充到占有一定体积的物体。虽然我们可以每次比较场景物体和正方形的相交,但是为了提高性能,一般使用物体的包围盒进行碰撞检测:
这里推荐我的另外一篇文章《对Obj模型的AABB包围盒加载器》:http://blog.csdn.net/mahabharata_/article/details/72593925
三、状态图在游戏中的应用:
相比起一般的软件开发,游戏开发对状态图的依赖应该是比较强的了。因为游戏中通常会涉及很多复杂的状态转换过程。比如:实现飞机移动的状态转换。这里也简单拿这个例子来进行一下说明:在程序中,飞机的非战斗状态主要包括以下几种行为(用枚举类型表示):
class Player { private: // 飞机运动状态 enum STATE { _STOP, // 停止 _RUN, // 直行 _LEFT, // 静止左转 _RIGHT, // 静止右转 _RUN_LEFT, // 运动左转 _RUN_RIGHT // 运动右转 }; short m_curState; // 飞机当前运行状态 public: void setCurrentState(short state); // 设置飞机的当前状态 short getCurrentState(); // 获取飞机的当前状态 void updateStates(); // 更新 void render(QPainter* painter); };
这是玩家类Player的简化代码,预设了6种非战斗的状态。为了能理顺完整的状态转换,因此设计了如下的状态图(为了方便绘制,没有体现左转的情况,但是可以对称得到):
在有了上面的状态图的情况下,便可以非常轻松地写出相应的状态转换代码:
void GameWidget::keyPressEvent(QKeyEvent *event) { if(event->isAutoRepeat()) return; switch(event->key()) { case Qt::Key_W: // 前进 if(m_player.getCurrentState()==Player::_STOP) m_player.setCurrentState(Player::_RUN); if(m_player.getCurrentState()==Player::_LEFT) m_player.setCurrentState(Player::_RUN_LEFT); if(m_player.getCurrentState()==Player::_RIGHT) m_player.setCurrentState(Player::_RUN_RIGHT); break; case Qt::Key_A: // 左 bA = true; if(m_player.getCurrentState()==Player::_STOP) m_player.setCurrentState(Player::_LEFT); if(m_player.getCurrentState()==Player::_RIGHT) m_player.setCurrentState(Player::_LEFT); if(m_player.getCurrentState()==Player::_RUN || m_player.getCurrentState()==Player::_RUN_RIGHT) m_player.setCurrentState(Player::_RUN_LEFT); break; case Qt::Key_D: // 右 bD = true; if(m_player.getCurrentState()==Player::_STOP) m_player.setCurrentState(Player::_RIGHT); if(m_player.getCurrentState()==Player::_LEFT) m_player.setCurrentState(Player::_RIGHT); if(m_player.getCurrentState()==Player::_RUN || m_player.getCurrentState()==Player::_RUN_LEFT) m_player.setCurrentState(Player::_RUN_RIGHT); break; case Qt::Key_J: //攻击 m_attackManager.setAttacked(true); break; case Qt::Key_Space: // 切换攻击模式 m_attackManager.changeAttackMode(); ui->lblAttackMode->setText(m_attackManager.getAttackMode()); break; case Qt::Key_R: if(m_timer.isActive()) m_timer.stop(); else m_timer.start(); break; case Qt::Key_Escape: m_timer.stop(); emit sig_closeGameWidget(); break; } } void GameWidget::keyReleaseEvent(QKeyEvent *event) { if(event->isAutoRepeat()) return; switch(event->key()) { case Qt::Key_W: if(m_player.getCurrentState()==Player::_RUN) m_player.setCurrentState(Player::_STOP); if(m_player.getCurrentState()==Player::_RUN_LEFT) m_player.setCurrentState(Player::_LEFT); if(m_player.getCurrentState()==Player::_RUN_RIGHT) m_player.setCurrentState(Player::_RIGHT); break; case Qt::Key_A: bA = false; if(m_player.getCurrentState() == Player::_RUN_LEFT && !bD) m_player.setCurrentState(Player::_RUN); if(m_player.getCurrentState() == Player::_LEFT && !bD) m_player.setCurrentState(Player::_STOP); if(m_player.getCurrentState() == Player::_RUN_LEFT && bD) m_player.setCurrentState(Player::_RUN_RIGHT); if(m_player.getCurrentState() == Player::_LEFT && bD) m_player.setCurrentState(Player::_RIGHT); break; case Qt::Key_D: bD = false; if(m_player.getCurrentState() == Player::_RUN_RIGHT && !bA) m_player.setCurrentState(Player::_RUN); if(m_player.getCurrentState() == Player::_RIGHT && !bA) m_player.setCurrentState(Player::_STOP); if(m_player.getCurrentState() == Player::_RUN_RIGHT && bA) m_player.setCurrentState(Player::_RUN_LEFT); if(m_player.getCurrentState() == Player::_RIGHT && bA) m_player.setCurrentState(Player::_LEFT); break; case Qt::Key_J: m_attackManager.setAttacked(false); break; } }
四、火焰碰撞的矩阵计算:
在2D游戏的开发中,其实会发现和3D游戏有很多相似之处。用到的主要数学方法有:
变换矩阵、 插值、图像遮罩等等。
这里介绍其中一个处理方法:火焰跟随飞机旋转的效果——借助变换矩阵去实现。
这个和OpenGL中三维变换矩阵的计算类似,只是相应的转换成了2维,但是为了方便计算,我们还是要使用齐次坐标。火焰虽然在视觉上看到的是不规则且不断变化的图形,但是在游戏中,我是用的是一个凸多边形作为一个检测区域,如下图:
已知玩家的坐标pos(Vec3),和倾斜角度dir(float)。那么我们可以首先确定一个原始的菱形(顶点坐标pi已知,为齐次坐标x,y,w),再计算相应的三维变换矩阵matrix(3*3),将顶点坐标pi乘以三维变换矩阵matrix便可求出火焰的范围。
计算的数学过程如下:
(1) 已知火焰顶点pi(x,y,w),和变换角度dir,
(2) 计算pi到火焰中心位置的平移矩阵mat1;
计算绕火焰中心位置旋转dir角度的旋转矩阵mat2;
计算从火焰中心到原位置pi的平移助阵Mat3;
(3) 得到三维变换矩阵matrix = mat3* mat2*mat1;
(4) 由点pi* matrix即可得出变换后的坐标pi'。
可以用如下代码得到:
// 将 pos从玩家中心移动到火焰中心 pos.setX(pos.x()+sin(3.14*dir/180.0)*90); pos.setY(pos.y()-cos(3.14*dir/180.0)*90); QPolygonF polygon; // 未变换的多边形 polygon<<QPointF(pos.x(),pos.y()-50) <<QPointF(pos.x()-35,pos.y()+35) <<QPointF(pos.x(),pos.y()+75) <<QPointF(pos.x()+35,pos.y()+35); QMatrix matrix; // 计算旋转矩阵matrix matrix.translate(pos.x(),pos.y()); // 平移到火焰中心 matrix.rotate(dir); // 旋转 matrix.translate(-pos.x(),-pos.y()); // 平移回原位置 polygon = polygon*matrix; // 多边形的坐标
结语:
整体来说,这个项目的代码量要在2天时间内完成,还算是比较多的。想要在博客里面完全阐述所有的设计细节,真的是不太可能,但是欢迎交流。本程序是个娱乐工作,所以代码在整理完成后,会抽时间全部上传。
相关文章推荐
- 多人联机射击游戏中的设计模式应用(二)
- 多人联机射击游戏中的设计模式应用(一)
- 多人联机射击游戏中的设计模式应用(一)
- 多人联机射击游戏中的设计模式应用
- 多人联机射击游戏中的设计模式应用(二)
- 多人联机射击游戏中的设计模式应用(二)
- 多人联机射击游戏中的设计模式应用(一)
- 多人联机射击游戏中的设计模式应用(二)
- 多人联机射击游戏中的设计模式应用(一)
- 多人联机射击游戏中的设计模式应用(二)
- 多人联机射击游戏中的设计模式应用(一)
- c++/qt设计模式-装饰者模式
- c++/qt 23种设计模式
- 设计模式 策略模式 以角色游戏为背景
- 游戏开发设计模式之状态模式 & 有限状态机 & c#委托事件(unity3d 示例实现)
- 设计模式在游戏中的应用--装饰模式(三)
- 游戏服务器设计之观察者模式
- 游戏中的设计模式:适配器模式
- 设计模式在游戏中的应用--策略模式(二)
- 设计模式之Bridge——游戏篇