iOS SDK: بناء لعبة حقائق – انشاء الواجهة واللمسات الأخيرة
|
سيعلمك هذا الدرس كيفية استخدام اطار عمل Sprite Kit لانشاء لعبة حقائق قائمة على طرح أسئلة. انها مصممة لكلا المستخدمين الجدد و المتقدمين. خلال هذه الفترة, ستقوم بتطبيق أساس Sprite Kit. تقسم دروس ألعاب الحقيقة الى ثلاثة أجزاء من أجل تغطية كل قسم بشكل كامل. و بعد هذا الدرس التعليمي ذات الأجزاء الثلاثة, سيتمكن القراء من انشاء لعبة نقر سؤال-و-جواب بما في ذلك الصوات و الاحياء و القوائم و القواعد و المؤقتات و تفاعل UIKit.
المقدمة
تقسم السلسلة الى ثلاثة أجزاء: بدء تشغيل المشروع, انشاء الواجهة و منطق اللعبة. سينتج كل جزء نتيجة عملية, و سينتج مجموع كل الأجزاء اللعبة النهائية. على الرغم من حقيقة أنه يمكن قراءة كل جزء بشكل مستقل, و لكن لفهم أفضل نقترح متابعة الدروس خطوة بخطوة. قمنا أيضا بتضمين كود المصدر لكل جزء بشكل منفصل. و بذلك نوفر طريقة لبدء الدرس من أي قسم بالسلسلة.
هذا هو القسم الثاني في سلسلة لعبة الحقائق باستخدام Sprite Kit. في هذا الدرس, ستقوم ببرمجة اختيار المستوى و واجهة مشهد اللعبة الأساسي. يركز هذا الدرس على عدة نواحي مثل custom UITableView, initializers صنف custom, قوائم الخواص و SKActions. سنقوم بشرح كل شيء لاحقا. اذا كنت لم تكمل الجزء الأول بعد, يمكنك تنزيل المشروع و المتابعة من حيث توقفت تماما.
الخطوة 1
تهدف اللعبة بشكل أساسي الى انشاء أسئلة متعددة مقسمة الى عدة مستويات. بهذه الطريقة, يجب عليك تشكيل واجهة معدلة لاختيار المستوى الذي ترغب باللعب به. من أجل تحقيق ذلك, يجب عليك إضافة Objective-C صنف (ملف -> جديد -> ملف). أطلق عليه اسم LevelSelect و اختر SKScene كصنف فرعي. سترى ملفين جديدين في مشروعك.
من أجل اختيار المستوى, ستستخدم مشهد UITableView و تشكله مع عدة خواص. بالإضافة الى ذلك, يجب عليك تسمية المستويات و مواصفاتهم. في LevelSelect.h, يجب عليك إضافة هذه الخواص. سيساعدك المقطع التالي:
@property (nonatomic, retain) UIButton* backButton;
@property (retain, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSArray *levelsArray;
@property (strong, nonatomic) NSArray *levelsDescriptionArray;
يحتوي المقطع أيضا على UIButton يدعى backButton. في هذه اللحظة, يعد الزر ذاتي الشرح; يساعد المستخدم بالرجوع من واجهة اختيار المستوى الى الواجهة الرئيسية.
الخطوة 2
تاليا, ركز على LevelSelect.m. تقتضي الخطوة الأولى بإضافة طريقة -(id)initWithSize:(CGSize)size. في هذا الصنف, ستقوم بتشكيل لون الخلفية. يمكنك اختيار اللون الذي تفضله.
-(id)initWithSize:(CGSize)size{
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:0.25 green:0.35 blue:0.15 alpha:1.0];
}
return self;
}
بما انك تستخدم مشاهد UIKit, يجب عليك إضافة طريقة -(void) didMoveToView:(SKView *)view. هذه الطريقة تعرف و تشكل backButton و tableView, تضع علامة عنوان للمشهد, و تعين و تبدأ levelsArray و levelsDescriptionArray و تضيف tableView الى القائمة الرئيسية. يمكن تشكيل backButton كالتالي:
_backButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
_backButton.frame = CGRectMake(CGRectGetMidX(self.frame)-100, CGRectGetMaxY(self.frame)-100, 200, 70.0);
_backButton.backgroundColor = [UIColor clearColor];
[_backButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal ];
UIImage *buttonExitImageNormal = [UIImage imageNamed:@"back.png"];
UIImage *strechableButtonExitImageNormal = [buttonExitImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
[_backButton setBackgroundImage:strechableButtonExitImageNormal forState:UIControlStateNormal];
[_backButton addTarget:self action:@selector(moveToHome) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_backButton];
بالإضافة الى ذلك, يجب عليك ان تنشئ طريقة moveToHomemethod و أن تستورد MyScene.h.
-(void) moveToHome{
MyScene* myScene = [[MyScene alloc] initWithSize:CGSizeMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame))];
[self removeUIViews];
[self.scene.view presentScene:myScene];
}
بما أنك تحتاج الى إزالة مشاهد UIKIt من أكثر من مكان, دعونا ننشئ طريقة تقوم بذلك بالضبط. تستدعى هذه الطريقة removeUIViews و هي موضحة بالاسفل:
-(void)removeUIViews{
[_backButton removeFromSuperview];
[_tableView removeFromSuperview];
}
تعد علامة هذه الواجهة بسيطة جدا. حاول برمجتها بنفسك. اذا واجهتك ايو مشاكل, سيساعدك المقطع التالي.
SKLabelNode *titleLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];titleLabel.text = @"Level Select!!";titleLabel.fontSize = 60;titleLabel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)+300);[self addChild:titleLabel];
الخطوة 3
يعد تشكيل tableView مخادعا بعض الشيء بما أننا نحتاج الى تشكيل خصائص متعددة, مثل حجم و موقع الاطار, و dataSource و المفوض.
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(CGRectGetMidX(self.frame)-150, CGRectGetMidY(self.frame)-250, 300, 400)];
_tableView.dataSource = self;
_tableView.delegate = self;
يتطلب السطران الثاني و الثالث الانفا الذكر خطوة إضافية بما انك تقوم بتعريف بنفس الوقت مشهدان. يعد مصدر البيانات ذاتي التعريف و لدى UITableView مفوض نشاط. في LevelSelect.h, عليك تمديد صنفك بـ UITableViewDataSource و UITableViewDelegate. يجب أن تبدو LevelSelect.h كالتالي:
@interface LevelSelect : SKScene < UITableViewDataSource, UITableViewDelegate >
بما أنك تقوم بتمديد UITableViewDataSource, يجب انشاء طرق إضافية, أعني:
1. -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
2. -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
تستخدم الأولى فقط لمعرفة عدد الصفوف الموجودة في مشهد اللائحة في الوقت الحقيقي. تعد الطريقة الثانية معقدة بما أنها تستخدم لتأهيل و تشكيل مشهد اللائحة. يغطي تشكيل مشهد اللائحة العنوان و الوصف و صورة الصف. من أجل استخدام الوصف في كل صف, يجب بدء كل خلية بستايل الخلية UITableViewCellStyleSubtitle.
تعد هذه الطريقة مسؤولة عن الخطوة الإضافية: تقوم بالتحقق من مستوى الاعب الحالي و تعطيل المستويات المتقدمة من actualPlayerLevel. يمكن ان ترى اعلان الطرق الكامل اسفلا:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *levels = [_levelsArray objectAtIndex:indexPath.row];
NSString *descriptions = [_levelsDescriptionArray objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Identifier"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Identifier"];
}
if (indexPath.row >= actualPlayerLevel)
[cell setUserInteractionEnabled:FALSE];
[cell.textLabel setText:levels];
cell.imageView.image = [UIImage imageNamed:@"appleLogo.png"];
[cell.detailTextLabel setText:descriptions];
return cell;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_levelsArray count];
}
ها هنا ملاحظتان تخصان طريقة -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath. لم تقم بعد ببدء خواص levelsArray, و levelsDescriptionArray و actualPlayerLevel. سيتم تعريف كل شيء في طريقة -(void) didMoveToView:(SKView *)view لا تنس إضافة خواص actualPlayerLevel الى صنفك:
@implementation LevelSelect{
long actualPlayerLevel;
}
الخطوة 4
يتم تعريف levelsArray بأسماء المستويات. يمكنك أن تدهوها "المستوى 1", المستوى 2" أو اية اسم اخر من اختيارك. يتبع levelsDescriptionArray نفس المبدأ, تعد وصفا لكل مستوى و يمكن تعريفه باسم من اختيارك. يعد الانشاء التالي ممكننا:
_levelsArray = [[NSArray alloc] initWithObjects:
@"Level 1.",
@"Level 2.",
@"Level 3.",
@"Level 4.",
@"Level 5.",
@"Level 6.",
@"Level 7.",
@"Level 8.",
@"Level 9.",
nil];
_levelsDescriptionArray = [[NSArray alloc] initWithObjects:
@"The adventure begins.",
@"A new step.",
@"Achivements?!",
@"Level 4 description",
@"Level 5 description",
@"Level 6 description",
@"Level 7 description",
@"Level 8 description",
@"Level 9 description",
nil];
أخيرا, تعد actualPlayerLevel نوع قيمة طويلة تمثل مستوى اللاعب. في الوقت الحالي, قل أن مستوى اللاعب هو 1.
1
actualPlayerLevel = 1;
تنتهي طريقة -(void) didMoveToView:(SKView *)view عند إضافة مشهد اللائحة الى الشاشة:
[self.view addSubview:_tableView];
أخيرا, تحتاج الى تغيير طريقة MyScene.m -(void) moveToGame. يجب أن تستدعي هذا الصنف الجديد بدلا من القديم. و مع ذلك يمكن انجاز ذلك بسهولة:
-(void) moveToGame{
LevelSelect* factsScene = [[LevelSelect alloc] initWithSize:CGSizeMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame))];
// Same code
// ...
}
عند هذه المرحلة يجب عليك إدارة المشروع و فحص الواجهة الجديدة. يجب عليك رؤية شيئا مشابها للصورة التالية:
3. واجهة الحقائق
الخطوة 1
تعد واجهة الحقائق حيث يحدث النشاط الحقيقي. لدى الواجهة عدة مشاهد تترجم الى خواص بشكل مباشر مثل عدد الأرواح المتبقية, المستوى الحالي و المؤقت و الازرار الصحيحة و الخاطئة. بالإضافة الى ذلك, ستقوم بانشاء بادئ custom لهذا الصنف. ان الغاية من هذا الانشاء هو الامكانية بالتقدم خلال اللعبة و القيم فيما يخص المستوى. و كذلك يتم تمرير الأرواح الى الصنف و بالنتيجة يتفاعل الصنف (يحلل البيانات).
مرة أخرى, سنستخدم SKLabelNode, UIButtons, و NSMutableArray. تظهر FactsScene.h الكاملة كالتالي:
@interface FactsScene : SKScene{
NSMutableArray* heartArray;
}
@property (nonatomic,weak) SKLabelNode* currentLevelLabel;
@property (nonatomic,weak) SKLabelNode* timerLevel;
@property (nonatomic, retain) UIButton* trueButton;
@property (nonatomic, retain) UIButton* falseButton;
-(id) initWithSize:(CGSize)size inLevel:(NSInteger)level withPlayerLives:(int)lives;
حان الوقت الان الى الانتقال FactsScene.m و انشاء بضعة كائنات. تحتاج الى كائنات إضافة من أجل استعادة و تخزين البيانات المستلمة من قبل البادئ. إضافة الى ذلك, تحتاج الى تخزين الوقت الأقصى الذي على اللاعب الإجابة فيه عن كل سؤال. قم بتعديل ملف الانشاء تبعا لذلك.
@implementation FactsScene{
NSUserDefaults* defaults;
NSString* musicPath;
NSInteger playerLives;
NSInteger playerLevel;
int maximumTime;
}
الان, يجب عليك كتابة البادئ و تخزين قيمه. يتم تعريف البادئ custom كبادئ عادي و لديه نفس البنية. و لكن لديه خواص أكثر كالمعاملات. يجب أن يبدو كالتالي:
-(id) initWithSize:(CGSize)size inLevel:(NSInteger)level withPlayerLives:(int)lives{
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:0.35 green:0.25 blue:0.5 alpha:1.0];
defaults = [NSUserDefaults standardUserDefaults];
playerLives = lives;
playerLevel = level;
maximumTime = 30;
}
return self;
}
الخطوة 2
في الوقت الحالي, يعد maximumTime 30 ثانية, و لكن ستتغير القيمة الى 60 ثانية (أو أي وقت اخر من اختيارك). الان في -(void) didMoveToView:(SKView *)view أضف صورة الخلفية, الصورة الامامية, أرواح اللاعب, المؤقت, الازرار الصحيحة و الخاطئة و أسئلة المستوى الإجمالي و الحالي. تعد الكود بسيطة من اجل الصورة الخلفية و الامامية و يجب عليك أن تكون قاردا على فعل ذلك بسهولة (باستخدام SKSpriteNode).
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:@"background.png"];
background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
background.size = CGSizeMake(768, 1024);
[self addChild:background];
SKSpriteNode *frontImage = [SKSpriteNode spriteNodeWithImageNamed:@"transparentCenterBorder.png"];
frontImage.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
frontImage.size = CGSizeMake(600, 450);
[self addChild:frontImage];
تمثل أرواح اللاعب بصورة قلب. بما انك ستعلن ثلاثة أرواح, يجب ان تضع ثلاثة قلوب في الشاشة. يجب عليك أيضا أن تستخدم NSMutableArray بما انك تحتاج الى تغيير حجمها ديناميكيا. سيقوم المقطع التالي بمساعدتك:
heartArray = [[NSMutableArray alloc] init];
for(NSInteger i = 0; i < playerLives; i++){
SKSpriteNode* liveImage = [SKSpriteNode spriteNodeWithImageNamed:@"hearth.png"];
liveImage.scale = .6;
liveImage.position = CGPointMake(CGRectGetMaxX(self.frame)-40-(i*50),CGRectGetMaxY(self.frame)-40);
[heartArray insertObject:liveImage atIndex:i];
[self addChild:liveImage];
}
يتم تشكيل الازرار الخاطئة و الصحيحة كـ:
_trueButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
_trueButton.frame = CGRectMake(CGRectGetMidX(self.frame)-350, CGRectGetMidY(self.frame)+300, 335, 106);
_trueButton.backgroundColor = [UIColor clearColor];
[_trueButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal ];
UIImage *buttonTrueImageNormal = [UIImage imageNamed:@"trueBtn.png"];
UIImage *strechableButtonTrueImageNormal = [buttonTrueImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
[_trueButton setBackgroundImage:strechableButtonTrueImageNormal forState:UIControlStateNormal];
[_trueButton addTarget:self action:@selector(touchWillProduceASound) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_trueButton];
_falseButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
_falseButton.frame = CGRectMake(CGRectGetMidX(self.frame)+10, CGRectGetMidY(self.frame)+300, 335, 106);
_falseButton.backgroundColor = [UIColor clearColor];
[_falseButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal ];
UIImage *buttonFalseImageNormal = [UIImage imageNamed:@"falseBtn.png"];
UIImage *strechableButtonFalseImageNormal = [buttonFalseImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
[_falseButton setBackgroundImage:strechableButtonFalseImageNormal forState:UIControlStateNormal];
[_falseButton addTarget:self action:@selector(touchWillProduceASound) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_falseButton];
لاحظ أن كلا الزرين يستدعيان طريقة touchWillProduceASound. هذه الطريقة تختبر فيما اذا كانت الإجابة المعطاة صحيحة أم خاطئة. في هذا الجزء, سنستخدم فقط حدث صوتي وتحد (الخاطئ).
-(void) touchWillProduceASound{
long soundFlag = [defaults integerForKey:@"sound"];
NSString* answer = @"False";
if (soundFlag == 1){
SKAction* sound;
if ([answer isEqualToString:@"False"]) {
sound = [SKAction playSoundFileNamed:@"wrong.mp3" waitForCompletion:YES];
NSLog(@"inside");
}
[self runAction:sound];
}
}
الخطوة 3
يعد SKLabelNode مؤقت اللعبة و هو يتغير كل ثانية. و لكن, يتم إنشاؤه بكل بساطة كـ SKLabelNode:
_timerLevel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
_timerLevel.text = @"30";
_timerLevel.fontSize = 70;
_timerLevel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)+350);
[self addChild:_timerLevel];
من أجل تحديث العلامة, يجب عليك انشاء SKAction تقوم بتعريف مؤقت customمن أجل استدعاء طريقة custom. و من ثم يجب ان شاء سلسلة SKAction:
SKAction *wait = [SKAction waitForDuration:1];
SKAction *updateTimer = [SKAction runBlock:^{
[self updateTimer];
}];
SKAction *updateTimerS = [SKAction sequence:@[wait,updateTimer]];
[self runAction:[SKAction repeatActionForever:updateTimerS]];
ستقوم باستخدام التحذير فيما يخص طريقة - (void)updateTimer لانك لم تقم بإنشائها بعد. تقوم الطريقة بعدة نشاطات و بنفس الوقت تأخذ بعين الاعتبار عدة خواص:
تفحص فيما اذا الصوت شغالا
تحدث خاصية و علامة maximumTime, منقصة قيمة واحدة في كل ثانية
تفحص the maximumTime, و اذا كانت القيمة صفر, فان النتيجة ستكون اما انهاء اللعبة أو تغيير السؤال (هنالك أكثر عن هذا الموضوع في الدرس التالي)
ننصحك بمحاولة كتابة الطرية باستخدام الكود-المستعارة الانفة الذكر. اذا واجهتك أية مشكلات, فان كامل الطريقة معروضة في الأسفل:
- (void)updateTimer{
maximumTime--;
if (maximumTime == 0){
long soundFlag = [defaults integerForKey:@"sound"];
if (soundFlag == 1){
SKAction* sound;
sound = [SKAction playSoundFileNamed:@"beep.mp3" waitForCompletion:YES];
[self runAction:sound];
}
if (playerLives < 1){
SKTransition* transition = [SKTransition fadeWithDuration:2];
MyScene* myscene = [[MyScene alloc] initWithSize:CGSizeMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame))];
[self removeUIViews];
[self.scene.view presentScene:myscene transition:transition];
} else{
// other
}
}
[_timerLevel setText:[[NSNumber numberWithInt:maximumTime] stringValue]];
}
الخطوة 4
هناك طريقة واحدة مفقودة, removeUIViews. انها تقوم بإزالة مشاهد UIKit من المشهد عندما يحصل الانتقال.
-(void)removeUIViews{
[_trueButton removeFromSuperview];
[_falseButton removeFromSuperview];
}
حان الوقت الان الى تشغيل المشروع و رؤية شاشة الحقائق!
توضيح شاشة الحقائق
3. قوائم الخواص
حان الوقت اضافة بعض البيانات الى التطبيق. اذهب الى ملف -> جديد -> ملف و اختر ملف قائمة الخواص (plist). اطلق عليها اسم "LevelDescription" و قم بتشكيلها.
من أجل تفادي وضع كل البيانات في ملف plist واحد, يمكنك تنزيل الملف مباشرة على ملف zip (بداية الصفحة). لا يغطي هذا الدرس تحليل البيانات بعمق, لذا من أجل تحليلي ذلك الملف يجب استخدام المقطع التالي. اذا واردتك أية شكوك, اكتب تعليقك في الصندوق أسفل الصفحة.
NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"LevelDescription" ofType:@"plist"];
NSMutableDictionary* dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
if ([dictionary objectForKey:@"Questions" ] != nil ){
NSMutableArray *array = [dictionary objectForKey:@"Questions"];
for(int i = 0; i < [array count]; i++){
NSMutableDictionary *questions = [array objectAtIndex:i];
NSLog(@"ID %@", [questions objectForKey:@"id"]);
NSLog(@"%@", [questions objectForKey:@"statement"]);
NSLog(@"%@", [questions objectForKey:@"isCorrect"]);
NSLog(@"%@", [questions objectForKey:@"additionalInfo"]);
}
}
الان, قم بتشغيل المشروع و مراقبة جذور لوحة المراقبة بينما تدخل مشهد الحقائق. لاحظ ان ملف plist يمكن أن يشكل في عدة طرق مختلفة. اذا أردت, يمكنك تغيير أنواع مصفوفة القاموس و التركيبات. لاحظ أنه يجب الإعلان عن التغييرات في دورة التحليل. ننصحك أن تلعب قليلا بملفات plist و تحليلات البيانات الاصلية.
الخلاصة
يجب أن تكون قادرا عند هذه المرحلة على استخدام و تشكيل UITableView, على التفاعل بين أطرعمل SpriteKit و UIKit و على انشاء SKTransitions و SKActions و انشاء و تحليل ملفات الخواص. في اخر قسم من هذه السلسلة, ستتعلم عن منطق اللعبة.
ليست هناك تعليقات: