บทเรียนตัวอย่างการพัฒนาเกมบน iPhone ด้วย Template ตัว SpriteKit ร่วมกับภาษา Swift กับการใช้หลัก Physics เพื่อเรียนรู้การทำงานของเกม
ถ้าเทียบหลัก Physics ในเกมแล้วนักพัฒนาที่เคยเขียน Cocos2D หรือ Box2D มาจะรู้ว่ามันช่วยเหลือ และผ่อนแรงในแง่ของการคำนวณในส่วนของ Colision Detect และเรื่องของ Gravity ให้แต่แรก จนแทบไม่ต้องไปแตะ Code ยุ่งยากส่วนนั้นเลย
ตัวอย่างในบทนี้ก็มาจาก เว็บไซต์ Avionicsdev ที่อธิบายได้แบบสบาย แอดวานซ์นิดๆ แต่ไม่เกินความพยายามของนักพัฒนาครับ โดย concept การอธิบายนั้นคือการทำ วัตถุเทลงมาใน แก้ว ขวด หรือตระก้า ถ้าล้นก็จะทะลักออกมา ถ้าเทหรือเอียงมันก็จะไหลออกไปในตำแหน่งที่เอียงนั้นๆ ก็เลยลองทำดู คือ ตระกร้า กับ ผลส้ม
เริ่มต้น เตรียมกราฟิก PNG ของตระกร้า และ ส้มให้เรียบร้อยครับ
ทีนี้ ขั้นตอนทำ พื้นผิวสำหรับทำ Collision Detect เราต้อง Mark เส้นรอบส่วนที่ วัตถุที่หล่น หรือ ส้มนั้นชนแล้วเด้งออก ซึ่งตำแหน่งที่เด้งออกคือ ขอบตระกร้านั่นเอง ถ้าเก่งอยู่แล้ว คำนวณ เองเลย แต่ถ้าไม่เก่งก็แนะนำให้ไปที่เว็บไซต์
เพื่อไปสร้าง ขอบเขตของ Collision Detect ส่วนของพื้นผิว ถ้านึกไม่ออกว่าทำไม มีตัวอย่างครับ
ต่อจากนั้นให้ วาดรูปตามขอบของตระกร้า (ตอนแรกๆ จะงง แต่พอทำความเข้าใจจะรู้และว่า ทำยังไง ที่จะได้รูปแบบข้างล่างนะครับ
ระบบจะทำการ Generated ตัว code ของ Collision Detect ของเราให้ทันที
มันไม่ใช่ภาษา Swift ครับ มันเป็น Objective-C ดังนั้นโปรเจ็คนี้ต้องทำในภาษา Swift สิ่งที่คุณต้อง แก้ไขทันทีคือ แก้ Syntax จาก Objective-C ให้เป็น Swift ซะ
ในตัวอย่างผมได้ทำการแก้ไขแล้วจะได้ดังนี้ครับ
override func didMoveToView(view: SKView) { let sprite: SKSpriteNode = SKSpriteNode(imageNamed: "basket.png") sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)) let path: CGMutablePathRef = CGPathCreateMutable() MoveToPoint(path, x: 7, y: 64, node: sprite) AddLineToPoint(path, x: 0, y: 62, node: sprite) AddLineToPoint(path, x: 0, y: 50, node: sprite) AddLineToPoint(path, x: 4, y: 49, node: sprite) AddLineToPoint(path, x: 17, y: 8, node: sprite) AddLineToPoint(path, x: 25, y: 1, node: sprite) AddLineToPoint(path, x: 29, y: 0, node: sprite) AddLineToPoint(path, x: 82, y: 0, node: sprite) AddLineToPoint(path, x: 91, y: 9, node: sprite) AddLineToPoint(path, x: 105, y: 49, node: sprite) AddLineToPoint(path, x: 109, y: 53, node: sprite) AddLineToPoint(path, x: 109, y: 63, node: sprite) AddLineToPoint(path, x: 99, y: 65, node: sprite) AddLineToPoint(path, x: 88, y: 30, node: sprite) AddLineToPoint(path, x: 81, y: 13, node: sprite) AddLineToPoint(path, x: 79, y: 10, node: sprite) AddLineToPoint(path, x: 29, y: 9, node: sprite) CGPathCloseSubpath(path); sprite.physicsBody = SKPhysicsBody(polygonFromPath: path) sprite.physicsBody?.dynamic = false sprite.zPosition = 10 self.addChild(sprite) self.runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration(0.05), SKAction.runBlock(self.generateFluid)]))) sprite.runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration(10), SKAction.rotateByAngle(6.28318531, duration: 5.0)]))) }
ต่อมาก็ได้เวลา Code เกมแล้วล่ะครับ สร้าง New Project มาใหม่เป็น Template แบบ SpriteKit ครับ
นำภาพ orange.png และ basket.png ไปไว้ใน Bundle Project ของเราให้เรียบร้อย หลังจากนั้นเปิดไฟล์ GameScene.swift ขึ้นมาครับ
ทำการสร้างฟังกืชันของ Range ที่ผลส้มจะหล่นมาก่อน ซึ่งเราจะให้มันหล่นแบบเทกระจาด
import SpriteKit extension Float { static func range(min: CGFloat, max: CGFloat) -> CGFloat { return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min } }
ต่อมาเตรียมพร้อมให้กับผลส้มของเราโดย เพิ่ม เมธอดฟังก์ชันต่อไปนี้ ใน
class GameScene: SKScene { }
เพิ่มเข้าโดยเอา ส่วนของ Collision Detect ที่ทำไว้ใส่ลงไป ให้เป็นแบบนี้
class GameScene: SKScene { override func didMoveToView(view: SKView) { let sprite: SKSpriteNode = SKSpriteNode(imageNamed: "basket.png") sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)) let path: CGMutablePathRef = CGPathCreateMutable() MoveToPoint(path, x: 7, y: 64, node: sprite) AddLineToPoint(path, x: 0, y: 62, node: sprite) AddLineToPoint(path, x: 0, y: 50, node: sprite) AddLineToPoint(path, x: 4, y: 49, node: sprite) AddLineToPoint(path, x: 17, y: 8, node: sprite) AddLineToPoint(path, x: 25, y: 1, node: sprite) AddLineToPoint(path, x: 29, y: 0, node: sprite) AddLineToPoint(path, x: 82, y: 0, node: sprite) AddLineToPoint(path, x: 91, y: 9, node: sprite) AddLineToPoint(path, x: 105, y: 49, node: sprite) AddLineToPoint(path, x: 109, y: 53, node: sprite) AddLineToPoint(path, x: 109, y: 63, node: sprite) AddLineToPoint(path, x: 99, y: 65, node: sprite) AddLineToPoint(path, x: 88, y: 30, node: sprite) AddLineToPoint(path, x: 81, y: 13, node: sprite) AddLineToPoint(path, x: 79, y: 10, node: sprite) AddLineToPoint(path, x: 29, y: 9, node: sprite) CGPathCloseSubpath(path); sprite.physicsBody = SKPhysicsBody(polygonFromPath: path) sprite.physicsBody?.dynamic = false sprite.zPosition = 10 self.addChild(sprite) self.runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration(0.05), SKAction.runBlock(self.generateFluid)]))) sprite.runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration(10), SKAction.rotateByAngle(6.28318531, duration: 5.0)]))) } }
ต่อจากนั้นเพิ่ม เมธอดฟังก์ชันของการทำงาน เล็กน้อยสำหรับ ส้ม ให้ไหลล่วงหล่น โดยมีการเรียกฟังก์ชัน Range ในการร่วงไม่จบไม่สิ้นใน Class GameScene()
func generateFluid() { let sprite: SKSpriteNode = SKSpriteNode(imageNamed: "orange") sprite.position = CGPointMake(Float.range(CGRectGetMidX(self.frame) - 40, max: CGRectGetMidX(self.frame) + 40), CGRectGetMidY(self.frame) + 350) sprite.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width/2) self.addChild(sprite) sprite.runAction(SKAction.sequence([SKAction.waitForDuration(20), SKAction.removeFromParent()])) }
คราวนี้จะลองทำการเทตระกร้า โดยใช้ interval และการหมุนแกนตำแหน่งของการเทเพิ่มเข้ามาให้กับ Basket ของเรา ให้เพิ่มฟังก์ชันนี้ลงใน Class GameScene() เช่นกัน
func offset(node: SKSpriteNode, isX: Bool)->CGFloat { return isX ? node.frame.size.width * node.anchorPoint.x : node.frame.size.height * node.anchorPoint.y } func AddLineToPoint(path: CGMutablePath!, x: CGFloat, y: CGFloat, node: SKSpriteNode) { CGPathAddLineToPoint(path, nil, (x * 2) - offset(node, isX: true), (y * 2) - offset(node, isX: false)) } func MoveToPoint(path: CGMutablePath!, x: CGFloat, y: CGFloat, node: SKSpriteNode) { CGPathMoveToPoint(path, nil, (x * 2) - offset(node, isX: true), (y * 2) - offset(node, isX: false)) }
ภาพรวมของ Source Code ทั้งหมดของ GameScene.swift จะเป็นดังนี้
// // GameScene.swift // BasketSwift // // Created by DAYDEV on 10/9/2557 BE. // Copyright (c) 2557 DAYDEV. All rights reserved. // import SpriteKit extension Float { static func range(min: CGFloat, max: CGFloat) -> CGFloat { return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min } } class GameScene: SKScene { override func didMoveToView(view: SKView) { let sprite: SKSpriteNode = SKSpriteNode(imageNamed: "basket.png") sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)) let path: CGMutablePathRef = CGPathCreateMutable() MoveToPoint(path, x: 7, y: 64, node: sprite) AddLineToPoint(path, x: 0, y: 62, node: sprite) AddLineToPoint(path, x: 0, y: 50, node: sprite) AddLineToPoint(path, x: 4, y: 49, node: sprite) AddLineToPoint(path, x: 17, y: 8, node: sprite) AddLineToPoint(path, x: 25, y: 1, node: sprite) AddLineToPoint(path, x: 29, y: 0, node: sprite) AddLineToPoint(path, x: 82, y: 0, node: sprite) AddLineToPoint(path, x: 91, y: 9, node: sprite) AddLineToPoint(path, x: 105, y: 49, node: sprite) AddLineToPoint(path, x: 109, y: 53, node: sprite) AddLineToPoint(path, x: 109, y: 63, node: sprite) AddLineToPoint(path, x: 99, y: 65, node: sprite) AddLineToPoint(path, x: 88, y: 30, node: sprite) AddLineToPoint(path, x: 81, y: 13, node: sprite) AddLineToPoint(path, x: 79, y: 10, node: sprite) AddLineToPoint(path, x: 29, y: 9, node: sprite) CGPathCloseSubpath(path); sprite.physicsBody = SKPhysicsBody(polygonFromPath: path) sprite.physicsBody?.dynamic = false sprite.zPosition = 10 self.addChild(sprite) self.runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration(0.05), SKAction.runBlock(self.generateFluid)]))) sprite.runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration(10), SKAction.rotateByAngle(6.28318531, duration: 5.0)]))) } func generateFluid() { let sprite: SKSpriteNode = SKSpriteNode(imageNamed: "orange") sprite.position = CGPointMake(Float.range(CGRectGetMidX(self.frame) - 40, max: CGRectGetMidX(self.frame) + 40), CGRectGetMidY(self.frame) + 350) sprite.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width/2) self.addChild(sprite) sprite.runAction(SKAction.sequence([SKAction.waitForDuration(20), SKAction.removeFromParent()])) } func offset(node: SKSpriteNode, isX: Bool)->CGFloat { return isX ? node.frame.size.width * node.anchorPoint.x : node.frame.size.height * node.anchorPoint.y } func AddLineToPoint(path: CGMutablePath!, x: CGFloat, y: CGFloat, node: SKSpriteNode) { CGPathAddLineToPoint(path, nil, (x * 2) - offset(node, isX: true), (y * 2) - offset(node, isX: false)) } func MoveToPoint(path: CGMutablePath!, x: CGFloat, y: CGFloat, node: SKSpriteNode) { CGPathMoveToPoint(path, nil, (x * 2) - offset(node, isX: true), (y * 2) - offset(node, isX: false)) } }
ทดสอบการ Run ตัว Project เกม Swift ของเราหน่อย
ดาวน์โหลด Source code ได้ที่: https://www.daydev.com/download/BasketSwift.zip
ลองเอาไปทำกันดูนะครับทุกคนตัวอย่างเมืองนอกเค้าก็ทำ เข้าใจเหมือนกันนะ
One Comment