libGDX. Урок 14. Падение нескольких мячей

Падение нескольких мячей

В этом уроке мы научимся запускать несколько мячей в нашей игре. Ко всему прочему мы изучим, как оптимизировать этот процесс.

Перво-наперво нам необходимо добавить «флаг» проверки «жив» мяч или нет (это понадобится нам для того, чтобы понимать отображать мяч или нет). Добавим его в классе Ball:

public Circle ballCircle; //круг для обнаружения столкновений мяча
public boolean isAlive; // флаг для проверки "жив" мяч или нет

 
Будем устанавливать флаг в значение false при его столкновении с землей или с корзиной. Добавьте следующую строку в метод checkCollisionsWithBasket():

// проверка, было ли столкновение между мячом и корзиной
if(Intersector.overlaps(ballCircle, GameManager.basket.basketRectangle)){
    isAlive=false;
    System.out.println("Collided with basket");
    return true;
}

И такую же строку добавьте в метод checkCollisionsWithGround():

// проверяем, упал ли мяч на землю?
if(position.y <= 0.0){
    isAlive=false;
    System.out.println("Collided with ground"); // для проверки, попозже удалим эту строчку.
    return true;
}

В классе GameManager установим к началу игры значение переменной isAlive как true:

ball.velocity.set(0, 0);
ball.isAlive=true;

Пока мячик жив, нам нужно отображать и обновлять позицию отображения его на экране устройства. Сделаем это следующим образом в методе renderGame():

if(ball.isAlive) {
    ball.update();
    ball.render(batch);
}

Теперь, если мы хотим запускать несколько мячей нужно создать массив. Сделаем это в классе GameManager:

private static final float BALL_RESIZE_FACTOR = 5500f;
public static Array<Ball> balls = new Array<Ball>(); // массив объектов ball

Далее создадим новый класс с именем SpawnManager, который будет обрабатывать создание и удаление объектов класса Ball:

класс в android studio

класс в android studio

 
Код для этого класса ниже:

package com.mygdx.basketball.managers;


import com.badlogic.gdx.graphics.Texture;

public class SpawnManager {

    static float delayTime = 0.8f; // задержка между появлением следующего мяча
    static float delayCounter = 0.0f; // счетчик для отслеживания задержки
    static float width,height; // ширина и высота области просмотра игры
    static Texture ballTexture; // текстура мяча

    public static void initialize(float width,float height,Texture ballTexture){

        SpawnManager.width = width;
        SpawnManager.height = height;
        SpawnManager.ballTexture = ballTexture;
        delayCounter=0.0f; // сброс счетчика задержки
    }

}

В этом классе мы объявили переменную delayTime для отслеживания задержки между созданием следующего мяча. Переменная delayCounter нужна нам для отслеживания времени, которое прошло с последнего создания мяча. Создавать экземпляры ball и инициализировать их мы будем в этом классе. Именно поэтому здесь объявлены переменные для хранения размеров области просмотра и текстуры мяча. Мы инициализируем эти значения теми, которые получаем в методе initialize() в классе GameManager.

После чего нам нужно создать метод createNewBall() в этом классе. Используем похожую логику инициализации мяча, которую до этого использовали в классе GameManager:

public static Ball createNewBall(){
    Ball ball = new Ball();
    ball.ballSprite = new Sprite(ballTexture);
    ball.ballSprite.setSize(ball.ballSprite.getWidth() *(width/BALL_RESIZE_FACTOR), ball.ballSprite.getHeight()* (width/BALL_RESIZE_FACTOR));
    ball.position.set(0.0f, height-ball.ballSprite.getHeight());
    ball.velocity.set(0, 0);
    ball.isAlive=true;
    Vector2 center = new Vector2();
    // установка центра вектора в центре спрайта мяча
    center.x=ball.position.x + (ball.ballSprite.getWidth()/2);
    center.y=ball.position.y + (ball.ballSprite.getHeight()/2);
    ball.ballCircle = new Circle(center, (ball.ballSprite.getHeight()/2));
    return ball;
}

Импортируйте все недостающие библиотеки в этот класс (об этом вам напомнит Android Studio). Помимо этого, переместите в класс SpawnManager переменную BALL_RESIZE_FACTOR.

Этот метод будем вызывать, когда захотим создать новый мяч. Создаем и инициализируем новый мяч и возвращаем его. Наряду с этим, нам необходимо избавиться от тех мячей, которые уже не видны пользователю (это те экземпляры, переменная isAlive которых приняла значение false). Сейчас мы создадим переменную для отслеживания индексов мячей, которые уже не нужны:

static Texture ballTexture; // текстура мяча
private static final float BALL_RESIZE_FACTOR = 5500f;
static List<Integer> removeIndices = new ArrayList<Integer>(); // хранит индексы шаров, которые нужно удалить

Для удаление этих объектов напишем метод cleanUp():

public static void cleanup(Array<Ball> balls){

    removeIndices.clear(); // очищаем список индексов
    for(int i=balls.size-1 ; i>=0 ; i--){
        if(!balls.get(i).isAlive){
            removeIndices.add(i); // получаем индексы объектов ball, которые нам не нужны (isAlive=false)
        }
    }

    // Удаляем объекты ball из массива, согласно индексу
    for (int i = 0 ; i<removeIndices.size(); i++) {
        balls.removeIndex(i);
    }
}

В этой части кода происходит следующее: мы проводим итерацию массива balls для обнаружения неактивных объектов. Записываем каждый такой объект в список removeIndices. Отметим что мы начинаем с конца массива и получаем индексы в убывающем порядке. Таким образом обеспечим правильное удаление элементов. После этого давайте создадим метод run(), который будет отвечать за логику тайминга и создания объектов («мячей»):

public static void run(Array<Ball> balls){
    // если счетчик delaycounter превысил значение в delayTime
    if(delayCounter>=delayTime){
        balls.add(createNewBall()); // создать новый мяч
        delayCounter=0.0f;// сбросить счетчик задержки
    }
    else{
        delayCounter += Gdx.graphics.getDeltaTime();
        //в противном случае аккумулируем счетчик задержки
    }
}

Здесь мы проверяем превысил или нет счетчик задержки времени (delayCounter) то значение, которое мы записали в переменную delayTime. Если превысил, то создаём новый мяч и добавляем в массив balls. В противном случае аккумулируем счетчик задержки.

 
Теперь нужно произвести некоторые модификации в классе GameManager. В первую очередь нужно удалить строки кода, которыми мы инициализируем одиночный экземпляр мяча. Код инициализации текстуры оставляем. Далее нам нужно добавить метод инициализации класса SpawnManager в метод initialize() класса GameManager:

    backgroundSprite.setSize(width, height); // устанавливаем размер заднего фона по размеру экрана предполагаемого устройства

    ball = new Ball();
    ballTexture = new Texture(Gdx.files.internal("ball.png"));
    SpawnManager.initialize(width, height, ballTexture);


    ball.ballSprite = new Sprite(ballTexture);
    ball.ballSprite.setSize(ball.ballSprite.getWidth()* (width/BALL_RESIZE_FACTOR), ball.ballSprite.getHeight()*(width/BALL_RESIZE_FACTOR));
    ball.position.set(0.0f, height-ball.ballSprite.getHeight());
    ball.velocity.set(0, 0);
    ball.isAlive = true;

    Vector2 center = new Vector2();
    // обозначим центр круга в центре спрайта нашего мяча
    center.x=ball.position.x + (ball.ballSprite.getWidth()/2);
    center.y=ball.position.y + (ball.ballSprite.getHeight()/2);
    ball.ballCircle = new Circle(center, (ball.ballSprite.getHeight()/2));
            
} // конец метода initialize()

Ну и наконец редактируем код в методе renderGame():

public static void renderGame(SpriteBatch batch){

    backgroundSprite.draw(batch);
    basket.render(batch);

    SpawnManager.run(balls);
    for(Ball ball:balls) {

        if (ball.isAlive) {
            ball.update();
            ball.render(batch);
        }
    }
    SpawnManager.cleanup(balls);

}

Запускаем нашу игру на эмуляторе и видим, что мячики падают с задержкой по времени:

игра приложение android studio libgdx

В следующем уроке добавим случайности для места появления мячей и немного соптимизируем код для снижения нагрузки на мобильное устройство потенциальных пользователей. Код для этого урока можно скачать внизу:

Скачать исходники