制作横版游戏KillBear第4课:边缘检测 地图滚动
在上一课中,我们学习了通过不同层的Joystick控制角色人物的移动,但仅实现人物到处跑还无法达成预期效果。本文上半部分将限定Hero的移动范围,使其不会跑出地图或撞到墙上(墙的范围参考第一篇);下半部分则会通过更新MapLayer来实现地图滚动效果。
开发环境
- Win64:vs2010
- Cocos2d-x v3.4Final
- TexturePackerGUI
- MapEdit
代码构建A
角色Role - Hero
在updateSelf函数中,我们会发现重复写了好几个坐标,但有些仅使用了一次。下面是更新后的updateSelf函数:
void Hero::updateSelf() // 刷新自己
{
if (this->getCurrActionState() == ACTION_STATE_WALK)
{
Vec2 currentP = this->getPosition(); // 当前坐标
Vec2 expectP = currentP + this->getVelocity(); // 期望坐标
Vec2 actualP = expectP; // 实际坐标
float mapWidth = global->tileMap->getContentSize().width; // 整张地图宽度
float herofat = this->getBodyBox().actual.size.width / 2; // 角色横向宽度,以受攻击的bodybox为准
float maptileHeight = global->tileMap->getTileSize().height;
// 不能跑到墙上去
if (expectP.y < 0 || expectP.y > maptileHeight * 3)
{
actualP.y = currentP.y;
}
// 不能跑出地图外面
if (expectP.x < herofat || expectP.x >= mapWidth - herofat)
{
actualP.x = currentP.x;
}
this->setPosition(actualP);
this->setLocalZOrder(Director::getInstance()->getVisibleSize().height - this->getPositionY());
}
}
这里,我们以Hero的BodyBox(蓝色)和HitBox(红色)为例,当移动时,以蓝色框宽度的一半作为判断条件,避免Hero跑出边缘。
此外,还需要注册Global的tileMap,否则Hero::updateSelf()调用时会因tileMap为NULL而报错。
Game - MapLayer
在initMapWithFile函数的最后插入以下代码:
global->tileMap = TileMap;
通过上述操作,我们的Hero就无法跑出地图了。
代码构建B
在开始实现地图滚动之前,有必要强调文件结构的重要性。之前参考他人代码实现地图滚动时,使用了Hero的速度作为参数,后期出现了奇葩的BUG:当Hero受到攻击时,虽然不能移动,但摇杆仍在传递Hero的速度,导致地图滚动异常甚至消失。
此次我们调整了文件结构,将所有关于地图的变化都放在MapLayer中进行,并采用了一个泛用性很高的2D横版移动算法,可同时跟踪X轴和Y轴。
Game - MapLayer
.h文件
添加用于更新地图的定时器代码:
void update(float dt);
void setViewpointCenter(Point pos);
.cpp文件
在init函数中添加或修改注释,并添加以下代码:
this->scheduleUpdate();
具体实现方法如下:
void MapLayer::update(float dt)
{
this->setViewpointCenter(global->hero->getPosition());
}
void MapLayer::setViewpointCenter(Point pos) // 这个是移动地图,同时跟踪X、Y轴标准算法
{
Size winSize = Director::getInstance()->getWinSize();
auto _map = global->tileMap;
// 如果主角坐标小于屏幕的一半,则取屏幕中点坐标,否则取对象的坐标
int x = MAX(pos.x, winSize.width / 2);
int y = MAX(pos.y, winSize.height / 2);
// 如果X、Y的坐标大于右上角的极限值,则取极限值的坐标(极限值是指不让地图超出屏幕造成出现黑边的极限坐标)
x = MIN(x, (_map->getMapSize().width * _map->getTileSize().width) - winSize.width / 2);
y = MIN(y, (_map->getMapSize().height * _map->getTileSize().height) - winSize.height / 2);
// 对象当前所在坐标
Point actualPosition = Vec2(x, y);
// 计算屏幕中点和所要移动的目的点之间的距离
Point centerOfView = Vec2(winSize.width / 2, winSize.height / 2);
Point viewPoint = centerOfView - actualPosition;
// 设定一下地图的位置,这里一定要注意,单纯移动自己或者是_MAP移动都是无效的
this->getParent()->setPosition(viewPoint);
}
虽然该算法并非从特定地方找到,但雨松MOMO带你走进游戏开发的世界之主角的移动与地图的平滑滚动对这个算法的原理解释得很好。
结果
通过上述代码的实现,我们达成了边缘检测和地图滚动的效果。即人物在移动时,地图会同步滚动,避免人物一出场就跑出屏幕范围,使其能够在屏幕上跑完整个地图。
由于本Demo的全部资源较少,目前手机性能基本处于过剩状态,因此暂不实现卡马克卷轴算法。