用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:公有变量类,储存整个游戏工程用到的公有变量,设计为单例模式。
#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状态时,玩家点击精灵才是有效的。
#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中定义的常量,待会可以看到。
#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实现是类似的,故不再多说。
#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的设计与实现。
人物的基本方法确定好后,再来考虑一下牧师和魔鬼之间有没有什么不同?其实并没有很大的不同。两种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的设计与实现。
相关文章推荐
- 用cocos2d 2.1制作一个过河小游戏(3): 船与河岸Sprite设计
- 用cocos2d 2.1制作一个过河小游戏(4): 游戏主逻辑BaseLayer设计
- 用cocos2d 2.1制作一个过河小游戏(1): 总概
- [SpriteKit] 系统框架中Cocos2d-x制作小游戏ZombieConga
- cocos2d-x开发笔记:获取Sprite上某一个点的透明度,制作不规则按钮
- cocos2d-x开发笔记:获取Sprite上某一个点的透明度,制作不规则按钮
- 使用cocos2d 2.1制作一条河游戏(4): 主要的游戏逻辑BaseLayer设计
- cocos2d-x 关于 Sprite应用 的一个小游戏 - Sky Defense
- Cocos2d-x 2.0.4 如何制作一个基于Tile的游戏(2)
- 制作一个塔防游戏 Cocos2d-x 2.1.4 (一)
- (译)如何使用cocos2d来制作一个打地鼠的游戏:第一部分
- 如何制作一个类似Tiny Wings的游戏(2) Cocos2d-x 2.1.4内含iOS版源代码
- 如何制作一个基于Tile的游戏(2) Cocos2d-x 3.0alpha0
- 如何制作一个塔防游戏 Cocos2d-x 3.0alpha0
- 如何制作一个横版格斗过关游戏 Cocos2d-x 2.0.4
- 制作了一个cocos2d-x下沿y轴旋转任意角度的精灵对象
- 如何制作一个塔防游戏 Cocos2d-x 2.0.4
- 使用Cocos2D 2.X制作一个简单iPhone游戏教程
- 丶制作一个数字猜猜看小游戏