Поворот объекта за мышкой во Flash

Всем привет!

Во многих играх нам необходимо, чтобы какой-либо игровой объект поворачивался в сторону нашего курсора.

Давайте сделаем что-нибудь такое, чтобы это тоже было в нашей копилке наработок)

Что будем делать?

Я решил сделать 2 вида объекта для примера.

1-й объект — это лук со стрелой, будет с ограниченным поворотом по углам, поворачиваться будет моментально за мышкой.

2-й объект — это башня танка, у неё нет ограничений по углам поворота и она будет поворачиваться с определённой скоростью.

В результате мы хотим получить вот такую картину:

Графика

Графику мы будем использовать по такому же принципу как описано в статье Рисуем в IDE, а кодим в FD. Я заранее подготовил графику в виде 4-х png файлов:

materialsДалее импортировал эти файлы в библиотеку созданного .fla файла. Лук и стрелу объединил в один клип, а корпус танка и башню в разные, т.к. башня будет поворачиваться отдельно от корпуса.

Все объекты в графике изначально повёрнуты вправо, т.к. это совпадает с углом флеш-объекта = 0 градусов.

Пушка танка на png повёрнута вниз, поэтому я повернул её вправа уже в самом клипе пушки.

Далее я объединяю клипы лука, корпуса танка и пушки в 1 клип (mcScene), располагаю их там как хотел бы видеть их в игре и даю имена клипам лука (bow) и пушки (tower), чтобы потом легко определить их в коде.

лук и танк во flashПаблишим библиотеку в swc и всё, на этом с графикой закончили.

Код

Создаём новый проект в FD, подключаем библиотеку swc и кодим.

Приведу результат трудов в виде листинга кода главного класса:

package 
{
    import flash.display.MovieClip;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Point;
    import monax.MyMath;

    /**
     * Пример поворота объектов за курсором мыши. Специально для cyber-code.ru
     * @author Monax
     */
    [Frame(factoryClass="Preloader")]
    public class Main extends Sprite 
    {
        private var _bow: MovieClip;
        private var _tower: MovieClip;
        
        // текущее положение мыши на нужном объекте.
        // пример: если нужно чтобы танк поворачивал дуло туда, куда указывает мышь, то этот объект - это земля, на которой находится танк
        // в нашем случае - это родительский объект - этот класс
        private var _mousePosX: Number;
        private var _mousePosY: Number;
        
        // параметры поворота лука
        private var _bowMaxAn: Number = 60; // максимальный угол поворота = 60 градусов
        private var _bowMinAn: Number = -90; // минимальный угол поворота = -90 градусов
        
        // параметры поворота башни
        private const TOWER_TURN_SPD: Number = 45; // скорость поворота башни, (градус / сек)
        
        
        public function Main():void 
        {
            if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init);
        }

        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            
            // создаём сцену
            var scene: MovieClip = new Scene_mc();
            scene.x = stage.stageWidth / 2;
            scene.y = stage.stageHeight / 2;
            addChild(scene);
            
            // записываем клипы лука и башни танка
            _bow = scene.getChildByName("bow") as MovieClip;
            _tower = scene.getChildByName("tower") as MovieClip;
            
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
        
        private function turnTankTower(aDT: Number):void 
        {
            /**
             * можно было бы просто ориентироваться по координатам текущей сцены, 
             * НО это было бы справедливо только для той игровой области, которая не смещалась (например по причине двигающейся камеры в игре)
             * поэтому я сделаю расчёт на это сразу. т.е. алгоритм будет универсальным и более общим, а не частным.
             * 
             * Мы переведём сначала все координаты в глобальные, там найдём угол поворота и будем к нему стремиться - вертеть пушку
             */
            // получаем глобальные координаты мыши (это координаты относительно левого верхнего угла плеера, независимо от смещений родительских спрайтов)
            var mousePos: Point = localToGlobal(new Point(_mousePosX, _mousePosY));
            // глобальные координаты центра пушки
            var towerPos: Point = _tower.localToGlobal(new Point());
            // новый угол поворота пушки
            // силами своей функции нахождения угла между двумя точками, вершина угла в (0;0)
            var nRot: Number = MyMath.getAngle2Deg(1, 0, mousePos.x - towerPos.x, mousePos.y - towerPos.y);
            // обрезаем угол согласно максимальному(180) и минимальному(-180) углам флеш-объектов
            while (nRot > 180) nRot -= 360;
            while (nRot < -180) nRot += 360;
            // текущий угол поворота пушки
            var tRot: Number = _tower.rotation;
            // рассчитываем направление поворота пушки, выбирая минимальный угол поворота, тем самым кротчайшую дугу
            var towerTurnDir: int = nRot - tRot <= 180 ? 1 : -1;
            if (nRot - tRot < 0) towerTurnDir = -1;
            if (nRot - tRot < -180) towerTurnDir = 1;
            // дельта-поворот за данный тик времени
            var dtTowerTurn: Number = towerTurnDir * TOWER_TURN_SPD * aDT;
            // поворачиваем
            if (_tower.rotation != nRot) {
                // меры для того, чтобы пушка не дёргалась, когда достигнет нужного угла взгляда
                var rotAbs: Number = Math.abs(nRot - _tower.rotation);
                var dtAbs: Number = Math.abs(dtTowerTurn);
                // смягчения при достижении целевого угла
                if (rotAbs <= dtAbs / 2) {
                    dtTowerTurn = nRot - _tower.rotation;
                }
                else if (rotAbs <= dtAbs) {
                    dtTowerTurn *= 0.5;
                }
                
                _tower.rotation += dtTowerTurn;
            }
        }
        
        private function turnBow():void 
        {
            // получаем глобальные координаты мыши (это координаты относительно левого верхнего угла плеера, независимо от смещений родительских спрайтов)
            var mousePos: Point = localToGlobal(new Point(_mousePosX, _mousePosY));
            // глобальные координаты центра пушки
            var bowPos: Point = _bow.localToGlobal(new Point());
            // новый угол поворота пушки
            // силами своей функции нахождения угла между двумя точками, вершина угла в (0;0)
            var nRot: Number = MyMath.getAngle2Deg(1, 0, mousePos.x - bowPos.x, mousePos.y - bowPos.y);
            // применяем ограничения
            if (nRot < _bowMinAn) nRot = _bowMinAn;
            if (nRot > _bowMaxAn) nRot = _bowMaxAn;
            _bow.rotation = nRot;
        }
        
        private function onEnterFrame(e:Event):void 
        {
            update(1 / stage.frameRate);
        }
        
        private function update(aDT:Number):void 
        {
            // записываем координаты, куда башне надо смотреть - это координаты мышки на земле
            // земля - это родитель танка, в нашем случае родитель - это текущий класс.
            _mousePosX = mouseX;
            _mousePosY = mouseY;
            
            turnTankTower(aDT);
            turnBow();
        }

    }

}

Всё уместилось в 1 файле и + 1 функция (MyMath.getAngle2Deg()) из моих личных запасов, которая находит угол между двумя точками с вершиной в координатах (0;0).

Я хорошо прокомментировал код, поэтому непоняток должно быть минимум.

Вся соль заключается в формулах и алгоритмах расчёта правильного угла поворота.

Материалы

Архив с исходниками: rotateToMouse.rar