воскресенье, 8 января 2012 г.

Пишем игру под Android: Часть 3 - Спрайтовая анимация, работа с несколькими спрайтами

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

  1. Пишем игру под Android: Часть 1 — Рисуем картинки на SurfaceView
  2. Пишем игру под Android: Часть 2 — Создаем первый спрайт
  3. Пишем игру под Android: Часть 3 — Спрайтовая анимация, работа с несколькими спрайтами
  4. Пишем игру под Android: Часть 4 — onTouchEvent и определение столкновений
  5. Пишем игру под Android: Часть 5 — Создание полноценной 2D игры
  6. Пишем игру под Android: Часть 6: Добавление звука
  7. Пишем игру под Android: Часть 7: Меню для игры и окно приветствия
  8. Пишем игру под Android: Часть 8: Фоновая музыка в игре

Если Вы прочли эти статьи, значит Вы можете продолжать дальше, начнем с первой части названия статьи.


Спрайтовая анимация

Открываем наш проект и пишем следующий код:

Sprite.java
import java.util.Random;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
 public class Sprite {
       /**Рядков в спрайте = 4*/
    private static final int BMP_ROWS = 4;
    
    /**Колонок в спрайте = 3*/
    private static final int BMP_COLUMNS = 3;
    
    /**Объект класса GameView*/
    private GameView gameView;
    
    /**Картинка*/
    private Bitmap bmp;
    
    /**Позиция по Х=0*/
    private int x = 5;
    
    /**Позиция по У=0*/
    private int y = 0;
    
    /**Скорость по Х=5*/
    private int xSpeed = 5;
    
    private int ySpeed = 5;
    
    /**Текущий кадр = 0*/
    private int currentFrame = 0;
    
    /**Ширина*/
    private int width;
    
    /**Ввыоста*/
    private int height;

    /**Конструктор*/
     public Sprite(GameView gameView, Bitmap bmp) 
     {
           this.gameView = gameView;
           this.bmp = bmp;
           this.width = bmp.getWidth() / BMP_COLUMNS;
           this.height = bmp.getHeight() / BMP_ROWS;
           
           Random rnd = new Random();
           xSpeed = rnd.nextInt(10)-5;
           ySpeed = rnd.nextInt(10)-5;
     }

     /**Перемещение объекта, его направление*/
     private void update() 
     {
         if (x >= gameView.getWidth() - width - xSpeed || x + xSpeed <= 0) 
         {
             xSpeed = -xSpeed;
         }
         
         x = x + xSpeed;
         
         if (y >= gameView.getHeight() - height - ySpeed || y + ySpeed <= 0) 
         {
             ySpeed = -ySpeed;
         }
         
         y = y + ySpeed;
         currentFrame = ++currentFrame % BMP_COLUMNS;
     }

     /**Рисуем наши спрайты*/
     public void onDraw(Canvas canvas) 
     {
         update();
         int srcX = currentFrame * width;
         int srcY = getAnimationRow() * height;
         Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
         Rect dst = new Rect(x, y, x + width, y + height);
         
         canvas.drawBitmap(bmp, src, dst, null);
     }
}


Наш спрайт содержит 4 анимации с 3 кадрами в каждой строке.

В зависимости от направления мы должны показать разную анимацию, например:

Нам нужна функция, которая получает в качестве параметров xSpeed ​​и ySpeed ​​и возвращает строку, которую мы будем использовать для создания анимации [0,1,2,3]

Мы собираемся использовать функцию Math.atan2(xSpeed​​, ySpeed​​) для расчета направления спрайтов во время выполнения функции.

atan2(х, у) дает угол радиана в double из (-PI до PI), но мы просто хотим получить результат в int — от 0 до 3, чтобы узнать, какую анимацию мы должны использовать.

Давайте рассмотрим следующий рисунок, я описываю координатами (х, у) каждое направление на спрайте, что у нас имеется: вверх (0, -1), вправо (1,0), вниз (0,1) и влево (0, -1).

Когда результат равен углу одного из заданных направлений спрайта мы будем использовать соответствующее движение — вверх, вправо, вниз или влево. Для этого я использую Math.round функции.

Хорошо, теперь самая сложная часть, я решил это с помощью этой функции и одной константы массива:

Sprite.java
// direction = 0 up, 1 left, 2 down, 3 right,
// animation = 3 up, 1 left, 0 down, 2 right
int[] DIRECTION_TO_ANIMATION_MAP = { 3, 1, 0, 2 };
 private int getAnimationRow() {
 double dirDouble = (Math.atan2(xSpeed, ySpeed) / (Math.PI / 2) + 2);
 int direction = (int) Math.round(dirDouble) % BMP_ROWS;
 return DIRECTION_TO_ANIMATION_MAP[direction];
}


