贪吃蛇大作战类游戏的实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/asd77882566/article/details/53314079

贪吃蛇大作战类游戏的实现

前段时间玩了一个叫做贪吃蛇大作战的手机游戏,一下子就喜欢上了,然后就有了尝试实现的想法。

制作的平台环境:vs2012/cocos2dx3.8.1/C++

关于贪吃蛇游戏的了解

记得在小时候玩的小游戏机和老式手机就有了贪吃蛇游戏,那时是一个格子一个格子的移动,就是不断的吃食物。现在已经发展到了更加平滑的滑动,加入了拟人的ai来对抗,甚至还可以联网一起竞技,在我最近玩到的这个手游,吸引我的大概就是流畅的操作和超更高分数努力以及和玩家竞争了。

期望实现的效果

最基本的是有像贪吃蛇大作战一样玩家控制一条蛇和其他ai一起抢食物变长,同时撞死对方,ai要比较真实,不会显得太死板,随着长度边长游戏仍能够保证流畅性。至于联网功能考虑了之后暂时放弃实现了。

系统设计

游戏中的基本对象有两类:

  1. 蛇,分玩家和ai;
  2. 食物,有多种类型,包括:随机产生的食物,蛇死后的食物,随机移动的星星;

实体(Entity)

所有对象都继承自它,有唯一id,位置信息

蛇身

是蛇的每一节身体,继承自Entity类,有一个Sprite

蛇的移动有轨迹性,后边的身体都是沿着脑袋的轨迹来走的,同时蛇能够加速,根据吃掉食物增加的分数能够边长和变粗。玩家的蛇完全由玩家输入来控制,ai则检测所处环境朝不同方向移动。

重要接口有:
void SetDir( Vec2 dir ); 设置要朝向的目标
void Rotate(); 会向朝向目标旋转,蛇头会有一个旋转速度,每帧都会朝目标朝向转,直到转向完成。
void Move(); 蛇头根据速度朝当前方向移动,更新蛇身路径。
void MoveBodies(); 根据蛇身路径移动蛇的身体。
void CheckEatFood(); 判断是否吃到食物。
void CheckDie(); 判断蛇是否撞到边界和其他蛇的身体。
void UpdateNormalAI(); ai的更新函数,目的是确定当前的移动方向。
void ChangeBodySize(); 更新蛇身体大小和间隔。

这些函数基本上囊括了蛇的功能。

食物

食物包含三种:

扫描二维码关注公众号,回复: 3160589 查看本文章
  1. 地图上随机的食物,这是分数最小的,也是最多的,出现位置就不再变化;
  2. 蛇死后身体变成的食物,分数中等,出现后位置也不变;能够吸引附近的ai加速抢吃。
  3. 地图上随机的星星,在地图上一直移动的食物,分数最高,能够吸引附近的ai加速抢吃。

同时蛇和食物的创建和管理都各自有一个管理器来管理,外部掌握的只是他们的id,外部获取对应对象的时候,需要通过管理器来获得指针,这样防止了野指针出现的可能性,但是浪费了一些性能。

算法需求

稍微具有一些技术性的地方有两个:
1. 蛇移动实现
2. 蛇ai实现

蛇身移动实现

参照贪吃蛇大作战蛇身的移动效果,我做了如下实现方式:首先定义了蛇的基本移动单位,是一个常亮数值N,蛇的基本速度就是它;而蛇还有一个当前速度倍率Scale,代表当前蛇的速度,通过更改它的值来实现加速,在这个实现里加速我设为2;蛇的移动路径存Path储为一个list< Vec2 >,每次移动就根据当前的朝向在头部插入Scale个路径点,每个点间隔为N;同时蛇身的间隔I是常量值N的倍数,它会根据蛇身长度增长和变大来变大,每帧每一个蛇身都会从Path的头部开始,每间隔I个位置放置一个身体。部分实现代码如下:

// Move()片段
for( int i = 1; i <= _curSpeedScale; i++ ) {
            _paths.push_front( _headPos + _dir * _initSpeed * i );
        }
    _headPos = _paths.front();
    MoveBodies();
    // 每隔常量秒,就删除过长的路径尾巴
    _clipPathDelta += dt;
    if( _clipPathDelta >= SNAKE_CLIP_PATH_INTERVAL ) {
        _clipPathDelta -= SNAKE_CLIP_PATH_INTERVAL;
        _paths.resize( 当前所需的路径点数量 );
    }
}

