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,并且我们并没有加入敌人,这实在不算是一个横版动作游戏。
本篇教程中我将带着大家完成这个游戏,包括实现人物的移动,地图的滚动,碰撞检测、创建敌人、人工智能以及音乐播放。
首先,你要有我们上一篇教程最后写好的项目,也就是我们的第一部分的代码。如果你还没有,你可以从这里下载。
接下来让我们开始吧!
主角动起来!
//walk actionNSMutableArray *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中调用该方法了(当然,调用了也没有事,而且可能逻辑更清晰一些),这是与原教程的另一个不同之处。 |
接下来切换到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可以实现同样地功能。
但是,这里却有另一个问题,那就是如果主角一直往右走,很快就从屏幕上消失了,也就是说,地图的视心无法随着主角移动。
- (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) {//idleNSMutableArray *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]];//attackNSMutableArray *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)]];//walkNSMutableArray *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 spriteself.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 spriteself.hitBox = [self createBoundingBoxWithOrigin:ccp(-self.centerToSides, -self.centerToBottom) andSize:CGSizeMake(self.centerToSides * 2, self.centerToBottom * 2)];self.attackBox = [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 actionNSMutableArray *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 actionNSMutableArray *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 actionNSMutableArray *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 actionNSMutableArray *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 detectionif (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 style="font-size:14px;">- (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) {//moveCGPoint 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 winif (alive == 0 && [self getChildByName:@"restart" recursively:YES] == nil) {[self endGameWithResult:kGameResultWin];}
//添加到[self.hero hurtWithDamage:robot.damage];之后
//check game overif (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教程:使用SpriteBuilder和Cocos2D 3.x开发横版动作游戏——Part 2相关推荐
- Cocos2D教程:使用SpriteBuilder和Cocos2D 3.x开发横版动作游戏——Part 1
本文是对教程How To Make A Side-Scrolling Beat Em Up Game Like Scott Pilgrim with Cocos2D – Part 1的部分翻译,加上个 ...
- Cocos2D来制作横版过关游戏1
本文实践自 Allen Tan 的文章< How To Make A Side-Scrolling Beat 'Em Up Game Like Scott Pilgrim with Cocos2 ...
- cocos2d-x横版格斗游戏教程4
上一篇我们已经可以看到英雄和机器人都处于无敌状态,现在让他们互相残杀吧,所以接下来将要实现碰撞检测功能.先来看看下面这张图: 这里碰撞检测采用比较简单的矩形,可以看到英雄和机器人在攻击的时候会把拳头伸 ...
- cocos2d-x横版格斗游戏教程1
转载:https://blog.csdn.net/zhanghefu/article/details/27586421 马上就要放假回家了,最近几天也比较闲,所以抽空来学习一下cocos2d-x 3. ...
- cocos2d-x横版格斗游戏教程3
https://blog.csdn.net/zhanghefu/article/details/27588955 这一篇要为英雄创造一些小伙伴了,并且需要让机器人会巡逻,会偷懒,会行走,还会攻击英雄, ...
- 【官方教程】使用Quick-Cocos2d-x搭建一个横版过关游戏(六)
这一篇是系列文章的最后一篇了,这一章我们会将剩下的UI界面和元素补齐,比如:游戏开始界面.过关界面,画面滚动. 游戏开始界面 在前面我们看到过主界面的结构,在我们的游戏开始界面中,我们只给它添加了一个 ...
- 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程03:碰撞检测》
2019独角兽企业重金招聘Python工程师标准>>> 3.碰撞检测 碰撞检测的概述: 碰撞在物理学中表现为两粒子或物体间极端的相互作用.而在游戏世界中,游戏对象在游戏世界自身并不受 ...
- python和java的格斗动画_《Genesis-3D开源游戏引擎--横版格斗游戏制作教程04:技能的输入与检测》...
4.技能的输入与检测 概述: 技能系统的用户体验,制约着玩家对整个游戏的体验.游戏角色的技能华丽度,连招的顺利过渡,以及逼真的打击感,都作为一款游戏的卖点吸引着玩家的注意.开发者在开发游戏初期,会根据 ...
- 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程01: 资源导入》
2019独角兽企业重金招聘Python工程师标准>>> 1. 资源导入 概述: 制作一款游戏需要用到很多资源,比如:模型.纹理.声音和脚本等.通常都是用其它相关制作资源软件,完成前期 ...
最新文章
- 梯度的直观理解_梯度下降最直观的理解
- 二叉树的最长的路径长度最大路径和
- java得到文件创建时间linux,linux java获取文件创建时间
- CF750E-New Year and Old Subsequence【动态dp】
- 【BZOJ】【1086】 【SCOI2005】王室联邦
- P8U8 IT这块出书门槛相对比较低
- Oracle函数之listagg函数
- Blender学习-考拉课程学习记录
- 三级等保 mysql8.0.24密码策略设置
- OpenCV-图片叠加
- 微信小程序在线成语接龙答题有奖1.5.1版源码
- 折腾nock给jsonp进行单元测试
- iTween之iTweenPath的使用
- 腾讯云服务器安装java服务部署环境
- linux jnlp显示异常,使用headless jnlp将slave连接到master时显示异常
- 删除“打开方式”里的其他程序
- 【Android】java.lang.SecurityException: getDeviceId: Neither user xxxxx nor current process has andro
- 多模态学习研究进展综述
- 树状图JQuery.ztree插件的使用
- C++ 与cocos2d-x-4.0完成太空飞机大战 (一)
热门文章
- pptx---基础概念解释
- OTM区块链应用离我们的生活有多近?
- 位深度怎么调_吉他大神是怎么炼成的?
- /etc/shadow(影子文件)内容详解
- # Linkage Mapper 版本及下载
- 谷歌SEO优化入门:Google SEO优化方法(2022最新)
- OpenRisc-58-ORPSoC调试环境的构建
- c语言劫持dll写法,[原创]DLL劫持生成器 源码开放(纯WINDOWS SDK)+ 实例分析
- 新课程背景下的教师专业发展问题及其对策
- 电脑桌面计算机的管理在哪,电脑设备管理器在哪里打开?5种打开方法总有适合你...