Arabic (العربية/عربي) translation by Rhat Memo (you can also view the original English article)
هذه هي الحلقة الخامسة من سلسلة دروس الـ Cocos2D لإستنساخ لعبة Centipede إلى iOS (أي-او-إس). قبل أن تبدء في هذا الدرس تأكد من أن تكمل الأجزاء السابقة.
في الحلقة السابقة...
فى الحلقة السابقة تناقشنا عن موضوع الذكاء الإصطناعي (AI) لي الـ caterpillar. وتعلمنا كيف نحرك caterpillar خلال حقل الانبات وحدود المستوى
في درس اليوم سنتطرق الى الكثير من المواضيع خلال نقاشنا لحركة\تفاعل اللاعب واطلاق الصواريخ.
الخطوة الاولى: التحكم باللاعب
التحكم باللاعب أمرٌ بسيط وسهل الى حد ما. أول شيء يتعين علينا القيام به هو أن نخبر الطبقة الخاصة بلعبتنا بأن تستجيب للَّمسات من أجل التفاعل معها. قم باضافة الكود التالي الى دالة init
الموجودة في GameLayer.m:
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
كما رأيتم في الدرس الاول، هذا السطر من الكود ببساطة سيسمح ل GameLayer بأن يكون delegate خاص باللمس وبالتالي سيسمح له باستلام الاشعارات من اللمسات. الآن هناك دالتان نحتاج الى انشائهما. اضف الدوال التالية الى GameLayer.m:
// 1 - (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { return YES; } // 2 - (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event { // 3 CGPoint touchLocation = [self convertTouchToNodeSpace:touch]; CGPoint oldTouchLocation = [touch previousLocationInView:touch.view]; oldTouchLocation = [[CCDirector sharedDirector] convertToGL:oldTouchLocation]; oldTouchLocation = [self convertToNodeSpace:oldTouchLocation]; // 4 int xChange = touchLocation.x - oldTouchLocation.x; int yChange = touchLocation.y - oldTouchLocation.y; int newX = self.player.position.x + xChange; int newY = self.player.position.y + yChange; // 5 if(newX < kGameAreaStartX + kGameAreaWidth - kGridCellSize && newX > kGameAreaStartX && newY > kGameAreaStartY + kGridCellSize / 2 && newY < kGameAreaStartY + (kGridCellSize * 3)) { __block BOOL collide = NO; CGPoint oldPosition = self.player.position; // 6 self.player.position = ccp(newX,newY); // 7 [self.sprouts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { Sprout *sprout = (Sprout *)obj; CGRect sproutRect = [sprout getBounds]; CGRect playerRect = [self.player getBounds]; if(CGRectIntersectsRect(sproutRect, playerRect)) { collide = YES; *stop = YES; } }]; // 8 if(collide) { self.player.position = oldPosition; } } }
- اولا يجب ان نصمم الدالة
ccTouchesBegan
لكي تسمح لمن استدعاها بأن يعلم بأننا نستجيب للَّمسات. اذا تجاهلت هذه الخطوة، فإن لعبتك ستنهار. - بعد ذلك، لدينا الدالة
ccTouchesMoved
والتي يتم استدعائها عندما يقوم اللاعب بسحب اصبعه على الشاشة. - احصل على مرجع للإحداثيات الخاصة بموقع اللمسة الحالية وللاإحداثيات الخاصة بموقع اللمسة السابقة.
- احصل على التغُّير بين الموقع القديم والموقع الجديد. سنستخدم هذا التغير لتحديد كم يجب على اللاعب أن يتحرك.
- هنا لدينا سلسلة من الاختبارات للتحقق من بقاء اللاعب داخل "منطقة اللاعب" او المربع الحدودي الوهمي الذي وضعناه لهم. و الكود السابع فى الواقع هو الذى يحرك اللاعب
- تحديث مكان اللاعب
- نقوم هنا بالتحقق لنرى اذا ما اصطدم اللاعب بأي من ال sprouts. تشكل ال sprouts عقبات بالنسبة للاعب ونحن نريد ان نقيد حركة اللاعب في حال كانوا في طريقه.
- اخيراَ، اذا كان هنالك تصادم، فإننا سنعيد موقع اللاعب الى الموقع الجيد الاخير.
الان اذا قمت بتشغيل اللعبة يجب ان تكون قادر على ان تسحب فى اى مكان فى المنطقة الخاصة باللعبة وان تحرك اللاعب فى منطقة ضيقة .
الخطوة الثانية: الكائن الصاروخى
كائن الصاروخ هو الذي يسمح لللاعب ولبقية اجسام اللعبة بالتفاعل. اللاعب سيطلق سيلاً مستمراً من الصواريخ بسرعة وتواتر متفاوتين بحسب المستوى الحالي.
قبل ان نبدأ بإنشاء الصاروخ، يجب أن ننشأ بعض الثوابت التي سنقوم باستعمالها. افتح GameConfig.h واضف السطور التالية اليه:
#define kMissileSpeed 1.0 #define kMissileMaxSpeed 10.0 #define kMissilesTotal 20 #define kMissileFrequency .6 //seconds #define kMinMissileFrequency .2
سأقوم بشرح كلٍ منها حالما نصل الى الكود الذي يليها. الآن، انشئ فئة فرعية subclass جديدة ل GameObject وسمها Missile. اضف الكود التالي الى Missile.h:
#import "cocos2d.h" #import "GameObject.h" @interface Missile : GameObject @property (nonatomic, assign) BOOL dirty; - (void)update:(ccTime)dt; @end
ستستعمل الخاصية dirty
في المستقبل لتدل على أن الصاروخ لم يعد موجوداً في اللعبة. يمكن ان يكون هذا بسبب الخروج من الشاشة او الاصطدلم بكائن اخر فى اللعبة منذ ان تتحرك الصواريخ باستمرار ستحتاج الى تحديث لطريقة تحريكهم
الآن، افتح Missile.m واضف الكود التالي:
#import "Missile.h" #import "GameLayer.h" #import "GameConfig.h" @implementation Missile @synthesize dirty = _dirty; // 1 - (id)initWithGameLayer:(GameLayer *)layer { if(self == [super initWithGameLayer:layer]) { self.sprite = [CCSprite spriteWithSpriteFrameName:@"missile.png"]; } return self; } - (void)update:(ccTime)dt { // 2 int inc = kMissileSpeed * (self.gameLayer.level + 1.5); // 3 if(inc > kMissileMaxSpeed) { inc = kMissileMaxSpeed; } // 4 int y = self.position.y + inc; self.position = ccp(self.position.x,y); // 5 if(self.position.y > kGameAreaStartY + kGameAreaHeight) { self.dirty = YES; } } @end
- دالة الـ init الخاصة بنا تبدو مألوفة للغاية وهي مسؤولة فقط عن انشاء الـ sprite الخاصة بالصاروخ.
- هذه هي السرعة التي سيتحرك فيها الصاروخ. وهويبدا بالقيمة الاساسية ويُسرع استنادا الى المستوى الحالى
- عند مرحلة ما، قد تخرج سرعة الصاروخ عن السيطرة. منعنا ذلك من خلال اضافة سرعة قصوى.
- هذان السطران هما في الواقع من يحركان الصاروخ الى الامام. نقوم بحساب قيمة y جديدة للصاروخ ومن ثم نحدث الموقع.
- اخيرا اذا اصطدم الصاروخ بالجزء العلوى نحول خاصية _dirty الى true سنقوم بعمل جمع للقمامة garbage collect للصواريخ ذات القيمة dirty داخل الـ GameLayer.
الخطوة الثالثة: اطلاق الصواريخ
الآن وبعد ما اصبح لدينا بعض الصواريخ لنطلقها، علينا ان نقوم باطلاقهم من اللاعب. بشكل عام، عندما يكون لديك عدد ضخم من الاجسام مثل الصواريخ، يفضل اعادة استخدام اكبر عدد ممكن منه وان لا تعيد حجز نسخ جديدة منها في الذاكرة اثناء تشغيل حلقة اللعبة الرئيسية. لحل هذه القضية سننشئ مصفوفتين: المصفوفة الاولى ستحتوي على كل الصواريخ الموجودة حاليا في اللعبة ( يعني التي اطلقت من قبل اللاعب) والمصفوفة الاخرى ستحتوي على مجموعة الصواريخ التي لم تطلق بعد. فى نهاية طريقة التحديث سننظف كل خاصية dirty من الصواريخ وننقلهم من مصفوفة "in play" داخل اللعب الى المصفوفة الاخرى "pool"
اضف الخصائص التالية إلى ملف GameLayer.h:
@property (nonatomic, retain) NSMutableArray *missilesWaiting; @property (nonatomic, retain) NSMutableArray *missilesFiring;
الان افتح GameLayer.m وقم باستيراد ملف Missile.h واضف السطور التالية الى طريقة init الخاصة بك
// 1 _missilesWaiting = [[NSMutableArray alloc] initWithCapacity:kMissilesTotal]; _missilesFiring = [[NSMutableArray alloc] initWithCapacity:kMissilesTotal]; // 2 for(int x = 0; x < kMissilesTotal; x++) { Missile *missile = [[Missile alloc] initWithGameLayer:self]; [self.missilesWaiting addObject:missile]; [missile release]; }
- تهيئة كلا من المصفوفتين
- كرر عملية إنشاء الصواريخ بعدد الـ
kMissilesTotal
مرة. مرة واحدة يتم انشاؤهم ونضيفهم الى مصفوفة
بعد ذلك سننتقل الى دالة التحديث لنضيف الكود التالى:
// 1 static float missleFireCount = 0; - (void)update:(ccTime)dt { // ...Caterpillar code... // 2 float frequency = kMinMissileFrequency; if(kMissileFrequency / (self.level * 1.25) > kMinMissileFrequency) { frequency = kMissileFrequency / self.level; } // 3 if(missleFireCount < frequency) { missleFireCount += dt; } else { missleFireCount = 0; // 4 if([self.missilesWaiting count] > 0) { Missile *missile = [self.missilesWaiting objectAtIndex:0]; [self.missilesFiring addObject:missile]; [self.missilesWaiting removeObjectAtIndex:0]; missile.position = self.player.position; [self.spritesBatchNode addChild:missile.sprite]; } } // 5 __block Missile *dirty = nil; [self.missilesFiring enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { Missile *missile = (Missile *)obj; [missile update:dt]; if(missile.dirty) { dirty = missile; *stop = YES; } }]; // 6 if(dirty) { dirty.dirty = NO; [self.missilesWaiting addObject:dirty]; [self.missilesFiring removeObject:dirty]; [self.spritesBatchNode removeChild:dirty.sprite cleanup:NO]; } }
- نحتاج الى انشاء عداد ثابت للمساعدة فى تسهيل التردد الذى ينطلق منه الصواريخ
- حساب تردد اطلاق الصواريخ كما يزداد المستوى يزداد التردد ايضا
- نحن نحتاج فقط الى اطلاق صاروخ جديد اذا كان التردد اكبر من او مساويا للتردد المحدد لكل مستوى
- سحب الصاروخ من مصفوفة الانتظار (اذا كان بها) واضافتها لمصفوفة الاطلاق فى هذه المرحلة ايضا اضفنا روح الصاروخ لعقدة الدفع التى يمكن استخلاصها
- التحقق من تعداد كافة الصواريخ من وجود واحد فذر اذا وجدنا واحد تذكر اى واحد يمكننا تحريكه مجددا الى مصفوفة الانتظار
- اذا كان هنالك صاروخ يحقق الخاصية dirty، فانقله من مصفوفة الانتظار الى مصفوفة الاطلاق واحذف ال sprite الخاصة به من الـ batch node.
هذا هو كامل الكود المطلوب لتحريك الصواريخ. تقدم للامام وقم بتشغيل اللعبة عند هذه النقطة شاهد الصواريخ منطلقة من اللاعب وانت تتحرك حولها صورة
خاتمة
الان نحن سمحنا للاعب وبعض الصواريخ بالتحرك الان بدات تشعر انها حقا لعبة كما اننا كشفنا بعض التصادم بين اللاعب و البراعم فى الجزء القادم من السلسلة سندخل اعمق فى كشف الاصطدام بين الصواريخ والبراعم والحشرة وبين اللاعب والحشرة
استمتعوا بالبرمجة