بناء لعبة سحق الوحش [Monster Smashing]باستخدام Cocos2D: تقنيات اللعبة و الصوت |
ستتعلم في هذا الدرس كيفية استخدام اطار عمل Cocos2D iOS من أجل انشاء العاب 2D بسيطة و مع ذلك متقدمة تهدف الى استخدام كل أجهزة iOS. انها مصممة لكلا المستخدمين الجدد و المتقدمين. خلال هذه الفترة, ستتعلم كل ما يخص مفاهيم Cocos2D الاساسية, تفاعل اللمس, القوائم و التنقلات, النشاطات, الجزيئات و التصادمات. تابع القراءة.
هذا المدخل الثالث و الأخير في سلسلة Monster Smashing. احرص على اكمال الدروس السابقة قبل البدء بهذا.
في درس اليوم, سنركز على نهاية اللعبة. سنضيف عدة خصائص, بما في ذلك جزيئات, أصناف, صوت و موسيقا. سنقوم باستخدام قائمة الخصائص لخلق الوحش. سنتعلم أيضا كيفية التوقف و استئناف عملية تدفق بيانات Cocos2D بالشكل الصحيح من دون طاقة معالجة كبيرة.
1. الارواح
نظام التصنيف في الألعاب يقوم عادة على استخدام الأهداف, الارواح أو اية معلومات مرتبطة بهذا. سنقوم باستخدام حياة اللاعب فقط في هذا الدرس. يرمز لكل حياة بقلب يوضع على الجهة العلوية اليسرى من الشاشة. توجد صورة القلب في الموارد.
سيبدأ المستخدم اللعبة بثلاثة أرواح. لفعل ذلك, سنبدأ بـ heartArray و نستخدم عقدة for لتلقي الصور لكل حياة. سنعين أيضا موقع الأرواح الثلاثة على الجهة العلوية اليسرى من الشاشة.
تقتضي الخطوة الأولى بإضافة خاصيتين جديدتين, NSMutableArray *hearthArray و NSInteger lives, الى صنف MonsterRun.h. بعد القيام بذلك, يمكننا الان إضافة الانشاء الصحيح, MonstrRun.m. سيساعدنا المقطع التالي لفعل ذلك.
lives = 3;
hearthArray = [[NSMutableArray alloc] init];
for(NSInteger i = 0; i lives; i++){
CCSprite *hearth = [CCSprite spriteWithFile:@"hearth.png"];
[hearthArray insertObject:hearth atIndex:i];
hearth.position = ccp( ((i+1)*50), winSize.height - 50);
[self addChild:hearth];
}
اذا قمت بإدارة المشروع الان, سترى الأرواح في اعلى الشاشة. و لكن لا يمكننا التفاعل معهم حتى الان. تقتضي الخطوة التالية بالتفاعل معهم و جعل المستخدم يخسر بعض الأرواح.
يجب إضافة المقطع التالي في طريقة addMonster:(ccTime)dt في قسم التكتل 4داخل دالة CCCallBlockN.
lives--;
[self removeChild:[hearthArray lastObject] cleanup:YES];
[hearthArray removeLastObject];
if(lives == 0)
[[CCDirector sharedDirector] replaceScene:[HelloWorldLayer scene]];
لاحظ أنه يجب اضافته مرتين, مرة لكل جملة if.
سيتم استدعاء الكود في كل مرة يغاد فيها sprite (الوحش) الشاشة. اذا غادر الوحش الشاشة, سيتم خسارة روح و في نفس الوقت ستتغير مصفوفة القلوب (heartArray), و سيتم استبعاد اخر موقع. سيتم إزالة الصورة من الشاشة أيضا.
اذا وصلنا الى صفر روح, ستتوقف اللعبة و سنرجع الى الشاشة الاولية.
2. الجزيئات
تستخدم الجزيئات كتأثيرات لإنجاز عدة تأثيرات من بينها الدخان أو الشلالات. في هذه الحالة, سنستخدمهم لتفجير الوحش الأحمر. في كل مرة يتم فيها لمس الوحش الأحمر, سنقوم بتفعيل الجزيء.
سنترك القسم غير كامل على جزأين. حان الوقت الان لإنهائه.
أضف الخاصية التالية CCParticleExplosion *particleExplosion الى صنف MonsterRun.h. في طريقة -(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event, حيث لديك if([m killMethod] == 2 الشرط, سنضيف مقطع كود جديد.
CCCallFuncND *emitter = [CCCallFuncND actionWithTarget:self selector:@selector(startExplosion:data:) data:monster];
CCSequence *sequencia = [CCSequence actions:emitter, nil];
if([defaults integerForKey:@"sound"]==1)
[[SimpleAudioEngine sharedEngine] playEffect:@"SplatEffect.caf"];
[splashPool runAction:sequencia];
[/sourcecode]
This code will provide the necessary requirements for the particle system. First we use the <code>CCCallFuncND</code> to call a selector <code>startExplosion:data:</code>. Then we create a particle sequence calling the above-mention "<strong>emitter</strong>" object.
The next step is to create the <code>CCParticleExplosion *particleExplosion</code> method. For that you can simply copy and paste the following snippet.
[sourcecode language="objective c"][/sourcecode]
//Positions the explosion emitter and sets it off
- (void)startExplosion:(id)sender data:(CCSprite*)monster {
particleExplosion = [[CCParticleExplosion alloc] initWithTotalParticles:809];
particleExplosion.texture = [[CCTextureCache sharedTextureCache] addImage:@"textureRed.png"];
particleExplosion.life = 0.0f;
particleExplosion.lifeVar = 0.708f;
particleExplosion.startSize = 40;
particleExplosion.startSizeVar = 38;
particleExplosion.endSize = 14;
particleExplosion.endSizeVar = 0;
particleExplosion.angle = 360;
particleExplosion.angleVar = 360;
particleExplosion.speed = 243;
particleExplosion.speedVar = 1;
CGPoint g = CGPointMake(1.15, 1.58);
particleExplosion.gravity = g;
ccColor4F startC = {0.89f, 0.56f, 0.36f, 1.0f};
particleExplosion.startColor = startC;
ccColor4F endC = {1.0f,0.0f,0.0f,1.0f};
particleExplosion.endColor = endC;
[self addChild:particleExplosion];
particleExplosion.position = [monster position];
[particleExplosion resetSystem];
}
ستقوم هذه الطريقة بـ
- تحديد خاصية particleExplosion
- تعريف عدد الجزيئات الكلي (809).
- تعريف نسيج الجزيئات (CCTextureCache)
- تعريف خاصيات متعددة, مثل بداية و نهاية الحجم, الزاوية, السرعة و اللون.
بعد تشكيل الجزيء, يمكننا وضعه في مركز الوحش. سيتحرك انفجار الجزيء دائما عندما يتحرك الوحش و سيكون دائما في المركز.
يمكنك الان بناء و إدارة المشروع. يمكنك رؤية مثال عن انفجار الجزيء في المثال التالي.
توضيح نظام الجزيء |
3. الصوت و الموسيقا
تعتبر التأثيرات الصوتية و الموسيقا الخلفية قوام كل الألعاب. في هذا القسم, سنشرح كيفية اضافتهم الى هذه اللعبة. سيكون لدينا نوعين من الأصوات, نقرة الوحش و الموسيقا الخلفية.
يتم انجاز الصوت في أجهزة iOS باستخدام محرك الصوت البسيط. من أجل تفعيله, استورد SimpleAudioEngine.h عند أصناف HelloWorldLayer.m و MonsterRun.m.
عند HelloWorldLayer.m, لدينا عدة خطوتها لننفذها. تقتضي الأولى بإضافة خاصية CCMenuItem *_soundOn قبل قسم @implementation.
يجب أن تبدو كالتالي:
#import "HelloWorldLayer.h"
#import "AppDelegate.h"
#import "MonsterRun.h"
#import "SimpleAudioEngine.h"
CCMenuItem *_soundOn;
// HelloWorldLayer implementation
@implementation HelloWorldLayer
...
يمكننا الان الانتقال الى طريقة -(id) init. نختار استخدام صنف NSUserDefaults ليساعدنا على تخزين تفضيلات المستخدم فيما يخص اختيار الصوت. سنقوم أولا بتعريف الرقم بالقيمة 1( مع الصوت) و تخزينه باستخدام مفتاح "الصوت". تقتضي الخطوة التالية بالتحقق من قيمة مفتاح "الصوت", و الرد تبعا لتلك القيمة. اذا كانت القيمة 1, سيتم بدء نظام الصوت. في غير ذلك, سيبقى متوقفا.
يري المقطع التالي هذا الانشاء.
// Check the sound system
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSObject * object = [defaults objectForKey:@"sound"];
if(object == nil){
NSNumber *soundValue = [[NSNumber alloc ] initWithInt:1];
[defaults setObject:soundValue forKey:@"sound"];
}
int soundDefault = [defaults integerForKey:@"sound"];
if (soundDefault == 1) {
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"backgroundSound.caf"];
[[SimpleAudioEngine sharedEngine] setEffectsVolume:0.4f];
}
else {
[[SimpleAudioEngine sharedEngine] pauseBackgroundMusic];
}
يمكنك الان تعديل CCMenuItemToggle *toggleItem.
CCMenuItemToggle *toggleItem;
if(soundDefault == 1)
toggleItem = [CCMenuItemToggle itemWithTarget:self selector:@selector(soundButtonTapped:) items:_soundOn, _soundOff, nil];
else
toggleItem = [CCMenuItemToggle itemWithTarget:self selector:@selector(soundButtonTapped:) items:_soundOff,_soundOn, nil];
مع هذا التغيير, ستتغير القائمة تبعا لمفضلات المستخدم الصوتية. لاحظ ان toggleItem تستدعي المختار soundButtonTapped:, لذا سنحتاج الى كتابة تلك الطريقة و انشاء المنطق الصحيح لتحقق من زر التغيير. اذا تم اختيار زر التغيير لـ _soundOn, يجب أن نبدأ الصوت و نخزن 1 في مفضلات NSUserDefaults. اذا تم اختيار زر التغيير لـ _soundOff, يجب أن نوقف موسيقا الخلفية و نخزن 0 في مفضلات NSUserDefaults.
استخدم المقطع التالي لخلق ذلك التأثير.
- (void)soundButtonTapped:(id)sender {
CCMenuItemToggle *toggleItem = (CCMenuItemToggle *)sender;
if (toggleItem.selectedItem == _soundOn) {
NSLog(@"Sound Enabled");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"backgroundSound.caf"];
[defaults setInteger:1 forKey:@"sound"];
[defaults synchronize];
} else {
NSLog(@"Sound Disabled");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:0 forKey:@"sound"];
[[SimpleAudioEngine sharedEngine] pauseBackgroundMusic];
[defaults synchronize];
}
}
يجب علينا الان أيضا تغيير صنف MonsterRun.m. يجب إضافة NSUserDefaults *defaults; خاصية عالمية. تاليا, سنقوم ببدئها و التحقق من مفضلات المستخدم. سيساعدنا المقطع التالي على انجاز ذلك.
- (void)soundButtonTapped:(id)sender {
defaults = [NSUserDefaults standardUserDefaults];
int soundDefault = [defaults integerForKey:@"sound"];
if (soundDefault == 1) {
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"backgroundSound.caf"];
[[SimpleAudioEngine sharedEngine] setEffectsVolume:0.4f];
}
مع هذه الكود يمكننا إضافة صوت الخلفية و طريقة تسمح للمستخدم باستخدام مفضلات نظام الافتراضي. يمكنك إدارة المشروع و اللعب بزر الصوت من أجل اختبار خاصية الصوت الجديدة.
4. قائمة الخواص
تعد قوائم الخواص (plist) مورد مفيد لتخزين المعلومات فيما يخص بيئة و تطور اللعبة. في هذه اللعبة, سنقوم بتخزين المعلومات المتعلقة بالوحوش. يمكن أن ترى Plist التي تم انشاؤها في الصورة التالية.
4. قائمة الخواص
تعد قوائم الخواص (plist) مورد مفيد لتخزين المعلومات فيما يخص بيئة و تطور اللعبة. في هذه اللعبة, سنقوم بتخزين المعلومات المتعلقة بالوحوش. يمكن أن ترى Plist التي تم انشاؤها في الصورة التالية.
توضيح شكل العدو. |
لن يتم مناقشة الطريقة المثلى لانشاء و تحليل plist. سنركز على هذه plist و طريقة ممكنة لتحليل البيانات منها.
تقتضي الخطوة الأولى بإضافة plist الى المشروع. انقر على ملف -> جديد -> ملف و اختر من الجانب الأيمن خيار المورد و من ثم قائمة الخواص (على اليسار). أطلق عليها اسم Enemy.plist و أضف المعلومات من الصورة السابقة.
يجب عليك الان تحليل البيانات و استخدامها لانشاء الوحوش بتلك المعلومات. سنقوم أولا بانشاء نصا مع طريق الى ملف plist و من ثم سنقوم بانشاء قاموس سيخزن المعلومات داخل plist. تقتضي الخطوة الأخيرة باستعادة خيارات الوحش. لكل وحش, سنقوم بإضافة نفس هذه الخصائص لهم.
// plist reading
NSString * plistPath = [[NSBundle mainBundle] pathForResource:@"Enemy" ofType:@"plist"];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
// load monster information from pList
if ([dictionary objectForKey:@"Monsters" ] != nil ){
NSMutableArray *array = [dictionary objectForKey:@"Monsters"];
for(int i = 0; i < [array count]; i++){
NSMutableDictionary *m = [array objectAtIndex:i];
Monster *m1 = [[Monster alloc] init];
[m1 setTag:(i+1)];
[m1 setMonsterSprite:[[NSString alloc] initWithString:[m objectForKey:@"monsterSprite"]]];
[m1 setSplashSprite:[[NSString alloc] initWithString:[m objectForKey:@"splashSprite"]]];
[m1 setMinVelocity:[[m objectForKey:@"minVelocity"] floatValue]];
[m1 setMaxVelocity:[[m objectForKey:@"maxVelocity"] floatValue]];
[m1 setMovement:[[m objectForKey:@"movement"] intValue]];
[m1 setKillMethod:[[m objectForKey:@"killMethod"] intValue]];
[_monsters addObject:m1];
}
}
يجب أن تبدو طريقة -(id) init الجديدة و النهائية كهذا:
-(id) init {
if( (self=[super init]) ) {
self.isTouchEnabled = YES;
CGSize winSize = [CCDirector sharedDirector].winSize;
CCSprite *dirt = [CCSprite spriteWithFile:@"WoodRetroApple_iPad_HomeScreen.jpg"];
dirt.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:dirt z:-2];
_monsters = [[NSMutableArray alloc] init];
// plist reading
NSString * plistPath = [[NSBundle mainBundle] pathForResource:@"Enemy" ofType:@"plist"];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
// load monster information from pList
if ([dictionary objectForKey:@"Monsters" ] != nil ){
NSMutableArray *array = [dictionary objectForKey:@"Monsters"];
for(int i = 0; i < [array count]; i++){
NSMutableDictionary *m = [array objectAtIndex:i];
Monster *m1 = [[Monster alloc] init];
[m1 setTag:(i+1)];
[m1 setMonsterSprite:[[NSString alloc] initWithString:[m objectForKey:@"monsterSprite"]]];
[m1 setSplashSprite:[[NSString alloc] initWithString:[m objectForKey:@"splashSprite"]]];
[m1 setMinVelocity:[[m objectForKey:@"minVelocity"] floatValue]];
[m1 setMaxVelocity:[[m objectForKey:@"maxVelocity"] floatValue]];
[m1 setMovement:[[m objectForKey:@"movement"] intValue]];
[m1 setKillMethod:[[m objectForKey:@"killMethod"] intValue]];
[_monsters addObject:m1];
}
}
lives = 3;
hearthArray = [[NSMutableArray alloc] init];
for(NSInteger i = 0; i lives; i++){
CCSprite *hearth = [CCSprite spriteWithFile:@"hearth.png"];
[hearthArray insertObject:hearth atIndex:i];
hearth.position = ccp( ((i+1)*50), winSize.height - 50);
[self addChild:hearth];
}
CCMenuItem *pauseButton = [CCMenuItemImage itemFromNormalImage:@"pauseButton.png" selectedImage:@"pauseButton.png" target:self selector:@selector(plusMinusButtonTapped:)];
pauseButton.position = ccp(winSize.width - 50 , winSize.height - 50 );
pauseMenu = [CCMenu menuWithItems:pauseButton, nil];
pauseMenu.position = CGPointZero;
[self addChild:pauseMenu];
[self schedule:@selector(addMonster:) interval:0.5];
_monstersOnScreen = [[NSMutableArray alloc] init];
defaults = [NSUserDefaults standardUserDefaults];
int soundDefault = [defaults integerForKey:@"sound"];
if (soundDefault == 1) {
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"backgroundSound.caf"];
[[SimpleAudioEngine sharedEngine] setEffectsVolume:0.4f];
}
}
return self;
}
لاحظ أنه يجب استخدام plists فوق طرق تخزين أخرى بما ان iOS تحسن لقراءة و كتابة و معالجة هذه القوائم.
5. التوقف و الاستئناف
يعد خيارا التوقف و الاستئناف خيارات في غاية الأهمية بما ان اللعبة لن تعمل كل الوقت. سيوفر التوقف و الاستئناف الصحيحين حياة البطارية و سيجعل الكود توجيه-الأداء.
تقتضي الخطوة الأولى بإضافة صنف اخر يدعى PausedScene.
يجب أن تبدو .h كهذا:
#import <Foundation/Foundation.h>
#import "cocos2d.h"
@interface PausedScene : CCScene {
}
+(CCScene *) scene;
@end
بينما سيبدو الانشاء كهذا:
#import "PausedScene.h"
#import "MonsterRun.h"
#import "HelloWorldLayer.h"
@implementation PausedScene
+(CCScene *) scene {
CCScene *scene = [CCScene node];
PausedScene *layer = [PausedScene node];
[scene addChild: layer];
return scene;
}
-(id) init {
if( (self=[super init]) ) {
CGSize winSize = [CCDirector sharedDirector].winSize;
CCSprite *background = [CCSprite spriteWithFile:@"WoodRetroApple_iPad_HomeScreen.jpg"];
background.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:background z:-2];
CCSprite *logo = [CCSprite spriteWithFile:@"MonsterSmashing.png"];
logo.scale = 1.2;
logo.position = ccp(winSize.width /2 , 800 );
[self addChild: logo];
CCMenuItem *resumeGameButtonImage = [CCMenuItemImage itemFromNormalImage:@"resume.png" selectedImage:@"resume.png" target:self selector:@selector(onStartGamePressed)];
CCMenuItem *exitGameButtonImage = [CCMenuItemImage itemFromNormalImage:@"exit.png" selectedImage:@"exit.png" target:self selector:@selector(mainMenuPressed)];
CCMenu *menu = [CCMenu menuWithItems:resumeGameButtonImage,exitGameButtonImage, nil];
menu.position = ccp(winSize.width * 0.5f, winSize.height * 0.4f);
[menu alignItemsVerticallyWithPadding:15];
[self addChild:menu];
}
return self;
}
- (void) mainMenuPressed{
CCScene *menuScene = [HelloWorldLayer scene];
[[CCDirector sharedDirector] replaceScene:menuScene];
}
- (void)onStartGamePressed {
[[CCDirector sharedDirector] popScene];
}
- (void) dealloc {
[super dealloc];
}
@end
في هذه المرحلة, يجب أن تكون ذاتية الشرح باستثناء طريقة - (void)onStartGamePressed. و لكن تعد تلك الطريقة بسيطة جدا. ستظهر شاشة و تجعل اخر شاشة ظاهرة.
في MonsterRun.h, أضف CCMenu *pauseMenu; خاصية. أصبحت الان كود MonsterRun.m كاملة, لذا سنلقي نظرة على الاسطر التالية.
CCMenuItem *pauseButton = [CCMenuItemImage itemFromNormalImage:@"pauseButton.png" selectedImage:@"pauseButton.png" target:self selector:@selector(plusMinusButtonTapped:)];
pauseButton.position = ccp(winSize.width - 50 , winSize.height - 50 );
pauseMenu = [CCMenu menuWithItems:pauseButton, nil];
pauseMenu.position = CGPointZero;
تعني الكودات المستخدمة لـ CCMenuItem معطى, سنضيف صورة (متوافرة بالموارد), مختار, و موقع.
يتم استدعاء طريقة - (void)plusMinusButtonTapped في كل مرة يتم لمس قائمة التوقف. اذا لم يتم توقيف اللعبة, سيتم "دفع" مشهد جديد, PauseScene, تشغيل و توقيف اللعبة. اذا تم توقيف اللعبة, سيتم استئناف الاحياء و التحقق فيما اذا يمكنها البدء بفحص افتراضي الصوت لدى المستخدم.
- (void)plusMinusButtonTapped:(id)sender {
if(![[CCDirector sharedDirector] isPaused]){
[self pauseSchedulerAndActions];
CCScene *menuScene = [PausedScene scene];
[[CCDirector sharedDirector] pushScene:menuScene];
}
else{
[[CCDirector sharedDirector] stopAnimation];
[[CCDirector sharedDirector] resume];
[[CCDirector sharedDirector] startAnimation];
int soundDefault = [defaults integerForKey:@"sound"];
if (soundDefault == 1)
[[SimpleAudioEngine sharedEngine] resumeBackgroundMusic];
}
}
تقدم الصورة التالية قامة التوقف مع خيارين, الاستئناف و الخروج. سيعيد الاستئناف اللاعب الى ساحة اللعب بينما سيأخذ الخروج المستخدم الى القائمة الأساسية.
توضيح قائمة التوقف. |
6. النتائج
تقدم الصورة التالية النسخة النهائية لوجهة اللعبة.
توضيح للعبة. |
تقدم الصورة التالية النسخة النهائية للقائمة الرئيسية.
توضيح للقائمة الأساسية |
عند هذه المرحلة, يجب عليك فهم و تنفيذ المهام التالية:
- إضافة أصناف custom
- استخدام, تعريف و إضافة نظام الجزيئات
- إضافة صوت custom و موسيقا خلفية
- معرفة NSUserDefaults و SimpleAudioEngine
- استخدام plists بسيطة و متقدمة
- توقيف و استئناف تقنيات Cocos2D
- معرفة كيفية انشاء لعبة
7. ملاحظات
لدى المؤلفين أعمالا إضافية تلخص بما يلي:
- خلفية custom
- تعديل ايقونة التطبيق (باستخدام دليل Apple)
- تغيير اسم التطبيق الذي يظهر على الجهاز أو المنافس, و ذلك بحذف النقاط في الاسم اذا كان الاسم طويلا جدا.
و بهذا نختم درسنا الثالث و الأخير الذي يعرض كيفية انشاء لعبة Monster Smasher باستخدام Cocos2D. بحلول هذه الوقت, يجب أن يكون لديك معرفة كافية و وافية عن انشاء لعبة Cocos2D بسيطة باستخدام محرك اللعبة نفسها. اذا كان لديك أي سؤال أو تعلق, لا تتردد و اتركهم في قسم التعليقات أسفلا.
ليست هناك تعليقات: