> 原文:http://www.jianshu.com/p/f1174b54dad3
> 作者:[PMST](http://www.jianshu.com/users/596f2ba91ce9/latest_articles)
游戲的雛形已經基本實現,呈現了背景,地面持續滾動,*Player*上下跳竄以及源源不斷的仙人掌。不過細心的你也應當發現有以下幾個不足:
1. Player可以通過不斷點擊升高到屏幕外。
2. 仙人掌表示不服:你丫想穿越我就穿越,當我是透明嗎?
因此本節的任務是設置場景精靈的物理體,當課時完畢,*Player*一旦觸碰到仙人掌就會下落,不能繼續游戲。
## 01.設置場景內精靈的物體形狀
暫且對游戲內容按下不表,先談談咱們真實的世界,重力加速度9.8g,非透明的物體之間碰撞會發生形變。而在*Sprite Kit*中的物理世界,首先我需要引出*Physics Shapes*?—— 物體形狀,就拿人來說,倘若我粗略地來形容一個人的物理體,我就會給出一個`x*y*z`(長寬高計算體積)的長方體,一旦外物觸碰輪廓表面,我就說兩者發生了接觸;不過若已精確角度來說,形容人的物理體以其皮膚表面為輪廓勾勒出一個體積,顯然這比先前的立方體來的精確太多了;當然有時候嫌麻煩,指定頭部(姑且就當成一個球體吧)作為人的物理體,因此除頭部外的身體都相當于是透明的,外物接觸了手、腿等都不算發生接觸,只有與頭部接觸才算。
講了那么多,現在回到游戲,開始塑造真實的物理世界,首先找到`didMoveToView()`方法,在最上方添加一行代碼設置場景物理世界的重力為(0,0),原因是我們打算使用自定義設置的參數:
~~~
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
//... 以下為早前內容
}
~~~
接著咱們要說**physicsBody**,譯為物理體。我們可以設置每個節點的物理體,那樣它就可以和其他同樣設置了物理體的節點發生碰撞、檢測接觸等,它有三個屬性,值均為UInt32類型:
* *categoryBitMask*: 表明當前body屬于哪個類別。
* *collisionBitMask*: 當前物體可以與哪些類別發生碰撞。
* *contactTestBitMask*:用于告知當前物體與哪些類別物理發生接觸時。
游戲中類似這種,我們往往用二進制數來表示物體,譬如*0b1*表明是*Player*,*0b10*表明障礙物,*0b100*表明地面。想必編程男都不陌生吧。OK,請在`enum Layer:CGFLoat{}`下方新增一個結構體用于表明分類,注意里面均為類型屬性:
~~~
struct PhysicsCategory {
static let None: UInt32 = 0
static let Player: UInt32 = 0b1 // 1
static let Obstacle: UInt32 = 0b10 // 2
static let Ground: UInt32 = 0b100 // 4
}
~~~
對于類型屬性,調用方法形如:`PhysicsCategory.None`,更多關于類型屬性,請參看官方文檔*Type properties*一節。
接下來我們主要添加以下物理體到場景中:
1. Player,這里我們將借助一個勾勒工具來繪制其物理體。
2. 障礙物,同上。
3. 地面,其實就是一條水平線。
為啥要設置以上三個物理體呢?因為設置完物理體后,我們才能知道誰和誰發生了接觸*contact*,如此進行下一步計算。至于`collision`咱們是不關心的,不需要設置。
## 設置地面的物理體
找到`setupBackground()`方法 在方法最下方添加如下內容:
~~~
func setupBackground(){
//...
//===以上為早前內容===
//===以下為新增內容===
let lowerLeft = CGPoint(x: 0, y: playableStart)//地板表面的最左側一點
let lowerRight = CGPoint(x: size.width, y: playableStart) //地板表面的最右側一點
// 1
self.physicsBody = SKPhysicsBody(edgeFromPoint: lowerLeft, toPoint: lowerRight)
self.physicsBody?.categoryBitMask = PhysicsCategory.Ground
self.physicsBody?.collisionBitMask = 0
self.physicsBody?.contactTestBitMask = PhysicsCategory.Player
}
~~~
對于1中,我們用一條平行線來實例化物理體,然后是三部曲,分別設置了其分類為*Ground*;不予其他任何物理發生碰撞(因為設置了0);設置了能與其發生接觸的物體有*Player*。
## 設置Player的物理體
找到`setupPlayer()`方法 同樣新增以下內容到方法最后:
~~~
func setupPlayer(){
player.position = CGPointMake(size.width * 0.2, playableHeight * 0.4 + playableStart)
player.zPosition = Layer.Player.rawValue
// 注意我們將worldNode.addChild(player)移到了最下方。
//=========以下為新增內容===========
let offsetX = player.size.width * player.anchorPoint.x
let offsetY = player.size.height * player.anchorPoint.y
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, 17 - offsetX, 23 - offsetY)
CGPathAddLineToPoint(path, nil, 39 - offsetX, 22 - offsetY)
CGPathAddLineToPoint(path, nil, 38 - offsetX, 10 - offsetY)
CGPathAddLineToPoint(path, nil, 21 - offsetX, 0 - offsetY)
CGPathAddLineToPoint(path, nil, 4 - offsetX, 1 - offsetY)
CGPathAddLineToPoint(path, nil, 3 - offsetX, 15 - offsetY)
CGPathCloseSubpath(path)
player.physicsBody = SKPhysicsBody(polygonFromPath: path)
player.physicsBody?.categoryBitMask = PhysicsCategory.Player
player.physicsBody?.collisionBitMask = 0
player.physicsBody?.contactTestBitMask = PhysicsCategory.Obstacle | PhysicsCategory.Ground
worldNode.addChild(player)// hey 我現在在這里!!!!
}
~~~
我們通過繪制路徑來勾勒出*Player*的自定義物理體,別吃驚,我只不過借助了某些工具,地址在[這里](http://stackoverflow.com/questions/19040144/spritekits-skphysicsbody-with-polygon-helper-tool),ps:可能需要翻墻。
## 設置仙人掌的物理體
同理我們只需要在產生仙人掌的實例方法中添加其物理體即可,請定位到`createObstacle()->SKSpriteNode`方法:
~~~
func createObstacle() -> SKSpriteNode {
let sprite = SKSpriteNode(imageNamed: "Cactus")
sprite.zPosition = Layer.Obstacle.rawValue
//========以下為新增內容=========
let offsetX = sprite.size.width * sprite.anchorPoint.x
let offsetY = sprite.size.height * sprite.anchorPoint.y
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, 3 - offsetX, 0 - offsetY)
CGPathAddLineToPoint(path, nil, 5 - offsetX, 309 - offsetY)
CGPathAddLineToPoint(path, nil, 16 - offsetX, 315 - offsetY)
CGPathAddLineToPoint(path, nil, 39 - offsetX, 315 - offsetY)
CGPathAddLineToPoint(path, nil, 51 - offsetX, 306 - offsetY)
CGPathAddLineToPoint(path, nil, 49 - offsetX, 1 - offsetY)
CGPathCloseSubpath(path)
sprite.physicsBody = SKPhysicsBody(polygonFromPath: path)
sprite.physicsBody?.categoryBitMask = PhysicsCategory.Obstacle
sprite.physicsBody?.collisionBitMask = 0
sprite.physicsBody?.contactTestBitMask = PhysicsCategory.Player
return sprite
}
~~~
注意到不管是哪種方式設置物理體,我們都需要設置其分類,碰撞掩碼以及測試接觸掩碼,不過這里我們并不需要碰撞,所以全部設為0,即None。
最后請點擊運行,你會發現場景中的*Player*、*仙人掌*以及地面表層都有一層輪廓。沒錯!這就是其各自的物理體。我們在*GameViewCOntroller*中通過設置了`skView.showsPhysics = true`來顯示的。
