好了,我们继续我们的IOS之旅。
想必大家玩儿手游的时候都用过虚拟摇杆,通过虚拟摇杆在控制屏幕上的元素移动,做各种动作等等。今天我们就通过引用一个开源的代码来完成虚拟摇杆和控制按键的制作,并通过摇杆来控制我们的飞船进行移动。
废话不多说(貌似已经说了不少),我们先介绍虚拟摇杆的开源代码SneakyInput,大家可以在网上下载SneakyInput的源代码,网址:
http://github.com/sneakyness/SneakyInput
我们用到其中的这些文件:
这里我提供这些文件的源代码,方便大家使用,大家可以自己创建这些类,然后把下面的代码拷贝进去,这里SneakyExtension是我们对SneakyInput的扩展。下面这部分代码有点多,大家耐心拷贝一下吧(不要喷我T_T):
SneakyButton.h:
#import"cocos2d.h"
@interfaceSneakyButton : CCNode <CCTouchOneByOneDelegate> {
CGPoint center;
float radius;
float radiusSq;
CGRect bounds;
BOOL active;
BOOL status;
BOOL value;
BOOL isHoldable;
BOOL isToggleable;
float rateLimit;
}
@property(nonatomic, assign) BOOL status;
@property(nonatomic, readonly) BOOL value;
@property(nonatomic, readonly) BOOL active;
@property(nonatomic, assign) BOOL isHoldable;
@property(nonatomic, assign) BOOL isToggleable;
@property(nonatomic, assign) float rateLimit;
//Optimizations(keep Squared values of all radii for faster calculations) (updated internallywhen changing radii)
@property(nonatomic, assign) float radius;
-(id)initWithRect:(CGRect)rect;
@end
SneakyButton.m:
#import"SneakyButton.h"
@implementationSneakyButton
@synthesizestatus, value, active, isHoldable, isToggleable, rateLimit, radius;
- (void)onEnterTransitionDidFinish
{
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:selfpriority:1 swallowsTouches:YES];
}
- (void) onExit
{
[[[CCDirector sharedDirector] touchDispatcher] removeDelegate:self];
}
-(id)initWithRect:(CGRect)rect{
self = [super init];
if(self){
bounds = CGRectMake(0, 0, rect.size.width, rect.size.height);
center = CGPointMake(rect.size.width/2, rect.size.height/2);
status = 1; //defaults to enabled
active = NO;
value = 0;
isHoldable = 0;
isToggleable = 0;
radius = 32.0f;
rateLimit = 1.0f/120.0f;
_position = rect.origin;
}
return self;
}
-(void)limiter:(float)delta{
value = 0;
[self unschedule: @selector(limiter:)];
active = NO;
}
- (void)setRadius:(float)r
{
radius = r;
radiusSq = r*r;
}
#pragma mark TouchDelegate
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
if (active) return NO;
CGPoint location = [[CCDirector sharedDirector] convertToGL:[touchlocationInView:[touch view]]];
location = [self convertToNodeSpace:location];
//Do a fast rect check before doing a circle hit check:
if(location.x < -radius || location.x > radius || location.y < -radius|| location.y > radius){
return NO;
}else{
float dSq = location.x*location.x + location.y*location.y;
if(radiusSq > dSq){
active = YES;
if (!isHoldable && !isToggleable){
value = 1;
[self schedule: @selector(limiter:) interval:rateLimit];
}
if (isHoldable) value = 1;
if (isToggleable) value = !value;
return YES;
}
}
return NO;
}
-(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
if (!active) return;
CGPoint location = [[CCDirector sharedDirector] convertToGL:[touchlocationInView:[touch view]]];
location = [self convertToNodeSpace:location];
//Do a fast rect check before doing a circle hit check:
if(location.x < -radius || location.x > radius || location.y < -radius|| location.y > radius){
return;
}else{
float dSq = location.x*location.x + location.y*location.y;
if(radiusSq > dSq){
if (isHoldable) value = 1;
}
else {
if (isHoldable) value = 0; active = NO;
}
}
}
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
if (!active) return;
if (isHoldable) value = 0;
if (isHoldable||isToggleable) active = NO;
}
-(void)ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
[self ccTouchEnded:touch withEvent:event];
}
@end
SneakyButtonSkinnedBase.h:
#import"cocos2d.h"
@classSneakyButton;
@interfaceSneakyButtonSkinnedBase : CCSprite {
CCSprite *defaultSprite;
CCSprite *activatedSprite;
CCSprite *disabledSprite;
CCSprite *pressSprite;
SneakyButton *button;
}
@property(nonatomic, retain) CCSprite *defaultSprite;
@property(nonatomic, retain) CCSprite *activatedSprite;
@property(nonatomic, retain) CCSprite *disabledSprite;
@property(nonatomic, retain) CCSprite *pressSprite;
@property(nonatomic, retain) SneakyButton *button;
@end
SneakyButtonSkinnedBase.m:
#import"SneakyButtonSkinnedBase.h"
#import"SneakyButton.h"
@implementationSneakyButtonSkinnedBase
@synthesizedefaultSprite, activatedSprite, disabledSprite, pressSprite, button;
- (void) dealloc
{
[defaultSprite release];
[activatedSprite release];
[disabledSprite release];
[pressSprite release];
[button release];
[super dealloc];
}
- (id) init
{
self = [super init];
if(self != nil){
self.defaultSprite = nil;
self.activatedSprite = nil;
self.disabledSprite = nil;
self.pressSprite = nil;
self.button = nil;
[self schedule:@selector(watchSelf)];
}
return self;
}
- (void) watchSelf
{
if (!self.button.status){
if(disabledSprite){
disabledSprite.visible = YES;
}
else {
disabledSprite.visible = NO;
}
}
else {
if(!self.button.active){
pressSprite.visible = NO;
if(self.button.value == 0){
activatedSprite.visible = NO;
if(defaultSprite){
defaultSprite.visible = YES;
}
}
else {
activatedSprite.visible = YES;
}
}
else {
defaultSprite.visible = NO;
if(pressSprite){
pressSprite.visible = YES;
}
}
}
}
- (void)setContentSize:(CGSize)s
{
_contentSize = s;
defaultSprite.contentSize = s;
button.radius = s.width/2;
}
- (void)setDefaultSprite:(CCSprite *)aSprite
{
if(defaultSprite){
if(defaultSprite.parent)
[defaultSprite.parent removeChild:defaultSprite cleanup:YES];
[defaultSprite release];
}
defaultSprite = [aSprite retain];
if(aSprite){
[self addChild:defaultSprite z:0];
[self setContentSize:defaultSprite.contentSize];
}
}
- (void)setActivatedSprite:(CCSprite *)aSprite
{
if(activatedSprite){
if(activatedSprite.parent)
[activatedSprite.parent removeChild:activatedSprite cleanup:YES];
[activatedSprite release];
}
activatedSprite = [aSprite retain];
if(aSprite){
[self addChild:activatedSprite z:1];
[self setContentSize:activatedSprite.contentSize];
}
}
- (void)setDisabledSprite:(CCSprite *)aSprite
{
if(disabledSprite){
if(disabledSprite.parent)
[disabledSprite.parent removeChild:disabledSprite cleanup:YES];
[disabledSprite release];
}
disabledSprite = [aSprite retain];
if(aSprite){
[self addChild:disabledSprite z:2];
[self setContentSize:disabledSprite.contentSize];
}
}
- (void)setPressSprite:(CCSprite *)aSprite
{
if(pressSprite){
if(pressSprite.parent)
[pressSprite.parent removeChild:pressSprite cleanup:YES];
[pressSprite release];
}
pressSprite = [aSprite retain];
if(aSprite){
[self addChild:pressSprite z:3];
[self setContentSize:pressSprite.contentSize];
}
}
- (void)setButton:(SneakyButton *)aButton
{
if(button){
if(button.parent)
[button.parent removeChild:button cleanup:YES];
[button release];
}
button = [aButton retain];
if(aButton){
[self addChild:button z:4];
if(defaultSprite)
[button setRadius:defaultSprite.contentSize.width/2];
}
}
@end
SneakyJoystick.h:
#import"cocos2d.h"
@interfaceSneakyJoystick : CCNode <CCTouchOneByOneDelegate> {
CGPoint stickPosition;
float degrees;
CGPoint velocity;
BOOL autoCenter;
BOOL isDPad;
BOOL hasDeadzone; //Turns Deadzone on/off for joystick, always YES if ifDpad ==YES
NSUInteger numberOfDirections; //Used only when isDpad == YES
float joystickRadius;
float thumbRadius;
float deadRadius; //Size of deadzone in joystick (how far you must move beforeinput starts). Automatically set if isDpad == YES
//Optimizations (keep Squared values of all radii for faster calculations)(updated internally when changing joy/thumb radii)
float joystickRadiusSq;
float thumbRadiusSq;
float deadRadiusSq;
}
@property(nonatomic, readonly) CGPoint stickPosition;
@property(nonatomic, readonly) float degrees;
@property(nonatomic, readonly) CGPoint velocity;
@property(nonatomic, assign) BOOL autoCenter;
@property(nonatomic, assign) BOOL isDPad;
@property(nonatomic, assign) BOOL hasDeadzone;
@property(nonatomic, assign) NSUInteger numberOfDirections;
@property(nonatomic, assign) float joystickRadius;
@property(nonatomic, assign) float thumbRadius;
@property(nonatomic, assign) float deadRadius;
-(id)initWithRect:(CGRect)rect;
@end
SneakyJoystick.m:
#import"SneakyJoystick.h"
#define SJ_PI3.14159265359f
#define SJ_PI_X_26.28318530718f
#define SJ_RAD2DEG180.0f/SJ_PI
#define SJ_DEG2RADSJ_PI/180.0f
@interfaceSneakyJoystick(hidden)
-(void)updateVelocity:(CGPoint)point;
-(void)setTouchRadius;
@end
@implementationSneakyJoystick
@synthesize
stickPosition,
degrees,
velocity,
autoCenter,
isDPad,
hasDeadzone,
numberOfDirections,
joystickRadius,
thumbRadius,
deadRadius;
- (void) dealloc
{
[super dealloc];
}
-(id)initWithRect:(CGRect)rect
{
self = [super init];
if(self){
stickPosition = CGPointZero;
degrees = 0.0f;
velocity = CGPointZero;
autoCenter = YES;
isDPad = NO;
hasDeadzone = NO;
numberOfDirections = 4;
self.joystickRadius = rect.size.width/2;
self.thumbRadius = 32.0f;
self.deadRadius = 0.0f;
//Cocos node stuff
_position = rect.origin;
}
return self;
}
- (void)onEnterTransitionDidFinish
{
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:selfpriority:1 swallowsTouches:YES];
}
- (void) onExit
{
[[[CCDirector sharedDirector] touchDispatcher] removeDelegate:self];
}
-(void)updateVelocity:(CGPoint)point
{
// Calculate distance and angle from the center.
float dx = point.x;
float dy = point.y;
float dSq = dx * dx + dy * dy;
if(dSq <= deadRadiusSq){
velocity = CGPointZero;
degrees = 0.0f;
stickPosition = point;
return;
}
float angle = atan2f(dy, dx); // in radians
if(angle < 0){
angle +=SJ_PI_X_2;
}
float cosAngle;
float sinAngle;
if(isDPad){
float anglePerSector = 360.0f / numberOfDirections * SJ_DEG2RAD;
angle = roundf(angle/anglePerSector) * anglePerSector;
}
cosAngle = cosf(angle);
sinAngle = sinf(angle);
// NOTE: Velocity goes from -1.0 to 1.0.
if (dSq > joystickRadiusSq || isDPad) {
dx = cosAngle * joystickRadius;
dy = sinAngle * joystickRadius;
}
velocity = CGPointMake(dx/joystickRadius, dy/joystickRadius);
degrees = angle * SJ_RAD2DEG;
// Update the thumb's position
stickPosition = ccp(dx, dy);
}
- (void)setIsDPad:(BOOL)b
{
isDPad = b;
if(isDPad){
hasDeadzone = YES;
self.deadRadius = 10.0f;
}
}
- (void)setJoystickRadius:(float)r
{
joystickRadius = r;
joystickRadiusSq = r*r;
}
- (void)setThumbRadius:(float)r
{
thumbRadius = r;
thumbRadiusSq = r*r;
}
- (void)setDeadRadius:(float)r
{
deadRadius = r;
deadRadiusSq = r*r;
}
#pragma mark TouchDelegate
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = [[CCDirector sharedDirector] convertToGL:[touchlocationInView:[touch view]]];
//if([background containsPoint:[background convertToNodeSpace:location]]){
location = [self convertToNodeSpace:location];
//Do a fast rect check before doing a circle hit check:
if(location.x < -joystickRadius || location.x > joystickRadius ||location.y < -joystickRadius || location.y > joystickRadius){
return NO;
}else{
float dSq = location.x*location.x + location.y*location.y;
if(joystickRadiusSq > dSq){
[self updateVelocity:location];
return YES;
}
}
return NO;
}
-(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = [[CCDirector sharedDirector] convertToGL:[touchlocationInView:[touch view]]];
location = [self convertToNodeSpace:location];
[self updateVelocity:location];
}
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = CGPointZero;
if(!autoCenter){
CGPoint location = [[CCDirector sharedDirector] convertToGL:[touchlocationInView:[touch view]]];
location = [self convertToNodeSpace:location];
}
[self updateVelocity:location];
}
-(void)ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
[self ccTouchEnded:touch withEvent:event];
}
@end
SneakyJoystickSkinnedBase.h:
#import"cocos2d.h"
@classSneakyJoystick;
@interfaceSneakyJoystickSkinnedBase : CCSprite {
CCSprite *backgroundSprite;
CCSprite *thumbSprite;
SneakyJoystick *joystick;
}
@property(nonatomic, retain) CCSprite *backgroundSprite;
@property(nonatomic, retain) CCSprite *thumbSprite;
@property(nonatomic, retain) SneakyJoystick *joystick;
- (void)updatePositions;
@end
SneakyJoystickSkinnedBase.m:
#import"SneakyJoystickSkinnedBase.h"
#import"SneakyJoystick.h"
@implementationSneakyJoystickSkinnedBase
@synthesizebackgroundSprite, thumbSprite, joystick;
- (void) dealloc
{
[backgroundSprite release];
[thumbSprite release];
[joystick release];
[super dealloc];
}
- (id) init
{
self = [super init];
if(self != nil){
self.backgroundSprite = nil;
self.thumbSprite = nil;
self.joystick = nil;
[self schedule:@selector(updatePositions)];
}
return self;
}
- (void)updatePositions
{
if(joystick && thumbSprite)
[thumbSprite setPosition:joystick.stickPosition];
}
- (void)setContentSize:(CGSize)s
{
_contentSize = s;
backgroundSprite.contentSize = s;
joystick.joystickRadius = s.width/2;
}
- (void)setBackgroundSprite:(CCSprite *)aSprite
{
if(backgroundSprite){
if(backgroundSprite.parent)
[backgroundSprite.parent removeChild:backgroundSprite cleanup:YES];
[backgroundSprite release];
}
backgroundSprite = [aSprite retain];
if(aSprite){
[self addChild:backgroundSprite z:0];
[self setContentSize:backgroundSprite.contentSize];
}
}
- (void)setThumbSprite:(CCSprite *)aSprite
{
if(thumbSprite){
if(thumbSprite.parent)
[thumbSprite.parent removeChild:thumbSprite cleanup:YES];
[thumbSprite release];
}
thumbSprite = [aSprite retain];
if(aSprite){
[self addChild:thumbSprite z:1];
[joystick setThumbRadius:thumbSprite.contentSize.width/2];
}
}
- (void)setJoystick:(SneakyJoystick *)aJoystick
{
if(joystick){
if(joystick.parent)
[joystick.parent removeChild:joystick cleanup:YES];
[joystick release];
}
joystick = [aJoystick retain];
if(aJoystick){
[self addChild:joystick z:2];
if(thumbSprite)
[joystick setThumbRadius:thumbSprite.contentSize.width/2];
else
[joystick setThumbRadius:0];
if(backgroundSprite)
[joystick setJoystickRadius:backgroundSprite.contentSize.width/2];
}
}
@end
SneakyExtension.h:
#import"SneakyButton.h"
#import"SneakyButtonSkinnedBase.h"
#import"SneakyJoystick.h"
#import"SneakyJoystickSkinnedBase.h"
@interfaceSneakyButton (Extension)
+(id) button;
+(id)buttonWithRect:(CGRect)rect;
@end
@interfaceSneakyButtonSkinnedBase (Extension)
+(id)skinnedButton;
@end
@interfaceSneakyJoystick (Extension)
+(id)joystickWithRect:(CGRect)rect;
@end
@interfaceSneakyJoystickSkinnedBase (Extension)
+(id)skinnedJoystick;
@end
SneakyExtension.m:
#import"SneakyExtension.h"
@implementationSneakyButton (Extension)
+(id) button
{
return [[[SneakyButton alloc] initWithRect:CGRectZero] autorelease];
}
+(id)buttonWithRect:(CGRect)rect
{
return [[[SneakyButton alloc] initWithRect:rect] autorelease];
}
@end
@implementationSneakyButtonSkinnedBase (Extension)
+(id)skinnedButton
{
return [[[SneakyButtonSkinnedBase alloc] init] autorelease];
}
@end
@implementationSneakyJoystick (Extension)
+(id)joystickWithRect:(CGRect)rect
{
return [[[SneakyJoystick alloc] initWithRect:rect] autorelease];
}
@end
@implementationSneakyJoystickSkinnedBase (Extension)
+(id)skinnedJoystick
{
return [[[SneakyJoystickSkinnedBase alloc] init] autorelease];
}
@end
有兴趣的同学可以学习一下这些源码,SneakyJoystick和SneakyButton类定义了摇杆和按钮的控制逻辑,SkinnedBase类则允许我们使用自己的图片设计摇杆和按钮的外观,Extension类定义了一些静态方法方便我们创建button和joystick。添加完成之后,我们看看我们的工程吧,是不是文件太多太乱了,我觉得为了我们自己看着舒服找着方便,做一下归类吧,选中要归类的文件右键把他们Group一下,注意这里的Group(或者理解为Folder)并不是真正存在在FileSystem中的,文件依然是乱七八糟的存放在工程路径下,这里只是XCode显示做了处理而已。我们归类之后整个工程看起来好多了:
下面是我们制作的Joystick和Button按钮的图片:
Joystick_stick.png:
Joystick_background.png:
buttonA-pressed.png:
buttonA.png:
buttonB-pressed.png:
buttonB.png:
然后我们用TexturePacker把这些图片组装起来,这里组装过程和前面介绍的一样,不再赘述了,需要提到的一点是,我把Joystick的两张图片和之前的飞船图片组装到一起了(这些图片都比较大,在TexturePacker中,不建议让组装后的图片的长度或者宽度超过2048,所以我们抽出两张来放到比较小的图片当中组装起来,然后重新publish创建之前的dark-travel-elements-hd.plist等4个文件),而剩下的4个按钮,我们组装起来,我起得名字叫buttons(你也可以起自己的名字),publish后生成4个文件:
buttons-hd.plist
buttons-hd.pvr.gz
buttons.plist
buttons.pvr.gz
接着我们在工程的Resources中删掉以前的dark-travel-elements那4个文件,然后把新publish生成的4个替换进去,并且把buttons相关的4个文件也添加进去:
下面我们开始添加用户控制逻辑,通常的做法是,新建一个Layer,然后将摇杆和按钮防止到这个层上,然后通过响应事件去控制其他层的元素,这里我们添加一个用户控制层UserInteractionLayer,UserInteractionLayer.h内容如下:
#import"CCLayer.h"
#import"SneakyButton.h"
#import"SneakyButtonSkinnedBase.h"
#import"SneakyJoystick.h"
#import"SneakyJoystickSkinnedBase.h"
#import"SneakyExtension.h"
@interfaceUserInteractionLayer : CCLayer{
SneakyButton* powerfulFireButton;
SneakyButton* miniFireButton;
SneakyJoystick* joystick;
ccTime totalTime;
ccTime nextShootTime;
ccTime miniTotalTime;
ccTime miniNextShootTime;
}
@end
这里我们定义了两个按钮和一个摇杆,摇杆用来控制飞船移动,两个按钮分别对应我们之前讨论过的,飞船发射的两种导弹。还有一些本地变量用来控制子弹发射,稍后讨论。
UserInteractionLayer.m内容如下:
#import"UserInteractionLayer.h"
#import"cocos2d.h"
#import"GameLayer.h"
#import"CommonUtility.h"
#import"AirCraft.h"
@implementationUserInteractionLayer
- (id)init{
if (self = [super init]) {
[self addPowerfulFireButton];
[self addMiniFireButton];
[self addJoystick];
[self scheduleUpdate];
}
return self;
}
-(void)addPowerfulFireButton{
float buttonRadius = 50;
powerfulFireButton = [SneakyButton button];
powerfulFireButton.isHoldable = YES;
SneakyButtonSkinnedBase* fireButtonSkin = [SneakyButtonSkinnedBaseskinnedButton];
fireButtonSkin.position = ccp([CommonUtility utility].screenWidth -buttonRadius - 50, buttonRadius + 50);
fireButtonSkin.defaultSprite = [CCSpritespriteWithSpriteFrameName:@"buttonA.png"];
fireButtonSkin.pressSprite = [CCSpritespriteWithSpriteFrameName:@"buttonA-pressed.png"];
fireButtonSkin.button = powerfulFireButton;
[self addChild:fireButtonSkin];
}
-(void)addMiniFireButton{
float buttonRadius = 50;
CCSprite* button = [CCSpritespriteWithSpriteFrameName:@"buttonB.png"];
miniFireButton = [SneakyButton button];
miniFireButton.isHoldable = YES;
SneakyButtonSkinnedBase* fireButtonSkin = [SneakyButtonSkinnedBaseskinnedButton];
fireButtonSkin.position = ccp([CommonUtility utility].screenWidth - 120 -button.contentSize.width, buttonRadius + 50);
fireButtonSkin.defaultSprite = button;
fireButtonSkin.pressSprite = [CCSpritespriteWithSpriteFrameName:@"buttonB-pressed.png"];
fireButtonSkin.button = miniFireButton;
[self addChild:fireButtonSkin];
}
-(void)addJoystick{
float stickRadius = 50;
joystick = [SneakyJoystick joystickWithRect:CGRectMake(0, 0, stickRadius,stickRadius)];
joystick.autoCenter = YES;
joystick.hasDeadzone = YES;
joystick.deadRadius = 10;
SneakyJoystickSkinnedBase* stickSkin = [SneakyJoystickSkinnedBaseskinnedJoystick];
stickSkin.position = CGPointMake(stickRadius + 50, stickRadius + 50);
stickSkin.backgroundSprite = [CCSpritespriteWithSpriteFrameName:@"Joystick_background.png"];
stickSkin.thumbSprite = [CCSpritespriteWithSpriteFrameName:@"Joystick_stick.png"];
stickSkin.joystick = joystick;
[self addChild:stickSkin];
}
-(void)update:(ccTime)delta{
totalTime += delta;
miniTotalTime += delta;
GameLayer* gameLayer = [GameLayer sharedGameLayer];
AirCraft* airCraft = [gameLayer defaultAirCraft];
if (powerfulFireButton.active && totalTime > nextShootTime) {
nextShootTime = totalTime + 0.5f;
[airCraft shootBulletAtGunLevel:2];
}
else if (miniFireButton.active && miniTotalTime > miniNextShootTime){
miniNextShootTime = miniTotalTime + 0.1f;
[airCraft shootBulletAtGunLevel:1];
}
if (!powerfulFireButton.active) {
nextShootTime = 0;
}
if (!miniFireButton.active) {
miniNextShootTime = 0;
}
CGPoint velocity = ccpMult(joystick.velocity, airCraft.speed);
if (velocity.x != 0 && velocity.y != 0)
{
[airCraft moveByX:velocity.x * delta andY:velocity.y * delta];
}
}
@end
我们添加按钮的两个方法比较容易理解,设置了button的一些属性,Radius设置了按钮半径,isHoldable设置按钮是否允许按钮被按住,然后用SkinnedBase类设置了按钮的一般状态和被按下时候的显示图片,这里注意一下,其实一般的按钮有4个状态:正常状态,按下状态,禁用状态,激活状态(焦点在按钮上),而在这里我们只需要设置按钮的2个状态就足够了。
下面说说添加摇杆的方法,autoCenter属性设置摇杆是否自动归位,一般游戏中我们都希望当手松开的时候,摇杆自动回到原始位置,所以这里我们设为YES,hasDeadzone和deadRadius两个属性设置了一个区域,在这个区域内我们移动摇杆的时候,摇杆不会响应我们的输入,换句话说,我们不希望摇杆太灵敏,我们手指放在摇杆上面,难免会有轻微的移动,我们不希望系统把这些移动也当做用户输入,所以设置一个deadZone,避免不必要的抖动。接着我们设置摇杆的按钮和背景,在游戏的时候,玩家操作的是摇杆的按钮,背景不动。
接着,我们重载了update方法,在这个方法中,我们对于按键进行判断,当操作飞船发射子弹的按键按下的时候,我们获取飞船对象,调用发射子弹的方法。我们用摇杆的velocity(位移)来更新aircraft的位置。nextShootTime和totalTime用来控制当玩家按住发射按钮的时候,我们应当以一定的时间间隔来发射子弹,当玩家快速点击的时候,则将nextShootTime置0。
这里我们发现有编译问题,因为我们在update方法中看到的GameLayer的sharedGameLayer还没有编写,defaultCraft也没有添加。这里涉及到这个控制问题,在此说明一下:我们新建一个控制Layer,通过这个Layer去控制GameLayer中的元素,因此我们需要定义一个方法返回GameLayer的实例,而且要控制的对象也要在GameLayer中添加方法进行获取(比如AirCraft对象)。
我们在GameLayer.h中添加以下代码:
+(GameLayer*)sharedGameLayer;
@property(readonly) AirCraft* defaultAirCraft;
在GameLayer.m中添加:
static GameLayer*instanceOfGameLayer;
+(GameLayer*)sharedGameLayer{
NSAssert(instanceOfGameLayer != nil, @"GameScene instance not yetinitialized!");
return instanceOfGameLayer;
}
-(AirCraft*)defaultAirCraft{
return (AirCraft*)[self getChildByTag:AirCraftTag];
}
修改GameLayer的init方法,为静态变量赋值:
- (id)init{
if (self = [super init]) {
instanceOfGameLayer = self;
…
接着我们在GameLayer的initCaches中把buttons的cache也添加进去:
[frameCacheaddSpriteFramesWithFile:@"buttons.plist"];
然后修改GameLayer的静态方法scene,将控制层添加到GameLayer所在的场景中:
+ (id)scene{
CCScene* scene = [CCScene node];
GameLayer* gameLayer = [GameLayer node];
[scene addChild:gameLayer z:0 tag:GameSceneLayerTagGame];
UserInteractionLayer* inputLayer = [UserInteractionLayer node];
[scene addChild:inputLayer z:1 tag:GameSceneLayerTagInput];
return scene;
}
好了,我们的摇杆制作好了,界面应该和下面一样:
移动左边的摇杆,你可以看到我们的飞船会随之移动。
本来准备把射击的部分也放在这一篇里的,这样的话篇幅就太长了,我们留到下一篇里继续~