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

    libGDX. Bullet. Динамика твердого тела.

    libGDX. Bullet. Динамика твердого тела.

    В предыдущих статьях (1, 2, 3) мы рассмотрели обработку столкновений с помощью библиотеки Bullet. Приступим к изучению следующей части этой библиотеки - динамика твердого тела. Добавим 3D миру некоторые свойства реального физического мира, такие как массу тел и гравитацию.

    Сначала мы должны добавить объектам некоторые физические свойства. Для этого мы должны заменить объект столкновений на объект твердого тела btRigidBody. Этот класс включает в себя все возможности btCollisionObject, а также содержит физические свойства тела (массу, учитывает силу трения, амортизационные свойства).

       static class ModelInstanceAdv extends ModelInstance implements Disposable{
            public final btRigidBody body;
            
      public ModelInstanceAdv(Model model, String node, btRigidBody.btRigidBodyConstructionInfo bodyInfo) {
                super(model, node);
                body = new btRigidBody(bodyInfo);
            }
            @Override
            public void dispose() {
                body.dispose();
            }
            
            static class Constructor implements Disposable{
                public final Model model;
                public final String node;
                public final btCollisionShape collShape;
                public final btRigidBody.btRigidBodyConstructionInfo bodyInfo;
                private static Vector3 inertia = new Vector3();
                public Constructor(Model model, String node, btCollisionShape collShape, float mass){
                    this.model = model;
                    this.node = node;
                    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, node, bodyInfo);
                }
                @Override
                public void dispose() {
                    collShape.dispose();
                    bodyInfo.dispose();
                }
            }
        }

    Теперь для создания объекта нам необходимо передать конструктору массу, по которой рассчитается вектор инерции. Если была передана нулевая масса - все параметры вектора инерции устанавливаем равными нулю. В реальности, конечно, объектов с нулевой массой не бывает, но в Bullet это означает, что этот объект не должен никак реагировать ни на какие приложенные к нему силы и столкновения. Именно таким свойством должна обладать "земля". Такие объекты называются статическими. Объекты обладающие массой большей нуля называются динамическими.

    Все необходимые данные помещаются в btRigidBodyConstructionInfo, который принимается в качестве параметра при создании объекта твердого тела.

    Теперь заполнение массива constructors будет выглядеть так (добавился еще один параметр в конце - масса):

    public void create () {
            .....
        constructors = new ArrayMap<String, ModelInstanceAdv.Constructor>(String.class, ModelInstanceAdv.Constructor.class);
        constructors.put("ground", new ModelInstanceAdv.Constructor(model, "ground", new btBoxShape(new Vector3(5f, 0.05f, 5f)), 0f));
        constructors.put("ball", new ModelInstanceAdv.Constructor(model, "ball", new btSphereShape(0.5f), 1f));
        constructors.put("box", new ModelInstanceAdv.Constructor(model, "box", new btBoxShape(new Vector3(0.5f, 0.5f, 0.5f)), 1f));
        constructors.put("cone", new ModelInstanceAdv.Constructor(model, "cone", new btConeShape(0.5f, 2f), 1f));
        constructors.put("cylinder", new ModelInstanceAdv.Constructor(model, "cylinder", new btCylinderShape(
                new Vector3(0.5f, 1f, 0.5f)), 1f));
        constructors.put("capsule", new ModelInstanceAdv.Constructor(model, "capsule", new btCapsuleShape( 0.5f, 1f), 1f));
            ......
        }

    Для применения динамики мы должны заменить btCollisionWorld на btDynamicsWorld, который расширит возможности мира.

        btDynamicsWorld dynWorld;
        btConstraintSolver solver;
        .....
        public void create () {
            .....
            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));
            contacter = new Contacter();
            instances = new Array<ModelInstanceAdv>();
            ModelInstanceAdv object = constructors.get("ground").create();
            instances.add(object);
            dynWorld.addRigidBody(object.body, GROUND_FLAG, ALL_FLAG);
        }
       
        public void birthObject(){
            ModelInstanceAdv obj = constructors.values[1 + MathUtils.random(constructors.size - 2)].create();
            obj.transform.setFromEulerAngles(MathUtils.random(360f), MathUtils.random(360f), MathUtils.random(360f));
            obj.transform.trn(MathUtils.random(5f, -5f), 9f, MathUtils.random(5f, -5f));
            obj.body.setWorldTransform(obj.transform);
            obj.body.setUserValue(instances.size);
            obj.body.setCollisionFlags(
                obj.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK);
            instances.add(obj);
            dynWorld.addRigidBody(obj.body, OBJECT_FLAG, GROUND_FLAG);
        }
        @Override
        public void dispose(){
            .....
            dynWorld.dispose();
            solver.dispose();
            .....
        }

    Также понадобится объект класса btConstraintSolver. Он нужен для прикрепления объектов друг к другу (в этой статье не рассматривается).

    После инициализации btDinamicsWorld, установим силу тяжести (гравитацию), которая будет действовать на все динамические объекты. В нашем случае она будет равна 10 метрам в секунду вдоль оси Y в отрицательном направлении.

    При создании объекта добавляем твердое тело в btDinamicsWorld методом addRigidBody. Метод addCollisionObject по-прежнему доступен, но addRigidBody гарантирует, что к объекту будут правильно применены силы.

    Поскольку теперь объекты падают под действием силы тяжести, нам не нужно перемещать объект вручную. Вместо этого мы должны дать команду DinamicsWorld обновить положение объектов.

         public void render () {
            .....
            final float delta = Math.min(1f/30f, Gdx.graphics.getDeltaTime());
            dynWorld.stepSimulation(delta, 5, 1f/60f);
            for(ModelInstanceAdv inst:instances){
                inst.body.getWorldTransform(inst.transform);
            }
            .....
        }

    Теперь вместо performDiscreteCollisionDetection мы вызываем stepSimulation для перерасчета состояния мира. Но внутри метода stepSimulation все еще вызывается performDiscreteCollisionDetection для обработки столкновений. DinamicsWorld использует для перерасчета состояния мира фиксированный шаг времени, который передается в третьем параметре (1f/60f). В случае если фактическое delta больше этого значения, то расчет будет произведен несколько раз. Максимальное число таких перерасчетов задается вторым параметром. Поскольку мы ограничили delta  числом 1f/30f, то фактически перерасчет никогда не будет произведен более двух раз.

    Так как Bullet теперь сам рассчитывает преобразования, мы должны получить их и передать экземплярам моделей. Делается это методом getWorldTransform.

    Поскольку нам теперь не нужно контролировать передвижение объектов, переменная moving нам больше не нужна. Её нужно удалить во всем коде, но это оставит пустым ContactListener. Для того чтобы убедиться, что он все еще слушает столкновения, добавим код, который будет изменять цвет объекта при контакте с землей.

        class Contacter extends ContactListener{
            @Override
            public boolean onContactAdded (int userValue0, int partId0, int index0,
                    int userValue1, int partId1, int index1){
                if(userValue0 == 0)
                    ((ColorAttribute) instances.get(userValue1).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
                if(userValue1 == 0)
                    ((ColorAttribute) instances.get(userValue0).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
                return true;
            }
        }

    Теперь посмотрим на этот код:

        public void render () {
            .....
            for(ModelInstanceAdv inst:instances){
                inst.body.getWorldTransform(inst.transform);
            }
            .....
        }

    Здесь мы перебираем все объекты и применяем к ним изменения положения. При большом количестве объектов на сцене, такой способ не слишком хорошо отражается на производительности. Но обычно из всего множества объектов в один момент времени изменяют свое положение в пространстве всего несколько. В Bullet существует механизм информирования о преобразовании объекта, который избавляет нас от необходимости перебирать все объекты. Для этого служит класс с методами обратного вызова btMotionState. Создадим свой класс наследующий btMotionState.

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

    Класс btMotionState имеет два метода, их оба нужно переопределить. Нам достаточно добавить обновления матриц. Конечно, здесь может быть и другой код (например, удалять объекты координата Y, которых стала отрицательной - упавшие с поверхности). Теперь нужно сообщить Bullet использовать этот класс для обновления состояний объектов.

        static class ModelInstanceAdv extends ModelInstance implements Disposable{
            public final btRigidBody body;
            public final MotionStateAdv motionState;
            
            public ModelInstanceAdv(Model model, String node, btRigidBody.btRigidBodyConstructionInfo bodyInfo) {
                super(model, node);
                body = new btRigidBody(bodyInfo);
                motionState = new MotionStateAdv();
                motionState.transform = transform;
                body.setMotionState(motionState);
            }
            @Override
            public void dispose() {
                body.dispose();
                motionState.dispose();
            }
            .....
        }

    Здесь мы создаем объект класса btMotionStateAdv, передаем ему матрицу созданного экземпляра модели и сообщаем Bullet обновлять положение объекта с помощью класса btMotionStateAdv. Последнее делается методом setMotionState.

    Также внесем некоторые изменения в метод создания нового объекта.

        public void birthObject(){
            ModelInstanceAdv obj = constructors.values[1 + MathUtils.random(constructors.size - 2)].create();
            obj.transform.setFromEulerAngles(MathUtils.random(360f), MathUtils.random(360f), MathUtils.random(360f));
            obj.transform.trn(MathUtils.random(5f, -5f), 9f, MathUtils.random(5f, -5f));
            obj.body.proceedToTransform(obj.transform);
            obj.body.setUserValue(instances.size);
            obj.body.setCollisionFlags(
                 obj.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK);
            instances.add(obj);
            dynWorld.addRigidBody(obj.body, OBJECT_FLAG, GROUND_FLAG);
        }

    Здесь мы вносим единственное изменение - заменяем вызов setWorldTransform на proceedToTransform. Этот метод обновляет не только матрицу объекта твердого тела, но и всех связанных объектов.

    Осталось удалить в render() код перебора объектов и обновления их матриц.

        public void render () {
            ......
            final float delta = Math.min(1f/30f, Gdx.graphics.getDeltaTime());
            dynWorld.stepSimulation(delta, 5, 1f/60f);
            if((birthTime -= delta) < 0){
                birthObject();
                birthTime = 1.5f;
            }
            .....
        }

    Запустим приложение. Происходит все то же самое. но теперь за обновление объектов у нас отвечает btMotionState.

    В статье использованы материалы туториала xoppa.

    Полный листинг главного класса.

    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.PerspectiveCamera;
    import com.badlogic.gdx.graphics.VertexAttributes.Usage;
    import com.badlogic.gdx.graphics.g3d.Environment;
    import com.badlogic.gdx.graphics.g3d.Material;
    import com.badlogic.gdx.graphics.g3d.Model;
    import com.badlogic.gdx.graphics.g3d.ModelBatch;
    import com.badlogic.gdx.graphics.g3d.ModelInstance;
    import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
    import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
    import com.badlogic.gdx.graphics.g3d.utils.CameraInputController;
    import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
    import com.badlogic.gdx.math.MathUtils;
    import com.badlogic.gdx.math.Matrix4;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.physics.bullet.Bullet;
    import com.badlogic.gdx.physics.bullet.collision.ContactListener;
    import com.badlogic.gdx.physics.bullet.collision.btBoxShape;
    import com.badlogic.gdx.physics.bullet.collision.btBroadphaseInterface;
    import com.badlogic.gdx.physics.bullet.collision.btCapsuleShape;
    import com.badlogic.gdx.physics.bullet.collision.btCollisionConfiguration;
    import com.badlogic.gdx.physics.bullet.collision.btCollisionDispatcher;
    import com.badlogic.gdx.physics.bullet.collision.btCollisionObject;
    import com.badlogic.gdx.physics.bullet.collision.btCollisionShape;
    import com.badlogic.gdx.physics.bullet.collision.btConeShape;
    import com.badlogic.gdx.physics.bullet.collision.btCylinderShape;
    import com.badlogic.gdx.physics.bullet.collision.btDbvtBroadphase;
    import com.badlogic.gdx.physics.bullet.collision.btDefaultCollisionConfiguration;
    import com.badlogic.gdx.physics.bullet.collision.btDispatcher;
    import com.badlogic.gdx.physics.bullet.collision.btSphereShape;
    import com.badlogic.gdx.physics.bullet.dynamics.btConstraintSolver;
    import com.badlogic.gdx.physics.bullet.dynamics.btDiscreteDynamicsWorld;
    import com.badlogic.gdx.physics.bullet.dynamics.btDynamicsWorld;
    import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody;
    import com.badlogic.gdx.physics.bullet.dynamics.btSequentialImpulseConstraintSolver;
    import com.badlogic.gdx.physics.bullet.linearmath.btMotionState;
    import com.badlogic.gdx.utils.Array;
    import com.badlogic.gdx.utils.ArrayMap;
    import com.badlogic.gdx.utils.Disposable;
    public class CollisionsTest extends ApplicationAdapter {
        public PerspectiveCamera cam;
        public Model model;
        public Array<ModelInstanceAdv> instances;
        public ArrayMap<String, ModelInstanceAdv.Constructor> constructors;
        public ModelBatch modelBatch;
        public Environment environment;
        public CameraInputController camController;
        boolean collision_flag;
        btCollisionShape groundShape, thingShape;
        btCollisionObject groundObject, thingObject;
        btCollisionConfiguration collisionConf;
        btDispatcher collisionDispatcher;
        float birthTime;
        Contacter contacter;
        
        btBroadphaseInterface broadphase;
        btDynamicsWorld dynWorld;
        btConstraintSolver solver;
        final static short GROUND_FLAG = 1 << 8;
        final static short OBJECT_FLAG = 1 << 9;
        final static short ALL_FLAG = -1;
        
        static class ModelInstanceAdv extends ModelInstance implements Disposable{
            public final btRigidBody body;
            public final MotionStateAdv motionState;
            
            public ModelInstanceAdv(Model model, String node, btRigidBody.btRigidBodyConstructionInfo bodyInfo) {
                super(model, node);
                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 String node;
                public final btCollisionShape collShape;
                public final btRigidBody.btRigidBodyConstructionInfo bodyInfo;
                private static Vector3 inertia = new Vector3();
                public Constructor(Model model, String node, btCollisionShape collShape, float mass){
                    this.model = model;
                    this.node = node;
                    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, node, bodyInfo);
                }
                @Override
                public void dispose() {
                    collShape.dispose();
                    bodyInfo.dispose();
                }
            }
        }
        
        class Contacter extends ContactListener{
            @Override
            public boolean onContactAdded (int userValue0, int partId0, int index0,
                    int userValue1, int partId1, int index1){
                if(userValue0 == 0)
                    ((ColorAttribute) instances.get(userValue1).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
                if(userValue1 == 0)
                    ((ColorAttribute) instances.get(userValue0).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
                return true;
            }
        }
        
        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();
            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(10f, 10f, 10f);
            cam.lookAt(0f, 0f, 0f);
            cam.near = 1f;
            cam.far = 300f;
            cam.update();
            camController = new CameraInputController(cam);
            Gdx.input.setInputProcessor(camController);
            
            ModelBuilder modelBuilder = new ModelBuilder();
            modelBuilder.begin();
            modelBuilder.node().id = "ground";
            modelBuilder.part("box", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.GRAY))).box(10f, 0.1f, 10f);
            modelBuilder.node().id = "ball";
            modelBuilder.part("ball", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.RED))).sphere(1f, 1f, 1f, 20, 20);
            modelBuilder.node().id = "box";
            modelBuilder.part("box", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.GREEN))).box(1f, 1f, 1f);
            modelBuilder.node().id = "cone";
            modelBuilder.part("cone", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.BLUE))).cone(1f, 2f, 1f, 20);
            modelBuilder.node().id = "cylinder";
            modelBuilder.part("cylinder", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.YELLOW))).cylinder(1f, 2f, 1f, 20);
            modelBuilder.node().id = "capsule";
            modelBuilder.part("capsule", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.MAGENTA))).capsule(0.5f, 2f, 20);
            model = modelBuilder.end();
            
            constructors = new ArrayMap<String, ModelInstanceAdv.Constructor>(String.class, ModelInstanceAdv.Constructor.class);
            constructors.put("ground", new ModelInstanceAdv.Constructor(model, "ground", new btBoxShape(new Vector3(5f, 0.05f, 5f)), 0f));
            constructors.put("ball", new ModelInstanceAdv.Constructor(model, "ball", new btSphereShape(0.5f), 1f));
            constructors.put("box", new ModelInstanceAdv.Constructor(model, "box", new btBoxShape(new Vector3(0.5f, 0.5f, 0.5f)), 1f));
            constructors.put("cone", new ModelInstanceAdv.Constructor(model, "cone", new btConeShape(0.5f, 2f), 1f));
            constructors.put("cylinder", new ModelInstanceAdv.Constructor(model, "cylinder", new btCylinderShape(new Vector3(0.5f, 1f, 0.5f)), 1f));
            constructors.put("capsule", new ModelInstanceAdv.Constructor(model, "capsule", new btCapsuleShape( 0.5f, 1f), 1f));
            
            
            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));
            contacter = new Contacter();
            instances = new Array<ModelInstanceAdv>();
            ModelInstanceAdv object = constructors.get("ground").create();
            instances.add(object);
            dynWorld.addRigidBody(object.body, GROUND_FLAG, ALL_FLAG);
        }
        @Override
        public void render () {
            Gdx.gl.glViewport ( 0 , 0 , Gdx.graphics.getWidth (), Gdx.graphics.getHeight ());
            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());
            dynWorld.stepSimulation(delta, 5, 1f/60f);
            if((birthTime -= delta) < 0){
                birthObject();
                birthTime = 1.5f;
            }
            camController.update();
            modelBatch.begin(cam);
            modelBatch.render(instances, environment);
            modelBatch.end();
        }
        
        public void birthObject(){
            ModelInstanceAdv obj = constructors.values[1 + MathUtils.random(constructors.size - 2)].create();
            obj.transform.setFromEulerAngles(MathUtils.random(360f), MathUtils.random(360f), MathUtils.random(360f));
            obj.transform.trn(MathUtils.random(5f, -5f), 9f, MathUtils.random(5f, -5f));
            obj.body.proceedToTransform(obj.transform);
            obj.body.setUserValue(instances.size);
            obj.body.setCollisionFlags(obj.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK);
            instances.add(obj);
            dynWorld.addRigidBody(obj.body, OBJECT_FLAG, GROUND_FLAG);
        }
        @Override
        public void dispose(){
            for(ModelInstanceAdv inst:instances)inst.dispose();
            instances.clear();
            for(ModelInstanceAdv.Constructor cons:constructors.values())cons.dispose();
            constructors.clear();
            dynWorld.dispose();
            solver.dispose();
            broadphase.dispose();
            collisionDispatcher.dispose();
            collisionConf.dispose();
            contacter.dispose();
            modelBatch.dispose();
            model.dispose();
        }
    }

    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
    Смартфон DEXP Ixion ML 5, обзор.
    Создание кастомного View-элемента интерфейса.
    Создание виджета - электронные часы с кастомным шрифтом
    Программируем калькулятор на андроид. Урок 1.

    Категория: libGDX | Добавил: Olelucoye (03.05.2015)
    Просмотров: 1653
    | Теги: Rigidbody, DynamicsWorld, объект твердого тела, 3D, libGDX | Рейтинг: 0.0/0
    Всего комментариев: 0
    Добавлять комментарии могут только зарегистрированные пользователи.
    [ Регистрация | Вход ]
    Меню сайта
    Категории раздела
    Андроид разработка [23]
    libGDX [24]
    Мои андроид проекты [6]
    Excel [7]
    Железяки [5]
    Скрипты в блокноте [4]
    Разное [1]
    Форма входа
    Статистика

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