您的位置:首页 > 移动开发 > Cocos引擎

用cocos2d 2.1制作一个过河小游戏(2): 牧师与魔鬼Sprite设计

2014-04-16 01:25 537 查看
接着上一篇,这次先来讲讲游戏中牧师与魔鬼的设计思路。首先让我们猜想一下在游戏中人物会有怎样的动作?没错,人物应该可以站在岸上,也可以坐在船上。当我点击一个人的时候,如果他在岸上,他就可以跳到船上(前提是船还没有满人);反之亦然。所以概括地说,每次点击一个人物,他可以响应我的点击事件,并且根据现在他所在的位置和环境条件进行移动。

人物的基本方法确定好后,再来考虑一下牧师和魔鬼之间有没有什么不同?其实并没有很大的不同。两种CCSprite加载的图片是不一样的,还有就是当牧师和魔鬼站在岸上的时候,人物的位置也是不一样的(仔细观察前一篇的效果图中,大家可以发现三个牧师和三个魔鬼都是分别对应有固定的位置的)。所以每个人物应当有一个自己的编号,来决定站在岸上时站的位置。

大概分析完后就开始开工吧。首先新建一个cocos2d工程。我使用的是2.1版本,所以一上来系统自动创建的文件还是很多的。



我是第一次用cocos2d来做项目,相信也有一些人可能和我一样是新手吧,上来就被这么多文件吓尿了。其实框架是很简单的:AppDelegate和以往Cocoa项目中的AppDelegate并没有太大区别,只是里面多了许多Cocos2d项目在初始化时需要做的一些工作,例如对平台、屏幕方向大小的确认等。这些暂时可以不必理会,因为我们要做的游戏是在iphone平台上的,屏幕是横向的,和默认设置一致。IntroLayer应该是整个Cocos2d项目的第一个CCScene里面的CCLayer,其作用就是弄个游戏刚进去时的加载界面给你瞅瞅。然后在IntroLayer.m文件的最后我们能看到这样一个函数:



onEnter是进入这个Layer时调用的函数,大意是说这个Layer出现后1秒钟就开始调用HelloWorldLayer.m中的scene方法初始化一个HelloWorldLayer里面的Layer。(关于CCDirector之类的概念如果不清楚可以上网先查查cocos2d工程中CCNode、CCDirector、CCLayer、CCScene等概念的区别和联系)

说白了,HelloWorldLayer就是我们游戏的第一个有效的Layer......

不过在我们工程里还是给它改个名字好,我改成了BaseLayer,里面完成了游戏的逻辑判断部分,是整个游戏设计的关键和中心。具体实现会在后面的帖子里面讲到。根据我们之前的分析,我们为工程添加如下几个类:

1. PersonSprite:人物类,继承自CCSprite,是牧师和魔鬼类的父类,并定义其中的成员变量和方法;

2. PriestSprite:牧师类,继承自PersonSprite;

3. DevilSprite:魔鬼类,继承自PersonSprite;

4. PublicArg:公有变量类,储存整个游戏工程用到的公有变量,设计为单例模式。

1. PersonSprite

 PersonSprite.h

#import "CCSprite.h"
#import "PublicArg.h"

@class PersonSprite;

@protocol MoveSpriteDelegate <NSObject>

@required
-(void)moveSprite:(PersonSprite *)sprite;

@end

@interface PersonSprite : CCSprite <CCTouchOneByOneDelegate>

@property (assign,nonatomic) Side personSide; //record the side the person is on
@property (strong,nonatomic) id<MoveSpriteDelegate> delegate;
@property (assign,nonatomic) int number; //the number of the person

@end

先来看看人物类的头文件。上面首先定义了一个MoveSpriteDelegate协议,下面还有一个实现该协议的id成员变量delegate。想必大家都知道这里要用到的是委托吧。没错, CCSprite并没有触屏接收的机制,所以我们需要在CCSprite中自己实现CCSprite的触屏实现。而由于整个游戏中BaseLayer要负责各个精灵的挪动,所以PersonSprite收到触屏请求后需要委托给BaseLayer来完成自己的移动行为。
除了delegate外,还有两个变量personSide和number。personSide表示当前人物是在哪边。Side类型是一个自定义枚举类型,定义如下:

typedef enum
{
Left = 100,Right = 101,BOAT = 102
} Side;

一个人物可能在左岸、右岸或者船上。
number则是人物编号,关系到人物在岸上时站的位置。下面来看看实现文件。

PersonSprite.m

#import "PersonSprite.h"

@implementation PersonSprite

-(void)onEnter
{
//register touch event
[[[CCDirector sharedDirector] touchDispatcher]addTargetedDelegate:self priority:1 swallowsTouches:YES];
[super onEnter];
}

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
return [self isContainsTouchPoint:touch];
}

-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
AppController *delegate = [[UIApplication sharedApplication]delegate];

if (delegate.gameState != InGame)
return;

[_delegate moveSprite:self];
}

-(void)onExit
{
[[[CCDirector sharedDirector] touchDispatcher]removeDelegate:self];
}

//get the rect of current sprite
-(CGRect)spriteRect
{
//culculate the rect of the sprite
return CGRectMake( _position.x - _contentSize.width*_anchorPoint.x,
_position.y - _contentSize.height*_anchorPoint.y,
_contentSize.width, _contentSize.height);
}

//detect whether the sprite is touched
-(BOOL)isContainsTouchPoint:(UITouch*)touch
{
CGRect spriteRect = [self spriteRect];
spriteRect.origin = CGPointZero;
CGPoint touchPointInView = [touch locationInView:[touch view]];

//convert to openGL coordinate
touchPointInView = [[CCDirector sharedDirector] convertToGL:touchPointInView];
CGPoint touchPoint = [self convertToNodeSpace:touchPointInView];

return CGRectContainsPoint(spriteRect, touchPoint);
}

