現在我們開始給忍者添加一些動作,首先從發射炮彈開始!實際上有多種方法來實現炮彈的發射,不過,在這裏要實現的方法時當用戶tap屏幕時,從忍者的方位到tap的方位發射一顆炮彈。
由於本文是針對初級開發者,所以在這裏我使用moveTo:
動作來實現,不過這需要做一點點的數學運算——因爲moveTo:
方法需要指定炮彈的目的地,但是又不能直接使用touch
point(因爲touch point僅僅代表需要發射的方向)。實際上我們需要讓炮彈穿過touch point,直到炮彈在屏幕中消失。
如下圖,演示了上面的相關內容:
如圖所示,我們可以通過origin point到touch point得到一個小的三角形。我們要做的就是根據這個小三角形的比例創建出一個大的三角形——而你知道你想要的一個端點是離開屏幕的地方。
爲了做這個計算,如果有一些基本的矢量方法可供調用(例如矢量的加減法),那麼會非常有幫助,但很不幸的時Sprite Kit並沒有提供相關方法,所以,我們必須自己實現。
不過很幸運的時這非常容易實現。將下面的方法添加到文件的頂部(implementation之前):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static inline CGPoint rwAdd(CGPoint a, CGPoint b) {
return CGPointMake(a.x + b.x, a.y + b.y);
}
static inline CGPoint rwSub(CGPoint a, CGPoint b) {
return CGPointMake(a.x - b.x, a.y - b.y);
}
static inline CGPoint rwMult(CGPoint a, float b) {
return CGPointMake(a.x * b, a.y * b);
}
static inline float rwLength(CGPoint a) {
return sqrtf(a.x * a.x + a.y * a.y);
}
// Makes a vector have a length of 1
static inline CGPoint rwNormalize(CGPoint a) {
float length = rwLength(a);
return CGPointMake(a.x / length, a.y / length);
}
|
上面實現了一些標準的矢量函數。如果你看得不是太明白,請看這裏關於矢量方法的解釋。
接着,在文件中添加一個新的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 1 - Choose one of the touches to work with
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
// 2 - Set up initial location of projectile
SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:@"projectile"];
projectile.position = self.player.position;
// 3- Determine offset of location to projectile
CGPoint offset = rwSub(location, projectile.position);
// 4 - Bail out if you are shooting down or backwards
if (offset.x <= 0) return;
// 5 - OK to add now - we've double checked position
[self addChild:projectile];
// 6 - Get the direction of where to shoot
CGPoint direction = rwNormalize(offset);
// 7 - Make it shoot far enough to be guaranteed off screen
CGPoint shootAmount = rwMult(direction, 1000);
// 8 - Add the shoot amount to the current position
CGPoint realDest = rwAdd(shootAmount, projectile.position);
// 9 - Create the actions
float velocity = 480.0/1.0;
float realMoveDuration = self.size.width / velocity;
SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
[projectile runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];
}
|
上面的代碼中做了很多事情,我們來詳細看看。
- SpriteKit爲我們做了很棒的一件事情就是它提供了一個UITouch的category,該category中有
locationInNode:
和previousLocationInNode:
方法。這兩個方法可以幫助我們定位到在SKNode內部座標系中touch的座標位置。這樣一來,我們就可以尋得到在場景座標系中touch的位置。 - 然後創建一個炮彈,並將其放置到忍者的地方,以當做其開始位置。注意,現在還沒有將其添加到場景中,因爲還需要先做一個合理性的檢查——該遊戲不允許忍者向後發射。
- 接着利用touch位置減去炮彈的當前位置,這樣就能獲得一個從當前位置到touch位置的矢量。
- 如果X值小於0,就意味着忍者將要向後發射,由於在這裏的遊戲中是不允許的(真實中的忍者是不回頭的!),所以就return。
- 否則,將可以將炮彈添加到場景中。
- 調用方法
rwNormalize
,將offset轉換爲一個單位矢量(長度爲1)。這樣做可以讓在相同方向上,根據確定的長度來構建一個矢量更加容易(因爲1
* length = length)。 - 在單位矢量的方向上乘以1000。爲什麼是1000呢?因爲着肯定足夠超過屏幕邊緣了 :]
- 將上一步中計算得到的位置與炮彈的位置相加,以獲得炮彈最終結束的位置。
- 最後,參照之前構建怪物時的方法,創建
moveTo:
和removeFromParent:
兩個actions。
編譯並允許程序,現在忍者可以發射炮彈了!