среда, 4 января 2012 г.

Пишем игру под Android: Часть 2 - Создаем первый спрайт

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

  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: Фоновая музыка в игре

И так теперь начнем.


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

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



Сделать похожий спрайт можно здесь. Сайт сам по себе корейский, но то не беда, Google переведет все :)

Рисуем спрайт на сцене


Растровое изображение создано, и нам нужно вставить его в наш проект. Создаем класс Sprite.java

Пишем в него вот такую красоту:

Sprite.java
import android.graphics.Bitmap;
import android.graphics.Canvas;
 public class Sprite {
    /**Объект класса GameView*/
    private GameView gameView;
    
    /**Картинка*/
    private Bitmap bmp;
    
    /**Позиция по Х=0*/
    private int x = 5;
    
    /**Скорость по Х=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;
       }
 
       /**Перемещение объекта, его направление*/
       private void update() 
       {
             if (x > gameView.getWidth() - bmp.getWidth() - xSpeed) {
                    xSpeed = -5;
             }
             if (x + xSpeed< 0) {
                    xSpeed = 5;
             }
             x = x + xSpeed;
       }

      /**Рисуем наши спрайты*/
       public void onDraw(Canvas canvas) 
       {
             update();
             canvas.drawBitmap(bmp, x , 10, null);
       }
}  


Как вы видите, весь код, о положении и скорости был перенесен (в том числе и границы экрана) в наш новый класс Sprite. Я сделал несколько маленьких изменений в скорости, предыдущие не совсем подходили.

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

Bitmap используем для рисования на холсте.

Открываем наш GameView.java выделяем старый код, удаляем (потому что я изменил сам класс приходится переделывать и Вам) и пишем следующий код:

GameView.java
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
 public class GameView extends SurfaceView {  
    /**Загружаем спрайт*/
    private Bitmap bmp;
        
    /**Поле рисования*/
    private SurfaceHolder holder;
    
    /**объект класса GameView*/
    private GameManager gameLoopThread;
    
    /**Объект класса Sprite*/
    private Sprite sprite;
   
    /**Конструктор*/
    public GameView(Context context) 
    {
          super(context);
          gameLoopThread = new GameManager(this);
          holder = getHolder();
          
          /*Рисуем все наши объекты и все все все*/
          holder.addCallback(new SurfaceHolder.Callback() 
          {
                        /*** Уничтожение области рисования */
                 public void surfaceDestroyed(SurfaceHolder holder) 
                 {
                        boolean retry = true;
                        gameLoopThread.setRunning(false);
                        while (retry) 
                        {
                               try 
                               {
                                     gameLoopThread.join();
                                     retry = false;
                               } catch (InterruptedException e) 
                               {
                               }
                        }
                 }

                 /** Создание области рисования */
                 public void surfaceCreated(SurfaceHolder holder) 
                 {
                        gameLoopThread.setRunning(true);
                        gameLoopThread.start();
                 }

                 /** Изменение области рисования */
                 public void surfaceChanged(SurfaceHolder holder, int format,
                               int width, int height) 
                 {
                 }
          });
          bmp = BitmapFactory.decodeResource(getResources(), R.drawable.bad1);
          sprite = new Sprite(this,bmp);
    }

    /**Функция рисующая все спрайты и фон*/
    protected void onDraw(Canvas canvas) 
    {
          canvas.drawColor(Color.BLACK);
          sprite.onDraw(canvas);
    }
}


Если сделали все правильно то у Вас должно получиться что то похожее на это:


Превращаем спрайт в анимацию


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

Добавляем вот эти переменные в Sprite.java
/**Рядков в спрайте = 4*/
private static final int BMP_ROWS = 4;
/**Колонок в спрайте = 3*/
private static final int BMP_COLUMNS = 3;


Обновляем метод update():

Sprite.java
private void update() 
      {
             if (x > gameView.getWidth() - width - xSpeed) {
                    xSpeed = -5;
             }
             if (x + xSpeed < 0) {
                    xSpeed = 5;
             }
             x = x + xSpeed;
             currentFrame = ++currentFrame % BMP_COLUMNS;
       }


Обновляем метод onDraw():

Sprite.java
public void onDraw(Canvas canvas) 
      {
             update();
             int srcX = currentFrame * width;
             int srcY = 1 * 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);
       }


И обновляем конструктор:

public Sprite(GameView gameView, Bitmap bmp) 
       {
             this.gameView = gameView;
             this.bmp = bmp;
             this.width = bmp.getWidth() / BMP_COLUMNS;
             this.height = bmp.getHeight() / BMP_ROWS;
       }


В конструкторе мы рассчитываем ширину и высоту спрайта. В методе update() фиксируем границы и добавляем текущие изменения значения кадра. В текущем кадре может быть только значения 0, 1 или 2. 

В методе OnDraw() мы используем метод canvas.drawBitmap (BMP, SRC, DST, NULL); 

SRC является прямоугольником внутри точечного рисунка, который будет описан и по которому будет совершаться переход с одного спрайта на другой. Вот что должно выйти если Вы сделали все правильно:

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

  1. В первом листинге класса GameView.java
    строчки:
    private GameView gameLoopThread;
    и
    gameLoopThread = new GameView(this);

    нужно заменить на
    private GameManager gameLoopThread;
    и
    gameLoopThread = new GameManager(this);
    соответственно.

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

    ОтветитьУдалить
  2. Потеряли переменную y в public void onDraw(Canvas canvas) класса Sprite

    ОтветитьУдалить
    Ответы
    1. все правильно, спрайт находится на 10 пикселей ниже границы по У

      Удалить
    2. Rect dst = new Rect(x, y, x + width, y + height);

      Здесь используется не объявленная переменная.

      Удалить
    3. y ? если он то объявите его рядом с x

      Удалить
    4. Да, у. В следующих уроках он есть ;) Попытался замедлить скорость анимации, но пока что не выходит... Пробовал менять FPS в GameManager.java. Даже пробовал явно задать время сна

      sleepTime = ticksPS-(System.currentTimeMillis() - startTime);
      sleepTime = 1000;
      try {
      if (sleepTime > 0)
      sleep(sleepTime);
      else
      sleep(1000);
      } catch (Exception e) {}

      все равно слишком быстро...

      Удалить
    5. Замедлить частоту проигыша кадров? Или скорость движения объекта? Если частоту проигрыша кадров то там нужно по колдовать с методов onDraw(), а если не можешь убрать скорость движения объекта то это в xSpeed нужно заменить 5 на 10 или 15.

      Удалить
    6. Я про частоту проигрыша кадров. Сейчас попробую currentFrame менять реже.

      Удалить
  3. Тут немного не правильно обозван FPS. У нас поток спит это время, а должна отрисовка спать. Понижая FPS мы замедляем всю игру. При низких ФПС, как я понимаю, мы должны получить в итоге ту же скорость перемещения персонажа, но рывками (телепортироваться персонаж наш должен по экрану :) ).

    ОтветитьУдалить
  4. Интересно. А что если Вам еще перевести следующую книгу
    Get started with game apps development
    for the Android platform
    Beginning
    Android Games
    Mario Zechner

    ОтветитьУдалить
  5. Сайт между прочим не корейский, а японский :)

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