ai的移动逻辑

最初设计的ai过于厉害,并且移动的非常假,虽然花费的很多逻辑实践但是依然效果不行,后来简化了实现,整体只考虑三个情景:
1. 首先判断蛇首的警戒范围是否有边界或者别的蛇身,有的话设置目标方向为相反方向,否则判断第二种情况;
2. 判断蛇首的视野范围是否有星星类型食物,有的话设置当前朝向为星星方向,并设置移动倍率为2,否则判断第三种情况;
3. 随机朝某个位置移动;

如果每帧ai都在更新的话,蛇的反应速度依然会跟快,需要设置一个ai更新间隔,通过调整这个间隔和蛇首的警戒范围来调整ai的反应速度,以能够让玩家有机会撞死ai。

优化

整体来说以上的步骤就已经实现了基本的贪吃蛇大作战的ai玩法功能,但是实际运行起来发现,过一段时间游戏就会非常的卡,每秒只达到30帧甚至20帧,做了一个性能分析功能(根据游戏编程精粹1里讲解的一个实现)发现,在CheckDie(),UpdateNormalAi(),CheckEatFood()里边占时很久,效果如下:
这里写图片描述
这里写图片描述

查看了一下代码,发现在这些函数里,都要获得所有的食物或者蛇身整体遍历来判断是否进入范围,这样随着蛇身越来越长食物越来越多游戏会变得越来越卡。
我的解决办法是将整个地图分成多个区域,所有移动的蛇身和食物每次变换位置的时候就会更新他们所处的区域,某个蛇首判断某范围的食物时,只需要获取该范围的几个区域里边的食物进行遍历判断就可以了,判断死亡时也是只取蛇首所在区域内的其他蛇身判断距离。
实现该功能之后,发现在运行一会RefreshNodeRegion( entityType, entity* )函数浪费了太多时间,但是把代码尽量优化之后仍然还是浪费过多时间,后来在与同事讨论优化的时候,他提到可以通过vs2012分析工具来分析函数占用cpu使用率,具体方法是通过工具栏的分析->分析向导来运行程序,一段时间后会计算出各个函数使用情况,发现使用最多的果然是在这个函数中,进入之后发现个这个函数每行代码的占用率,发现了问题所在,原代码如下:

int id = entity->GetId();
CCAssert(  _entities.find( id ) != _entities.end(), "" );

std::vector<Vec2> oldRegions = _entities[id];
_entities[id].clear();
GetCoverRegion( entity->GetPosition(), entity->GetRadius(), _entities[id] );

auto oItr = oldRegions.begin(), nItr = _entities[id].begin();
while( oItr != oldRegions.end() ) {
    RemoveNodeInRegion( type, id, (*oItr).y, (*oItr).x );
    ++oItr;
}
while( nItr != _entities[id].end() ) {
    AddNodeInRegion( type, id, (*nItr).y, (*nItr).x );
    ++nItr;
}

由于_entities是一个 std::map< int, std::vector< Vec2 > >, 每次使用_entities[id]都会导致重新搜寻一遍_entities找到对应id的信息,优化后改成了这样:

int id = entity->GetId();
auto itr = _entities.find( id );
CCAssert(  itr != _entities.end(), "" );

std::vector<Vec2> oldRegions( itr->second );
itr->second.clear();
GetCoverRegion( entity->GetPosition(), entity->GetRadius(), _entities[id] );

auto oItr = oldRegions.begin(), nItr = itr->second.begin();
while( oItr != oldRegions.end() ) {
    RemoveNodeInRegion( type, id, (*oItr).y, (*oItr).x );
    ++oItr;
}
    while( nItr != itr->second.end() ) {
    AddNodeInRegion( type, id, (*nItr).y, (*nItr).x );
    ++nItr;
}

关闭所有打印信息,重新运行,游戏基本上能够50帧以上流畅运行,至此该游戏的实现终于完成了, 通过这个游戏的实现过程我也更加了解了游戏优化的一些方法!

游戏运行demo和源码在此下载

猜你喜欢

转载自blog.csdn.net/asd77882566/article/details/53314079