自幼時玩過迷魂車之後,心中就埋下了一個製作開車遊戲的夢想。不過長大後開始寫遊戲,卻一直沒花時間想通這個四輪車前輪轉後輪不轉的運動軌跡,到底要如何不藉助物理引擎的幫助就能模擬出來。
如果你也跟我一樣在心中有著這個困擾,那就來對地方了。請接著看下去,跟著我一解幼時的疑惑吧。
經典的迷魂車其實並沒有解決這個四輪車的問題,迷魂車在轉彎時,只是以中心為軸旋轉至前進的方向而己。
這樣的設計符合迷魂車容易操縱的遊戲需求,但如果我們要更貼近真實四輪車的運動軌跡,就不能用這麼簡單的方法來處理了。
合理的輪胎軌跡應該會在輪胎正後方產生一條同向的胎痕,但是在迷魂車的後輪上,我們看到後輪後方的胎痕和後輪的方向並不一致,這就是迷魂車開起來不像真正四輪車的原因。
這個演算法講開了其實很簡單,我們在每幀將車子依方向盤及車速移動的時候,依以下四個步驟逐步操作,
我們用TypeScript來寫個示意程式碼,首先定義一下車子需要的屬性。
class Car {
/** 車子的中心位置,用一個 Point 物件來表示。
* Point是一個向量的類別,裏面除了x和y,還有一些向量的運算函式。
* 今天我們只會用到以下兩個函式,往後的日子會有專門的文章解說向量。
* - Point.add(other): 兩個向量相加
* - Point.sub(other): 兩個向量相減
*/
position = new Point();
/** 車體的旋轉角度 */
rotation = 0;
/** 方向盤的角度,也就是前輪的旋轉角度 */
steerAngle = 0;
/** 兩個前輪中心相對於車子中心的相對位置 */
headCenter = new Point(8, 0);
/** 兩個後輪中心相對於車子中心的相對位置 */
tailCenter = new Point(-10, 0);
/** 車子速度 */
speed = 1;
}
/** 新增一個車子的實體,方便等一下講解 */
let car = new Car();
Point是CG的基本模組提供的向量類別,原始碼在這兒,小哈往後幾日會再專門寫文章介紹向量各種運算的幾何意義。
接著假設我們把方向盤轉了一個角度,再套用四輪車演算法,來計算更新後車子的新位置與新角度。
/** 轉動方向盤,這邊設定的0.52單位是弧度,一般程式中都是使用弧度來計算
* 180度大約等於3.14的弧度。
*/
car.steerAngle = 0.52;
/** 步驟(1)是要定義車體的結構,這個我們在定義Car時已經完成了 */
/** 步驟(2): 依方向盤角度以及車子速度移動前輪中心 */
// 首先找到前輪中心原本的絕對位置
let headCenterOld = car.position.add(car.headCenter);
// 用極座標算出前輪移動的向量
let moveVector = Point.polar(car.speed, car.steerAngle);
// 計算移動完成後前輪中心的位置
headCenterNew = headCenterOld.add(moveVector);
/** 步驟(3): 計算更新後的車體角度(也就是後輪角度) */
// 先找到後輪中心的絕對位置
let tailCenterOld = car.position.add(car.tailCenter);
// 將前後輪中心連成一個向量
let carVector = headCenterNew.sub(tailCenterOld);
// 計算後輪轉向新的前輪中心後的新角度
car.rotation = Math.atan2(carVector.y, carVector.x);
/** 步驟(4): 將車子拉向前輪新的位置 */
// 將前後輪連成的向量縮小至前輪中心離車子中心的距離
carVector.normalize(car.headCenter.length);
// 車體中心應該就等於新的前輪中心減去carVector
car.position = headCenterNew.sub(carVector);
如此一來,車子就被更新到新的位置和角度,持續用這個方法計算車子每一幀的位置和角度,就能真實地反應四輪車的運動軌跡。
大家可以前往CG看看實際用這個演算法製作出來的車子,開起來是什麼感覺。