2010-04-11 40 views
8

這是我在嘗試使用LÖVE引擎實現遊戲時遇到的一個問題,它使用Lua腳本涵蓋了box2d物理遊戲編程box2d - 使用扭矩定向類似炮塔的物體

目標很簡單:一個類似炮塔的物體(從頂部看,在2D環境中)需要定向自身,以便指向目標。

炮塔位於x,y座標上,目標位於tx,ty。我們可以認爲x,y是固定的,但是tx,ty往往會在一瞬間變化(即它們會是鼠標光標)。

轉塔有一個轉子,可以在任何給定的時刻,順時針或逆時針施加轉動力(轉矩)。該力量的大小有一個稱爲maxTorque的上限。

該轉檯還具有一定的轉動慣量,該轉動慣量對質量作用於線性運動的角度運動起作用。沒有任何形式的摩擦,所以如果它有一個角速度,炮塔將繼續旋轉。

炮塔有一個小的AI功能,重新評估其方向,以驗證它指向正確的方向,並激活旋轉器。這發生在每個dt(〜每秒60次)。它現在看起來像這樣:

function Turret:update(dt) 
    local x,y = self:getPositon() 
    local tx,ty = self:getTarget() 
    local maxTorque = self:getMaxTorque() -- max force of the turret rotor 
    local inertia = self:getInertia() -- the rotational inertia 
    local w = self:getAngularVelocity() -- current angular velocity of the turret 
    local angle = self:getAngle() -- the angle the turret is facing currently 

    -- the angle of the like that links the turret center with the target 
    local targetAngle = math.atan2(oy-y,ox-x) 

    local differenceAngle = _normalizeAngle(targetAngle - angle) 

    if(differenceAngle <= math.pi) then -- counter-clockwise is the shortest path 
    self:applyTorque(maxTorque) 
    else -- clockwise is the shortest path 
    self:applyTorque(-maxTorque) 
    end 
end 

......它失敗。讓我來解釋兩種說明情況:

  • 炮塔在目標角上「擺動」。
  • 如果目標是「恰好在炮塔後面,只是一點點順時針」,炮塔將開始施加順時針轉矩,並繼續使用它們直到它超過目標角度。此時它將開始在相反的方向上施加扭矩。但它會獲得顯着的角速度,所以它會持續順時針旋轉一段時間......直到目標將「在後面,但有一點逆時針」。它會重新開始。所以炮塔會振盪,甚至會繞着圓圈。

我認爲我的炮塔應該在到達目標角度(如停車前的汽車剎車)之前開始在「最短路徑的相反方向」施加扭矩。直覺上,我認爲炮塔應該「開始在最短路徑的相反方向上施加扭矩,當它到達目標物體的中途時」。我的直覺告訴我,它與角速度有關。然後有一個事實,即目標是移動的 - 我不知道是否應該考慮到這一點,或者忽略它。

如何計算何時炮塔必須「開始剎車」?

回答

1

好的我相信我得到了解決方案。

這是基於Beta的想法,但有一些必要的調整。這裏有雲:

local twoPi = 2.0 * math.pi -- small optimisation 

-- returns -1, 1 or 0 depending on whether x>0, x<0 or x=0 
function _sign(x) 
    return x>0 and 1 or x<0 and -1 or 0 
end 

-- transforms any angle so it is on the 0-2Pi range 
local _normalizeAngle = function(angle) 
    angle = angle % twoPi 
    return (angle < 0 and (angle + twoPi) or angle) 
end 

function Turret:update(dt) 

    local tx, ty = self:getTargetPosition() 
    local x, y = self:getPosition() 
    local angle = self:getAngle() 
    local maxTorque = self:getMaxTorque() 
    local inertia = self:getInertia() 
    local w = self:getAngularVelocity() 

    local targetAngle = math.atan2(ty-y,tx-x) 

    -- distance I have to cover 
    local differenceAngle = _normalizeAngle(targetAngle - angle) 

    -- distance it will take me to stop 
    local brakingAngle = _normalizeAngle(_sign(w)*2.0*w*w*inertia/maxTorque) 

    local torque = maxTorque 

    -- two of these 3 conditions must be true 
    local a,b,c = differenceAngle > math.pi, brakingAngle > differenceAngle, w > 0 
    if((a and b) or (a and c) or (b and c)) then 
    torque = -torque 
    end 

    self:applyTorque(torque) 
