Параллаксный фон

от
GameDev    java me

При разработке игр, фон играет немаловажную роль. Благодаря ему можно усилить восприятие игрового мира, скрасить игровой процесс, передать игроку соответствующее настроение.
  journey_to_silius.jpg ninja-cat.png

Одним из простых, но интересных способов улучшить задний фон и восприятие глубины сцены, является применение параллаксной прокрутки фона, то есть, чем дальше объект от камеры, тем медленнее он передвигается.
Вот как бы выглядела игра Mario с этим эффектом. (нажмите для просмотра анимации)
  parallax-scrolling-mario.gif

Приступим к реализации.

Я подготовил несколько фоновых слоёв: небо, тучи, облака, горы, равнины, железная дорога, трава и столб. Прежде чем выводить это всё на экран, давайте создадим класс Background. Он будет хранить изображение и координаты для вывода на экран.
  1. public class Background {
  2.  
  3.     protected final Image background;
  4.     protected double x, y;
  5.  
  6.     public Background(Image background, int x, int y) {
  7.         this.background = background;
  8.         this.x = x;
  9.         this.y = y;
  10.     }
  11.  
  12.     public void move(double dx, double dy) {
  13.         x += dx;
  14.         y += dy;
  15.     }
  16.  
  17.     public void draw(Graphics g) {
  18.         g.drawImage(background, (int)x, (int)y);
  19.     }
  20. }
Координаты хранятся в double, потому что иначе нельзя сделать плавное перемещение, например на 0.1 — когда за 10 тактов нужно переместить картинку на 1 пиксель.

Заполним массив объектов Background и выведем на экран:
  1. public void onStartApp(int width, int height) {
  2.     backgrounds = new Background[BACKGROUNDS_COUNT];
  3.     try {
  4.         int i = 0;
  5.         backgrounds[i++] = new Background("sky.png", 0, 0);
  6.         backgrounds[i++] = new Background("clouds2.png", 0, 0);
  7.         Image temp = Image.createImage("mountains.png");
  8.         backgrounds[i++] = new Background(temp, 0, height - temp.getHeight());
  9.         // ...
  10.     } catch (IOException ioe) { }
  11. }
  12.  
  13. public void onPaint(Graphics g) {
  14.     for (int i = 0; i < BACKGROUNDS_COUNT; i++) {
  15.         backgrounds[i].draw(g);
  16.     }
  17. }

Вот что получилось:
  parallax_01.jpg

Теперь добавим перемещение. Нужно слегка изменить класс Background, чтобы он отрисовывал ещё одну картинку и нормализировал координаты, если они выйдут далеко за пределы допустимых.
  1. public class Background {
  2.  
  3.     protected final Image background;
  4.     protected double x, y;
  5.  
  6.     public Background(Image background, int x, int y) {
  7.         this.background = background;
  8.         this.x = x;
  9.         this.y = y;
  10.     }
  11.  
  12.     public int getWidth() {
  13.         return background.getWidth();
  14.     }
  15.  
  16.     public void move(double dx, double dy) {
  17.         x += dx;
  18.         y += dy;
  19.         if (x <= -getWidth()) {
  20.             x += getWidth();
  21.         }
  22.     }
  23.  
  24.     public void draw(Graphics g) {
  25.         g.drawImage(background, (int)x, (int)y);
  26.         if (x < Main.getWidth() - getWidth()) {
  27.             g.drawImage(background, (int)x + getWidth() - 1, (int)y);
  28.         }
  29.     }
  30. }

Поскольку не каждый фон нужно двигать (например, небо), можно создать подкласс StaticBackground, в котором вызов метода move(dx, dy) не изменяет координаты:
  1. public class StaticBackground extends Background {
  2.  
  3.     public StaticBackground(Image background, int x, int y) {
  4.         super(background, x, y);
  5.     }
  6.  
  7.     public final void move(double dx, double dy) {
  8.         // prevent call to superclass
  9.     }
  10. }


Изменим инициализацию неба и добавим прокрутку:
  1. public void onStartApp(int width, int height) {
  2.     backgrounds = new Background[BACKGROUNDS_COUNT];
  3.     try {
  4.         int i = 0;
  5.         backgrounds[i++] = new StaticBackground("sky.png", 0, 0);
  6.         // ...
  7.     } catch (IOException ioe) { }
  8. }
  9.  
  10. public void onUpdate() {
  11.     for (int i = 0; i < BACKGROUNDS_COUNT; i++) {
  12.         backgrounds[i].move(-3, 0);
  13.     }
  14. }

Теперь всё выглядит так (анимация):
  parallax_02.gif


Добавим класс ParallaxBackground. При перемещении будем умножать координаты на некоторый коэффициент. Если число будет меньше единицы, то фон будет перемещаться медленнее, если больше единицы — быстрее.
  1. public class ParallaxBackground extends Background {
  2.  
  3.     protected final double scale;
  4.  
  5.     public ParallaxBackground(Image background, int x, int y, double scale) {
  6.         super(background, x, y);
  7.         this.scale = scale;
  8.     }
  9.  
  10.     public void move(double dx, double dy) {
  11.         super.move(dx * scale, dy * scale);
  12.     }
  13. }

Настроим каждый фон. За точку отсчёта возьмём железную дорогу — остальные объекты будут двигаться быстрее или медленнее относительно неё. Чем дальше объект от железной дороги, тем меньше должен быть коэффициент.

Самый дальний объект, не считая неба — тучи. Им мы присвоим значение 0.09, что будет означать перемещение на 1 пиксель примерно за 11 тактов.
  1. backgrounds[i++] = new ParallaxBackground("clouds2.png", 0, 0, 0.09);

Горы будут немного ближе — 0.2, то есть перемещение на 1 пиксель за 5 тактов.
  1. backgrounds[i++] = new ParallaxBackground(temp, 0, height - temp.getHeight(), 0.2);

Трава, напротив, должна перемещаться быстрее, чем железная дорога, так как она визуально ближе, поэтому присвоим ей коэффициент 2. Получим:
  1. int i = 0;
  2. backgrounds[i++] = new StaticBackground("sky.png", 0, 0);
  3. backgrounds[i++] = new ParallaxBackground("clouds2.png", 0, 0, 0.09);
  4. Image temp = Image.createImage("mountains.png");
  5. backgrounds[i++] = new ParallaxBackground(temp, 0, height - temp.getHeight(), 0.2);
  6. temp = Image.createImage("ground.png");
  7. backgrounds[i++] = new ParallaxBackground(temp, 0, height - temp.getHeight(), 0.7);
  8. backgrounds[i++] = new ParallaxBackground("clouds1.png", 0, 0, 0.35);
  9. temp = Image.createImage("rails.png");
  10. backgrounds[i++] = new Background(temp, 0, height - 3 * temp.getHeight() / 2);
  11. temp = Image.createImage("grass.png");
  12. backgrounds[i++] = new ParallaxBackground(temp, 0, height - 2 * temp.getHeight() / 3, 2);
  13. temp = Image.createImage("post.png");
  14. backgrounds[i++] = new ParallaxBackground("post.png", 900, height - temp.getHeight(), 6) {
  15.     public int getWidth() {
  16.         return 10000;
  17.     }
  18. };
Для столба мы переопределили ширину, чтобы он не так часто появлялся.

И вот что вышло в итоге:



Скачать: PC | Java ME | Android
Исходник (JECP): ParallaxExpress_src.zip
Фон SVG: vector_background.zip
  • +18
  • views 7814