Cocos2D教程:使用SpriteBuilder和Cocos2D 3.x开发横版动作游戏——Part 2

本文是“使用Cocos2D 3.x开发横版动作游戏”系列教程的第二篇,同时也是最后一篇。是对How To Make A Side-Scrolling Beat Em Up Game Like Scott Pilgrim with Cocos2D – Part 2的翻译,加上个人理解而成。最重要的是将文中所有代码转换为Cocos2D 3.x版本。众所周知,3.x与2.x的区别非常之大,在触摸机制、渲染机制等方面都与之前版本有了本质的区别。这里将本人摸索的结果加上,供大家参考。

在上一篇教程中,我们已经加载了TiledMap,创建了Hero,并且实现了一个简单地虚拟摇杆。但是我们的虚拟摇杆还无法投入到使用中,无法移动我们的Hero,并且我们并没有加入敌人,这实在不算是一个横版动作游戏。

本篇教程中我将带着大家完成这个游戏,包括实现人物的移动,地图的滚动,碰撞检测、创建敌人、人工智能以及音乐播放。

首先,你要有我们上一篇教程最后写好的项目,也就是我们的第一部分的代码。如果你还没有,你可以从这里下载。

接下来让我们开始吧!

主角动起来!

上一篇教程的最后,如果大家编译并运行项目,点击我们的虚拟摇杆,会发现项目会crash,因为我们还没有实现协议方法。我们马上就会进行实现,不过首先,既然英雄要动起来,那么我们要准备好walk的动作,打开Hero.m,在init方法中添加下面的代码:
//walk action
        NSMutableArray *walkFrames = [NSMutableArray arrayWithCapacity:8];
        for (int i = 0; i < 8; i++) {
            CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"hero_walk_%02d.png", i]];
            [walkFrames addObject:frame];
        }
        CCAnimation *walkAnimation = [CCAnimation animationWithSpriteFrames:walkFrames delay:1.0 / 12.0f];
        self.walkAction = [CCActionRepeatForever actionWithAction:[CCActionAnimate actionWithAnimation:walkAnimation]];

到现在你应该对这些代码很熟悉了,因为创建所有的动画动作的流程基本上都是一样的:依次创建精灵帧并保存->用这些精灵帧生成动画对象->使用动画对象生成动作对象->赋值给相关属性。

接下来,找到ActionSprite.m,添加下面的方法
- (void)walkWithDirection:(CGPoint)direction {
    if (self.state == kActionStateIdle) {
        [self stopAllActions];
        [self runAction:self.walkAction];
        self.state = kActionStateWalk;
    }
    
    if (self.state == kActionStateWalk) {
        self.velocity = ccp(direction.x * self.walkSpeed, direction.y * self.walkSpeed);
        if (self.velocity.x >= 0) {
            self.scaleX = 1.0;
        } else {
            self.scaleX = -1.0;
        }
    }
}

这里检测之前角色的状态,规定只有当角色是idle状态时才可以切换到walk状态。然后让该角色执行创建好的walk的动作,接下来,根据传入进来的参数——角色移动的方向来决定角色的“朝向”,也就是scaleX属性(默认朝右)。之前提到过,direction是一个点,横坐标的正负决定着角色x轴的移向,纵坐标的正负决定着角色y轴的移向。

准备工作完成之后,我们就可以来实现之前没有实现的三个协议方法了。
找到GameLayer.m,在最后添加这三个方法的实现:
#pragma mark - SimpleDPadDelegate

- (void)simpleDPad:(SimpleDPad *)dPad didChangeDirectionTo:(CGPoint)direction {
    [self.hero walkWithDirection:direction];
}

- (void)simpleDPad:(SimpleDPad *)dPad isHoldingDirection:(CGPoint)direction {
    [self.hero walkWithDirection:direction];
}

- (void)simpleDPadTouchEnded:(SimpleDPad *)dPad {
    if (self.hero.state == kActionStateWalk) {
        [self.hero idle];
    }
}

每当你按下、移动我们的虚拟摇杆时就会触发主角的walkWithDirection:方法,而当你抬起手指时,主角又会进入idle状态并执行idle的动作。
现在编译然后运行app,按下虚拟摇杆移动主角。这次不会再crash了!

不过等一下- -我们的英雄好像只能原地踏步,并没有实际移动。为什么会这样?
让我们回到刚才的walkWithDirection:方法,仔细看一下,会发现这个方法里只不过是改变了角色的velocity属性,并没有实际上改变角色的位置。因此我们还需要“实时”更新角色的位置。
分析一下这里的实现思路:ActionSprite类需要移动,但是他不会知道自己在地图中的具体位置,因此他永远不知道何时走到地图的边缘,何时与地图上的障碍物碰撞……。他所知道的就只有自己的“期望”位置——由用户操纵虚拟摇杆决定的下一步该去的位置。因此我们需要在GameLayer中将这个“期望”位置加以判断并切换为“真正”位置。所以说,移动的实现要同时用到这两个类。
之前在ActionSprite里我们已经定义了一个CGPoint类型的属性——desiredPosition。这个就表示角色的“期望”位置,现在我们切到ActionSprite.m中,添加下面的方法:
#pragma mark - Schedule

- (void)update:(CCTime)delta {
    if (self.state == kActionStateWalk) {
        self.desiredPosition = ccpAdd(self.position, ccpMult(self.velocity, delta));
    }
}

这个方法干了什么呢?首先判断状态,然后将角色的速度(有正负)乘以时间(得到距离),加上当前的位置对应坐标,得到的就是角色的“期望”位置。
注意:在3.x中,我们不需要显示调用[self scheduleUpdate];了,当我们重写了CCNode的update:方法时,Cocos2D会自动为我们每帧调用这里的方法。因此,此时我们实现了Hero的“期望”位置更新逻辑之后,就不需要在GameLayer的update中调用该方法了(当然,调用了也没有事,而且可能逻辑更清晰一些),这是与原教程的另一个不同之处。
接下来切换到GameLayer.m,添加如下代码:
#pragma mark - Schedule

- (void)update:(CCTime)delta {
    [self updatePosition];
}

- (void)updatePosition {
    float posX = MIN(self.tileMap.tileSize.width * self.tileMap.mapSize.width - self.hero.centerToSides, MAX(self.hero.centerToSides, self.hero.desiredPosition.x));
    float posY = MIN(ROAD_MAP_SIZE * self.tileMap.tileSize.height + self.hero.centerToBottom, MAX(self.hero.centerToBottom, self.hero.desiredPosition.y));
    
    self.hero.position = ccp(posX, posY);
}

#pragma mark - EndGame

- (void)dealloc {
    [self unscheduleAllSelectors];
}

然后在顶部添加宏定义
#define ROAD_MAP_SIZE 3
在这段代码中,你设置了一个定时器,不断刷新主角的位置。主角的位置并不是简单地设置为“期望”位置,因为主角不能跑到地图之外。拿x方向来说,这里用了centerToSides作为最小值,地图横线距离减去centerToSides作为最大值,规定主角在这个范围之间移动。Cocos2D中有一个宏ccClamp可以实现同样地功能。
现在编译并运行,你会看到,你的主角真正动起来了,而且碰到左侧边界以及上下边界时都会停下来。


但是,这里却有另一个问题,那就是如果主角一直往右走,很快就从屏幕上消失了,也就是说,地图的视心无法随着主角移动。
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值