la programmation de jeu physique BOX2D - une tourelle d'orienter comme objet à l'aide des couples

StackOverflow https://stackoverflow.com/questions/2617476

  •  26-09-2019
  •  | 
  •  

Question

Ceci est un problème que je frappe en essayant de mettre en place un jeu en utilisant le moteur, qui couvre LÖVE box2d avec les scripts Lua.

L'objectif est simple:. Une tourelle objet (vu du haut, sur un environnement 2D) doit s'orienter afin qu'il pointe vers une cible

La tourelle est sur les coordonnées x, y, et la cible est en tx, ty. On peut considérer que x, y sont fixés, mais tx, ty ont tendance à varier d'un instant à l'autre (à savoir qu'ils seraient le curseur de la souris).

La tourelle comporte un rotor qui peut appliquer une force de rotation (couple) sur un moment donné, dans le sens horaire ou anti-horaire. L'ampleur de cette force a une limite supérieure appelée Maxtorque.

La tourelle a également une certaine inertie de rotation, qui agit pour le déplacement angulaire de la même façon masse agit pour un mouvement linéaire. Il n'y a pas de frottement d'aucune sorte, de sorte que la tourelle gardera rotation si elle a une vitesse angulaire.

La tourelle a une petite fonction AI que ré-évalue son orientation pour vérifier qu'il pointe vers la bonne direction, et active le dispositif de rotation. Cela se produit chaque dt (~ 60 fois par seconde). Il ressemble à ce moment:

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

... elle échoue. Permettez-moi de vous expliquer avec deux situations d'exemple:

  • La tourelle "autour du" oscille targetAngle.
  • Si la cible est « juste derrière la tourelle, un peu dans le sens horaire », la tourelle commencer à appliquer des couples dans le sens horaire, et gardez-les appliquer jusqu'à l'instant où elle dépasse l'angle cible. À ce moment-là, il va commencer à appliquer des couples sur le sens opposé. Mais il aura acquis une vitesse angulaire importante, il continuera à aller dans le sens horaire pendant un certain temps ... jusqu'à ce que la cible sera « juste derrière, mais un sens anti-horaire bits ». Et il va commencer à nouveau. Ainsi, la tourelle oscillera ou même aller dans les cercles ronds.

Je pense que ma tourelle doit commencer à appliquer des couples dans le « sens opposé du chemin le plus court » avant d'atteindre l'angle cible (comme un freinage voiture avant l'arrêt).

Intuitivement, je pense que la tourelle doit « commencer à appliquer des couples sur le sens opposé du chemin le plus court quand il est à mi-chemin de l'objectif cible ». Mon intuition me dit qu'il a quelque chose à voir avec la vitesse angulaire. Et puis il y a le fait que la cible est mobile -. Je ne sais pas si je dois tenir compte d'une façon ou ignorer simplement

Comment puis-je calculer lorsque la tourelle doit « commencer freinage »?

Était-ce utile?

La solution 3

D'accord, je crois que je suis la solution.

Ceci est basé sur l'idée de Beta, mais avec quelques modifications nécessaires. Ici, il va:

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

Le concept est simple: je dois calculer combien « l'espace » (angle) les besoins de la tourelle afin d'arrêter complètement. Cela dépend de la rapidité de la tourelle se déplace et combien le couple peut-il appliquer à lui-même. En un mot, c'est ce que je calcule avec brakingAngle.

Ma formule pour le calcul de cet angle est légèrement différent de la version bêta de. Un de mes amis m'a aidé avec la physique, et bien, ils semblent fonctionner. Ajout du signe de w était mon idée.

Je devais mettre en œuvre une fonction « normalisation », qui met tout retour d'angle dans la zone 0-2Pi.

Dans un premier temps ce fut un enchevêtrés if-else-if-else. Étant donné que les conditions dans lesquelles très répétitif, j'ai utilisé une certaine logique booléenne afin de simplifier l'algorithme. L'inconvénient est que, même si elle fonctionne bien et il est pas compliqué, il ne transpire pas pourquoi cela fonctionne.

Une fois que le code est un peu plus dépuration Je vais poster un lien vers une démo ici.

Merci beaucoup.

EDIT: Exemple de travail LÖVE est maintenant disponible . Les choses importantes sont des acteurs à l'intérieur / AI.lua (le fichier .love peut être ouvert avec un uncompressor zip)

Autres conseils

Pensez arrière. La tourelle doit « commencer freinage » quand il a juste assez pour décélérer de sa vitesse angulaire actuelle à un point mort, ce qui est la même que la chambre, il aurait besoin d'accélérer d'un arrêt mort à sa vitesse angulaire actuelle, qui est

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

Vous pouvez aussi avoir des problèmes avec de petites oscillations autour de la cible si votre temps de marche est trop grande; qui aurez besoin d'un peu plus de finesse, vous devez freiner un peu plus tôt, et plus doucement. Ne vous inquiétez pas à ce sujet jusqu'à ce que vous voyez.

Ce devrait être assez bon pour l'instant, mais il y a une autre prise qui peut vous faire trébucher plus tard: décider quel chemin à parcourir. si vous allez cette façon déjà va parfois le long chemin est plus rapide autour,. Dans ce cas, vous devez décider quel chemin prend moins de temps, ce qui est difficile, mais encore une fois, traverser ce pont quand vous venez à elle.

EDIT:
Mon équation était mauvaise, il devrait être Inertie / 2 * Maxtorque, pas 2 * Maxtorque / Inertie (c'est ce que je reçois pour essayer de faire l'algèbre au clavier). Je l'ai fixé il.

Essayez ceci:

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)

Cela semble être un problème qui peut être résolu avec un régulateur PID . Je les utilise dans mon travail pour contrôler une sortie de chauffage pour régler une température.

Pour le composant « P », on applique un couple de rotation qui est proportionnelle à la différence entre l'angle de la tourelle et l'angle cible i.e..

P = P0 * differenceAngle

Si ce oscille encore trop (il un peu) puis ajouter un composant « I »,

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

Si ce dépassement trop, ajouter un terme « D »

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

P0, I0 et D0 sont des constantes que vous pouvez régler pour obtenir le comportement que vous voulez (à savoir à quelle vitesse les tourelles répondent etc.)

Tout comme une pointe, normalement P0> I0> D0

Utilisez ces termes pour déterminer la quantité de couple est appliqué à savoir

magnitudeAngMomentum = P + I + D

EDIT:

Voici une application écrite en utilisant Traitement que les utilisations PID. Il fonctionne en fait très bien sans moi ou D. voir travailler ici


// 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;
}

On pourrait trouver une équation de vitesse angulaire fonction de la distance angulaire du rotor lorsque le couple d'accélération est appliquée, et pour le même équation pour quand on applique le couple de freinage.

Puis modifier l'équation de rupture de telle sorte qu'il intesects l'axe de la distance angulaire à l'angle requis. Avec ces deux équations, vous pouvez calculer la distance angulaire à laquelle ils se croisent qui vous donnera le point de rupture.

pourrait être tout à fait tort cependant, ne se fait comme ça depuis longtemps. Probablement une solution plus simple. Je suppose que l'accélération est pas linéaire.

Une version simplifiée de ce problème est assez simple à résoudre. Supposons que le moteur a un couple infini, ie il peut changer instantanément la vitesse. Ceci est évidemment pas physiquement précis, mais rend le problème beaucoup plus simple à résoudre et à la fin est pas un problème.

Mise au point sur une vitesse angulaire cible pas un angle cible.

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 );

La raison pour laquelle cela fonctionne est la tourelle essaie automatiquement de se déplacer plus lentement car il atteint son angle cible.

Le couple infini est masqué par le fait que la tourelle ne cherche pas à fermer la distance instantanément. Au contraire, il essaie de fermer la distance en un timestep. En outre, depuis la plage de -pi à pi est assez petit les accélérations peut-être fou ne se montrent. La vitesse angulaire maximale à maintenir les rotations de la tourelle à la recherche réaliste.

Je ne l'ai jamais travaillé sur l'équation réelle pour résoudre avec un couple au lieu de la vitesse angulaire, mais je l'imagine regardera un peu comme les équations PID.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top