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

    Игра Flower. Ловим капли.

    Игра Flower. Ловим капли.

    В предыдущей статье мы подготовили ресурсы для игры. Приступаем к написанию кода.

    Сначала настроим стартеры для платформ. Открываем десктоп-проект, находим DesktopLauncher.java. Видим:

    public class DesktopLauncher {
        public static void main (String[] arg) {
            LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
            new LwjglApplication(new FlowerGame(), config);
        }
    }

    Для запуска проекта этого достаточно, но мы хотим задать несколько своих параметров - размеры окна и заголовок. Для этого их нужно передать в объект настроек config, который создается первой строкой метода main. Получится так:

    public class DesktopLauncher {
        public static void main (String[] arg) {
            LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
            config.title = "Flower";
            config.width = 800;
            config.height = 480;
            new LwjglApplication(new FlowerGame(), config);
        }
    }

    Теперь игра на компьютере будет запускаться в окне размером 800х480 пикселей с заголовком "Flower".

    В андроид-проекте открываем AndroidLauncher.java. Здесь мы не можем контролировать размер окна - оно всегда будет во весь экран устройства. Но мы можем отключить неиспользуемые источники ввода - акселерометр и компас, чтобы игра не потребляла энергию батареи, поддерживая работоспособность этих приборов.

    public class AndroidLauncher extends AndroidApplication {
        @Override
        protected void onCreate (Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
            config.useAccelerometer = false;
            config.useCompass = false;
            initialize(new FlowerGame(), config);
        }
    }

    Переходим к проекту с постфиксом -core. В нем находится главный класс нашей игры (его имя мы указываем при создании проекта в поле Game class. У меня это FlowerGame). Здесь мы описываем жизненный цикл игры. Какие методы он в себя включает:

    create() - вызывается один раз при создании приложения, здесь мы создаем объекты, инициализируем переменные - в общем ведем подготовительные работы.

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

    resize(int width, int height) - вызывается при изменении размеров окна (для десктопа)

    pause() - вызывается на Андроид когда нажата кнопка Home или при входящем звонке, на десктопе вызывается один раз при закрытии игры перед методом dispose(). Рекомендуется в этом методе сохранять состояние игры.

    resume() - вызывается только на Андроид, когда приложение возвращается из состояния паузы в рабочее состояние.

    dispose() - вызывается во время уничтожения игры, здесь нужно освобождать ресурсы.

    Практически все ресурсы, используемые в libGDX (текстуры, камеры, отрисовщики, модели, звуки и многие другие), не являются "родными" для java и сборщик мусора их не уничтожает. Поэтому их нужно удалять самим в методе dispose(). Если вы не знаете, нужно ли уничтожать какой-то объект вручную, проверьте его на наличие метода object.dispose() - если он у объекта есть - уничтожать нужно.

    Объявим несколько объектов - текстуры  капли, сообщение о победе/поражении, атлас текстур, массив регионов текстур, звуки капли и дождя, ортографическая камера и отрисовщик текстур SpriteBatch.

        SpriteBatch batch;
        Texture dropImage;
        Texture gameOver;
        Texture win;
        Sound dropSound;
        Music rainMusic;
        OrthographicCamera camera;
        TextureAtlas potatlas;
        TextureRegion[] pots;

    Что такое текстуры, атласы и регионы мы уже рассматривали, со звуками тоже все просто. Что такое SpriteBatch и OrthographicCamera?

    SpriteBatch - отрисовщик спрайтов (текстур) - накапливает текстуры необходимые  для отрисовки кадра, затем разом отрисовывает их. Это нужно, чтобы текстуры не появлялись одна за одной, а картинка показывалась единой сразу.

    Ортографическая камера - это наше 2D-окно в мир игры. Даже создавая двумерную игру, мы отрисовываем объекты в трехмерном пространстве (только от нас это скрыто). Ортографическая камера представляет двумерную проекцию трехмерного мира.

    Проинициализируем эти объекты в методе create()

        @Override
        public void create () {
            batch = new SpriteBatch();
            dropImage = new Texture("drop.png");
            gameOver = new Texture("game_over.png");
            win = new Texture("win.png");
            
            potatlas = new TextureAtlas(Gdx.files.internal("pots/potatlas.atlas"));
            pots = new TextureRegion[7];
            pots[0] = potatlas.findRegion("pot0");
            pots[1] = potatlas.findRegion("pot1");
            pots[2] = potatlas.findRegion("pot2");
            pots[3] = potatlas.findRegion("pot3");
            pots[4] = potatlas.findRegion("pot4");
            pots[5] = potatlas.findRegion("pot5");
            pots[6] = potatlas.findRegion("pot6");

            dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
            rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));
            rainMusic.setLooping(true);
            rainMusic.play();
            
            camera = new OrthographicCamera();
            camera.setToOrtho(false, 800, 480);
        }

    Тут все достаточно понятно - привязываем текстуры и атлас, из атласа получаем регионы, привязываем звуки, фоновый звук дождя зацикливаем и сразу воспроизводим, камеру настраиваем на размер 800х480, т.е. на весь экран.

    Сразу же напишем код освобождения памяти в dispose() :

    @Override
        public void dispose(){
            dropImage.dispose();
            dropSound.dispose();
            rainMusic.dispose();
            batch.dispose();
            potatlas.dispose();
        }

    Уничтожать регионы текстур отдельно не нужно, так как они являются только ссылками на области атласа.

    Еще нам понадобятся несколько объектов Rectangle для хранения положений и размеров капель и горшка. Также мы будем использовать их для определения коллизий (столкновений). Еще нужен будет объект Vector3 для хранения координат нажатия на экран или крика мышью. Также  заведем переменную, в которой будем хранить время образования последней капли. Объявим их и инициализируем.

        Rectangle bucket, raindrop;
        Vector3 touchPos;
        Array<Rectangle> raindrops;
        long lastDropTime;
           
        @Override
        public void create () {
           .......

            bucket = new Rectangle();
            bucket.x = 800/2 - 64/2;
            bucket.y = 20;
            bucket.width = 64;
            bucket.height = 64;
            touchPos = new Vector3();
            raindrop = new Rectangle();
            raindrops = new Array<Rectangle>();
        }

    Ведро помещаем в середину оси x и на 20 пикселей выше нижнего края экрана. Размер задаем 64х64 пикселя. Для генерирования капель и помещения их массив напишем метод spawnRaindrop:

    private void spawnRaindrop(){
            Rectangle raindrop = new Rectangle();
            raindrop.x = MathUtils.random(0, 800-64);
            raindrop.y = 480;
            raindrop.width = 32;
            raindrop.height = 32;
            raindrops.add(raindrop);
            lastDropTime = TimeUtils.nanoTime();
        }

    Здесь случайным образом определяем положение новой капли на оси x и помещаем её вверх экрана. Размеры устанавливаем равными размеру текстуры. Добавляем каплю в массив и обновляем время появления новой капли.

    Переходим к методу render()

        @Override
        public void render () {
            Gdx.gl.glClearColor(0, 0, 0.2f, 1);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
            camera.update();
            batch.setProjectionMatrix(camera.combined);
            batch.begin();
            batch.draw(pots[0], bucket.x, bucket.y);
            for(Rectangle drop: raindrops){
                batch.draw(dropImage, drop.x, drop.y);
            }
           batch.end();

    Сначала очищаем экран, закрашивая его синим цветом. Обновляем камеру (хотя в нашем случае это необязательно, так как в процессе игры свойства камеры не меняются, но считается хорошей практикой обновлять камеру раз за кадр) передаем отрисовщику матрицу проекции камеры, для согласования системы координат. begin - дает команду отрисовщику начать сбор текстур. Рисуем ведро в координатах записанных в прямоугольнике bucket. В цикле отрисовываем все капли из массива. end - сбор текстур закончен, отображаем все на экране.

    Дальше обрабатываем ввод. isTouched - показывает, что было касание экрана или клик мышью. Получаем координаты касания/клика, переводим их в систему координат камеры и помещаем ведро в точку касания/клика. Также обрабатываем нажатия клавиш вправо-влево (для ПК) - перемещаем ведро на 200 пикселей умноженное на время прошедшее с отрисовки последнего кадра, т.е фактически мы задаем скорость перемещения 200 пикселей в секунду.

            if(Gdx.input.isTouched()){
                touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
                camera.unproject(touchPos);
                bucket.x = touchPos.x - 64/2;
            }
            if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
            if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();
            if(bucket.x <0)bucket.x = 0;
            if(bucket.x > 800 - 64)bucket.x = 800 - 64;
            if(TimeUtils.nanoTime() - lastDropTime > 1000000000)spawnRaindrop();

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

    Дальше создаем итератор для перебора всех элементов массива капель. Смещаем каждую каплю вниз со скоростью 200 пикселей в секунду. Проверяем достигла ли капля нижнего края, если достигла - удаляем её из массива. Также проверяем не пересекается ли капля с ведром. В случае если пересекается воспроизводим звук капли и тоже удаляем каплю из массива.
                
            Iterator<Rectangle> iter = raindrops.iterator();
                while(iter.hasNext()){
                    raindrop = iter.next();
                    raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
                    if(raindrop.y + 64 < 0){
                        iter.remove();
                    }
                    if(raindrop.overlaps(bucket)){
                        dropSound.play();
                        iter.remove();
                    }
                }

    Теперь нам нужно сделать так, чтобы изображение ведра по мере ловли капель менялось и когда цветок в ведре расцветет игра оканчивалась победой. А также при падении нескольких капель мимо ведра, игра должна оканчиваться проигрышем. Для этого заведем три целочисленные переменные и внесем следующие изменения в метод render:

        int progress = 0;
        int dropCatched = 0;
        int dropOver = 0;

    @Override
        public void render () {
            ....
            batch.draw(pots[progress], bucket.x, bucket.y);
            ....
            if(progress == 6)batch.draw(win, 800/2-512/2, 480/2-128/2);
            if(dropOver == 5)batch.draw(gameOver, 800/2-512/2, 480/2-128/2);
            ....
            if(progress < 6 & dropOver <5){
                if(TimeUtils.nanoTime() - lastDropTime > 1000000000)spawnRaindrop();
                
                Iterator<Rectangle> iter = raindrops.iterator();
                while(iter.hasNext()){
                    raindrop = iter.next();
                    raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
                    if(raindrop.y + 64 < 0){
                        iter.remove();
                        dropOver++;
                    }
                    if(raindrop.overlaps(bucket)){
                        dropCatched++;
                        if(dropCatched == 10){
                            dropCatched = 0;
                            progress++;
                        }
                        dropSound.play();
                        iter.remove();
                    }
                }
            }
        }

    Каждая пойманная капля меняет dropCatched на единицу, по достижении 10 dropCatched обнуляется, а progress увеличивается на единицу. В зависимости от значения progress, для изображения ведра используется соответствующий элемент из массива pots. Каждая пропущенная капля увеличивает dropOver на единицу.

    Когда количество пропущенных капель достигает 5, отображаем текстуру с надписью GameOver. А когда progress достигнет максимального значения (6), т.е. изображение ведра будет с полностью распустившимся цветком, отображаем текстуру о победе. В обоих случаях останавливаем образование новых капель и движение существующих. Запускаем и наслаждаемся. Полный листинг главного класса смотрим здесь.


     

    TexturePacker.Создаем атлас текстур.
    Создаем проект на движке libGDX
    Кастомизация EditText
    Кастомизация SeekBar'а
    9-patch изображения для Андроид
    Кастомный ползунок в виде дуги (аналог SeekBar)
    Анимация в Андроид
    Кастомизация элементов управления в Android
    Смартфон DEXP Ixion ML 5, обзор.
    Создание кастомного View-элемента интерфейса.
    Создание виджета - электронные часы с кастомным шрифтом
    Программируем калькулятор на андроид. Урок 1.

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

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