9

これは、 Luaスクリプトでbox2dをカバーするLÖVEエンジンを使用してゲームを実装しようとしたときに発生した問題です。

目的は単純です。砲塔のようなオブジェクト(2D環境で上から見たもの)は、ターゲットを指すように向きを変える必要があります。

タレットはx、y座標上にあり、ターゲットはtx、ty上にあります。x、yは固定されていると考えることができますが、tx、tyは瞬間ごとに変化する傾向があります(つまり、マウスカーソルになります)。

タレットにはローターがあり、時計回りまたは反時計回りの任意の瞬間に回転力(トルク)を加えることができます。その力の大きさには、maxTorqueと呼ばれる上限があります。

タレットには一定の回転慣性もあり、質量が線形運動に作用するのと同じように角運動に作用します。いかなる種類の摩擦もないので、角速度があれば砲塔は回転し続けます。

タレットには小さなAI機能があり、その向きを再評価して正しい方向を指していることを確認し、ローテーターをアクティブにします。これはdtごとに発生します(1秒あたり約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

...失敗します。2つの実例となる状況で説明させてください。

  • タレットはtargetAngleの周りで「振動」します。
  • ターゲットが「タレットのすぐ後ろ、時計回りに少しだけ」の場合、タレットは時計回りのトルクを適用し始め、ターゲットの角度を超える瞬間までそれらを適用し続けます。その瞬間、それは反対方向にトルクを加え始めます。しかし、それはかなりの角速度を獲得しているので、しばらくの間時計回りに進み続けます...ターゲットが「すぐ後ろですが、少し反時計回り」になるまで。そして、それは再び始まります。そのため、砲塔は振動するか、丸い円を描くことさえあります。

私の砲塔は、目標角度に達する前に「最短経路の反対方向」にトルクを加え始める必要があると思います(停止する前に車がブレーキをかけるように)。

直感的には、砲塔は「目標目標のほぼ半分になったら、最短経路の反対方向にトルクをかけ始める」べきだと思います。私の直感は、それが角速度と関係があることを教えてくれます。そして、ターゲットがモバイルであるという事実があります-どういうわけかそれを考慮に入れるべきか、それとも単に無視するべきかはわかりません。

タレットが「ブレーキをかけ始める」必要がある時期を計算するにはどうすればよいですか?

4

5 に答える 5

3

後ろ向きに考えてください。タレットは、現在の角速度から完全停止まで減速するのに十分な余裕があるときに「ブレーキを開始」する必要があります。これは、完全停止から現在の角速度まで加速するために必要な余裕と同じです。

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

また、ステップ時間が長すぎると、ターゲットの周りの小さな振動で問題が発生する場合があります。それにはもう少し精巧さが必要で、もう少し早く、より穏やかにブレーキをかける必要があります。見るまでは気にしないでください。

今のところはこれで十分ですが、後でつまずく可能性があるもう 1 つの問題があります。すでに遠回りしている場合は、遠回りした方が速い場合もあります。その場合、どちらの方が時間がかからないかを判断する必要がありますが、これは難しくありませんが、ここでも橋に来たら渡ってください。

編集:
私の方程式は間違っていました。2*maxTorque/Inertia ではなく、Inertia/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)
于 2010-04-11T16:30:41.920 に答える
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です。

この角度を計算するための私の式は、ベータ版とは少し異なります。私の友人が物理学で私を助けてくれました。w の記号を追加するのは私の考えでした。

任意の角度を 0-2Pi ゾーンに戻す「正規化」機能を実装する必要がありました。

当初、これは複雑な if-else-if-else でした。条件が非常に反復的であるため、アルゴリズムを単純化するためにいくつかのブール論理を使用しました。欠点は、問題なく機能し、複雑でなくても、なぜ機能するのかがわからないことです。

コードがもう少し浄化されたら、デモへのリンクをここに投稿します。

どうもありがとう。

編集: 動作中の LÖVE サンプルは、こちら で利用できるようになりました。重要なものはactors/AI.lua内にあります(.loveファイルはzipアンコンプレッサーで開くことができます)

于 2010-04-14T00:19:53.670 に答える
1

これはPID コントローラーで解決できる問題のようです。仕事でヒーターの出力を制御して温度を設定するために使用しています。

「P」コンポーネントの場合、タレット角度とターゲット角度の差に比例するトルクを適用します。

P = P0 * differenceAngle

これでも振動が大きすぎる場合 (少し振動します)、「I」コンポーネントを追加します。

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

これがオーバーシュートしすぎる場合は、「D」項を追加します

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

P0I0およびD0は、必要な動作を得るために調整できる定数です (つまり、タレットの応答速度など)。

ヒントとして、通常はP0>>I0D0

これらの用語を使用して、適用されるトルクの量を決定します。

magnitudeAngMomentum = P + I + D

編集:

これは、 PID を使用するProcessingを使用して作成されたアプリケーションです。実際には I や D がなくても問題なく動作します。ここで動作することを確認してください


// 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;
}
于 2010-04-11T15:59:46.330 に答える
0

加速トルクが適用されたときのローターの角速度と角距離の式を見つけることができ、制動トルクが適用されたときの同じ式を見つけることができます。

次に、角距離軸と必要な角度で交差するように、破断方程式を修正します。これらの 2 つの方程式を使用すると、それらが交差する角度距離を計算して、ブレーク ポイントを得ることができます。

ただし、このようなことを長い間行っていないため、完全に間違っている可能性があります。おそらくもっと簡単な解決策です。加速は線形ではないと仮定しています。

于 2010-04-11T15:34:58.660 に答える
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 );

これが機能する理由は、砲塔が目標角度に達すると自動的にゆっくりと移動しようとするためです。

無限のトルクは、タレットが瞬時に距離を縮めようとしないという事実によって隠されています。代わりに、1 つのタイムステップで距離を縮めようとします。また、-pi から pi の範囲が非常に小さいため、非常識な加速度が現れることはありません。最大角速度により、タレットの回転がリアルに見えます。

角速度の代わりにトルクで解くための実際の方程式を解いたことはありませんが、PID 方程式によく似ていると思います。

于 2010-04-12T14:57:40.920 に答える