@end

在OnEnter和OnExit方法中,我们分别注册和取消CCSprite接收触屏事件的功能。PersonSprite遵守CCTouchOneByOneDelegate, 所以可以实现ccTouchBegan和ccTouchEnded设置手指触屏下去的瞬间和松开的时候做的事情。注意ccTouchBegan需要返回一个布尔值,这个函数不可或缺。spriteRect函数和 isContainsTouchPoint 函数分别计算PersonSprite在屏幕中的大小以及判断触点是否在PersonSprite的有效区域内。总的来说设置并不困难。
值得一提的是,在ccTouchEnded方法中,我们看到一个InGame值。这也是一个枚举,定义了游戏当前运行的阶段,枚举定义如下:

typedef enum
{
BeforeGame, //state: before game start
InGame, //state: game running
BlockGame, //state: boat is moving and nothing can be done
PauseGame, //state: game pause
EndGame //state: game end
} GameState;

我是把GameState和之前的Side枚举都放在了AppDelegate中。设置游戏状态是很重要的:如在暂停的时候玩家不能点击人物、船在移动的时候不能点击人物、游戏结束的时候释放资源,等等。这些都要通过游戏状态来判断。在这里,很简单地,我们认为只有在InGame状态时,玩家点击精灵才是有效的。

2. PriestSprite

PriestSprite.h

#import "PersonSprite.h"

@interface PriestSprite : PersonSprite

+(id)initPriestWithFile:(NSString *)file withNumber:(int)number;

@end

PriestSprite.m
#import "PriestSprite.h"

@implementation PriestSprite

+(id)initPriestWithFile:(NSString *)file withNumber:(int)number
{
PriestSprite * temp = [PriestSprite spriteWithFile:file];

temp.number = number;
temp.personSide = Right;
[temp setScale:0.5f];

PublicArg *arg = [PublicArg sharedArg];

[temp setPosition:ccp(arg.priest_begin_right.x + number * arg.person_interval, arg.priest_begin_right.y)];

return temp;
}

@end

由于在PersonSprite中我们已经完成了许多事情,所以子类的实现就比较简单了。initPriestWithFile:withNumber: 是我们自己定义的一个初始化函数,用来初始化Priest的各个变量。值得关注的是,代码中Priest根据自己的number情况已经把自己放到合适的位置上去了。arg.priest_begin_right、arg.person_interval、arg.priest_begin_right等都是PublicArg中定义的常量,待会可以看到。

3. DevilSprite

DevilSprite.h

#import "PersonSprite.h"

@interface DevilSprite : PersonSprite

+(id)initDevilWithFile:(NSString *)file withNumber:(int)number;

@end

DevilSprite.m
@implementation DevilSprite

+(id)initDevilWithFile:(NSString *)file withNumber:(int)number
{
DevilSprite * temp = [DevilSprite spriteWithFile:file];

temp.number = number;
temp.personSide = Right;
[temp setScale:0.5f];

PublicArg *arg = [PublicArg sharedArg];

[temp setPosition:ccp(arg.devil_begin_right.x + number * arg.person_interval, arg.devil_begin_right.y)];

return temp;
}

@end

Devil和Priest实现是类似的,故不再多说。

4. PublicArg

PublicArg.h

#import <Foundation/Foundation.h>
#import "AppDelegate.h"

@interface PublicArg : NSObject

@property CGPoint boatRightPoint; //central location of boat when on the right
@property CGPoint boatLeftPoint; //central location of boat when on the left

@property CGPoint priest_begin_right; //left-most priest's location on right bank
@property CGPoint devil_begin_right; //left-most devil's location on right bank
@property CGPoint priest_begin_left; //right-most priest's location on right bank
@property CGPoint devil_begin_left; //right-most devil's location on right bank
@property int person_interval; //interval between 2 persons

+(PublicArg *)sharedArg;

@end

PublicArg.m
#import "PublicArg.h"

@implementation PublicArg

static PublicArg *arg = nil;

+(PublicArg *)sharedArg
{
@synchronized(self)
{
if (arg == nil)
{
arg = [[super allocWithZone:NULL]init];
}
}
return arg;
}

- (id)init
{
if (self = [super init]) {
AppController *delegate = [[UIApplication sharedApplication]delegate];
CGSize screenSize = delegate.screenSize;

_boatRightPoint = ccp(screenSize.width/2+80, screenSize.height/2-105);
_boatLeftPoint = ccp(screenSize.width/2-80, screenSize.height/2-105);

_priest_begin_right = ccp(screenSize.width/2 + 140,screenSize.height/2-40);
_devil_begin_right = ccp(screenSize.width/2 + 220,screenSize.height/2-40);
_priest_begin_left = ccp(screenSize.width/2 - 180,screenSize.height/2-45);
_devil_begin_left = ccp(screenSize.width/2 - 260,screenSize.height/2-45);
_person_interval = 25;
}
return self;
}

+ (id)allocWithZone:(NSZone*)zone {
return [self sharedArg];
}

- (id)copyWithZone:(NSZone *)zone {
return self;
}

@end


PublicArg中定义了各种游戏常量:牧师和魔鬼站在左右边的时候最靠边的牧师、魔鬼的水平位置和竖直位置;船在两边停靠的坐标;两个站在岸上的人之间的距离等等。在.m文件中对这些量进行初始化。而且类的实现使用了单例模式,只有在第一次调用shareArg的时候对变量初始化。

大概先是这样。下次将会讲解一下船BoatSprite和两岸BankSprite的设计与实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: