台球游戲的核心算法和AI(1)


 

前言:
  08年的時候, 寫過一個台球游戲, 用的是java, 不過代碼真的是用傳說中的神器notepad寫的(你信嗎? 其實是用GVIM寫的, ^_^), 很多類都在同一java文件中編寫. 可見當時的JAVA水平真的不咋地, 時過進遷, 還是一樣的不咋地.
  這邊是當時的CSDN下載鏈接: java(台球游戲), 實現比較簡單. 后來寫過一個版本, 比這個要強大許多, 可惜源碼丟失了.
  效果展示入下圖所示:
  
  本文想講述下台球游戲中核心算法的實現, 以及游戲AI的設計技巧. 當然自己也有個小願望, 希望能實現一個html5版的台球游戲.

基礎物理知識:
  • 摩擦阻力
  其滿足牛頓第二定律:
  f = m * a
  速度與加速度關系公式:
  vt = v0 + a * t
  地面摩擦力與運動物體的方向相反, 阻礙物體的向前運動.
  • 動量守恆
  假設物體A質量為m1, 速度為v1, 物體B質量為m2, 速度為v2, 碰撞后速度分別為v1', v2'.
  則滿足動量守恆定律:
  m1 * v1 + m2 * v2 = m1 * v1' + m2 * v2'
  

  • 碰撞類型和能量守恆定律
  1). 完全彈性碰撞
  動能沒有損失, 則滿足如下公式:
  1/2 * m1 * v1^2 + 1/2 * m2 * v2^2 = 1/2 * m1 * v1'^2 + 1/2 * m2 * v2'^2
  注: 前后物體的動能保持均衡, 沒有其他能量的轉化.
  結合之前的動量守恆定律, 我們可以進一步得到:
  v1' = [(m1-m2) * v1 + 2 * m2 * v2] / (m1 + m2)
  v2' = [(m2-m1) * v2 + 2 * m1 * v1] / (m1 + m2)
  2). 完全非彈性碰撞
  則存在其他能量的轉化, 動能不守恆.
  且此時兩物體粘連, 速度一致, 即v1'=v2', 此時動能損失最大.
  3). 彈性碰撞
  介於完全彈性碰撞和完全非彈性碰撞兩者之間. 動能有損失的.

物理模型:
  台球游戲中, 最核心的就是其物理模型的抽象及其碰撞算法的執行過程了.
  鑒於是2D版的台球游戲, 因此我們對物理模型做下簡化, 球運動的方向必然穿越球的中心.
  把每個台球抽象為圓(x, y, radius), 而台球桌邊框抽象為線段((x1, y1), (x2, y2)).
  • 碰撞檢測
  1). 檢測球與球碰撞
  我們假定球A(x1, y1, r), 球B(x2, y2, r). 則滿足條件:
  (x1 - x2) ^ 2 + (y1 - y2) ^ 2 <= (2*r) ^ 2
  則發生碰撞, 否則沒有發生碰撞
  2). 檢測球與球台邊框碰撞
  相對比較簡單. 求球心到邊框的垂直距離即可, 若小於等於則發生碰撞, 若大於則沒有.
  • 碰撞反應
  1). 球與球的碰撞反應
  
  動量是向量, 其在正交的兩個方向上, 互相守恆. 我們選取兩球圓心的直線為x軸, 垂直於圓心直線的為y軸. 如上圖所述.
  x軸上滿足動量守恆:
  m1 * Vx + m2 * Ux = m1 * Vx' + m2 * Ux';
  並假定兩球碰撞是完全彈性碰撞, 兩球質量相等m1=m2, 依據基礎物理知識篇的結論.
  Vx' = [(m1-m2) * Vx + 2 * m2 * Ux] / (m1 + m2) = Ux;
  Ux' = [(m2-m1) * Ux + 2 * m1 * Vx] / (m1 + m2) = Vx;
  在X軸方向, 兩球交換速度, 而在Y軸方向, 兩球分速度不變.
  Vy' = Vy;
  Uy' = Uy;
  最終碰撞后的速度公式為:
  V' = Vx' + Vy' = Ux + Vy;
  U' = Ux' + Uy' = Vx + Uy;
  2). 球與邊框的碰撞反應
  把台球邊框視為質量無窮大, 則簡單把運動的球, 其在垂直邊框的分方向反向即可.
  
  假定碰撞碰撞平面為x軸
  Vx' = Vx;
  Vy' = -Vy;
  最終速度公式為:
  V' = Vx' + Vy' = Vx - Vy;

碰撞執行算法:
  游戲的主循環往往遵循如下代碼結構:

    while ( true ) {
        game.update(time_interval);
        game.render();
    }

  這個時間間隔(time_interval), 由游戲的FPS來確定. 以24幀為例, 每40毫秒刷新一次.
  對於台球本身而言, 若以該time_interval為更新周期, 使得運動的球體滿足:
  Vt = V0 + a * t
  運行距離為:
  S = V0 * t + 1/2 * a * t^2.
  然后來檢測球體是否發生了碰撞, 然后進行碰撞反應處理. 看似沒有問題.
  但是當球體初速度很快時, 在time_interval中有可能, 發生穿越現象.
  如下圖所展示的現象:
  
  紫色球在t2時刻, 和藍球檢測到碰撞, 但實際上, 在紫球在t1~t2之間的某時刻和藍球發生了碰撞.
  為了解決該問題, 在具體的算法中, 需要引入更細的時間分片slice, 該過程在具體的update中進行模擬.
  整個台球場景的更新函數:

void update(time_interval) {
	
	while time_interval > 0: 
		// 碰撞檢測
		if detectionCollide(time_interval, least_time, ball_pairs):
			// 游戲更新least_time
			billiards.update(least_time)
			// 對碰撞的兩球進行碰撞反應
			collideReaction(ball_pairs=>(ball, other))
			// time_interval 減少 least_time
			time_interval -= least_time
		else:
			// 游戲更新least_time
			billiards.update(time_interval)
			time_interval = 0

}

  注: 碰撞反應, 按物理模型篇講述的來.
  而具體的碰撞檢測算法為:

/*
	@brief
		在time_interval 時間內, 返回最先碰撞的球或台球邊, 以及時間點
*/
bool detectionCollide(time_interval, least_time, ball_pairs) {
	
	res = false;
	least_time = time_interval;
	
	foreach ball in billiards:
		foreach otherBall in billiards:
			// 求出兩球的距離
			S = distance(ball, otherBall)
			// 以某一球作為參考坐標系, 則令一球速度向量變為 U’=U-V
			// 在圓心的直線作為x軸
			Ux(relative) = Ux(other ball) - Vx(ball)
			// 	若該方向使得兩球遠離, 則直接忽略
			if Ux(relative) < 0:
				continue
			// 	某該方向使得兩球接近, 則可求其碰撞的預期時間點
			A' = 2 * A;	// 加速度為原來的兩倍
			
			// 取兩者最小的時間點
			delta_time = min(time_interval, Ux(relative) / Ax’)
			// 預期距離 小於 兩球距離,則在time_interval中不會發生碰撞
			if 1/2 * Ax’ * delta_time ^ 2 + Ux(relative) * delta_time < S - 2*r:
				continue
				
			// 解一元二次方程, 使用二分搜索逼近求解
			res_time <= slove(1/2 * Ax’ * x ^ 2 + Ux(relative) * x = S - 2 * r)
			
			if res_time < least_time: 
				ball_pairs <= (ball, otherBall)
				least_time = res_time
				res = true
		
		foreach wall in billiards:
			S = distance(ball, wall)
			// 設垂直於平面的方向為x軸
			if Vx < 0:
				continue
			
			// 取兩者最小的時間點
			delta_time = min(time_interval, Vx / Ax)
			// 預期距離 小於 兩球距離,則在time_interval中不會發生碰撞
			if 1/2 * Ax * delta_time ^ 2 + Vx * delta_time < S - r:
				continue
			
			// 解一元二次方程, 使用二分搜索逼近求解	
			res_time <= slove(1/2 * A * x ^ 2 + Vx * x = S - r)
			
			if res_time < least_time:
				ball_pairs <= (ball, walll)
				least_time = res_time
				res = true
			
	return res

}

  注: 對於一元二次方程, 也可以借助分1000個細粒度時間片, 然后計算逼近求解.
  台球模擬碰撞算法過程, 大致就是如上所述.
  計算最復雜的時刻, 其實就是開球, 打散一堆球的時候.

總結:
  本文參考了"NEHE的OPENGL中文教程 第30課 碰撞檢測與模型運動". 當然實現台球游戲, 未必真的需要該算法, 很多開發者直接使用box2d就能完美並輕松的實現. 參考"使用 cocos2d-x Box2d 的實現". 后續的文章, 想講述下台球游戲的AI如何設計和實現. 望一同努力.

寫在最后:
  
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫博客能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.

   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM