Box2D — Физика движения авто своими руками
Приветствую всех читателей хабра. В этом топике я постараюсь показать вам как просто можно создать простую физику движения передне— и полноприводного автомобиля. Итак, поехали!
Вступление- Полное отсутствие дрифта/скольжения
- Различная скорость при движении по осям X и Y (неизвестно, чем это вызвано, но мне такое не надо)
- Сила тяги (оно же у нас будет являться скоростью и ускорением)
- Продольное трение колесa
- Поперечное трение колеса
По оси X — поперечное трение колеса По оси Y вниз — продольное трение колеса По оси Y вверх — скорость и ускорение Все вышеописанные оси всегда относительны центра каждого колеса и его угла поворота.
Пишем кодВ качестве физического движка я выбрал Box2D. Для облегчения написания кода я все—таки решил использовать обертку QuickBox2D, потому как она существенно облегчает создание примитивов в Box2D.
Подключаем необходимые классы:
- import Box2D. Collision . Shapes . * ;
- import Box2D. Common . Math . * ;
- import Box2D. Dynamics . * ;
- import Box2D. Dynamics . Joints . * ;
- import Box2D. Collision . * ;
- import Box2D. Common . * ;
- import com. actionsnippet . qbox . * ;
- var world:QuickBox2D = new QuickBox2D ( this , < debug: false , frim: true >) ;
- world. gravity = new b2Vec2 ( 0 , 0 ) ; //вектор гравитации
- world. start ( ) ; //старт отрисовки и просчета физики
- world. createStageWalls ( ) ; //фича от QuickBox'a - создает 4 стены по размерам stage
- world. mouseDrag ( ) ; //и разрешаем перемещать все тела мышью
Прописываем необходимые переменные или константы:
- //константы и переменные:
- private const FWD= 1 ; //передний
- private const AWD= 2 ; //полный
- private const RWD= 3 ; //задний
- private var _privod: Number ; //текущий привод авто, выбирается при создании
- private var _maxTurnAngle: Number ; // максимальный угол поворота колес
- private var _width : Number ; //длина авто
- private var _height : Number ; //ширина
- private var _axisOffset: Number ; //отступ колес по длине, относительно центра авто
- private const _angularDamping = 7 ; //замедление кручения колеса
- private const _angularDampingReverse = 3 ; //то же, но при заднем ходе
- private const _linearDamping = 0.25 ; //замедление скольжения колеса, что-то вроде трения
- private const _linearDampingReverse = 0.5 ; // и для заднего хода
- public var _carBody:QuickObject; //объект автомобиля
- private var _frontAxisBodyLeft:QuickObject; //и его колес
- private var _frontAxisBodyRight:QuickObject;
- private var _rearAxisBodyLeft:QuickObject;
- private var _rearAxisBodyRight:QuickObject;
- private var _frontLeftJoint:QuickObject; //шарнирные соединения для передних колёс
- private var _frontRightJoint:QuickObject;
- private var _rearLeftJoint:QuickObject; //шарнирные соединения для задних колёс
- private var _rearRightJoint:QuickObject;
- protected var axisHeight: Number = 4 ; //ширина колеса
- protected var axisWidth: Number = 7 ; //длина
- public var drive: int = 0 ; //переменные для управлления автомобилем
- public var steer: int = 0 ;
- //drive = 1 - едем вперед
- //drive = 0 - отпускаем газ и катимся по инерции, либо просто стоим
- //drive = -1 - едем назад
- //
- //steer = -1 - рулим влево
- //steer = 0 - никуда не рулим
- //steer = 1 - рулим вправо
Создаем авто с колёсами:
- //создаем авто
- _width = 76 ;
- _height = 40 ;
- _maxTurnAngle = 15 * Globals. DEG_TO_RAD ; //максимальный угол поворота передних колес
- _axisOffset: Number = ( _width / 4 - axisWidth ) / Globals. RATIO //отступ колес по длине
- _privod = FWD; // ну и наш привод, пока что функционируют передний и полный
- _carBody = world. addBox ( ) ;
- this ._frontAxisBodyLeft = this . world . addBox ( ) ;
- this ._frontAxisBodyRight = this . world . addBox ( ) ;
- this ._frontLeftJoint = this . world . addJoint ( ) ;
- this ._frontRightJoint = this . world . addJoint ( ) ;
- this ._rearAxisBodyLeft = this . world . addBox ( ) ;
- this ._rearAxisBodyRight = this . world . addBox ( ) ;
- this ._rearLeftJoint = this . world . addJoint ( ) ;
- this ._rearRightJoint = this . world . addJoint ( ) ;
Ну вот, мы создали наше авто. Вид его примерно как на картинке выше, но только оно будет повернуто на 90% по часовой. Теперь перед нами стоит главная задача: научить наше авто ездить. Для достижения эффекта езды мы будем прикладывать ApplyForce(..) к нужным колесам (для переднего привода — передние колеса. для полного — все колеса) Для просчета физики авто и управления им используем accelerate() и вызываем её при каждом обновлении мира:
- stage . addEventListener ( KeyboardEvent. KEY_DOWN , keyDownList ) ;
- stage . addEventListener ( KeyboardEvent. KEY_UP , keyUpList ) ;
- world. addEventListener ( QuickBox2D. STEP ,onStep ) ;
- .
- var bup: Boolean ;
- var bdown: Boolean ;
- var bleft: Boolean ;
- var bright: Boolean ;
- function keyDownList ( e :KeyboardEvent ) : void ;
- function keyUpList ( e :KeyboardEvent ) : void ;
- function onStep ( evt:Event ) : void ;
- if ( bdown ) < car. drive =- 1 >;
- if ( ! bup && ! bdown ) ( car. drive = 0 ) ;
- if ( bleft ) < car. steer =- 1 >;
- if ( bright ) < car. steer = 1 >;
- if ( ! bleft && ! bright ) < car. steer = 0 >;
- > ;
Ну и собственно сама процедура accelerate() (разбираем построчно, полный код процедуры будет позже): 1) Для начала мы разбудим наши тела
- this ._carBody. body . WakeUp ( ) ;
- this ._frontAxisBodyLeft. body . WakeUp ( ) ;
- this ._frontAxisBodyRight. body . WakeUp ( ) ;
2) Затем добавим необходимые силы трения(общую, по X, и по Y) чтобы авто ехало вперед, а не скользило при резком повороте, и при движении по инерции — замедлялось:
- if ( this . drive > 0 ) < //едем вперёд
- this ._acceleration = this ._accelerationForward;
- this . setLinearDamping ( this ._linearDamping ) ;
- this . setAngularDamping ( this ._angularDamping ) ;
- >
- else < //едем назад или стоим
- this ._acceleration = this ._accelerationBackwards;
- this . setLinearDamping ( this ._linearDamping ) ;
- this . setAngularDamping ( this ._angularDamping ) ;
- >
- this . addFriction ( this ._rearAxisBodyLeft. body , 0 ) ;
- this . addFriction ( this ._rearAxisBodyRight. body , 0 ) ;
- this . addFriction ( this ._frontAxisBodyLeft. body , 1 ) ;
- this . addFriction ( this ._frontAxisBodyRight. body , 1 ) ;
3) setLinearDamping() и setAngularDamping()
- private function setLinearDamping ( linDamp: Number ) : void
- private function setAngularDamping ( angDamp: Number ) : void
4) Ну и самая интересная на мой взгляд процедура addFriction(), реализацию которой я так долго искал и не нашел, и вот реализовал сам:
- function addFriction ( targetBody:b2Body, isFront: Number )
- spd = targetBody. GetWorldVector ( spd2 ) ;
- targetBody. SetLinearVelocity ( spd ) ;
- >
- if ( this ._privod == RWD )
- >
- >
Данная процедура раскладывает глобальный вектор скорости spd у каждого колеса на локальный spd2 и прикладывает трения по X и по Y. Единственные момент, который не укладывается в моей голове так это то, почему же все-таки оси повернуты на 90% по часовой? То есть чтобы отучить авто скользить и добавить трения по оси X которую я зарисовывал выше сейчас нам приходится добавлять трение на ось Y (оно же — поперечное трение колеса).
5) Продолжаем дополнять accelerate(), просчитываем углы по которым потом будем прикладывать силы скорости и ускорения (для передних колес):
- var carAngle: Number = this ._carBody. body . GetAngle ( ) ;
- if ( this . steer ! = 0 ) < // при езде вперед
- this ._steerAngle = this ._steerAngle + this . steer * this ._steeringAcceleration;
- >
- if ( this ._steerAngle > carAngle + this ._steeringRange )
- else if ( this ._steerAngle < carAngle - this ._steeringRange )
- if ( this . drive < 0 ) < //при езде назад
- if ( this . steer == 0 )
- else if ( this . steer == - 1 )
- else if ( this . steer == 1 )
- >
- if ( this . steer == 0 )
6)Поворачиваем передние колёса:
- this ._frontAxisBodyLeft. body . SetXForm ( _frontAxisBodyLeft. body . GetXForm ( ) . position , this ._steerAngle ) ;
- this ._frontAxisBodyRight. body . SetXForm ( _frontAxisBodyRight. body . GetXForm ( ) . position , this ._steerAngle ) ;
7)Ну и в зависимости от привода прикладываем необходимые силы к нужным колёсам: