พัฒนาเกมบน iOS (iPhone/iPod/iPad) ตอนที่ 3 สร้างเกม Shooting เบื้องต้น

บทความนี้เป็นบทความที่ตีพิมพ์ในนิตยสาร Pantip Guide ฉบับที่ 52 (ก.ค. – ก.ย.) โดยเป็นการสอนพัฒนาเกมบนระบบปฏิบัติการ iOS (iPhone / iPod / iPad) ซึ่งในตอนนี้จะอธิบายกล่าวถึงการสร้างเกมแนว Shooting เบื้องต้น

 

สวัสดีท่านผู้อ่านทุกท่านครับ หลังจากสองตอนที่ผ่านมาผมได้แนะนำ cocos2d ไปพอสมควรแล้ว ในบทความนี้จะขออธิบายถึงวิธีการทำเกม Shooting ซึ่งเป็นเกมพื้นฐานสำหรับการเริ่มต้นทำเกม ซึ่งเกม Shooting ที่จะอธิบายในบทความนี้เป็นแบบเบื้องต้นโดยมีตัวละคะฝั่งผู้เล่น ตัวละครฝั่งคอมพิวเตอร์ และกระสุนที่ใช้ยิง วิธีการเล่นคือเมื่อผู้เล่นกด (ทัชหน้าจอ) ก็จะปล่อยกระสุนเพื่อยิงคู่ต่อสู้

 

ขั้นตอนการทำเกม Shooting ก่อนอื่นเตรียมรูปสำหรับใช้ทำเกมที่ทำจากโปรแกรมสร้างรูปทั่วไปโดยบันทึกเป็นไฟล์นามสกุล .png เพื่อให้สามารถสร้างรูปที่มีพื้นหลังโปร่งแสง จำนวน 3 รูป คือรูป 1. รูป PC (Player character) คือรูปที่เราสามารถควบคุมได้ 2. รูป NPC (Non-Player character) คือรูปคู่ต่อสู้หรือศัตรู และ 3. รูปอาวุธหรือกระสุนปืน แสดงดังรูปที่ 1

 

รูปที่ 1 รูปที่ใช้ประกอบการทำเกม Shooting

 

จากนั้นสร้างโปรเจคใหม่แล้วตั้งชื่อ ซึ่งในตัวอย่างนี้ตั้งชื่อว่า iOSGame03 แสดงดังรูปที่ 2 แล้วนำไฟล์รูปทั้ง 3 รูปเข้าไปในโปรเจคโดยใส่ไว้ในโฟลเดอร์ Resources ดังรูปที่ 3 ทั้งนี้โปรแกรมจะขึ้นหน้าต่างถามว่าคุณจะบันทึกและคัดลอกไว้ในโปรเจคหรือไม่ ให้เราเลือกเครื่องหมายถูกที่ช่อง Copy items… ตรงนี้สำคัญมากเนื่องจากว่าถ้าไม่เลือกถูก เวลาเราย้ายโปรเจครูปที่เราใส่จะไม่ตามไม่ด้วยและจะทำให้หาไฟล์รูปไม่เจอ แต่ถ้าเราเลือกถูกเวลาจะย้ายโปรเจคเราสามารถคัดลอกไปทั้งโฟลเดอร์ได้เลย

 

รูปที่ 2 สร้างโปรเจค iOSGame03

 

รูปที่ 3 นำไฟล์รูปเข้าโปรเจค

 

ขั้นตอนต่อไปเป็นการแสดงรูป pc.png ที่หน้าจอกึ่งกลางด้านซ้าย โดยเพิ่มคำสั่งในเมธอด init ที่อยู่ในไฟล์ HelloWorldScene.m ดังนี้

 

-(id) init
{
	if( (self=[super init] )) {		
		CGSize size = [[CCDirector sharedDirector] winSize];
	
		CCSprite *pc = [CCSprite spriteWithFile:@"pc.png"];
		pc.position = ccp(pc.contentSize.width/2, size.height/2);
		
		[self addChild: pc];
	}
	return self;
}

 

เมื่อรันแล้วจะพบว่ามีรูป pc แสดงกึ่งกลางด้านซ้ายของหน้าจอ ดังรูปที่ 4

 

รูปที่ 4 แสดงผลรูป pc.png

 

ขั้นตอนต่อไปเป็นการสร้าง npc ให้เกิดแบบสุ่ม โดยให้ npc เกิดจากตำแหน่งด้านขวาของหน้าจอและค่อยๆ เคลื่อนที่มาทางด้านซ้าย ด้วยความเร็วที่แตกต่างกัน โดยเพิ่มเมธอด addNPC เข้าไปในไฟล์ HelloWorldScene.m ดังนี้

 

-(void)addNPC {
	
	CCSprite *npc = [CCSprite spriteWithFile:@"npc.png"]; 
	
	CGSize winSize = [[CCDirector sharedDirector] winSize];
	int minY = npc.contentSize.height/2;
	int maxY = winSize.height - npc.contentSize.height/2;
	int rangeY = maxY - minY;
	int actualY = (arc4random() % rangeY) + minY;//สุ่มตำแหน่งการเกิดของ npc
	
	npc.position = ccp(winSize.width + (npc.contentSize.width/2), actualY);
	[self addChild:npc];
	
	// คำนวณความเร็วในการเคลื่อนที่
	int minDuration = 2.0;
	int maxDuration = 4.0;
	int rangeDuration = maxDuration - minDuration;
	int actualDuration = (arc4random() % rangeDuration) + minDuration;
	
	// การทำให้ npc เคลื่อนที่จากขวาไปซ้าย
	id actionMove = [CCMoveTo actionWithDuration:actualDuration 
				position:ccp(-npc.contentSize.width/2, actualY)];
	id actionMoveDone = [CCCallFuncN actionWithTarget:self 
				selector:@selector(spriteMoveFinished:)];
				//เมื่อเคลื่อนที่พ้นจอจะเรียกเมธอด spriteMoveFinished
	
	[npc runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
}

 

จากข้างต้นจะเห็นว่าเมื่อ npc เคลื่อนที่พ้นขอบจอด้านซ้ายแล้วจะเรียกเมธอด spriteMoveFinished ซึ่งเป็นเมธอดสำหรับทำลายตัว npc โดยมีคำสั่งดังนี้

 

-(void)spriteMoveFinished:(id)sender {
	CCSprite *sprite = (CCSprite *)sender;
	[self removeChild:sprite cleanup:YES];
}

 

จากนั้นสร้างเมธอดไว้สำหรับสร้าง npc ตามเวลาสุ่ม โดยสร้างเมธอด gameLogic ดังนี้

 

-(void)gameLogic:(ccTime) dt
{
	[self addNPC];
}

 

เพิ่มคำสั่งเพื่อเรียกเมธอด gameLogic ตามช่วงเวลาที่กำหนด โดยเพิ่มคำสั่ง ไว้ในเมธอด init โดยวางไว้บรรทัดก่อนคำสั่ง return โดยใช้ schedule เพื่อเรียกเมธอด gameLogic ทุกๆ 1 วินาที ดังนี้

 

[self schedule:@selector(gameLogic:) interval:1.0];

 

จากนั้นทดลองรันโปรแกรมดู จะพบว่ามี npc เกิดขึ้นทุกๆ 1 วินาที เคลื่อนที่จากด้านขวาไปซ้าย ดังรูปที่ 5

 

รูปที่ 5 แสดงการเกิด NPC

 

ขั้นตอนต่อไปคือสร้าง projectile เมื่อเราทัชที่หน้าจอ โดยให้ต้นกำเนิดของ projectile เกิดจากตำแหน่งเดียวกับตำแหน่งของ pc และวิ่งเป็นเส้นตรงไปยังทิศทางของตำแหน่งที่ทัชหน้าจอ วิธีการคือให้เราเพิ่มเมธอดซึ่งเป็น override method ที่มีชื่อว่า ccTouchesEnded ดังนี้

 

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
	
	// หาตำแหน่งที่ทัชหน้าจอ
	UITouch *touch = [touches anyObject];
	CGPoint location = [touch locationInView:[touch view]];
	location = [[CCDirector sharedDirector] convertToGL:location];
	
	// ให้ตำแหน่งเริ่มต้นตรงกับตำแหน่ง npc คือตำแหน่งตรงกลางด้านซ้ายของหน้าจอ
	CGSize winSize = [[CCDirector sharedDirector] winSize];
	CCSprite *projectile = [CCSprite spriteWithFile:@"projectile.png"];
	projectile.position = ccp(25, winSize.height/2 + 10);
	
	int offX = location.x - projectile.position.x;
	int offY = location.y - projectile.position.y;
	
	if (offX <= 0) return;
	
	[self addChild:projectile];
	
	// คำนวณตำแหน่งปลายทางของ projectile ซึ่งมีทิศทางเดียวกับตำแหน่งที่เราทัช
	int realX = winSize.width + (projectile.contentSize.width/2);
	float ratio = (float) offY / (float) offX;
	int realY = (realX * ratio) + projectile.position.y;
	CGPoint realDest = ccp(realX, realY);
	
	// คำนวณเวลาการเคลื่อนที่
	int offRealX = realX - projectile.position.x;
	int offRealY = realY - projectile.position.y;
	float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
	float velocity = 480/1; // 480pixels/1sec
	float realMoveDuration = length/velocity;
	
	// สร้าง action ให้ projectile เคลื่อนที่
	[projectile runAction:[CCSequence actions:
	 [CCMoveTo actionWithDuration:realMoveDuration position:realDest],
	 [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)],
	 nil]];
}

 

ทั้งนี้คำสั่งข้างต้นนี้จะทำงานได้ จะต้องใส่คำสั่งเพื่อให้ยอมรับการทัชหน้าจอ โดยใส่คำสั่ง

 

self.isTouchEnabled = YES;

 

ไว้ในเมธอด init โดยใส่ไว้ก่อนคำสั่ง return และเมื่อรันแล้วจะได้ผลลัพธ์ดังรูปที่ 6

 

รูปที่ 6 แสดงรูป projectile เมื่อทัชหน้าจอ

 

สำหรับขั้นตอนถัดไปคือการเช็คชน นั่นคือเมื่อ projectile ที่ยิงออกไปชนกับ npc ก็ให้วัตถุทั้งสองชิ้นหายไป คำสั่งนี้ค่อนข้างซับซ้อน โดยเริ่มจากสร้างตัวแปรที่เป็น Array ไว้สำหรับเก็บวัตถุ projectile และ npc โดยประกาศไว้ในไฟล์ HelloWorldScene.h ดังนี้

 

@interface HelloWorld : CCLayer
{
	NSMutableArray *_npcs;
	NSMutableArray *_projectiles;
}

 

จากนั้นใส่คำสั่งจองหน่วยความจำให้กับตัวแปรทั้งสองตัว ในเมธอด init ของไฟล์ HelloWorldScene.m ดังนี้

 

-(id) init
{
	if( (self=[super init] )) {
		
		_npcs = [[NSMutableArray alloc] init];
		_projectiles = [[NSMutableArray alloc] init];
		
		CGSize size = [[CCDirector sharedDirector] winSize];
	
		CCSprite *pc = [CCSprite spriteWithFile:@"pc.png"];
		pc.position = ccp(pc.contentSize.width/2, size.height/2);
		
		[self addChild: pc];
	}
	
	[self schedule:@selector(gameLogic:) interval:1.0];

	[self schedule:@selector(update:)];	
	
	self.isTouchEnabled = YES;
	return self;
}

 

เพิ่มคำสั่งสำหรับเก็บ object npc ลงในอาเรย์ _npcs โดยใส่ไว้ก่อนปิดวงเล็บในเมธอด addNPC ดังนี้

 

npc.tag = 1;
[ _npcs addObject:npc];

 

และเช่นกันเพิ่มคำสั่งสำหรับเก็บ object projectile ลงในอาเรย์ _projectiles โดยใส่ไว้ก่อนปิดวงเล็บในเมธอด ccTouchesEnded ดังนี้

 

projectile.tag = 2;
[ _projectiles addObject:projectile];

 

และเมื่อมีวัตถุเคลื่อนที่ออกนอกจอก็จะต้องนำออกจากอาเรย์ด้วย โดยใส่คำสั่ง removeObject ในเมธอด spriteMoveFinished ดังนี้

 

-(void)spriteMoveFinished:(id)sender {
	CCSprite *sprite = (CCSprite *)sender;
	[self removeChild:sprite cleanup:YES];
	
	if (sprite.tag == 1) { // ถ้าเป็น npc
		[ _npcs removeObject:sprite];
	} else if (sprite.tag == 2) { // ถ้าเป็น projectile
		[ _projectiles removeObject:sprite];
	}
}

 

