Вторник, 12.12.2017, 09:27
Поиск
Никнэйм
Сертификат на никнейм Olelucoye, зарегистрирован на Тимофеев Константин Михайлович
Зарегистрируй свой никнейм
Обратная связь
olelucoye.tk@yandex.ru
Реклама AdSense
Реклама
Друзья сайта
  • Лига медицинского права
  • Гостиница "Зай"
  • FAQ по системе
  • Инструкции для uCoz
  • Главная » Статьи » libGDX

    libGDX. Scene2D. Сенсорное управление персонажем.

    libGDX. Scene2D. Сенсорное управление персонажем.

    Поскольку libGDX мультиплатформенный движок, в нем предусмотрена обработка сенсорного управления. В этой статье рассмотрим, как можно управлять персонажем на Андроид-устройствах.

    Для этого нам пригодиться Scene2D. Для этого мы расположим на сцене (stage) несколько актеров-кнопок и реализуем обработку касаний на них. В качестве персонажа будем использовать модель робота, созданного в Blender'е. Вот здесь можно посмотреть уроки по созданию такого робота.

    Персонаж имеет несколько анимаций, которые мы будем использовать в одной из следующих статей (пока рассмотрим движение без анимации). Также понадобится атлас текстур с изображениями актеров-кнопок и текстура для поверхности земли. Все эти файлы нам нужно поместить в папку assets Андроид-проекта. Взять их можно здесь.

    Начнем с такой заготовки.

    public class interface2d extends ApplicationAdapter implements InputProcessor {
        PerspectiveCamera cam;
        public ModelBatch modelBatch;
        public Environment environment;
        public CameraInputController camController;
        public AssetManager assets;
        boolean loading;
        Model ground, man;
        ModelInstanceAdv groundInst, manInst;
        btDynamicsWorld dynWorld;
        btBroadphaseInterface broadphase;
        btConstraintSolver solver;
        btCollisionConfiguration collisionConf;
        btDispatcher collisionDispatcher;
        InputMultiplexer multiplexer;
        boolean forward = false;
        boolean back = false;
        boolean right = false;
        boolean left = false;
        int rotateAngle = 0;
        float jump = 1;
        float direction = 0, oldDir = 0;
        int scrX, scrY;
        float scaling;
        int oldX =0, oldY = 0, newX = 0, newY = 0;
        public Material land;
        
        static class ModelInstanceAdv extends ModelInstance implements Disposable{
            public final btRigidBody body;
            public final MotionStateAdv motionState;
            
            public ModelInstanceAdv(Model model, btRigidBody.btRigidBodyConstructionInfo bodyInfo) {
                super(model);
                body = new btRigidBody(bodyInfo);
                motionState = new MotionStateAdv();
                motionState.transform = transform;
                body.setMotionState(motionState);
            }
            @Override
            public void dispose() {
                body.dispose();
                motionState.dispose();
            }
            
            static class Constructor implements Disposable{
                public final Model model;
                public final btCollisionShape collShape;
                public final btRigidBody.btRigidBodyConstructionInfo bodyInfo;
                private static Vector3 inertia = new Vector3();
                public Constructor(Model model, btCollisionShape collShape, float mass){
                    this.model = model;
                    this.collShape = collShape;
                    if(mass > 0f){
                        collShape.calculateLocalInertia(mass, inertia);
                    }else{inertia.set(0f, 0f, 0f);}
                    this.bodyInfo = new btRigidBody.btRigidBodyConstructionInfo(mass, null, collShape, inertia);
                }
                
                public ModelInstanceAdv create(){
                    return new ModelInstanceAdv(model, bodyInfo);
                }
                @Override
                public void dispose() {
                    collShape.dispose();
                    bodyInfo.dispose();
                }
            }
        }
        
        static class MotionStateAdv extends btMotionState{
            Matrix4 transform;
            @Override
            public void getWorldTransform(Matrix4 WorldTrans){
                WorldTrans.set(transform);            
            }
            @Override
            public void setWorldTransform(Matrix4 WorldTrans){
                transform.set(WorldTrans);
            }
        }
            
        @Override
        public void create () {
            Bullet.init();
            assets = new AssetManager();
            assets.load("data/robot3.g3db", Model.class);
            loading = true;
            modelBatch = new ModelBatch();
            environment = new Environment();
            environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
            environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
            cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
            cam.position.set(0f, 8f, -8f);
            cam.lookAt(0f,4f, 0f);
            cam.near = 1f;
            cam.far = 300f;
            cam.update();
            collisionConf = new btDefaultCollisionConfiguration();
            collisionDispatcher = new btCollisionDispatcher(collisionConf);
            broadphase = new btDbvtBroadphase();
            solver = new btSequentialImpulseConstraintSolver();
            dynWorld = new btDiscreteDynamicsWorld(collisionDispatcher, broadphase, solver, collisionConf);
            dynWorld.setGravity(new Vector3(0f, -10f, 0f));
            
            scrX = Gdx.graphics.getWidth();
            scrY = Gdx.graphics.getHeight();
            scaling = scrX/1280f;
                    
            multiplexer = new InputMultiplexer(); 
            camController = new CameraInputController(cam);
            multiplexer.addProcessor(camController);
            multiplexer.addProcessor(this);
            Gdx.input.setInputProcessor(multiplexer);
        }
        
        public void doneLoading(){
            TextureAttribute land_attr = TextureAttribute.createDiffuse(new Texture("img/land.jpg"));
            land = new Material(land_attr);
            ModelBuilder modelBuilder = new ModelBuilder();
            ground = modelBuilder.createBox(200f, 0.1f, 200f, land, Usage.Position|Usage.Normal|Usage.TextureCoordinates);
            ModelInstanceAdv.Constructor temp = new ModelInstanceAdv.Constructor(ground, Bullet.obtainStaticNodeShape(ground.nodes), 0f);
            groundInst = temp.create();
            dynWorld.addRigidBody(groundInst.body);
            
            man = assets.get("data/robot3.g3db", Model.class);
            ModelInstanceAdv.Constructor temp2 = new ModelInstanceAdv.Constructor(man,  createConvexHullShape(man, false), 50f);
            manInst = temp2.create();
            manInst.transform.setToTranslation(0f, 2f, 0f);
            manInst.body.proceedToTransform(manInst.transform);
            manInst.body.setActivationState(Collision.DISABLE_DEACTIVATION);
            manInst.body.setAngularFactor(new Vector3(0f, 0f, 0f));
            dynWorld.addRigidBody(manInst.body);
            loading = false;
        }
        
        @Override
        public void render () {
            if(loading&&assets.update()){
                doneLoading();
            }
            
            Gdx.gl.glClearColor(0.3f, 0.5f, 1f, 1f);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT);
            final float delta = Math.min(1f/30f, Gdx.graphics.getDeltaTime());
            
            if(!loading){
                oldDir = direction;
                if(rotateAngle != 0){
                    direction = (direction + rotateAngle*360*delta)%360;
                    manInst.transform.rotate(new Vector3(0f, 1f, 0f), rotateAngle*360*delta);
                }
                if(forward){
                    manInst.transform.trn((float)(1.6 * delta*Math.sin(Math.toRadians(direction))),
                        0f, (float)(1.6 * delta*Math.cos(Math.toRadians(direction))));
                        }
                if(back){
                    manInst.transform.trn(-(float)(1.6 * delta*Math.sin(Math.toRadians(direction))),
                       0f, -(float)(1.6 * delta*Math.cos(Math.toRadians(direction))));
                }
                if(right){
                    manInst.transform.trn((float)(1.6 * delta*Math.sin(Math.toRadians(direction-90))),
                        0f, (float)(1.6 * delta*Math.cos(Math.toRadians(direction-90))));
                }
                if(left){
                    manInst.transform.trn((float)(1.6 * delta*Math.sin(Math.toRadians(direction+90))),
                       0f, (float)(1.6 * delta*Math.cos(Math.toRadians(direction+90))));
                }
                if(jump < 1){
                    jump = jump + 2*delta;
                    manInst.transform.trn(0f, delta*6, 0f);
                }
                manInst.body.proceedToTransform(manInst.transform);
                dynWorld.stepSimulation(delta, 5, 1f/60f);
                Vector3 posP = new Vector3();
                manInst.transform.getTranslation(posP);
                cam.position.set((float) (posP.x - 15*Math.sin(Math.toRadians(direction))), 6f, (float) (posP.z - 15*Math.cos(Math.toRadians(direction))));
                cam.rotate(direction - oldDir, 0f, 1f, 0f);
                cam.update();
                camController.update();
                modelBatch.begin(cam);
                modelBatch.render(groundInst, environment);
                modelBatch.render(manInst, environment);
                modelBatch.end();
            }
        }
        
        @Override
        public void dispose(){
            dynWorld.dispose();
            solver.dispose();
            broadphase.dispose();
            collisionDispatcher.dispose();
            collisionConf.dispose();
            modelBatch.dispose();
            ground.dispose();
            man.dispose();
        }
        @Override
        public boolean keyDown(int keycode) {
            switch(keycode){
            case Keys.UP:
                forward = true;
                break;
            case Keys.DOWN:
                back = true;
                break;
            case Keys.RIGHT:
                right = true;
                break;
            case Keys.LEFT:
                left = true;
                break;
            case Keys.SPACE:
                jump = 0;
                break;
            }
            return true;
        }
        @Override
        public boolean keyUp(int keycode) {
            switch(keycode){
            case Keys.UP:
                forward = false;
                break;
            case Keys.DOWN:
                back = false;
                break;
            case Keys.RIGHT:
                right = false;
                break;
            case Keys.LEFT:
                left = false;
                break;
            }
            return true;
        }
        @Override
        public boolean mouseMoved(int screenX, int screenY) {
            if(oldX == 0 & oldY == 0 & newX == 0 & newY == 0){
                newX = screenX;
                oldX = screenX;
                newY = screenY;
                oldY = screenY;
            }else{
                oldX = newX;
                oldY = newY;
                newX = screenX;
                newY = screenY;
            }
            if(newX-oldX > 5){
                rotateAngle = -1;
            }else if(newX-oldX < -5){
                rotateAngle = 1;
            }else{rotateAngle = 0;}
            return true;
        }
    }

    Код в основном уже знаком: ModelInstanceAdvanced - расширенный класс экземпляра модели (читать тут), MotionStateAdv - переопределенный метод применения преобразований объектов (читать тут), doneLoading - загрузка моделей и подготовка экземпляров (тут). Обратите внимание на метод manInst.body.setAngularFactor(new Vector3(0f, 0f, 0f)) - с помощью него мы не разрешаем модели вращаться без нашего участия по всем осям, т.е. твердое тело не будет вращаться под действием внешних сил. Но это не значит, что мы не сможем вращать модель с помощью кода.

    Реализуем в главном классе интерфейс InputProcessor для обработки ввода. Использовать будем методы keyDown, keyUp и mouseMoved. Их будем использовать для управления персонажем в Desktop-проекте. В них мы изменяем флаги forward, back, right, left, jump и rotateAngle, которые будут отвечать за направление движения, поворот и прыжок.

    В onCreate создаем освещение, камеру, объект мира и настраиваем мультиплексор-контроллер. Камеру помещаем немного позади персонажа, как это обычно бывает в играх с видом от третьего лица

    В render мы проверяем состояние флагов движения и в зависимости от их состояния перемещаем и/или поворачиваем персонаж. Формулы, по которым вычисляется новое положение персонажа, учитывают поворот, т.е. если персонаж был повернут относительно первоначального положения, направление его движения не будет совпадать с направлением осей, поэтому в координаты перемещения вносится поправка на угол (вспоминаем школьную геометрию  - решение треугольников). Затем перемещаем и поворачиваем камеру, так чтобы она снова была позади персонажа - т.е камера будет следовать за персонажем.

    Этот код уже работоспособен, его можно запустить из Desktop-проекта и поперемещать персонаж.

    Теперь можно заняться сенсорным управлением. Переопределим класс Actor.

    public class ButtonActor extends Actor{
            TextureRegion region;
            final float pointX, pointY, scaleXY;
            
            public ButtonActor(TextureRegion tex, float oX, float oY, float sca, String name){
                region = new TextureRegion(tex);
                pointX = oX*sca*64;
                pointY = oY*sca*40;
                scaleXY = sca;
                this.setTouchable(Touchable.enabled);
                setName(name);
            }
            
            @Override
            public void draw(Batch batch, float parentAlpha){
                batch.draw(region, pointX, pointY, getOriginX(), getOriginY(),
                        region.getRegionWidth(), region.getRegionHeight(), scaleXY, scaleXY, getRotation ());
            }
            
            @Override
            public Actor hit (float x, float y, boolean touchable) {
                return x >= pointX && x < pointX +100*scaleXY && y >= pointY && y < pointY + 100*scaleXY ? this : null;
            }
        }

    В конструктор передаем изображение кнопки, положение на экране, масштаб и имя актера. Вычисляем координаты левого нижнего угла актера. Для того чтобы на экранах с разным разрешением кнопки располагались одинаково, разделим экран на прямоугольники сеткой 20х20. За эталон возьмем разрешение 1280х600. Соответственно размер каждой ячейки сетки получится 64х40. Умножив эти значения на значение масштаба, получим размер сетки для конкретного разрешения экрана. В параметрах конструктора будем получать положение актера в этой сетке. Умножая положение в сетке на её размер, получим экранные координаты.

    В методе draw рисуем актера в полученных координатах, не забывая отмасштабировать размер кнопки. В методе hit сверяем координаты касания с координатами актера - если кнопка была нажата возвращаем самого актера, иначе null.

    Также нужно переопределить класс Stage.

    public class TheStage extends Stage{
            public boolean touchDown(int screenX, int screenY, int pointer, int button) {
                screenY = scrY - screenY;
                Actor actor = hit(screenX, screenY, true);
                if(actor == null){
                    aName = "null";
                    return false;
                }else{
                    aName = actor.getName();
                    if(aName.equals("forward")){
                        forward = true;
                    }
                    if(aName.equals("back")){
                        back = true;
                    }
                    if(aName.equals("right")){
                        right = true;
                    }
                    if(aName.equals("left")){
                       left = true;
                    }
                    if(aName.equals("rotate_left")){
                        rotateAngle = -1;                    
                    }
                    if(aName.equals("rotate_right")){
                        rotateAngle = 1;                    
                    }
                    if(aName.equals("jump")){
                        if(jump >= 1)jump = 0;
                    }
                    return true;
                }
            }
            @Override
            public boolean touchUp(int screenX, int screenY, int pointer, int button) {
                screenY = scrY - screenY;
                Actor actor = hit(screenX, screenY, true);
                if(actor == null){
                    aName = "null";
                    return false;
                }else{
                    aName = actor.getName();
                    if(aName.equals(null)){
                        return false;
                    }else if(aName.equals("rotate_right")||aName.equals("rotate_left")){
                        rotateAngle = 0;
                        return true;
                    }else if(aName.equals("forward")){
                        forward = false;
                        return true;
                    }else if(aName.equals("back")){
                        back = false;
                        return true;
                    }else if(aName.equals("right")){
                        right = false;
                        return true;
                    }else if(aName.equals("left")){
                        left = false;
                        return true;
                    }else{
                        return false;
                    }
                }
            }
            @Override
            public boolean touchDragged(int screenX, int screenY, int pointer) {
                return false;
            }
            
            @Override
            public Actor hit(float x, float y, boolean touchable) {
                Actor  actor  = super.hit(x,y,touchable);
                return actor;
            }
        }

    У сцены реализуем touchDown и touchUp. Когда произошло событие, вызываем hit, который возвращает актера на котором произошло касание/отпускание, либо null. Перед вызовом нужно привести координату Y из системы отсчета сенсора в систему экрана. Если hit вернул нам актера, извлекаем из него имя и в зависимости от имени актера меняем тот или иной флаг.

    Метод hit у нас просто вызывает метод суперкласса.

    Классы мы подготовили, переходим к размещению актеров.

    public class interface2d extends ApplicationAdapter implements InputProcessor {
        ......
        TheStage stage;
        TextureAtlas buttons;
        ButtonActor btnForward, btnBack, btnRight, btnLeft, btnRotateRight, btnRotateLeft, btnJump;
        ......   
        @Override
        public void create () {
            ......
            scrX = Gdx.graphics.getWidth();
            scrY = Gdx.graphics.getHeight();
            scaling = scrX/1280f;
            
            buttons = new TextureAtlas(Gdx.files.internal("img/buttons.atlas"));
            stage = new TheStage();
            btnForward = new ButtonActor(buttons.findRegion("forward"), 16f, 5f, scaling, "forward");
            btnBack = new ButtonActor(buttons.findRegion("back"), 16f, 1f, scaling, "back");
            btnRight = new ButtonActor(buttons.findRegion("right"), 18f, 3f, scaling, "right");
            btnLeft = new ButtonActor(buttons.findRegion("left"), 14f, 3f, scaling, "left");
            btnRotateRight = new ButtonActor(buttons.findRegion("rotate_right"), 1f, 2f, scaling, "rotate_right");
            btnRotateLeft = new ButtonActor(buttons.findRegion("rotate_left"), 3f, 2f, scaling, "rotate_left");
            btnJump = new ButtonActor(buttons.findRegion("jump"), 5f, 2f, scaling, "jump");
            
            stage.addActor(btnForward);
            stage.addActor(btnBack);
            stage.addActor(btnRight);
            stage.addActor(btnLeft);
            stage.addActor(btnRotateRight);
            stage.addActor(btnRotateLeft);
            stage.addActor(btnJump);
                    
            multiplexer = new InputMultiplexer(); 
            ....
            multiplexer.addProcessor(stage);
            Gdx.input.setInputProcessor(multiplexer);
        }
       
        @Override
        public void render () {
                .....
                stage.draw();
            }
        }
    }

    В onCreate получаем размер экрана устройства и вычисляем масштаб, который будем передавать в конструктор актеров. Инициализируем атлас. Создаем кнопки передавая регионы текстур, положение на сетке, масштаб и имя. Затем добавляем актеров в сцену. Поскольку stage реализует обработку ввода, добавляем его в мультиплексор. Отрисовываем сцену в render. Готово. Можно тестить на Андроид-устройстве. Но кнопки будут работать и в Desktop-проекте.

     

    libGDX. Scene2D.
    libGDX. Формы столкновений.
    libGDX. Bullet. Динамика твердого тела. Часть 2.
    libGDX. Bullet. Динамика твердого тела.
    libGDX. Обработка столкновений. Библиотека Bullet. Часть 3.
    libGDX. Обработка столкновений. Библиотека Bullet. Часть 2.
    libGDX. Обработка столкновений. Библиотека Bullet.
    libGDX. Интерактивное взаимодействие с 3D объектами.
    libGDX. Отбраковка объектов не попадающих в обзор камеры.
    3D модель  для libGDX. Пишем код.
    3D модель для движка libGDX
    Строим модель с помощью ModelBuilder
    libGDX. Основы 3D программирования.
    Игра Flower. Ловим капли.
    TexturePacker.Создаем атлас текстур.
    Создаем проект на движке libGDX
    Кастомизация EditText
    Кастомизация SeekBar'а
    9-patch изображения для Андроид
    Кастомный ползунок в виде дуги (аналог SeekBar)
    Анимация в Андроид
    Кастомизация элементов управления в Android
    Создание кастомного View-элемента интерфейса.
    Создание виджета - электронные часы с кастомным шрифтом

    Категория: libGDX | Добавил: Olelucoye (02.06.2015)
    Просмотров: 3333 | Комментарии: 1
    | Теги: STAGE, scene2d, сенсорное управление персонажем, actor, андроид, libGDX | Рейтинг: 0.0/0
    Всего комментариев: 1
    1 ReSeD   (22.10.2015 10:30)
    Интересная статья)

    Добавлять комментарии могут только зарегистрированные пользователи.
    [ Регистрация | Вход ]
    Меню сайта
    Категории раздела
    Андроид разработка [23]
    libGDX [24]
    Мои андроид проекты [6]
    Excel [7]
    Железяки [5]
    Скрипты в блокноте [4]
    Разное [1]
    Форма входа
    Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0
    Яндекс Метрика
    Яндекс.Метрика