Ниже я пытаюсь объяснить Вам, что я делаю в этой функции:

  1. с atan2(xSpeed​​, ySpeed​​) Я получаю радиан угла из (-PI до PI)
  2. Я делю угол PI / 2 и получаю вдвое больше по сравнению с (-2 до 2)
  3. Я добавляю 2 для изменения диапазона от (0 до 4)
  4. Я использую %, чтобы уменьшить диапазон (от 0 до 3) (0 и 4, были в том же направлении)
  5. Задаем на карте каждой строке — направление, к нужной анимации с использованием массива {3, 1, 0, 2}

Добавляем в конец класса Sprite.java код который я написал выше и переходим к следующей части названия статьи.

Работа с несколькими спрайтами


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

Для начал изменим наш главный класс рисования — GameView.java для внесения в него списка спрайтов. Добавим в начало класса:
private List<Sprite> sprites = new ArrayList<Sprite>();

Нам нужно скопировать все спрайты которые Вы создали в каталоге drawable, как мы делали с bad1.png во втором уроке. Затем мы создадим новый спрайт с каждым изображением.

GameView.java
sprites.add(createSprite(R.drawable.bad1));
sprites.add(createSprite(R.drawable.bad2));
sprites.add(createSprite(R.drawable.bad3));
sprites.add(createSprite(R.drawable.bad4));
sprites.add(createSprite(R.drawable.bad5));
sprites.add(createSprite(R.drawable.bad6));
sprites.add(createSprite(R.drawable.good1));
sprites.add(createSprite(R.drawable.good2));
sprites.add(createSprite(R.drawable.good3));
sprites.add(createSprite(R.drawable.good4));
sprites.add(createSprite(R.drawable.good5));
sprites.add(createSprite(R.drawable.good6));


Создаем новый метод который будет возвращать изображение спрайта:

GameView.java
private Sprite createSprite(int resouce) {
 Bitmap bmp = BitmapFactory.decodeResource(getResources(), resouce);
 return new Sprite(this,bmp);
}

и обновляем onDraw():

GameView.java
for(Sprite sprite : sprites) {
 sprite.onDraw(canvas);
}


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


Добавляем простые две строчки в конструктор Sprite.java:

Sprite.java
x = rnd.nextInt(gameView.getWidth() - width);
y = rnd.nextInt(gameView.getHeight() - height);


Но если Вы сейчас запустите проект то получите сообщение об ошибке потому что width у нас равен 0. Если Вы помните, мы используем метод в surfaceCreated, который знает когда запущена сцена и когда можно рисовать. Для исправления проблемы нам нужно записать в surfaceCreated() метод создания спрайтов, т.е. createSprites();

GameView.java
public void surfaceCreated(SurfaceHolder holder) {
 createSprites();
 gameLoopThread.setRunning(true);
 gameLoopThread.start();
}


Все, проблема решена, игра работает. Запускаем и смотрим как человечки бегают по экрану :)

Исходные коды & Скачать с Git

9 комментариев:

  1. Спасибо, за проделанную работу! Осталось собраться и реализовать задуманное:)

    ОтветитьУдалить
  2. В следующем уроке расскажу как удалить объекты с экрана. Будем убивать человечков =)

    ОтветитьУдалить
  3. сделал на основе твоего урока за пару часов типа игры -перетаскивать человечков http://dl.dropbox.com/u/6114624/MultiplySprites.apk
    http://dl.dropbox.com/u/6114624/multsprytes.zip
    чтоб они в квадрат не попали,без подсчета очков и тд,но как демонстрация актуальности твоих уроков самое то,особенно учитывая что я начинающий в андроиде да и в самой java

    ОтветитьУдалить
    Ответы
    1. Событие онТач я буду рассматривать в 4й части)) Забежал даже дальше чем я рассказал)

      Удалить
  4. Ухты,Спасибо большущее за такие материалы. Сам не тупой, по английски читаю кой че, но на родном языке явно легче разобратся -_-. Спасиб

    ОтветитьУдалить
  5. В конструкторе спрайта при рандоме приложение вылетает, а когда явно задаёшь число (x=50 или y=50), то всё запускается. Пробовал рандом вынести в GameView.java - тоже самое. В чем может быть проблема ?

    ОтветитьУдалить
    Ответы
    1. 50 это очень много. Явно с таким я не сталкивался, но я точно могу сказать что 10 вам будет достаточно, этот параметр означает скорость, вы ставите около 400 фпс, вот у вас оно и вылетает скорее всего, попробуйте поменьше числа ставить.

      Удалить