เพิ่มคำสั่งสำหรับตรวจสอบการชน โดยสร้าง Override method ที่ชื่อว่า update โดยเมธอดนี้ทำงานตามความถี่ในการแสดงผลหน้าจอ ซึ่งจะเหมาะกับคำสั่งที่ต้องทำงานอยู่ตลอดเวลา โดยในที่นี้จะใส่คำสั่งสำหรับตรวจสอบการชนกันของวัตถุ ดังนี้

 

- (void)update:(ccTime)dt {
	
	NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
	for (CCSprite *projectile in _projectiles) {
		CGRect projectileRect = CGRectMake(projectile.position.x -
                           (projectile.contentSize.width/2), 
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width, projectile.contentSize.height);
		
		NSMutableArray *npcsToDelete = [[NSMutableArray alloc] init];
		for (CCSprite *npc in _npcs) {
			CGRect npcRect = CGRectMake(
			npc.position.x - (npc.contentSize.width/2), 
			npc.position.y - (npc.contentSize.height/2), 
			npc.contentSize.width, npc.contentSize.height);
			
			if (CGRectIntersectsRect(projectileRect, npcRect)) {
				[npcsToDelete addObject:npc];				
			}						
		}
		
		for (CCSprite *npc in npcsToDelete) {
			[ _npcs removeObject:npc];
			[self removeChild:npc cleanup:YES];									
		}
		
		if (npcsToDelete.count > 0) {
			[projectilesToDelete addObject:projectile];
		}
		[npcsToDelete release];
	}
	
	for (CCSprite *projectile in projectilesToDelete) {
		[ _projectiles removeObject:projectile];
		[self removeChild:projectile cleanup:YES];
	}
	[projectilesToDelete release];
}

 

และขั้นตอนสุดท้ายเป็นสิ่งที่สำคัญอย่างยิ่ง นั่นคือการจัดการหน่วยความจำเนื่องจากอุปกรณ์มีหน่วยความจำที่จำกัด จำเป็นอย่างยิ่งที่ต้องจัดการหน่วยความจำให้ดี โดยสิ่งที่ทำได้อย่างแรกคือการคืนหน่วยความจำของตัวแปรที่ไม่ได้ใช้ โดยเขียนคำสั่งเพิ่มในเมธอด dealloc ดังนี้

 

- (void) dealloc
{
	[ _npcs release];
	_npcs = nil;
	[ _projectiles release];
	_projectiles = nil;
	[super dealloc];
}

 

เป็นอันเสร็จเรียบร้อย เมื่อตรวจสอบแล้วไม่พบ Error ก็ทดลองรัน แล้วถ้าไม่มีอะไรผิดพลาดก็จะได้ผลลัพธ์ดังรูปที่ 7 โดยเมื่อทัชหน้าจอแล้วจะมีกระสุนออกมา และถ้ากระสุนชนกับ npc วัตถุทั้งสองชิ้นก็จะหายไป

 

รูปที่ 7 ผลลัพธ์เกม Shooting เบื้องต้น

 

จากข้างต้นเกมประเภท Shooting นี้สามารถนำไปออกแบบเพิ่มเติมหรือดัดแปลงให้เข้ากับเกมที่เราออกแบบขึ้นมาใหม่ได้ หรือจากตัวอย่างนี้ถ้าจะทำให้สมบูรณ์มากขึ้น ผู้อ่านสามารถเพิ่มการควบคุมตัวละครให้เคลื่อนที่ หรือเพิ่มคะแนนเมื่อยิงกระสุนโดนคู่ต่อสู้ หรือถ้าหากคู่ต่อสู้วิ่งมาถึงตัวละครทำให้พลังชีวิตตัวละครลดลง และมีอื่นๆ อีกมากมาย ลองทำดูนะครับ
บทความนี้อ้างอิงมาจาก: http://www.raywenderlich.com/

มนตรี อินทโชติ
สาขาวิชาคอมพิวเตอร์เกมมัลติมีเดีย
คณะเทคโนโลยีสารสนเทศ
มหาวิทยาลัยรังสิต

Leave a Reply