Параллаксный фон
от aNNiMON
При разработке игр, фон играет немаловажную роль. Благодаря ему можно усилить восприятие игрового мира, скрасить игровой процесс, передать игроку соответствующее настроение.
![ninja-cat.png ninja-cat.png](/ablogs/file348/ninja-cat.png)
Одним из простых, но интересных способов улучшить задний фон и восприятие глубины сцены, является применение параллаксной прокрутки фона, то есть, чем дальше объект от камеры, тем медленнее он передвигается.
Вот как бы выглядела игра Mario с этим эффектом. (нажмите для просмотра анимации)
Приступим к реализации.
Я подготовил несколько фоновых слоёв: небо, тучи, облака, горы, равнины, железная дорога, трава и столб. Прежде чем выводить это всё на экран, давайте создадим класс Background. Он будет хранить изображение и координаты для вывода на экран.
Координаты хранятся в double, потому что иначе нельзя сделать плавное перемещение, например на 0.1 — когда за 10 тактов нужно переместить картинку на 1 пиксель.
Заполним массив объектов Background и выведем на экран:
Вот что получилось:
Теперь добавим перемещение. Нужно слегка изменить класс Background, чтобы он отрисовывал ещё одну картинку и нормализировал координаты, если они выйдут далеко за пределы допустимых.
Поскольку не каждый фон нужно двигать (например, небо), можно создать подкласс StaticBackground, в котором вызов метода move(dx, dy) не изменяет координаты:
Изменим инициализацию неба и добавим прокрутку:
Теперь всё выглядит так (анимация):
Добавим класс ParallaxBackground. При перемещении будем умножать координаты на некоторый коэффициент. Если число будет меньше единицы, то фон будет перемещаться медленнее, если больше единицы — быстрее.
Настроим каждый фон. За точку отсчёта возьмём железную дорогу — остальные объекты будут двигаться быстрее или медленнее относительно неё. Чем дальше объект от железной дороги, тем меньше должен быть коэффициент.
Самый дальний объект, не считая неба — тучи. Им мы присвоим значение 0.09, что будет означать перемещение на 1 пиксель примерно за 11 тактов.
Горы будут немного ближе — 0.2, то есть перемещение на 1 пиксель за 5 тактов.
Трава, напротив, должна перемещаться быстрее, чем железная дорога, так как она визуально ближе, поэтому присвоим ей коэффициент 2. Получим:
Для столба мы переопределили ширину, чтобы он не так часто появлялся.
И вот что вышло в итоге:
Скачать:
PC |
Java ME |
Android
Исходник (JECP):
ParallaxExpress_src.zip
Фон SVG:
vector_background.zip
![journey_to_silius.jpg journey_to_silius.jpg](/ablogs/file347/journey_to_silius.jpg)
![ninja-cat.png ninja-cat.png](/ablogs/file348/ninja-cat.png)
Одним из простых, но интересных способов улучшить задний фон и восприятие глубины сцены, является применение параллаксной прокрутки фона, то есть, чем дальше объект от камеры, тем медленнее он передвигается.
Вот как бы выглядела игра Mario с этим эффектом. (нажмите для просмотра анимации)
![parallax-scrolling-mario.gif parallax-scrolling-mario.gif](/ablogs/file349/parallax-scrolling-mario.gif)
Приступим к реализации.
Я подготовил несколько фоновых слоёв: небо, тучи, облака, горы, равнины, железная дорога, трава и столб. Прежде чем выводить это всё на экран, давайте создадим класс Background. Он будет хранить изображение и координаты для вывода на экран.
- public class Background {
- protected final Image background;
- protected double x, y;
- public Background(Image background, int x, int y) {
- this.background = background;
- this.x = x;
- this.y = y;
- }
- public void move(double dx, double dy) {
- x += dx;
- y += dy;
- }
- public void draw(Graphics g) {
- g.drawImage(background, (int)x, (int)y);
- }
- }
Заполним массив объектов Background и выведем на экран:
- public void onStartApp(int width, int height) {
- backgrounds = new Background[BACKGROUNDS_COUNT];
- try {
- int i = 0;
- backgrounds[i++] = new Background("sky.png", 0, 0);
- backgrounds[i++] = new Background("clouds2.png", 0, 0);
- Image temp = Image.createImage("mountains.png");
- backgrounds[i++] = new Background(temp, 0, height - temp.getHeight());
- // ...
- } catch (IOException ioe) { }
- }
- public void onPaint(Graphics g) {
- for (int i = 0; i < BACKGROUNDS_COUNT; i++) {
- backgrounds[i].draw(g);
- }
- }
Вот что получилось:
![parallax_01.jpg parallax_01.jpg](/ablogs/file350/parallax_01.jpg)
Теперь добавим перемещение. Нужно слегка изменить класс Background, чтобы он отрисовывал ещё одну картинку и нормализировал координаты, если они выйдут далеко за пределы допустимых.
- public class Background {
- protected final Image background;
- protected double x, y;
- public Background(Image background, int x, int y) {
- this.background = background;
- this.x = x;
- this.y = y;
- }
- public int getWidth() {
- return background.getWidth();
- }
- public void move(double dx, double dy) {
- x += dx;
- y += dy;
- if (x <= -getWidth()) {
- x += getWidth();
- }
- }
- public void draw(Graphics g) {
- g.drawImage(background, (int)x, (int)y);
- if (x < Main.getWidth() - getWidth()) {
- g.drawImage(background, (int)x + getWidth() - 1, (int)y);
- }
- }
- }
Поскольку не каждый фон нужно двигать (например, небо), можно создать подкласс StaticBackground, в котором вызов метода move(dx, dy) не изменяет координаты:
- public class StaticBackground extends Background {
- public StaticBackground(Image background, int x, int y) {
- super(background, x, y);
- }
- public final void move(double dx, double dy) {
- // prevent call to superclass
- }
- }
Изменим инициализацию неба и добавим прокрутку:
- public void onStartApp(int width, int height) {
- backgrounds = new Background[BACKGROUNDS_COUNT];
- try {
- int i = 0;
- backgrounds[i++] = new StaticBackground("sky.png", 0, 0);
- // ...
- } catch (IOException ioe) { }
- }
- public void onUpdate() {
- for (int i = 0; i < BACKGROUNDS_COUNT; i++) {
- backgrounds[i].move(-3, 0);
- }
- }
Теперь всё выглядит так (анимация):
![parallax_02.gif parallax_02.gif](/ablogs/file351/parallax_02.gif)
Добавим класс ParallaxBackground. При перемещении будем умножать координаты на некоторый коэффициент. Если число будет меньше единицы, то фон будет перемещаться медленнее, если больше единицы — быстрее.
- public class ParallaxBackground extends Background {
- protected final double scale;
- public ParallaxBackground(Image background, int x, int y, double scale) {
- super(background, x, y);
- this.scale = scale;
- }
- public void move(double dx, double dy) {
- super.move(dx * scale, dy * scale);
- }
- }
Настроим каждый фон. За точку отсчёта возьмём железную дорогу — остальные объекты будут двигаться быстрее или медленнее относительно неё. Чем дальше объект от железной дороги, тем меньше должен быть коэффициент.
Самый дальний объект, не считая неба — тучи. Им мы присвоим значение 0.09, что будет означать перемещение на 1 пиксель примерно за 11 тактов.
- backgrounds[i++] = new ParallaxBackground("clouds2.png", 0, 0, 0.09);
Горы будут немного ближе — 0.2, то есть перемещение на 1 пиксель за 5 тактов.
- backgrounds[i++] = new ParallaxBackground(temp, 0, height - temp.getHeight(), 0.2);
Трава, напротив, должна перемещаться быстрее, чем железная дорога, так как она визуально ближе, поэтому присвоим ей коэффициент 2. Получим:
- int i = 0;
- backgrounds[i++] = new StaticBackground("sky.png", 0, 0);
- backgrounds[i++] = new ParallaxBackground("clouds2.png", 0, 0, 0.09);
- Image temp = Image.createImage("mountains.png");
- backgrounds[i++] = new ParallaxBackground(temp, 0, height - temp.getHeight(), 0.2);
- temp = Image.createImage("ground.png");
- backgrounds[i++] = new ParallaxBackground(temp, 0, height - temp.getHeight(), 0.7);
- backgrounds[i++] = new ParallaxBackground("clouds1.png", 0, 0, 0.35);
- temp = Image.createImage("rails.png");
- backgrounds[i++] = new Background(temp, 0, height - 3 * temp.getHeight() / 2);
- temp = Image.createImage("grass.png");
- backgrounds[i++] = new ParallaxBackground(temp, 0, height - 2 * temp.getHeight() / 3, 2);
- temp = Image.createImage("post.png");
- backgrounds[i++] = new ParallaxBackground("post.png", 900, height - temp.getHeight(), 6) {
- public int getWidth() {
- return 10000;
- }
- };
И вот что вышло в итоге:
![](https://annimon.com/images/16x9.png)
Скачать:
![](/forum/img/unknown.png)
![](/forum/img/unknown.png)
![](/forum/img/unknown.png)
Исходник (JECP):
![](/forum/img/unknown.png)
Фон SVG:
![](/forum/img/unknown.png)