вторник, 8 мая 2012 г.

(АРХИВ GLES1)Стрельба. (Создание и удаление множества однотипных спрайтов)

Для начала подготовим код для наших "пуль". Будут использованы анимированные спрайты. В моем случае это 8 кадров, каждый размером 32*32 пискела. При нажатии на любую область экрана из самолетика (из-под левого и правого крыла) - должна вылетать пара шариков плазмы. И лететь относительно быстро вверх по экрану. Должен быть некоторый счетчик выстрелов в секунду, разрешающий делать выстрелы не чаще, чем например 3 штуки в секунду. Конечно, улетевшие вверх, за область экрана, шарики - надо удалять из сцены.

Вот код:
// добавить в начало - к переменным
private AnimatedSprite mAnimatedShotSprite;
private BitmapTextureAtlas mAnimatedShots;
private TiledTextureRegion mAnimatedSpriteShotTextureRegion;

private float shotSpeed = 200;
private float fireCounter = 0, fireRate= 0.33f;
private float delta, prevTime = 0;

// добавить в onLoadResources()

this.mAnimatedShots = new BitmapTextureAtlas(64, 32, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
this.mAnimatedSpriteShotTextureRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset
(this.mAnimatedShots, this, "shots5.png", 0, 0, 4, 2);
this.mEngine.getTextureManager().loadTexture(this.mAnimatedShots);

// добавим это в OnLoadScene() после создания сцены:
this.mBackSprite1 = new Sprite(0, 0, 320, 480, this.mBackTextureRegion1){
  @Override
  public boolean onAreaTouched(TouchEvent pSceneTouchEvent,
             float pTouchAreaLocalX, float pTouchAreaLocalY)
    {
     createShot(sX,sY);
     return true;
    }  
 };
mScene.registerTouchArea(this.mBackSprite1); 

// добавим этот код в основной цикл игры:
delta = AE_Test1_Activity.this.mEngine.getSecondsElapsedTotal() - prevTime;
prevTime = AE_Test1_Activity.this.mEngine.getSecondsElapsedTotal();
fireCounter += delta;
updateShots(pSecondsElapsed);

// добавим в код функцию создания выстрелов. Вставляем ее в самый конец кода:
private void createShot(float x, float y){
 if (fireCounter >= fireRate)
 {
  this.mAnimatedShotSprite = new AnimatedSprite(x+14, y+10, this.mAnimatedSpriteShotTextureRegion);
  this.mAnimatedShotSprite.animate(100);
  this.mAnimatedShotSprite.setUserData("shot");
  this.mEngine.getScene().attachChild(this.mAnimatedShotSprite);
  this.mAnimatedShotSprite = new AnimatedSprite(x+36, y+10, this.mAnimatedSpriteShotTextureRegion);
  this.mAnimatedShotSprite.animate(100);
  this.mAnimatedShotSprite.setUserData("shot");
  this.mEngine.getScene().attachChild(this.mAnimatedShotSprite);
  fireCounter = 0;
}}

// добавим функцию обновления и удаления пуль
private void updateShots(float pSecondsElapsed) {
 for (int i = 0; i < this.mEngine.getScene().getChildCount(); i++)
 {
  if(this.mEngine.getScene().getChild(i).getUserData() == "shot")
   this.mEngine.getScene().getChild(i).setPosition(this.mEngine.getScene().getChild(i).getX(),
     this.mEngine.getScene().getChild(i).getY() - shotSpeed * pSecondsElapsed);
  if (this.mEngine.getScene().getChild(i).getY() < -100.0f)
   this.mEngine.getScene().detachChild(this.mEngine.getScene().getChild(i));
 };  
}
Кода много, так что надо бы поподробнее описать некоторые моменты. Строчка 6 - это скорость наших пуль. 200 пикселов в секунду. Строчка 7 - fireCounter будет хранить время, прошедшее с прошлого выстрела. А FireRate - это минимальный промежуток между выстрелами. Треть секунды. То есть три выстрела в секунду. Строчка 8 - delta будет хранить время, прошедшее с предыдущего кадра. PrevTime поможет нам вычислить дельту.

Вот так выглядит текстура для выстрелов.
Строчка 13(и 14) - задание текстурного региона. В нашем случае у нас 8 спрайтов - 2 ряда по 4.  (см. рисунок). Последние 2 параметра текстурного региона - это и есть количество "столбцов" и "строк" в текстурном атласе. В 20й строчке мы создаем возможность тыкать по всему бэкграунду. И программа будет реагировать на наши нажатия. И делать то, что описано в нашей функции. В нашем случае мы будем вызывать особенную функцию CreateShots(), которая будет создавать для нас спрайты выстрелов. Кстати, она же и будет проверять время, прошедшее с прошлого выстрела. И будет создавать новый выстрел только в том случае, если с моменты предыдущего выстрела прошло достаточно времени. Тело этой функции начинается в строчке 35. В 27й строчке регистрируем область нажатий в движке. Строчки 30,31,32 занимаются временнЫми вычислениями. Хотя, возможно, вычисление FireCounter-а можно бы и вынести отсюда. Но это потом, при рефакторинге. Сейчас хочется быстрого результата. В строчке 33 вызывается функция, которая будет перемещать пули по экрану. Она же будет удалять из памяти те пули, которые улетели далеко за пределы экрана.Тело этой функции начинается в строчке 51. Добрались до функции создания выстрелов createShot(). Первым делом в строчке 37 она проверяет, прошло ли достаточно времени с момента предыдущего выстрела. Если да, то создаются 2 анимированых спрайта. Кстати, в качестве аргументов функция создания выстрелов принимает некие координаты. Очевидно, что ей нужно передать текущие координаты нашего корабля. Наибольший интерес представляют строчки 41 и 45. К каждому объекту в движке можно подписать так называемую UserData. Например, типа String. Это мы и делаем, подписывая к каждой пуле специальную метку "shot". Позже она нам пригодится. Ну а в нашей функции мы завершаем все строчкой 47, в которой обнуляем счетчик, считающий время, прошедшее с предыдущего выстрела. Наконец, функция updateShots(). Вообще в данном виде она является довольно корявым костылем. Однако, из нее можно выродить функцию апдэйта всех объектов на экране. Подумаем об этом потом. Сейчас хочется результата. Строчка 52 создает цикл, пробегающий по всем объектам в сцене. Причем, будут пробегаться абсолютно все объекты - строчки с текстом, главный герой и все пульки. Строчка 54 как раз выбирает из всего списка именно пульки. Она проверяет метки объектов, метка "shot", которую мы присвоили пулям - позволяет их выбирать из общего списка объектов. И в строчке 55-56 мы меняем их координату по y. Ну а строчка 57 проверяет, какие объекты улетели далеко за верхний край экрана (-100 пикселов). И если таковые имеются - удаляет их строчка 58. На этом с выстрелами все. Кстати. Кто дочитал до сюда - тот молодец. И тому дается задание - найти косяк в функции updateShots(). Внимательно посмотрите - для каких объектов вызывается проверка улетания наверх, за область экрана.

2 комментария:

  1. Привет! Посмотри в сторону GenericPool. Он специально сделан для возможности генерации одинаковых спрайтов в больших количествах. Еще замечания: используется один и тот же регион без deepCopy() - все пули будут анимироваться одинаково в один момент времени. Вместо аттач/детач эффективней делать ignoreUpdate+setVisible

    ОтветитьУдалить
  2. Спасибо, попробую применить.

    ОтветитьУдалить