本文将带您了解关于Cocos2D教程:使用SpriteBuilder和Cocos2D3.x开发横版动作游戏——Part2的新内容,同时我们还将为您解释cocoscreator横屏的相关知识,另外,我们
本文将带您了解关于Cocos2D教程:使用SpriteBuilder和Cocos2D 3.x开发横版动作游戏——Part 2的新内容,同时我们还将为您解释cocos creator横屏的相关知识,另外,我们还将为您提供关于cocos2d html5 3.8.1 .修复Cocos2d Particle Builder plist 粒子文件 base64 图片 解压错误、Cocos2d 卡牌塔防 游戏 cocos2d-x游戏开发之旅 第14 15 16 章 源代码调试 注意点 出现无法打开包括文件:“cocos2d.h”: No such file or direct、cocos2d-iphone – cocos2d convertToWorldSpace、cocos2d-iphone – Cocos2d只显示CCSprite的一部分的实用信息。
本文目录一览:- Cocos2D教程:使用SpriteBuilder和Cocos2D 3.x开发横版动作游戏——Part 2(cocos creator横屏)
- cocos2d html5 3.8.1 .修复Cocos2d Particle Builder plist 粒子文件 base64 图片 解压错误
- Cocos2d 卡牌塔防 游戏 cocos2d-x游戏开发之旅 第14 15 16 章 源代码调试 注意点 出现无法打开包括文件:“cocos2d.h”: No such file or direct
- cocos2d-iphone – cocos2d convertToWorldSpace
- cocos2d-iphone – Cocos2d只显示CCSprite的一部分
Cocos2D教程:使用SpriteBuilder和Cocos2D 3.x开发横版动作游戏——Part 2(cocos creator横屏)
本文是“使用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,并且我们并没有加入敌人,这实在不算是一个横版动作游戏。
本篇教程中我将带着大家完成这个游戏,包括实现人物的移动,地图的滚动,碰撞检测、创建敌人、人工智能以及音乐播放。
首先,你要有我们上一篇教程最后写好的项目,也就是我们的第一部分的代码。如果你还没有,你可以从这里下载。
接下来让我们开始吧!
主角动起来!
//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]];
到现在你应该对这些代码很熟悉了,因为创建所有的动画动作的流程基本上都是一样的:依次创建精灵帧并保存->用这些精灵帧生成动画对象->使用动画对象生成动作对象->赋值给相关属性。
- (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轴的移向。
#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的动作。
#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中调用该方法了(当然,调用了也没有事,而且可能逻辑更清晰一些),这是与原教程的另一个不同之处。 |
#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可以实现同样地功能。
但是,这里却有另一个问题,那就是如果主角一直往右走,很快就从屏幕上消失了,也就是说,地图的视心无法随着主角移动。
- (void)updateViewPointCenter:(CGPoint)point { float x = MAX(VISIBLE_SIZE.width / 2,point.x); float y = MAX(VISIBLE_SIZE.height / 2,point.y); x = MIN(x,(self.tileMap.mapSize.width * self.tileMap.tileSize.width) - VISIBLE_SIZE.width / 2); y = MIN(y,(self.tileMap.mapSize.height * self.tileMap.tileSize.height) - VISIBLE_SIZE.height / 2); CGPoint actualPoint = ccp(x,y); CGPoint centerPoint = ccp(VISIBLE_SIZE.width / 2,VISIBLE_SIZE.height / 2); CGPoint viewPoint = ccpsub(centerPoint,actualPoint); self.position = viewPoint; }
接着在update:方法中调用该方法:
//在update:方法中添加下面这一行 [self updateViewPointCenter:self.hero.position];这个方法比较三个点的x、y的大小:传入点,当前视中心点,到达最右边缘后视中心点。初始状态下“当前视中心点”也可以看做“到达最左边缘后视中心点”。也就是说,当传入点在左右边缘范围内移动时,视中心点随着该点的移动而不断切换。而到达两侧边缘后,视中心点固定不变。这样就达到了随着传入点(主角的位置)移动视图的效果。另外,我们这里与传统纵版射击类游戏不同的是,很多纵版射击类游戏通过两张背景拼接、移动Sprite来达到移动背景的效果。而这里移动的却是layer,也就是self.position。
敌人出现!
- (id)init { self = [super initWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"robot_idle_00.png"]]; if (self) { //idle NSMutableArray *idleFrame = [NSMutableArray arrayWithCapacity:5]; for (int i = 0; i < 5; i++) { CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[Nsstring stringWithFormat:@"robot_idle_%02d.png",i]]; [idleFrame addobject:frame]; } CCAnimation *idleAnimation = [CCAnimation animationWithSpriteFrames:idleFrame delay:1.0 / 12.0f]; self.idleAction = [CCActionRepeatForever actionWithAction:[CCActionAnimate actionWithAnimation:idleAnimation]]; //attack NSMutableArray *attackFrame = [NSMutableArray arrayWithCapacity:5]; for (int i = 0; i < 5; i++) { CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[Nsstring stringWithFormat:@"robot_attack_%02d.png",i]]; [attackFrame addobject:frame]; } CCAnimation *attackAnimation = [CCAnimation animationWithSpriteFrames:attackFrame delay:1.0 / 24.0f]; self.attackAction = [CCActionSequence actionOne:[CCActionAnimate actionWithAnimation:attackAnimation] two:[CCActionCallFunc actionWithTarget:self selector:@selector(idle)]]; //walk NSMutableArray *walkFrame = [NSMutableArray arrayWithCapacity:6]; for (int i = 0; i < 6; i++) { CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[Nsstring stringWithFormat:@"robot_walk_%02d.png",i]]; [walkFrame addobject:frame]; } CCAnimation *walkAnimation = [CCAnimation animationWithSpriteFrames:walkFrame delay:1.0 / 12.0f]; self.walkAction = [CCActionRepeatForever actionWithAction:[CCActionAnimate actionWithAnimation:walkAnimation]]; self.walkSpeed = 80; self.centerToBottom = 39.0f; self.centerToSides = 29.0f; self.hitPoints = 100; self.damage = 10; } return self; }
很熟悉不是吗?创建动画、创建动作、为属性赋值……
@property (strong,nonatomic) NSMutableArray *robots;
然后打开GameLayer.m,引入Robot.h头文件,添加initRobots方法并在init中调用,initRobots方法实现如下:
- (void)initRobots { int robotsNumber = 50; self.robots = [NSMutableArray arrayWithCapacity:robotsNumber]; Robot *temp = [Robot node]; int minX = VISIBLE_SIZE.width + temp.centerToSides; int minY = temp.centerToBottom; int maxX = self.tileMap.mapSize.width * self.tileMap.tileSize.width - temp.centerToSides; int maxY = ROAD_MAP_SIZE * self.tileMap.tileSize.height + temp.centerToBottom; for (int i = 0; i < robotsNumber; i++) { Robot *robot = [Robot node]; [self addChild:robot z:-5]; [self.robots addobject:robot]; [robot setPosition:ccp(RANDOM_RANGE(minX,maxX),RANDOM_RANGE(minY,maxY))]; robot.desiredPosition = robot.position; robot.scaleX = -1; [robot idle]; } }
这里,你指定了机器人总数,并通过我们之前定义的宏来创建随机数,用来决定Robots的位置,然后将这些robots保存起来并添加到地图上去。
- (void)reorderActors { for (CCNode *sprite in self.children) { if ([sprite isKindOfClass:[Actionsprite class]]) { [self reorderChild:sprite z:self.tileMap.mapSize.height * self.tileMap.tileSize.height - sprite.position.y]; } } }
#import "CCNode_Private.h"
这里根据精灵在地图上的y轴的位置来动态调整精灵的z轴,这样就不会出现上述问题了,现在编译运行,你就可以看到正确的遮挡关系了:
碰撞检测
//struct typedef struct BoundingBox { CGRect actual; CGRect original; } BoundingBox;这个结构体是干什么用的呢?它定义了两个CGRect类型的变量。original用来表示精灵的初始化状态下的坐标与大小,由于精灵(主角和Robot)是会动的,所以我们需要一个actual来记录某一时刻精灵的实际位置与大小(大小往往不变,变的是方向,后文会有详述)。
//boundingBox @property (assign,nonatomic) BoundingBox hitBox; @property (assign,nonatomic) BoundingBox attackBox; //create bounding Box - (BoundingBox)createBoundingBoxWithOrigin:(CGPoint)origin andSize:(CGSize)size;
提供一个方法用来创建BoundingBox。然后在Actionsprite.m中实现:
#pragma mark - BoundingBox - (BoundingBox)createBoundingBoxWithOrigin:(CGPoint)origin andSize:(CGSize)size { BoundingBox Box; Box.original.origin = origin; Box.original.size = size; Box.actual.origin = ccpAdd(self.position,ccp(origin.x,origin.y)); Box.actual.size = size; return Box; } - (void)transformBoundingBox { //to consider the direction of sprite,we should use scale //onle _xxx Could be assinged,self.xxx is not assingable _hitBox.actual.origin = ccpAdd(self.position,ccp(self.hitBox.original.origin.x * self.scaleX,self.hitBox.original.origin.y * self.scaleY)); _hitBox.actual.size = CGSizeMake(self.hitBox.original.size.width * self.scaleX,self.hitBox.original.size.height * self.scaleY); _attackBox.actual.origin = ccpAdd(self.position,ccp(self.attackBox.original.origin.x * self.scaleX,self.attackBox.original.origin.y * self.scaleY)); _attackBox.actual.size = CGSizeMake(self.attackBox.original.size.width * self.scaleX,self.attackBox.original.size.height * self.scaleY); } #pragma mark - Setter - (void)setPosition:(CGPoint)position { [super setPosition:position]; [self transformBoundingBox]; }
第一个方法中根据传入的CGPoint和CGSize创建BoundingBox,此时actual和original的区别是一个position。
//based on center of sprite self.hitBox = [self createBoundingBoxWithOrigin:ccp(-self.centerToSides,-self.centerToBottom) andSize:CGSizeMake(self.centerToSides * 2,self.centerToBottom * 2)]; self.attackBox = [self createBoundingBoxWithOrigin:ccp(self.centerToSides,-10) andSize:CGSizeMake(20,20)];
同样地,打开Robot.m, 在init方法中添加对自己的hitBox和attackBox的定义:
//based on center of sprite self.hitBox = [self createBoundingBoxWithOrigin:ccp(-self.centerToSides,-5) andSize:CGSizeMake(25,20)];
现在,我们来解释一下这两个Box分别是什么作用。玩游戏的时候,我们会有一个常识——角色会“出拳”(近战)来攻击敌人,只有当出的拳打中敌人的身体时才算击中。这就是attackBox和hitBox。下面有一副图清晰地解释了原因:
之前将状态机的时候为角色定义了五种状态:平常、攻击、走路、受伤、死亡。我们已经实现了三种,现在还差受伤和死亡状态,接下来马上就会用到了,所以我们现在给出实现。
- (void)hurtWithdamage:(CGFloat)damage { if (self.state != kActionStateKnockedOut) { [self stopAllActions]; [self runAction:self.hurtAction]; self.state = kActionStateHurt; self.hitPoints -= damage; if (self.hitPoints <= 0) { [self knockOut]; } } } - (void)knockOut { [self stopAllActions]; [self runAction:self.knockedOutAction]; self.hitPoints = 0; self.state = kActionStateKnockedOut; }
只要角色没有死亡,被攻击时就会切换到受伤状态,执行动画动作,然后判断生命值,若HP低于0,则变成死亡状态。
//hurt action NSMutableArray *hurtFrames = [NSMutableArray arrayWithCapacity:3]; for (int i = 0; i < 3; i++) { CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[Nsstring stringWithFormat:@"hero_hurt_%02d.png",i]]; [hurtFrames addobject:frame]; } CCAnimation *hurtAnimation = [CCAnimation animationWithSpriteFrames:hurtFrames delay:1.0 / 12.0f]; self.hurtAction = [CCActionSequence actionOne:[CCActionAnimate actionWithAnimation:hurtAnimation] two:[CCActionCallFunc actionWithTarget:self selector:@selector(idle)]]; //knock out action NSMutableArray *knockOutFrames = [NSMutableArray arrayWithCapacity:5]; for (int i = 0; i < 5; i++) { CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[Nsstring stringWithFormat:@"hero_knockout_%02d.png",i]]; [knockOutFrames addobject:frame]; } CCAnimation *knockOutAnimation = [CCAnimation animationWithSpriteFrames:knockOutFrames delay:1.0 / 12.0f]; self.knockedOutAction = [CCActionSequence actionOne:[CCActionAnimate actionWithAnimation:knockOutAnimation] two:[CCActionBlink actionWithDuration:2.0f blinks:10.0f]];类似地,打开Robot.m,在同样地位置添加:
//hurt action NSMutableArray *hurtFrames = [NSMutableArray arrayWithCapacity:3]; for (int i = 0; i < 3; i++) { CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[Nsstring stringWithFormat:@"robot_hurt_%02d.png",i]]; [hurtFrames addobject:frame]; } CCAnimation *hurtAnimation = [CCAnimation animationWithSpriteFrames:hurtFrames delay:1.0 / 12.0f]; self.hurtAction = [CCActionSequence actionOne:[CCActionAnimate actionWithAnimation:hurtAnimation] two:[CCActionCallFunc actionWithTarget:self selector:@selector(idle)]]; //knock out action NSMutableArray *knockOutFrames = [NSMutableArray arrayWithCapacity:5]; for (int i = 0; i < 5; i++) { CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[Nsstring stringWithFormat:@"robot_knockout_%02d.png",i]]; [knockOutFrames addobject:frame]; } CCAnimation *knockOutAnimation = [CCAnimation animationWithSpriteFrames:knockOutFrames delay:1.0 / 12.0f]; self.knockedOutAction = [CCActionSequence actionOne:[CCActionAnimate actionWithAnimation:knockOutAnimation] two:[CCActionBlink actionWithDuration:2.0f blinks:10.0f]];
这里规定,受伤后立刻恢复为idle状态,死亡后闪烁两秒。
//添加到touchBegan的[self.hero attack]后 //collision detection if (self.hero.state == kActionStateAttack) { for (Robot *robot in self.robots) { if (robot.state != kActionStateKnockedOut) { if (fabsf(robot.position.y - self.hero.position.y) < 10) { if (CGRectIntersectsRect(robot.hitBox.actual,self.hero.attackBox.actual)) { [robot hurtWithdamage:self.hero.damage]; } } } } }
这一小段代码做了三件事:
简单的AI与决策树
@property (assign,nonatomic) double nextDecisionTime;在Robot.m中的init方法中进行初始化:
self.nextDecisionTime = 0;见名知意,这个属性表示下一次决策时间。
接下来应该是本篇教程中最麻烦的一个方法了,别着急,其实也是很好理解的。
<span>- (void)updateRobots:(CCTime)delta { int alive = 0; int randomChoice = 0; float distanceSQ = 0; for (Robot *robot in self.robots) { [robot update:delta]; if (robot.state != kActionStateKnockedOut) { alive++; if (CURRENT_TIME > robot.nextDecisionTime) { distanceSQ = ccpdistanceSQ(robot.position,self.hero.position); if (distanceSQ <= 50 * 50) { robot.nextDecisionTime = CURRENT_TIME + FLOAT_RANDOM_RANGE(0.1,0.5); randomChoice = RANDOM_RANGE(0,1); if (randomChoice == 0) { if (self.position.x >= robot.position.x) { robot.scaleX = 1.0; } else { robot.scaleX = -1.0; } //attack and collision detection [robot attack]; if (robot.state == kActionStateAttack) { if (self.hero.state != kActionStateKnockedOut) { if (fabsf(robot.position.y - self.hero.position.y) < 10) { if (CGRectIntersectsRect(robot.attackBox.actual,self.hero.hitBox.actual)) { [self.hero hurtWithdamage:robot.damage]; } } } } } } else { [robot idle]; } } else if (distanceSQ <= VISIBLE_SIZE.width * VISIBLE_SIZE.width) { robot.nextDecisionTime = CURRENT_TIME + FLOAT_RANDOM_RANGE(0.5,1.0); randomChoice = RANDOM_RANGE(0,2); if (randomChoice == 0) { //move CGPoint direction = ccpnormalize(ccpsub(self.hero.position,robot.position)); [robot walkWithDirection:direction]; } else { [robot idle]; } } //distance } //if (CURRENT_TIME > robot.nextDecisionTime) } //if (robot.state != kActionStateKnockedOut) } //foreach }</span>
然后,在update:方法中添加调用:
[self updateRobots:delta];
让我们来慢慢看看这一段代码做了哪些事:
//在updatePosition方法中添加 for (Robot *robot in self.robots) { float robotPosX = MIN(self.tileMap.tileSize.width * self.tileMap.mapSize.width - robot.centerToSides,MAX(robot.centerToSides,robot.desiredPosition.x)); float robotPosY = MIN(ROAD_MAP_SIZE * self.tileMap.tileSize.height + robot.centerToBottom,MAX(robot.centerToBottom,robot.desiredPosition.y)); robot.position = ccp(robotPosX,robotPosY); }
- (void)endGameWithResult:(GameResult)result { Nsstring *label = nil; if (result == kGameResultLost) { label = @"Game Over"; } else if (result == kGameResultWin) { label = @"You Win!"; } CCButton *restartBtn = [CCButton buttonWithTitle:label fontName:@"Arial" fontSize:30]; [restartBtn setPosition:CENTER]; [restartBtn setTarget:self selector:@selector(restartGame)]; restartBtn.name = @"restart"; [self.scene addChild:restartBtn z:500]; } - (void)restartGame { CCTransition *trans = [CCTransition transitionFadeWithDuration:0.4f]; trans.outgoingSceneAnimated = YES; trans.incomingSceneAnimated = YES; [[CCDirector sharedDirector] replaceScene:[GameScene node] withTransition:trans]; }其中GameResult是自己定义的枚举变量,其值也都在if-else块中体现出来了。
我们知道,胜利的时机是所有机器人都被打败的时候,也就是之前那个用于计算活着的机器人数量的变量alive为0的时候。失败的时机是主角的HP为0的时候,也就是说处理胜负的逻辑都体现在updateRobots:方法中。
//添加到updateRobots方法的最下端,最外层循环之外 //check game win if (alive == 0 && [self getChildByName:@"restart" recursively:YES] == nil) { [self endGameWithResult:kGameResultWin]; }
//添加到[self.hero hurtWithdamage:robot.damage];之后 //check game over if (self.hero.state == kActionStateKnockedOut && [self getChildByName:@"restart" recursively:YES] == nil) { [self endGameWithResult:kGameResultLost]; }和这群机器人玩得愉快!
锦上添花:游戏音效
注意:3.x中Cocos2D已经不用SimpleAudioEngine来处理音乐了,而是采用OpenAL,对应的类名为OALSimpleAudio,对于我们使用来说大同小异。同样的单例、同样的prepare,同样的play…… |
- (void)initMusics { [[OALSimpleAudio sharedInstance] preloadBg:@"latin_industries.aifc"]; [[OALSimpleAudio sharedInstance] playBg:@"latin_industries.aifc" loop:YES]; OALSimpleAudio *audio = [OALSimpleAudio sharedInstance]; [audio preloadEffect:@"pd_botdeath.caf"]; [audio preloadEffect:@"pd_herodeath.caf"]; [audio preloadEffect:@"pd_hit0.caf"]; [audio preloadEffect:@"pd_hit1.caf"]; }
打开Actionsprite.m,在hurtWithdamage:方法中添加:
int randomSound = RANDOM_RANGE(0,1); [[OALSimpleAudio sharedInstance] playEffect:[Nsstring stringWithFormat:@"pd_hit%d.caf",randomSound]];
打开Hero.m,重写knockOut方法:
- (void)knockOut { [super knockOut]; [[OALSimpleAudio sharedInstance] playEffect:@"pd_herodeath.caf"]; }
打开Robot.m,同上:
- (void)knockOut { [super knockOut]; [[OALSimpleAudio sharedInstance] playEffect:@"pd_botdeath.caf"]; }
一切完成!!现在编译并运行,享受你自己写的完整的横版动作游戏吧
何去何从?
cocos2d html5 3.8.1 .修复Cocos2d Particle Builder plist 粒子文件 base64 图片 解压错误
plist 粒子是 Cocos2d Particle Builder 生成的
cocos2dx 版本 3.8.1
路径
frameworks\cocos2d-html5\cocos2d\compression\ZipUtils.js
文件名称
ZipUtils.js.js
已修改 13
删除 1
以下是补丁.
diff --git a/frameworks/cocos2d-html5/cocos2d/compression/ZipUtils.js b/frameworks/cocos2d-html5/cocos2d/compression/ZipUtils.js index ccecd63..8288b98 100644 --- a/frameworks/cocos2d-html5/cocos2d/compression/ZipUtils.js +++ b/frameworks/cocos2d-html5/cocos2d/compression/ZipUtils.js @@ -25,7 +25,19 @@ cc.unzip = function () { */ cc.unzipBase64 = function () { var tmpInput = cc.Codec.Base64.decode.apply(cc.Codec.Base64,arguments); - return cc.Codec.GZip.gunzip.apply(cc.Codec.GZip,[tmpInput]); + var ret; + try{ + ret = cc.Codec.GZip.gunzip.apply(cc.Codec.GZip,[tmpInput]); + } + catch(e){ + console.log('unzip base64 error try again'); + if(tmpInput.length>8){ + tmpInput = tmpInput.slice(7,tmpInput.length); + ret = tmpInput; + } + } + + return ret; }; /**
希望有帮助
Cocos2d 卡牌塔防 游戏 cocos2d-x游戏开发之旅 第14 15 16 章 源代码调试 注意点 出现无法打开包括文件:“cocos2d.h”: No such file or direct
Cocos2d 卡牌塔防 游戏 cocos2d-x游戏开发之旅 第14 15 16 章 源代码调试 注意点
出现无法打开包括文件:“cocos2d.h”: No such file or directory
第一步:把书中源代码文件夹拷贝到以下目录。
打开此文件夹后,显示如下:
用VS2012打开proj.win32里边的Chapter16_2_CardDefence08.win32.vcxproj,按下图打开:
第二步:在附加包含目录中,添加如下代码:
如果出现fatalerror C1083: 无法打开包括文件:“cocos2d.h”:No such file or directory
解决方法如下:选择项目属性--》C/C++--》附件包含目录设置为:
复制:$(ProjectDir)..\..\..\cocos2dx;$(ProjectDir)..\..\..\cocos2dx\include;$(ProjectDir)..\..\..\cocos2dx\kazmath\include;$(ProjectDir)..\..\..\cocos2dx\platform\win32;$(ProjectDir)..\..\..\cocos2dx\platform\third_party\win32\OGLES;..\Classes;%(AdditionalIncludeDirectories);
同时注意把E:\cocos2d-x-2.2.3\extensions
E:\cocos2d-x-2.2.3\cocosDenshion\android
E:\ cocos2d-x-2.2.3\cocos2dx\platform\third_party\win32\libraries
加到C/C++--》附件包含目录中去。
第三步:把E:\cocos2d-x-2.2.3\Debug.win32下的所有lib文件和dll文件拷贝至自己项目的Debug.win32下面。
最终运行成功画面如下:
cocos2d-iphone – cocos2d convertToWorldSpace
>我有spriteA,它是父母.
>我将它设置为40,40位置并将其添加到场景中.
>我有第二个spriteB,我设置在80,80位置.
>我将spriteB添加到spriteA.
>我打印出spriteB的位置:80,80.
>然后我打印[self convertToWorldspace:spriteB.position].而且,我仍然得到80,80.
spriteB的位置应该不同吗?
[spriteA convertToWorldspace:spriteB.position];
“convertToWorldspace:”方法将节点的变换应用于给定位置.你应该总是从父亲的精灵中调用这个方法.
cocos2d-iphone – Cocos2d只显示CCSprite的一部分
它接缝的contentSize属性没有很好的结果.
解决方法
因此,要使用剪切方法,只需下载ClippingNode class from here,并将要剪切的CCSprite添加到该ClippingNode.然后调用其中一个方法来指定限制绘图的区域.我目前正在使用它来创建一个进度条,所以我肯定它确实很有用.
关于Cocos2D教程:使用SpriteBuilder和Cocos2D 3.x开发横版动作游戏——Part 2和cocos creator横屏的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于cocos2d html5 3.8.1 .修复Cocos2d Particle Builder plist 粒子文件 base64 图片 解压错误、Cocos2d 卡牌塔防 游戏 cocos2d-x游戏开发之旅 第14 15 16 章 源代码调试 注意点 出现无法打开包括文件:“cocos2d.h”: No such file or direct、cocos2d-iphone – cocos2d convertToWorldSpace、cocos2d-iphone – Cocos2d只显示CCSprite的一部分等相关知识的信息别忘了在本站进行查找喔。
本文标签: