問題
你想讓模型能夠漂亮的加速而不是從靜止直接提高到最大速度。圖4-9顯示了你想實現的這種加速過程。
圖4-9 模型的速度時間映像
解決方案
通過添加加速度,你可以定義你的模型的速度變化有多快。加速度就是每幀速度的增加量。
工作原理
你需要儲存模型的位置和旋轉,這是因為你需要知道哪個方向是向前的。在本例中,你只是讓模型繞著它的向上的y軸旋轉並沿著X和Z軸移動。如果你想讓模型只基於X和Z軸沿著一個表面移動可參加教程4-17。
因為模型以後的速度取決於當前速度,所以你需要儲存當前速度。你將儲存速度向量而不是標量。這個速度向量包含模型當前前進的方向,它的長度表示速度的大小。所以在類中添加三個變數:
Vector3 modelPosition = new Vector3(); float modelYRot = 0; Vector3 modelVelocity= new Vector3();
你也可以定義模型的最大加速度和轉彎速度:
const float modelMaxAcceleration = 30.0f;const float modelMaxTurnSpeed = 0.002f;
在Update方法中接受自上一幀以來經過的時間為參數,檢測模型移動的距離:
float elapsedSeconds = (float)gameTime.ElapsedGameTime.Milliseconds / 1000.0f; float forwardReq= 0; float angleReq = 0; if (keyState.IsKeyDown(Keys.Up)) forwardReq += 1.0f; if(keyState.IsKeyDown(Keys.Down)) forwardReq -= 1.0f; if (keyState.IsKeyDown(Keys.Left))angleReq += 1.0f; if (keyState.IsKeyDown(Keys.Right)) angleReq -= 1.0f;
當模型向前加速時變數forwardReq為正,減速或向後加速時為負。變數angleReq表示模型左轉還是右轉。
在光滑表面上加速
下面的代碼添加基本加速行為:
Matrix rotMatrix = Matrix.CreateRotationY(angle); Vector3 forwardDir = Vector3.Transform(new Vector3(0, 0, -1), rotMatrix); velocity = velocity + elapsedTime * forwardReq *maxAccel *forwardDir; modelPosition += velocity; modelYRot += rotationReq * maxRotSpeed* velocity.Length();
前兩行代碼計算模型當前的Forward向量,這個向量用來獲得模型加速的方向。這個Forward向量是在向上y-軸上附加在預設的(0,0,-1) Forward方向上的旋轉獲得的。
注意:本教程中的模型只能繞y軸旋轉,所以Forward向量只是基於繞向上y軸的旋轉。可參加教程2-3和2-4學習計算整個3D空間或四元數的旋轉。
接下來的代碼計算速度。在前一個速度的基礎上基於使用者輸入添加一個新的向量。自上一幀經過的時間越長,越需要調整這個速度向量。另外,加速度越大,越需要調整這個速度向量。最後你還要考慮模型的最大加速度。將這三個因素相乘獲得這一幀需要調整多少速度。因為你需要將一個Vector3添加到速度向量上,要乘以帶有forwardDir的這個值擷取想要添加的向量。
注意:如果沒有旋轉,速度和moveDirection會指向相同的方向,只是簡單使速度向量變大讓模型移動地更快。
最後,這個Vector3添加到模型的位置,模型的旋轉被調整。模型運動得越快,轉向也越快。
當使用這個代碼時,你注意到有兩個缺點。首先,模型將一直加速,它沒有一個最大速度。你想4-9所示增加速度並終止於一個確定的最大速度。使用這個代碼,模型將一直以相同的步進加速。
第二,如果模型在一個方向上的速度很大,在旋轉模型後,它可能仍在相同的方向。如果模型是在一個冰面上當然不錯,但通常這並不是你想要的結果。
添加摩擦力
在真實情況中,模型的速度會因為模型與空氣、表面之間的摩擦等慢慢減小。當你停止加速,摩擦力會讓速度減小直至停止。當持續加速,摩擦力會導致速度增加到一個特定值不在增加。
你可以通過減去前面的速度擷取摩擦力。下面的代碼添加了摩擦力:
velocity = velocity * (1 - friction * elapsedTime) + elapsedTime * forwardReq *maxAccel *forwardDir;
兩幀之間的時間越長,摩擦力的效果越明顯,所以你要根據流逝時間乘以friction變數。
讓模型保持在向前方向運動
雖然模型現在已經以一個比較自然的方式加速了,但在它旋轉時仍有一點瑕疵(除非是在空間遊戲或冰面滑行遊戲中)。通常,你只想讓模型沿著前進方向移動。
你需要知道沿著Forward 方向上的速度向量是多少。這可以通過點乘得到:它將Velocity (V) 向量投影到Forward (F)上,4-10所示,並返回投影速度的大小。
圖4-10 將速度向量投影到Forward向量上
所以在更新速度後使用下列代碼:
float forwardSpeed = Vector3.Dot(velocity, forwardDir);velocity = forwardSpeed * forwardDir; modelPosition += velocity * elapsedTime; modelYRot+= rotationReq * maxRotSpeed * forwardSpeed;
forwardSpeed變數表示Velocity向量在Forward方向上分量的大小,然後乘以Forward方向並將結果儲存在一個新的Velocity向量中。通過這個方法,你可以保證模型將沿著向前方向移動。
注意:使用forwardSpeed變數的一個額外好處是當模型向前運動是它為正,向後運動時它為負,而velocity. Length ()總是正的。
代碼 Accelerate方法根據加速度調整模型的位置,速度和旋轉,將它們和最大加速度和旋轉速度相加。你還要擷取使用者輸入和摩擦力變數。
private float Accelerate(ref Vector3 position, ref float angle, ref Vector3 velocity,float forwardReq, float rotationReq, float elapsedTime, float maxAccel, float maxRotSpeed,float friction) { Matrix rotMatrix = Matrix.CreateRotationY(angle); Vector3 forwardDir= Vector3.Transform(new Vector3(0, 0, -1), rotMatrix); velocity = velocity * (1- friction * elapsedTime) + elapsedTime * forwardReq * maxAccel *forwardDir; loat forwardSpeed = Vector3.Dot(velocity, forwardDir); velocity = forwardSpeed * forwardDir; modelPosition += velocity * elapsedTime; modelYRot += rotationReq * maxRotSpeed* forwardSpeed; return forwardSpeed; }
這個方法返回forwardSpeed變數,若模型向後運動則為負值。
擴充閱讀
你可以通過允許乘以加速度施加在模型上擴充這個方法,例如,重力加速度,你可以加上這個加速度將總和作為forwardDir。