游戏的代码有点长,链接我放在评论区了,我的博客中也免费发布了该代码,需要留言的自取即可。代码参考了一位youtube编程大佬的视频,该视频在b站地址为: https://www.bilibili.com/video/BV14z4y1r7wX?p=12 下文是我对该游戏中一些重要问题的处理方法的原理与理解:
游戏展示图
游戏介绍: 通过左右键控制飞船摆动,上键控制飞船加速,空格键馈控制飞船发射子弹。当子弹击中小行星后,单个小行星会分裂成两个朝向随机方向的小行星,并获取相应分数,如果子弹击毁了所有小行星,则会重新生成两个大的小行星,并给予更多分数奖励。而当飞船碰撞到小行星时,游戏结束,重置游戏。
一、飞船
1.飞船的绘制
飞船是一个等腰三角形,这里只定义该三角形的三个顶点,即可创建出飞船对象vecModelShip。利用头文件中封装好的DrawWireFrameModel函数,传入飞船对象,飞船坐标,飞船角度,即可完成飞船的绘制,如下:
vecModelShip =
{
{ 0.0f, -4.0f},
{-2.0f, +2.0f},
{+2.0f, +2.0f}
};
DrawWireFrameModel(vecModelShip, player.x, player.y,player.angle);
2.飞船的角度
飞船的角度通过angle变量来实现,其具体体现相当于下图中的角度Θ,当持续按下左右方向键后,飞船的angle值会持续++或–某一固定值直到Θ。 此时飞船的前进速度为v,则旋转一定角度后,飞船的横向速度dx与纵向速度dy可以用 ????????dx=v * sinΘ ????????dy=v * cosΘ 表示,经过时间t后,飞船的位置x,y可以用 ????????x += dx * t ????????y += dy * t 表示,这样飞船的坐标xy就可以通过angle来控制,而飞船对象图形的角度旋转,在上文封装好的函数中传入angle就可以实现绘制了。 通过以上方法,既解决了因角度变化而导致飞船坐标移动的问题,也解决了飞船对象图形的旋转问题。
二、小行星
1.小行星的绘制
小行星对象vecModelAsteroid主体上是一个圆,绘制的原理是截取圆上若干点verts(这里取verts=20个),通过封装好的DrawWireFrameModel()函数,将20个点连成线,函数调用与飞船相似。 需要注意的是,小行星不像飞船一样只有一个,所以他是以数组的形式存在着的,对于小行星对象的操作,都要以遍历的方法进行:
for (auto& a : vecAsteroids)
DrawWireFrameModel((vecModelAsteroid, a.x, a.y, a.angle, (float)a.nSize, FG_YELLOW)
2.“凹凸不平”效果的形成
而为了让小行星看起来“凹凸不平”,这20个点,并不是绝对在圆上,而是生成一个较小的随机数,让点与圆心距离在某一区间内随机生成,这样可以营造出小行星的效果,该方法的具体实现如下:
int verts = 20;
for (int i = 0; i < verts; i++)
{
float noise = (float)rand() / (float)RAND_MAX * 0.4f + 0.8f;
vecModelAsteroid.push_back(make_pair(noise * sinf(((float)i / (float)verts) * 6.28318f),
noise * cosf(((float)i / (float)verts) * 6.28318f)));
}
3.小行星的分裂
小行星的分裂上,会随机生成两个分裂方向,然后在原来的原点位置,push_back两个方向的,大小减半的新小行星对象newvecModelAsteroid,然后删除旧的小行星,添加完成后,在原小行星组中,加上新生成的这两个小行星,如下所示:
for (auto& a : vecAsteroids)
{
if (a.nSize > 4)
{
float angle1 = ((float)rand() / (float)RAND_MAX) * 6.283185f;
float angle2 = ((float)rand() / (float)RAND_MAX) * 6.283185f;
newAsteroids.push_back({ (int)a.nSize >> 1 ,a.x, a.y, 10.0f * sinf(angle1), 10.0f * cosf(angle1), 0.0f });
newAsteroids.push_back({ (int)a.nSize >> 1 ,a.x, a.y, 10.0f * sinf(angle2), 10.0f * cosf(angle2), 0.0f });
}
}
三、子弹
子弹的绘制比较简单,它是以数组的形式存在的。因为子弹是一个像素点,而其角度是用户按下“space”键时继承的该时刻飞船的角度angle,所以变化不多,直接调用封装的Draw函数,传入子弹数组的横纵坐标x,y即可。
for (auto b : vecBullets)
Draw(b.x, b.y);
四、边界溢出的处理
当飞船或小行星或子弹运动到屏幕之外时,即 ?????x<0 || y< 0 || x>ScreenWidth() || y >ScreenHeight() 根据对象的不同有以下两种处理方式:
1.飞船和小行星
飞船和小行星来到屏幕之外时,会回到另一边,比如飞船一直往左走到x<0了,则飞船就从右边ScreenWidth()出现,继续往左走,即对于飞船和小行星来说,是如下图的空间: 处理该情况的WrapCoordinates函数如下所示:
void WrapCoordinates(float ix, float iy, float& ox, float& oy)
{
ox = ix;
oy = iy;
if (ix < 0.0f) ox = ix + (float)ScreenWidth();
if (ix >= (float)ScreenWidth()) ox = ix - (float)ScreenWidth();
if (iy < 0.0f) oy = iy + (float)ScreenHeight();
if (iy >= (float)ScreenHeight()) oy = iy - (float)ScreenHeight();
}
2.子弹
子弹超出边界直接删除!不用WrapCoordinates函数
五、碰撞判定
一个是飞船与小行星碰撞的判定,一个是子弹与小行星碰撞的判定。 两种情况非常相似,其核心就是判定子弹或飞船与小行星中心的距离与半径的关系,该距离小于等于半径即判定二者发生碰撞,封装的函数如下:
bool IsPointInsideCircle(float cx, float cy, float radius, float x, float y)
{
return sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)) < radius;
}
总结
以上就是针对该C++游戏的所有重点问题,至于游戏的开启、更新、重置问题,在代码中有详细的注释,在实战方面会用即可。 有关问题欢迎与我讨论~
|