Box2D — Физика движения авто своими руками

Box2D — Физика движения авто своими руками

Приветствую всех читателей хабра. В этом топике я постараюсь показать вам как просто можно создать простую физику движения передне— и полноприводного автомобиля. Итак, поехали!

Вступление
  1. Полное отсутствие дрифта/скольжения
  2. Различная скорость при движении по осям X и Y (неизвестно, чем это вызвано, но мне такое не надо)
Подготовка
  1. Сила тяги (оно же у нас будет являться скоростью и ускорением)
  2. Продольное трение колесa
  3. Поперечное трение колеса

По оси X — поперечное трение колеса По оси Y вниз — продольное трение колеса По оси Y вверх — скорость и ускорение Все вышеописанные оси всегда относительны центра каждого колеса и его угла поворота.

Пишем код

В качестве физического движка я выбрал Box2D. Для облегчения написания кода я все—таки решил использовать обертку QuickBox2D, потому как она существенно облегчает создание примитивов в Box2D.

Подключаем необходимые классы:

  1. import Box2D. Collision . Shapes . * ;
  2. import Box2D. Common . Math . * ;
  3. import Box2D. Dynamics . * ;
  4. import Box2D. Dynamics . Joints . * ;
  5. import Box2D. Collision . * ;
  6. import Box2D. Common . * ;
  7. import com. actionsnippet . qbox . * ;
  1. var world:QuickBox2D = new QuickBox2D ( this , < debug: false , frim: true >) ;
  2. world. gravity = new b2Vec2 ( 0 , 0 ) ; //вектор гравитации
  3. world. start ( ) ; //старт отрисовки и просчета физики
  4. world. createStageWalls ( ) ; //фича от QuickBox'a - создает 4 стены по размерам stage
  5. world. mouseDrag ( ) ; //и разрешаем перемещать все тела мышью

Прописываем необходимые переменные или константы:

  1. //константы и переменные:
  2. private const FWD= 1 ; //передний
  3. private const AWD= 2 ; //полный
  4. private const RWD= 3 ; //задний
  5. private var _privod: Number ; //текущий привод авто, выбирается при создании
  6. private var _maxTurnAngle: Number ; // максимальный угол поворота колес
  7. private var _width : Number ; //длина авто
  8. private var _height : Number ; //ширина
  9. private var _axisOffset: Number ; //отступ колес по длине, относительно центра авто
  10. private const _angularDamping = 7 ; //замедление кручения колеса
  11. private const _angularDampingReverse = 3 ; //то же, но при заднем ходе
  12. private const _linearDamping = 0.25 ; //замедление скольжения колеса, что-то вроде трения
  13. private const _linearDampingReverse = 0.5 ; // и для заднего хода
  14. public var _carBody:QuickObject; //объект автомобиля
  15. private var _frontAxisBodyLeft:QuickObject; //и его колес
  16. private var _frontAxisBodyRight:QuickObject;
  17. private var _rearAxisBodyLeft:QuickObject;
  18. private var _rearAxisBodyRight:QuickObject;
  19. private var _frontLeftJoint:QuickObject; //шарнирные соединения для передних колёс
  20. private var _frontRightJoint:QuickObject;
  21. private var _rearLeftJoint:QuickObject; //шарнирные соединения для задних колёс
  22. private var _rearRightJoint:QuickObject;
  23. protected var axisHeight: Number = 4 ; //ширина колеса
  24. protected var axisWidth: Number = 7 ; //длина
  25. public var drive: int = 0 ; //переменные для управлления автомобилем
  26. public var steer: int = 0 ;
  27. //drive = 1 - едем вперед
  28. //drive = 0 - отпускаем газ и катимся по инерции, либо просто стоим
  29. //drive = -1 - едем назад
  30. //
  31. //steer = -1 - рулим влево
  32. //steer = 0 - никуда не рулим
  33. //steer = 1 - рулим вправо

Создаем авто с колёсами:

  1. //создаем авто
  2. _width = 76 ;
  3. _height = 40 ;
  4. _maxTurnAngle = 15 * Globals. DEG_TO_RAD ; //максимальный угол поворота передних колес
  5. _axisOffset: Number = ( _width / 4 - axisWidth ) / Globals. RATIO //отступ колес по длине
  6. _privod = FWD; // ну и наш привод, пока что функционируют передний и полный
  7. _carBody = world. addBox (
  8. ) ;
  9. this ._frontAxisBodyLeft = this . world . addBox (
  10. ) ;
  11. this ._frontAxisBodyRight = this . world . addBox (
  12. ) ;
  13. this ._frontLeftJoint = this . world . addJoint (
  14. ) ;
  15. this ._frontRightJoint = this . world . addJoint (
  16. ) ;
  17. this ._rearAxisBodyLeft = this . world . addBox (
  18. ) ;
  19. this ._rearAxisBodyRight = this . world . addBox (
  20. ) ;
  21. this ._rearLeftJoint = this . world . addJoint (
  22. ) ;
  23. this ._rearRightJoint = this . world . addJoint (
  24. ) ;

Ну вот, мы создали наше авто. Вид его примерно как на картинке выше, но только оно будет повернуто на 90% по часовой. Теперь перед нами стоит главная задача: научить наше авто ездить. Для достижения эффекта езды мы будем прикладывать ApplyForce(..) к нужным колесам (для переднего привода — передние колеса. для полного — все колеса) Для просчета физики авто и управления им используем accelerate() и вызываем её при каждом обновлении мира:

  1. stage . addEventListener ( KeyboardEvent. KEY_DOWN , keyDownList ) ;
  2. stage . addEventListener ( KeyboardEvent. KEY_UP , keyUpList ) ;
  3. world. addEventListener ( QuickBox2D. STEP ,onStep ) ;
  4. .
  5. var bup: Boolean ;
  6. var bdown: Boolean ;
  7. var bleft: Boolean ;
  8. var bright: Boolean ;
  9. function keyDownList ( e :KeyboardEvent ) : void
  10. ;
  11. function keyUpList ( e :KeyboardEvent ) : void
  12. ;
  13. function onStep ( evt:Event ) : void
  14. ;
  15. if ( bdown ) < car. drive =- 1 >;
  16. if ( ! bup && ! bdown ) ( car. drive = 0 ) ;
  17. if ( bleft ) < car. steer =- 1 >;
  18. if ( bright ) < car. steer = 1 >;
  19. if ( ! bleft && ! bright ) < car. steer = 0 >;
  20. > ;

Ну и собственно сама процедура accelerate() (разбираем построчно, полный код процедуры будет позже): 1) Для начала мы разбудим наши тела

  1. this ._carBody. body . WakeUp ( ) ;
  2. this ._frontAxisBodyLeft. body . WakeUp ( ) ;
  3. this ._frontAxisBodyRight. body . WakeUp ( ) ;

2) Затем добавим необходимые силы трения(общую, по X, и по Y) чтобы авто ехало вперед, а не скользило при резком повороте, и при движении по инерции — замедлялось:

  1. if ( this . drive > 0 ) < //едем вперёд
  2. this ._acceleration = this ._accelerationForward;
  3. this . setLinearDamping ( this ._linearDamping ) ;
  4. this . setAngularDamping ( this ._angularDamping ) ;
  5. >
  6. else < //едем назад или стоим
  7. this ._acceleration = this ._accelerationBackwards;
  8. this . setLinearDamping ( this ._linearDamping ) ;
  9. this . setAngularDamping ( this ._angularDamping ) ;
  10. >
  11. this . addFriction ( this ._rearAxisBodyLeft. body , 0 ) ;
  12. this . addFriction ( this ._rearAxisBodyRight. body , 0 ) ;
  13. this . addFriction ( this ._frontAxisBodyLeft. body , 1 ) ;
  14. this . addFriction ( this ._frontAxisBodyRight. body , 1 ) ;

3) setLinearDamping() и setAngularDamping()

  1. private function setLinearDamping ( linDamp: Number ) : void
  2. private function setAngularDamping ( angDamp: Number ) : void

4) Ну и самая интересная на мой взгляд процедура addFriction(), реализацию которой я так долго искал и не нашел, и вот реализовал сам:

  1. function addFriction ( targetBody:b2Body, isFront: Number )
  2. spd = targetBody. GetWorldVector ( spd2 ) ;
  3. targetBody. SetLinearVelocity ( spd ) ;
  4. >
  5. if ( this ._privod == RWD )
  6. >
  7. >

Данная процедура раскладывает глобальный вектор скорости spd у каждого колеса на локальный spd2 и прикладывает трения по X и по Y. Единственные момент, который не укладывается в моей голове так это то, почему же все-таки оси повернуты на 90% по часовой? То есть чтобы отучить авто скользить и добавить трения по оси X которую я зарисовывал выше сейчас нам приходится добавлять трение на ось Y (оно же — поперечное трение колеса).

5) Продолжаем дополнять accelerate(), просчитываем углы по которым потом будем прикладывать силы скорости и ускорения (для передних колес):

  1. var carAngle: Number = this ._carBody. body . GetAngle ( ) ;
  2. if ( this . steer ! = 0 ) < // при езде вперед
  3. this ._steerAngle = this ._steerAngle + this . steer * this ._steeringAcceleration;
  4. >
  5. if ( this ._steerAngle > carAngle + this ._steeringRange )
  6. else if ( this ._steerAngle < carAngle - this ._steeringRange )
  7. if ( this . drive < 0 ) < //при езде назад
  8. if ( this . steer == 0 )
  9. else if ( this . steer == - 1 )
  10. else if ( this . steer == 1 )
  11. >
  12. if ( this . steer == 0 )

6)Поворачиваем передние колёса:

  1. this ._frontAxisBodyLeft. body . SetXForm ( _frontAxisBodyLeft. body . GetXForm ( ) . position , this ._steerAngle ) ;
  2. this ._frontAxisBodyRight. body . SetXForm ( _frontAxisBodyRight. body . GetXForm ( ) . position , this ._steerAngle ) ;

7)Ну и в зависимости от привода прикладываем необходимые силы к нужным колёсам:

📎📎📎📎📎📎📎📎📎📎