end 

這背後的概念很簡單:我需要計算炮塔,以完全停止多少「空間」(角)的需要。這取決於炮塔移動速度有多快,以及它能夠施加多少扭矩。簡而言之,這就是我用brakingAngle計算的結果。

我的計算角度的公式與Beta的稍有不同。我的一位朋友幫助我解決了物理問題,好吧,他們似乎在工作。添加w的符號是我的想法。

我不得不實現一個「正常化」功能,它將任何角度都放回0-2Pi區域。

最初這是一個糾纏if-else-if-else。由於條件非常重複,我使用了一些boolean logic以簡化算法。缺點是,即使它工作正常並且不復雜,它也不會爲什麼會起作用。

一旦代碼更加清理一點,我會在這裏發佈一個演示鏈接。

非常感謝。

編輯:工作LÖVE樣品現在可用here。重要的東西是演員內部/ AI.lua(.love文件可以用zip壓縮器打開)

0

加速轉矩時,您可以找到轉子角速度與角距離的等式,並找到施加制動轉矩時的相同公式。

然後修改破壞方程,使其在要求的角度上合成角距離軸。有了這兩個方程式,您就可以計算出它們相交的角距,從而爲您提供突破點。

雖然可能完全錯誤,但長期以來沒有這樣做過。可能是一個更簡單的解決方案。我假設加速度不是線性的。

1

這似乎是一個問題,可以用PID controller來解決。我在工作中使用它們來控制加熱器輸出來設定溫度。

對於「P」成分,你施加扭矩成正比的炮塔角度和目標角度即

P = P0 * differenceAngle

之間的差異如果仍然振盪太多(它會有點),然後添加一個 '我' 成分,

integAngle = integAngle + differenceAngle * dt 
I = I0 * integAngle

如果超調太多,然後添加一個 'd' 一詞

derivAngle = (prevDifferenceAngle - differenceAngle)/dt 
prevDifferenceAngle = differenceAngle 
D = D0 * derivAngle

P0I0D0是您可以調整以獲得所需行爲的常數(即,如何快速炮塔響應等)

正如一個尖端,通常P0>I0>D0

使用這些術語來確定即

magnitudeAngMomentum = P + I + D

EDIT被多少扭矩施加:

以下是使用Processing使用PID編寫的應用程序。它實際上工作正常,沒有我或D.看到它工作here


// Demonstration of the use of PID algorithm to 
// simulate a turret finding a target. The mouse pointer is the target 

float dt = 1e-2; 
float turretAngle = 0.0; 
float turretMass = 1; 
// Tune these to get different turret behaviour 
float P0 = 5.0; 
float I0 = 0.0; 
float D0 = 0.0; 
float maxAngMomentum = 1.0; 

void setup() { 
    size(500, 500); 
    frameRate(1/dt); 
} 

void draw() { 
    background(0); 
    translate(width/2, height/2); 

    float angVel, angMomentum, P, I, D, diffAngle, derivDiffAngle; 
    float prevDiffAngle = 0.0; 
    float integDiffAngle = 0.0; 

    // Find the target 
    float targetX = mouseX; 
    float targetY = mouseY; 
    float targetAngle = atan2(targetY - 250, targetX - 250); 

    diffAngle = targetAngle - turretAngle; 
    integDiffAngle = integDiffAngle + diffAngle * dt; 
    derivDiffAngle = (prevDiffAngle - diffAngle)/dt; 

    P = P0 * diffAngle; 
    I = I0 * integDiffAngle; 
    D = D0 * derivDiffAngle; 

    angMomentum = P + I + D; 

    // This is the 'maxTorque' equivelant 
    angMomentum = constrain(angMomentum, -maxAngMomentum, maxAngMomentum); 

    // Ang. Momentum = mass * ang. velocity 
    // ang. velocity = ang. momentum/mass 
    angVel = angMomentum/turretMass; 

    turretAngle = turretAngle + angVel * dt; 

    // Draw the 'turret' 
    rotate(turretAngle); 
    triangle(-20, 10, -20, -10, 20, 0); 

    prevDiffAngle = diffAngle; 
} 
+0

這種方法的麻煩在於它是爲諸如加熱系統之類的東西而設計的,溫度的一階導數; egarcia正在控制扭矩,這是第二個。 P會因爲針對a = 0而不是w = 0而瘋狂地超調,我沒有幫助振盪,D可能會工作,但它會使過程變慢。 – Beta 2010-04-11 16:40:53

+0

你說得對,我給出的例子並不直接處理扭矩。然而,當考慮到炮塔旋轉「機制」中的摩擦力時,'maxAngleMomentum'與'maxTorque'成正比 - 使用任意單位時可以認爲它們是可互換的。 – Brendan 2010-04-12 15:41:30

+0

實現看起來不錯。 「保存角動量」的想法很有趣。然而,這不是我所要求的 - 我想要施加扭矩,而最終你自己設定角度。但是用於製作演示和代碼優雅的+1。 – kikito 2010-04-14 00:23:21

2

回想一想。當炮塔剛好有足夠的空間從當前角速度減速到死點時,炮塔必須「開始剎車」,這與從停止點加速到當前角速度所需的房間相同,即

|differenceAngle| = w^2*Inertia/2*MaxTorque. 

如果您的步進時間過長,您也可能在目標周圍出現小振盪時出現問題;那需要多一點技巧,你必須更早一點,更輕緩地剎車。除非你看到它,否則不要擔心。

現在應該已經足夠好了,但還有另一個可能會在以後發生的事情:決定走哪條路。如果你已經這麼做了,有時候走很長的路也會更快。在這種情況下,你必須決定哪種方式需要更少的時間,這並不困難,但是當你來到它時,再次穿過那座橋。

編輯:
我的公式是錯誤的,它應該是慣性/ 2 * maxTorque,而不是2 * maxTorque /慣性(這就是我得到試圖在鍵盤上做代數)。我修復了它。

試試這個:

local torque = maxTorque; 
if(differenceAngle > math.pi) then -- clockwise is the shortest path 
    torque = -torque; 
end 
if(differenceAngle < w*w*Inertia/(2*MaxTorque)) then -- brake 
    torque = -torque; 
end 
self:applyTorque(torque) 
+0

嗨測試版,感謝您的回答。我應該提到我的數學不是很強大。我該怎麼處理這個等式?有些東西似乎隱含在你身上,但我看不到它。 – kikito 2010-04-11 17:32:13

+0

毫米我給了這個更多的想法。根據我謙卑的嘗試,角度= MaxTorque * t * t /慣性,在給定的時間段t。你如何從這個等式走向你在你的例子中展示的那個逃避我。 – kikito 2010-04-11 18:27:22

+0

你幾乎可以得到它:加速度α=最大轉矩/慣性,w =α* t,但是爲了計算角度,你必須使用時間段內的平均速度,所以angle =(maxTorque * t/Inertia)* t/2 。現在取w = alpha * t並將其平方:w * w = alpha * alpha * t * t,並用它去掉t * t:angle = alpha * t * t/2 = alpha *(w * w/alpha * alpha)/ 2 = w * w/2 * alpha = w * w * Inertia/2 * maxTorque。 – Beta 2010-04-12 07:36:27

0

這個問題的一個簡化版本是非常簡單的解決。假設電機有無限的扭矩,即它可以瞬間改變速度。這顯然不是物理準確的,但會使問題更容易解決,最終不會成爲問題。

專注於目標角速度而非目標角度。

current_angle = "the turrets current angle"; 
target_angle = "the angle the turret should be pointing"; 
dt = "the timestep used for Box2D, usually 1/60"; 
max_omega = "the maximum speed a turret can rotate"; 

theta_delta = target_angle - current_angle; 
normalized_delta = normalize theta_delta between -pi and pi; 
delta_omega = normalized_deta/dt; 
normalized_delta_omega = min(delta_omega, max_omega); 

turret.SetAngularVelocity(normalized_delta_omega); 

這個工作原因是炮塔自動嘗試移動到達目標角度時較慢。

無限轉矩被轉塔不試圖瞬間關閉距離的事實所掩蓋。相反,它試圖在一個時間步中關閉距離。另外,由於-pi到pi的範圍很小,所以可能的瘋狂加速不會顯示出來。最大的角速度使炮塔的旋轉看起來逼真。

我從來沒有計算出用扭矩而不是角速度求解的實際方程,但我想它看起來很像PID方程。

+0

我喜歡你的想法,我會爲未來記住它。感謝分享它。如果你有興趣,我設法找到方程式並編制一個工作示例。 – kikito 2010-04-14 00:24:33