Sprite Kit Manual Movement(2)

There are two ways to make a sprite move in Sprite Kit. The first, which you might have noticed in the last chapter if you looked at the template code provided by Apple, is to use a concept calledactions. You’ll learn more about actions in the next chapter.


在Sprite Kit中有兩種方法來處理sprite的移動.第一個,在蘋果提供的模板代碼中所使用的概念—actions.下一章的時候再講…


The second way to make a sprite move is the more “classic” way – and that’s to set the position yourself manually over time.


第二種,比較傳統的方式~隨着時間不斷的手動設置sprite的位置來使其移動.


It’s important to learn this way first,because it gives you the most control and helps you understand what actions do foryou.


考慮到其超強的可操作性,同時還可以幫助你理解actions究竟都做了什麼,搞明白這種”笨”辦法還是很有必要的~


However, in order to set a sprite’s position over time, you need to have a method that is called periodically as the game runs. This introduces a new topic – the Sprite Kit game loop.


無論如何,爲了讓sprite動起來,遊戲運行的時候你都需要週期性的調用一個方法~這就引出了一個新的話題 — Sprite Kit 遊戲循環.


The Sprite Kit game loop


A game works like a flipbook animation. You draw a successive sequence of images, and when you flip through them fast enough, it gives the illusion of movement.


遊戲的工作原理類似翻圖動畫.畫上一堆連續的圖片,如果你快速的切換他們,就會有種動起來的效果.


Each individual picture that you draw is called a frame. Games typically try to draw frames between 30 to 60 times per second so that the animations feel smooth. This rate of drawing is called the frame rate, or specifically frames per second (FPS). By default, Sprite Kit shows you this in the bottom right corner of your game:


每一張單獨的圖片我們稱之爲一幀.通常來說,運行順暢的遊戲幀率在30到60之間(每秒幀數,FPS).默認情況下,SpriteKit會在屏幕的右下角顯示當前的幀率


Note that you should only pay attention to the FPS display on an actual device, as you’ll get very different performance on the Simulator.


需要注意的是,你緊緊需要注意在真機測試時的幀率,因爲其與模擬機的運行效率完全不同.


each frame, Sprite Kit does the following:


每顯示一幀,SpriteKit都會執行以下操作


Calls a method on your scene called update:. This is where you can put code that you want to run every frame. It’s a perfect spot for code that updates the position or rotation of your sprites.


調用場景中一個叫做update:的方法,這裏要編寫的是你希望每一幀都運行的代碼,非常適合更新sprite的位置和旋轉角度.


2. Does some other stuff. You’ll revisit the game loop in other chapters.


一些其他的東西,之後會講到


3. Renders the scene. Sprite Kit then draws all of the objects that are in your scene graph. Behind the scenes, this is issuing OpenGL draw commands for you.


演出開始!隨後SpriteKit會將所有的元素畫到場景上,底層則是通過OpenGL來幫助實現的.


Sprite Kit tries to draw frames as fast as possible, up to 60 FPS. However, note that if you take too long in your update: method, or if Sprite Kit has to draw more sprites at once than the hardware can handle, the frame rate might decrease.


SpriteKit會嘗試用最快速的速度來繪製每一幀,最快可以到達60FPS.需要注意的是,如果你在update:方法中消耗了太多的時間,或者SpriteKit需要同時繪製的內容太多,幀率都會下降.


You’ll learn more about ways to resolve performance issues like this in Chapter 25, “Performance”, but for now you just need to know two things:


在第25章—性能中將會詳細闡述提升效率的方法,但是就目前而言,記住這麼兩件事就夠了:


Keep your update: method fast. For example, you want to avoid slow algorithms in this method since it’s called each frame.


保持update:方法的高效,舉例來說,你會希望避免在繪製每一幀的時候調用運行速度過慢的算法.


2. Keep your count of nodes as low as possible. For example, it’s good to remove nodes from the scene graph when they’re off screen and you no longer need them.


儘量減少頁面上的節點.舉例來說,將移動到場景之外,而你有不在需要的節點刪掉會是一個不錯的選擇.


Now that you know that update: is called each frame and is a good spot to update the position of your sprites, let’s make this zombie move!


現在你知道了update:方法會在繪製每一幀的時候調用,也就使其成爲了調整sprite位置的最佳選擇~接下來,該讓殭屍動起來了!


Moving the zombie


To start, you’ll implement a simple but not ideal method: moving the zombie a fixed amount per frame.

我們將會以一個不那麼理想的方法開場:每一幀讓殭屍移動一個固定的距離.

每一幀移動固定距離


Inside MyScene.m, add the following method:


在MyScene.m文件中添加下列方法


  1. - (void)update:(NSTimeInterval)currentTime { 
  2.     _zombie.position = CGPointMake(_zombie.position.x +2, _zombie.position.y); 
- (void)update:(NSTimeInterval)currentTime {
	_zombie.position = CGPointMake(_zombie.position.x + 2, _zombie.position.y);
}


Here you update the position of the zombie to be two more points along the x-axis than last time, and keep the same position along the y-axis. This makes the zombie move from left to right.


這樣,每一次殭屍會在x軸上向右移動兩個點,而在y軸的位置不變,這讓殭屍從左到右移動.


This is great stuff, but you might notice that the movement feels a bit jagged or stuttered. The reason for this goes back to the Sprite Kit game loop.


感覺不錯,但你可能意識到了移動的過程有些許卡頓.原因在於SpriteKit的遊戲循環.


Remember that Sprite Kit tries to draw frames as quickly as possible. However, there will usually be some variance in the amount of time it takes to draw each frame: sometimes a bit longer, sometimes a bit quicker.


SpriteKit會嘗試用最快速度來繪製每一幀,但是通常來說,繪製每一幀的時間都不一樣,有時長一點,有時短一點.


To see this yourself, add some code to print out how much time has elapsed since the last update. Add these variables to MyScene’s private variables section


通過添加一段代碼可以看到這一區別,首先給MyScene添加兩個私有變量:


  1. NSTimeInterval _lastUpdateTime;  
  2. NSTimeInterval _dt; 
	NSTimeInterval _lastUpdateTime; 
	NSTimeInterval _dt;


Then add these lines to the beginning of update:


然後在update:方法開始處添加如下代碼:


  1. if (_lastUpdateTime) { 
  2.   _dt = currentTime - _lastUpdateTime; 
  3. } else
  4.   _dt = 0
  5. _lastUpdateTime = currentTime; 
  6. NSLog(@"%0.2f milliseconds since last update",_dt *1000); 
if (_lastUpdateTime) {
  _dt = currentTime - _lastUpdateTime;
} else {
  _dt = 0;
}
_lastUpdateTime = currentTime;
NSLog(@"%0.2f milliseconds since last update", _dt * 1000);


Build and run, and you’ll see something like this in the console:


運行程序,就會看到如下輸出內容:


ZombieConga[80642:70b] 0.00 milliseconds since last update

ZombieConga[80642:70b] 23.37 milliseconds since last update

ZombieConga[80642:70b] 16.88 milliseconds since last update

ZombieConga[80642:70b] 16.90 milliseconds since last update

ZombieConga[80642:70b] 16.17 milliseconds since last update

ZombieConga[80642:70b] 17.28 milliseconds since last update


The correct solution is to figure out how fast you want the zombie to move per second, and then multiply this by the fraction of a second since the last update. Let’s give this a shot.


解決這個問題的辦法是想清楚你希望殭屍每秒移動的距離,然後用它乘上上一次更新到現在的時間~放手一試~


通過時間增量來計算速率


Start by adding this constant to the top of the file, right after #import “MyScene.h”:


首先在#import “MyScene.h”下面添加一個常量:


  1. staticconstfloat ZOMBIE_MOVE_POINTS_PER_SEC =120.0
	static const float ZOMBIE_MOVE_POINTS_PER_SEC = 120.0;


Here you’re saying that in one second, the zombie should move 120 points (about 1/5 of the screen).


這裏確定了殭屍每秒鐘移動120個點,大約是屏幕的1/5


Next, add a new private variable:


接下來添加一個私有變量:


  1. CGPoint _velocity; 
	CGPoint _velocity;


So far you have seen CGPoints used to represent positions. However, it’s also quite common and handy to use CGPoints to represent 2D vectors instead.A 2D vector represents a direction and a length.


目前爲止,我們知道了CGPoint可以用來表示一個位置.除此之外,它也經常用來表示二維矢量.一個二維矢量可以確定方向和距離


However, note that the velocity has no set position. After all, you should be able to make the zombie move in that direction, at that speed, no matter where the zombie starts.


畢竟速率不能指明方向.當這些搞定之後,無論殭屍在哪,你都可以控制殭屍按照指定的方向,速度來移動了.


Try this out by adding the following new method:


添加下邊這段代碼,然後試一下:


  1. - (void)moveSprite:(SKSpriteNode *)sprite velocity:(CGPoint)velocity 
  2.     // 1 
  3.     CGPoint amountToMove = CGPointMake(velocity.x * _dt, velocity.y * _dt); 
  4.     NSLog(@"Amount to move: %@", NSStringFromCGPoint(amountToMove)); 
  5.     // 2 
  6.     sprite.position = CGPointMake(sprite.position.x + amountToMove.x, sprite.position.y + amountToMove.y); 
- (void)moveSprite:(SKSpriteNode *)sprite velocity:(CGPoint)velocity
{
    // 1
    CGPoint amountToMove = CGPointMake(velocity.x * _dt, velocity.y * _dt);
    NSLog(@"Amount to move: %@", NSStringFromCGPoint(amountToMove));
    // 2
    sprite.position = CGPointMake(sprite.position.x + amountToMove.x, sprite.position.y + amountToMove.y);
}


Here you’ve refactored the code into a reusable method that takes the sprite to be moved and a velocity vector by which to move it. Let’s go over this line-by-line:


這裏我們把負責處理sprite移動的代碼重成了一個可重用的方法,一行一行的來看一下:


1. Velocity is in points per second, and you need to figure out how much to move the zombie this frame. To determine that, this section multiplies the points per second by the fraction of seconds since the last update. You now have a point representing the zombie’s position (which you can also think of as a vector from the origin to the zombie’s position), and a vector representing the distance and direction to move the zombie this frame:


速率是每秒要移動的點數,你需要計算出當前的幀裏殭屍需要移動的距離.用之前聲明的每秒移動的點數乘以上次更新到現在所花的時間,就可以確定這個值.現在你有了在這一幀裏殭屍的原點和移動殭屍的矢量


2. To determine the new position for the zombie, just add the vector to the point:


把矢量的值加到原點上就是殭屍的目標位置了


Finally, inside update:, replace the line that sets the zombie’s position with the following:


最後,把update:方法中設置殭屍位置的方法替換成下面的內容:


  1. [self moveSprite:_zombie velocity:CGPointMake(ZOMBIE_MOVE_POINTS_PER_SEC,0)]; 
	[self moveSprite:_zombie velocity:CGPointMake(ZOMBIE_MOVE_POINTS_PER_SEC, 0)];


now the zombie will move much more smoothly across the screen.


現在殭屍移動變得平滑多了


If this still looks jittery to you, be sure to try it out on an actual device instead of on the Simulator, which has different performance characteristics.

如果現在看起來還是不夠流暢,試試真機測試,會有不一樣的性能表現.


向着觸點移動


The goal is for the zombie to move toward where you tap, and keep going even after passing the tap, until you tap another location to draw his attention.


這一節的目標是讓殭屍向着你在屏幕上點擊的位置移動,即便過了這個點,殭屍的方向和速度依然不會改變,直到你再次觸碰屏幕上的其他位置


There are four steps to make this work – let’s cover them one at a time.


我們分四步來實現這個功能,一個一個來~


Step 1: Find the offset vector

第一步:確定偏移量


First you need to figure out the offset between the location of the player’s tap and the location of the zombie. You can get this by simply subtracting the zombie’s position from the tap position.


首先,你需要確定殭屍位置到觸點的偏移量,簡單的用觸點的位置減去殭屍位置即可.


Try this out by adding the following method:


添加這段代碼:


  1. - (void)moveZombieToward:(CGPoint)location { 
  2.     CGPoint offset = CGPointMake(location.x - _zombie.position.x, location.y - _zombie.position.y); 
- (void)moveZombieToward:(CGPoint)location {
	CGPoint offset = CGPointMake(location.x - _zombie.position.x, location.y - _zombie.position.y);
}


Step 2: Find the length of the offset vector

第二步:確定偏移的距離


Think of the offset vector as the hypotenuse of a right triangle, where the lengths of the other two sides of the triangle are defined by the x and y components of the vector:


把偏移量想想成爲一個直角三角形的斜邊,這樣通過x和y就可以計算出偏移的距離了


Add the following line to the bottom of moveZombieToward:


在moveZombieToward:方法裏面添加這段代碼:


  1. CGFloat length = 
  2. sqrtf(offset.x * offset.x + offset.y * offset.y); 
CGFloat length =
sqrtf(offset.x * offset.x + offset.y * offset.y);


Step 3: Make the offset vector a set length

第三步:把偏移量轉化爲固定的距離


Currently, you have an offset vector where:

• The direction of the vector points toward where the zombie should go.

The length of the vector is the length of the line between the zombie’s current position and where the player taps.


現在,你有了殭屍移動方向的向量和殭屍當前位置於用戶觸點之間的距離.

What you want is a velocity vector where:

• The direction points toward where the zombie should go.

• The length is ZOMBIE_MOVE_POINTS_PER_SEC (the constant you defined earlier – 120 points per second).


而我們需要的則是用來表示殭屍移動方向的點以及殭屍每秒需要移動的距離


So you’re halfway there – your vector points in the right direction, but isn’t the right length. How do you make a vector pointing in the same direction as the offset vector, but a certain length?


所以我們已經成功了一半了,你的向量點所表示的方向是正確的,但是距離卻不正確.如何得到一個表示正確的方向與距離的點?


The first step is to convert the offset vector into a unit vector, which means a vector of length 1. According to geometry, you can do this by simply dividing the offset vector’s x and y components by the offset vector’s length.


第一步是把偏移向量轉化成爲單位向量,也就是說用1來表示一個完整單位的向量.根據幾何學,用便宜向量的x和y除以向量的長度即可.


Once you have this unit vector, which you know is length 1, it’s easy to multiply it by ZOMBIE_MOVE_POINTS_PER_SEC to make it the exact length you want.


一旦你得到了單位向量,用它乘以ZOMBIE_MOVE_POINTS_PER_SEC就可以得到你希望的長度.


Give it a try. Add the following lines to the bottom of moveZombieToward:


添加下面的代碼到moveZombieToward:方法試一下


  1. CGPoint direction = CGPointMake(offset.x / length, offset.y / length); 
  2. _velocity = CGPointMake(direction.x * ZOMBIE_MOVE_POINTS_PER_SEC, direction.y * ZOMBIE_MOVE_POINTS_PER_SEC); 
CGPoint direction = CGPointMake(offset.x / length, offset.y / length);
_velocity = CGPointMake(direction.x * ZOMBIE_MOVE_POINTS_PER_SEC, direction.y * ZOMBIE_MOVE_POINTS_PER_SEC);


Step 4: Hook up to touch events

第四步:關聯觸摸事件


In Sprite Kit, to get notifications of touch events on a node, you simply need to set that node’s userInteractionEnabled property to YES and then override that node’s touchesBegan:, touchesMoved: and/or touchesEnded: methods. Unlike other SKNode objects, SKScene’s userInteractionEnabled property is set to YES by default.


在SpriteKit中,只需要簡單的把userInteractionEnabled屬性設置爲YES,然後重寫節點的touchesBegan:,touchesMoved:,以及touchesEnded:(其實還有一個touchesCancelld:)方法,就可以截獲用戶觸摸事件.與其他SKNode不同的時,SKScene默認的userInteractionEnable就是YES.


To see it in action, implement these touch handling methods for MyScene as follows:


爲MyScene實現下列的觸摸響應方法,觀察效果:


  1. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  
  2.     UITouch *touch = [touches anyObject]; 
  3.     CGPoint touchLocation = [touch locationInNode:self.scene];  
  4.     [self moveZombieToward:touchLocation];  
  5. }  
  6. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {  
  7.     UITouch *touch = [touches anyObject]; 
  8.     CGPoint touchLocation = [touch locationInNode:self.scene];  
  9.     [self moveZombieToward:touchLocation];  
  10. }  
  11.  
  12. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {  
  13.     UITouch *touch = [touches anyObject]; 
  14.     CGPoint touchLocation = [touch locationInNode:self.scene];  
  15.     [self moveZombieToward:touchLocation];  
  16. }  
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInNode:self.scene]; 
    [self moveZombieToward:touchLocation]; 
} 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInNode:self.scene]; 
    [self moveZombieToward:touchLocation]; 
} 

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInNode:self.scene]; 
    [self moveZombieToward:touchLocation]; 
} 

Finally, inside update:, edit the call to moveSprite: so that it passes in _velocity instead of the hardcoded amount:


最後更新以下update:方法,調用moveSprite:方法,這樣就避免了在程序中使用硬編碼的問題.


  1. [self moveSprite:_zombie velocity:_velocity]; 
[self moveSprite:_zombie velocity:_velocity];


And now the zombie will chase toward where you tap. Just don’t get too close – he’s hungry!


現在殭屍就會朝着你的觸點方向移動了.記住不要靠的太近 — 丫就一吃貨!



邊界檢測


As you played the latest version of the game, you might have noticed that the zombie happily runs straight off the screen if you let him. While I admire his enthusiasm, in Zombie Conga you would like him to stay on the screen at all times, bouncing off an edge if he hits one.


在遊戲的上一個版本中,你應該意識到了這個殭屍會屁顛屁顛的跑到屏幕外邊去…好吧,我們對這個吃貨的熱情表示欽佩,但是在這個遊戲裏,我們希望它能始終待在屏幕裏面,如果”撞到南牆”,那就該回頭了.


The basic idea is this: you need to check if the newly calculated position is beyond any of the screen edges, and make the zombie bounce away if so. To do this, add this new method:


最基礎的方案是這樣的:如果在計算新的位置時發現他超出了屏幕範圍,就應該把殭屍彈開.添加下面的方法來實現這個效果:


  1. - (void)boundsCheckPlayer  
  2.     // 1 
  3.     CGPoint newPosition = _zombie.position; CGPoint newVelocity = _velocity; 
  4.     // 2 
  5.     CGPoint bottomLeft = CGPointZero; 
  6.     CGPoint topRight = CGPointMake(self.size.width,self.size.height); 
  7.     // 3 
  8.     if (newPosition.x <= bottomLeft.x)  
  9.     { 
  10.          newPosition.x = bottomLeft.x; newVelocity.x = -newVelocity.x
  11.     } 
  12.  
  13.     if (newPosition.x >= topRight.x)  
  14.     { 
  15.         newPosition.x = topRight.x; newVelocity.x = -newVelocity.x
  16.     } 
  17.  
  18.     if (newPosition.y <= bottomLeft.y)  
  19.     { 
  20.         newPosition.y = bottomLeft.y
  21.         newVelocity.y = -newVelocity.y;  
  22.     } 
  23.      
  24.     if (newPosition.y >= topRight.y)  
  25.     { 
  26.         newPosition.y = topRight.y; newVelocity.y = -newVelocity.y
  27.     } 
  28.     // 4 
  29.     _zombie.position = newPosition; 
  30.     _velocity = newVelocity;  
- (void)boundsCheckPlayer 
{
    // 1
    CGPoint newPosition = _zombie.position; CGPoint newVelocity = _velocity;
    // 2
    CGPoint bottomLeft = CGPointZero;
    CGPoint topRight = CGPointMake(self.size.width,self.size.height);
    // 3
    if (newPosition.x <= bottomLeft.x) 
    {
         newPosition.x = bottomLeft.x; newVelocity.x = -newVelocity.x;
    }

    if (newPosition.x >= topRight.x) 
    {
        newPosition.x = topRight.x; newVelocity.x = -newVelocity.x;
    }

    if (newPosition.y <= bottomLeft.y) 
    {
        newPosition.y = bottomLeft.y;
        newVelocity.y = -newVelocity.y; 
    }
    
    if (newPosition.y >= topRight.y) 
    {
        newPosition.y = topRight.y; newVelocity.y = -newVelocity.y;
    }
    // 4
    _zombie.position = newPosition;
    _velocity = newVelocity; 
}


Let’s go over this section-by-section:


一段一段來分析:


You store the position and velocity in variables. This is required because when you set the position property on a node, you can’t just set one component – you have to set the entire position in one shot,.For example, _zombie.position = CGPointMake(100, 100) is OK, but _zombie.position.x = 100 will result in a compiler error. Making a temporary CGPoint works around this.


我們把位置和速率屬性保存在變量裏.這是必須的,因爲你在設置位置屬性的時候不能夠僅僅設置一個元素,你需設置整個positon.舉例來說,_zombie.position = CGPointMake(100, 100)這是可以的,但是_zombie.position.x = 100就會產生一個編譯器錯誤.


2. This gets the bottom left and top right coordinates of the screen.


這一步獲取了屏幕的屏幕左下角和右上角的座標.


3. Here you check the position to see if it’s beyond or at any of the screen edges. If it is, you clamp the position and reverse the appropriate velocity component to make the zombie bounce in the opposite direction.


這裏則檢查了殭屍的位置是否超過了屏幕的某一個邊界.如果超過了,就讓殭屍定在哪裏,然後把適當的速率元素反轉來把殭屍彈向相反的方向


4. You set the zombie to the new position.


把殭屍設置到新的位置上


Now call your new method at the end of update:


現在在update:方法的最後調用我們新添加的方法:


  1. [self boundsCheckPlayer]; 
[self boundsCheckPlayer];


And now you have a zombie bouncing around the screen. I told you he was ready to party!


現在殭屍已經可以在屏幕裏面玩彈球了~我早就說它已經準備好參加之後的派對了!


旋轉殭屍


The zombie is moving nicely, but he always faces the same direction. Granted, he is undead, but this zombie is on the curious side and would like to turn to see where he’s going!


雖然殭屍已經移動的很好了,但頭疼的是它始終面朝同一個方向.雖然他已經死了,但它依然對前方的道路充滿好奇~


You already have a vector that includes the direction the zombie is facing: _velocity. You just need to get the angle to rotate so that the zombie faces in that direction.


你已經有了殭屍移動方向的向量:_velocity.只需要確定以下需要旋轉的角度,殭屍就會朝向正確的方向了.


You may remember from trigonometry the mnemonic SOH CAH TOA, where the last part stands for:


你可能還記得那些三角定理(完全忘了),最後一部分是:


tan(angle) = opposite / adjacent

正切(角度) = 對邊 / 鄰邊


Since you have the lengths of the opposite and adjacent sides, you can rewrite the

above formula as follows to get the angle of rotation:


既然我們已經有了對邊和鄰邊的長度,我們可以簡單的把上邊的公式改寫以下就可以得到需要旋轉的角度了:


angle = arctan(opposite / adjacent)

角度 = 反正切(對邊 / 鄰邊)


If none of this trigonometry rings any bells, don’t worry. Just think of it as a formula that you type in to get the angle – that’s all you need to know.


如果對這些東西完全不靈光…沒事,知道通過這玩意兒能得到角度就行了...


Try it out by adding the following new method:


添加這麼個方法試試:


  1. - (void)rotateSprite:(SKSpriteNode *)sprite toFace:(CGPoint)direction 
  2.     sprite.zRotation = atan2f(direction.y, direction.x); 
- (void)rotateSprite:(SKSpriteNode *)sprite toFace:(CGPoint)direction
{
    sprite.zRotation = atan2f(direction.y, direction.x);
}


This just uses the equation from above. Note that this works because the zombie image is set up to be facing to the right. If the zombie were facing up instead, you’d have to add an additional rotation to compensate.


這裏就是對上面公式的應用.需要注意的是,由於殭屍默認就面朝右邊代碼才能正常工作.如果殭屍一開始臉是朝上的,那就需要額外旋轉一定角度.(怎麼算就自己研究去吧…)


Now call this new method at the end of update:


現在,在update:方法的最後調用這個方法:


  1. [self rotateSprite:_zombie toFace:_velocity]; 
[self rotateSprite:_zombie toFace:_velocity];


And now the zombie rotates to face the direction in which he’s moving:


現在,殭屍就可以轉向它移動的方向了.


Congratulations, you’ve given your zombie some life! The sprite moves smoothly, bounces off the edges of the screen and rotates – a great start to a game.


可喜可賀,你賦予了殭屍新生!我們的sprite移動的非常平滑,會被限制在屏幕範圍只能,同時還會轉身 — 漂亮的邁出了通向遊戲世界的第一步!


But you’re not done yet – it’s time for you to try out some of this on your own to

make sure you’ve got this stuff down!


但是別高興的太早,是你自己實現點什麼東西來確保你真的學會這些了.


挑戰


This chapter has three challenges, and they’re particularly important ones. Performing these challenges will give you useful practice with vector math and introduce some new math utilities you will be using throughout the rest of the book.


這一章有3個挑戰,都很重要!搞定他們可以幫助你練習關於向量的數學知識,同時還將介紹一些剩下的章節中將要使用的數學工具.


Challenge 1: Math utilities

挑戰1:數學工具


As you may have noticed while working on this game, you frequently have to perform calculations on points and vectors: adding and subtracting points, finding lengths, and so on.


在這款遊戲的開發過程中,我們要對點和向量進行頻繁的運算,加,減,求距離等等.


So far in this chapter, you’ve done this all yourself inline. That’s a fine way of doing things, but can get tedious and repetitive in practice. It’s also error-prone.


目前爲止,這些都是我們通過編碼手動實現的.這對於解決問題是非常不錯的辦法,但是對於這些枯燥乏味的工作難免感到厭倦,而且也容易寫錯.


Open MyScene.m and add the following functions to your file, right after #import “MyScene.h”:


打開MyScene.m文件,在#import “MyScene.h”下面添加這麼幾個函數


  1. staticinline CGPoint CGPointAdd(const CGPoint a,const CGPoint b) 
  2.     return CGPointMake(a.x + b.x, a.y + b.y); 
  3.  
  4. staticinline CGPoint CGPointSubtract(const CGPoint a,const CGPoint b) 
  5.     return CGPointMake(a.x - b.x, a.y - b.y); 
  6.  
  7. staticinline CGPoint CGPointMultiplyScalar(const CGPoint a,const CGFloat b) 
  8.     return CGPointMake(a.x * b, a.y * b); 
  9.  
  10. staticinline CGFloat CGPointLength(const CGPoint a)  
  11.     return sqrtf(a.x * a.x + a.y * a.y);  
  12.  
  13. staticinline CGPoint CGPointNormalize(const CGPoint a)  
  14.     CGFloat length = CGPointLength(a); 
  15.     return CGPointMake(a.x / length, a.y / length);  
  16.  
  17. staticinline CGFloat CGPointToAngle(const CGPoint a)  
  18.     return atan2f(a.y, a.x);  
static inline CGPoint CGPointAdd(const CGPoint a, const CGPoint b)
{
    return CGPointMake(a.x + b.x, a.y + b.y);
}

static inline CGPoint CGPointSubtract(const CGPoint a,const CGPoint b)
{
    return CGPointMake(a.x - b.x, a.y - b.y);
}

static inline CGPoint CGPointMultiplyScalar(const CGPoint a, const CGFloat b)
{
    return CGPointMake(a.x * b, a.y * b);
}

static inline CGFloat CGPointLength(const CGPoint a) 
{
    return sqrtf(a.x * a.x + a.y * a.y); 
}

static inline CGPoint CGPointNormalize(const CGPoint a) 
{
    CGFloat length = CGPointLength(a);
    return CGPointMake(a.x / length, a.y / length); 
}

static inline CGFloat CGPointToAngle(const CGPoint a) 
{
    return atan2f(a.y, a.x); 
}


These are helper functions that implement many of the operations you’ve been working with already. For example, look at moveSprite:velocity::


這些都是對我們要處理的運算非常有用的方法,舉個例子,看一下moveSprite:velocity:方法:


  1. - (void)moveSprite:(SKSpriteNode *)sprite velocity:(CGPoint)velocity 
  2.     // 1 
  3.     CGPoint amountToMove = CGPointMake(velocity.x * _dt, velocity.y * _dt); 
  4.     NSLog(@"Amount to move: %@", NSStringFromCGPoint(amountToMove)); 
  5.     // 2 
  6.     _zombie.position
  7.     CGPointMake(_zombie.position.x + amountToMove.x
  8.     _zombie.position.y + amountToMove.y); 
- (void)moveSprite:(SKSpriteNode *)sprite velocity:(CGPoint)velocity
{
    // 1
    CGPoint amountToMove = CGPointMake(velocity.x * _dt, velocity.y * _dt);
    NSLog(@"Amount to move: %@", NSStringFromCGPoint(amountToMove));
    // 2
    _zombie.position =
    CGPointMake(_zombie.position.x + amountToMove.x,
    _zombie.position.y + amountToMove.y);
}


You could simplify the first line by calling CGPointMultiplyScalar, and you could simplify the second line by calling CGPointAdd.


你可以簡單的調用CGPointMultiplyScalar來重寫第一行,調用CGPointAdd來重寫第二行


Your challenge is to modify the game to use these new routines, and verify that the game still works as expected.


這裏你的挑戰是用這些方法來重構你的遊戲,並且要保證遊戲仍然正常運轉.


Challenge 2: Stop that zombie!

挑戰2:讓他停下來!


In Zombie Conga, when you tap the screen the zombie moves toward that point – but then continues to move beyond that point.


在Zombie Conga中,當你點擊了屏幕之後,殭屍會一直朝着那個點移動,但是當到達了那裏之後它仍然會繼續向前走.


That is the behavior you want for Zombie Conga, but in some games you might want the zombie to stop where you tap. Your challenge is to modify the game to do this.


這是在這款遊戲中我們所需要的效果,但是有些遊戲中,你可能希望殭屍停在你點擊的地方.你的挑戰就是實現這一效果.


Challenge 3: Smooth moves

挑戰3:平滑的動作

Currently, the zombie immediately rotates to face where you tap. This can be a bit jarring – it would be nicer if the zombie would smoothly rotate over time to face the new direction.


現在你的殭屍會立即轉向你點擊的位置.這樣就略顯突兀了,如果能夠慢慢的轉向那個方向會好很多.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章