Article for GamerDeveloperFeaturedGame DevelopmentGame DevelopmentiOS DeveloperProgramming LanguageSwift

เขียนเกม iPhone ด้วย SpriteKit ภาษา Swift กับการใช้หลัก Physics

บทเรียนตัวอย่างการพัฒนาเกมบน iPhone ด้วย Template ตัว SpriteKit ร่วมกับภาษา Swift กับการใช้หลัก Physics เพื่อเรียนรู้การทำงานของเกม

ถ้าเทียบหลัก Physics ในเกมแล้วนักพัฒนาที่เคยเขียน Cocos2D หรือ Box2D มาจะรู้ว่ามันช่วยเหลือ และผ่อนแรงในแง่ของการคำนวณในส่วนของ Colision Detect และเรื่องของ Gravity ให้แต่แรก จนแทบไม่ต้องไปแตะ Code ยุ่งยากส่วนนั้นเลย

ตัวอย่างในบทนี้ก็มาจาก เว็บไซต์ Avionicsdev ที่อธิบายได้แบบสบาย แอดวานซ์นิดๆ แต่ไม่เกินความพยายามของนักพัฒนาครับ โดย concept การอธิบายนั้นคือการทำ วัตถุเทลงมาใน แก้ว ขวด หรือตระก้า ถ้าล้นก็จะทะลักออกมา ถ้าเทหรือเอียงมันก็จะไหลออกไปในตำแหน่งที่เอียงนั้นๆ ก็เลยลองทำดู คือ ตระกร้า กับ ผลส้ม

เริ่มต้น เตรียมกราฟิก PNG ของตระกร้า และ ส้มให้เรียบร้อยครับ

ส้ม
ส้ม
ตระกร้า
ตระกร้า

ทีนี้ ขั้นตอนทำ พื้นผิวสำหรับทำ Collision Detect เราต้อง Mark เส้นรอบส่วนที่ วัตถุที่หล่น หรือ ส้มนั้นชนแล้วเด้งออก ซึ่งตำแหน่งที่เด้งออกคือ ขอบตระกร้านั่นเอง ถ้าเก่งอยู่แล้ว คำนวณ เองเลย แต่ถ้าไม่เก่งก็แนะนำให้ไปที่เว็บไซต์

http://dazchong.com/spritekit

เพื่อไปสร้าง ขอบเขตของ Collision Detect ส่วนของพื้นผิว ถ้านึกไม่ออกว่าทำไม มีตัวอย่างครับ

วางตระกร้าลงไป
วางตระกร้าลงไป

ต่อจากนั้นให้ วาดรูปตามขอบของตระกร้า (ตอนแรกๆ จะงง แต่พอทำความเข้าใจจะรู้และว่า ทำยังไง ที่จะได้รูปแบบข้างล่างนะครับ

วาดผิดก็วาดใหม่นะ
วาดผิดก็วาดใหม่นะ

ระบบจะทำการ Generated ตัว code ของ Collision Detect ของเราให้ทันที

มันภาษา Swift?
มันภาษา Swift?

มันไม่ใช่ภาษา 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 ครับ

สร้าง Template SpriteKit ขึ้นมา
สร้าง Template SpriteKit ขึ้นมา
เลือก Swift นะ ดูดีห้ามเลือก SceneKit
เลือก Swift นะ ดูดีห้ามเลือก SceneKit

นำภาพ 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

ลองเอาไปทำกันดูนะครับทุกคนตัวอย่างเมืองนอกเค้าก็ทำ เข้าใจเหมือนกันนะ

Asst. Prof. Banyapon Poolsawas

อาจารย์ประจำสาขาวิชาการออกแบบเชิงโต้ตอบ และการพัฒนาเกม วิทยาลัยครีเอทีฟดีไซน์ & เอ็นเตอร์เทนเมนต์เทคโนโลยี มหาวิทยาลัยธุรกิจบัณฑิตย์ ผู้ก่อตั้ง บริษัท Daydev Co., Ltd, (เดย์เดฟ จำกัด)

Related Articles

Back to top button

Adblock Detected

เราตรวจพบว่าคุณใช้ Adblock บนบราวเซอร์ของคุณ,กรุณาปิดระบบ Adblock ก่อนเข้าอ่าน Content ของเรานะครับ, ถือว่าช่วยเหลือกัน