Генерация ландшафта на основе генерации шумов (noise)

Опубликовал – 10.01.2013

Добро время суток товарищи, сегодня попробуем написать простенький генератор карт для 2д игр, посредством генерации шумов.

Так уж повелось, что писать мы это добро будем на java. На уникальность материала не претендую, так как все мы учимся в инете, и там уже давно ничего уникального не осталось. Результат нашего небольшого кода (строк 200) будет нечто подобное:


или даже такое:

Итак, для начала нам понадобится любой, самый простой шум(noise). Напишем пожалуй его сами:

public class Noise {
    public static final Random random = new Random();
//ширина и высота нашей карты
    public int w;
    public int h;
//карта "шумов" будет содержать значения от -1.0D до 1.0D
    public double[] map;

    public Noise(int w, int h, int featureSize) {
        this.w = w;
        this.h = h;
        this.map = new double[w * h];
        this.generateNoise(featureSize);
    }

    public void generateNoise(int featureSize) {
//просто пробегаемся и выставляем заданный шаг либо "высокие" точки 1.0D, либо "низкие" -1.0D
        for (int y = 0; y < h; y += featureSize) {
            for (int x = 0; x < w; x += featureSize) {
                setSample(x, y, random.nextDouble() * 2 - 1);
            }
        }
    }

    public double getSample(int x, int y) {
        return this.map[(x & (w - 1)) + (y & (h - 1)) * w];
    }

    public void setSample(int x, int y, double value) {
        this.map[(x & (w - 1)) + (y & (h - 1)) * w] = value;
    }
}

если выполнить код такой:

Noise noise = new Noise(128, 128, 32);

получим примерно следующий шум:
Зеленые точки это места самые "высокие" месте т.е. приближены к 1.0D красные же будут "утопающие" приближены к -1.0D

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

//определяем размер шага
        int stepSize = featureSize;
//скаляр длины
        double scale = 1.0 / w;
//множитель для убывания
        double scaleMod = 1;
        do {
            int halfStep = stepSize / 2;
            for (int y = 0; y < h; y += stepSize) {
                for (int x = 0; x < w; x += stepSize) {
//берем 4 значения 1 - центр, 2 - справа, 3 - снизу, 4 - справа низ.
                    double a = getSample(x, y);
                    double b = getSample(x + stepSize, y);
                    double c = getSample(x, y + stepSize);
                    double d = getSample(x + stepSize, y + stepSize);
//усредняем эти значения не хитрой формулой. и добавляем рандом "гашения" т.е. чем дальше от центра шага тем тусклее
                    double e = (a + b + c + d) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale;
//выставляем правое нижнее значение
                    setSample(x + halfStep, y + halfStep, e);
                }
            }
//с каждой итерацией уменьшаем шаг
            stepSize /= 2;
//с каждой итерацией уменьшается скаляр
            scale *= (scaleMod * 0.8);
//с каждой итерацией уменьшаем множитель
            scaleMod *= 0.3;
        } while (stepSize > 1);

Получается вот такая красотень :)

Дальше интересней, нам надо аппроксимировать наши значения раскидав на соседние области (все это происходит в том же цикле, который был описан выше):

//принцип тот же. пробегаемся по значениям, берем соседей, усредняем и заполняем.
            for (int y = 0; y < h; y += stepSize) {
                for (int x = 0; x < w; x += stepSize) {
                    double a = getSample(x, y);
                    double b = getSample(x + stepSize, y);
                    double c = getSample(x, y + stepSize);
                    double d = getSample(x + halfStep, y + halfStep);
                    double e = getSample(x + halfStep, y - halfStep);
                    double f = getSample(x - halfStep, y + halfStep);

                    double h = (a + b + d + e) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale * 0.5;
                    double g = (a + c + d + f) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale * 0.5;
                    setSample(x + halfStep, y, h);
                    setSample(x, y + halfStep, g);
                }
            }

Выходит неплохо, правда?

Мы все еще помним, что зеленые это "высокие" области на карте, а красные - "низкие", попробую представить в чб варианте, так мы сможем разглядеть глубину


альтернатива :)

Итак, отлично, шум у нас уже есть, мы научились генерировать такие замечательные облака. А что с ними делать дальше? А ведь дальше самое интересное, мы можем нагенерить, например, несколько шумов их вершины приводить к вполне логичным областям.
Дальше я покажу небольшой кусок кода. Который генерит несколько подобных шумов, и делает из них карты, для вполне себе игр ;)

//цвета поверхностей
    public static final int WATER = 0x0000ff;
    public static final int ROCK = 0xaabbcc;
    public static final int GRASS = 0x00ff00;
    public static final int SOIL = 0xffff00;
    public static final int TREE = 0x007700;

    public static int[] createMap(int w, int h) {
//создаем шумы с мелким шагом в 16 пикселей, они понадобится для гор.
        Noise mnoise1 = new Noise(w, h, 16);
        Noise mnoise2 = new Noise(w, h, 16);
        Noise mnoise3 = new Noise(w, h, 16);

//и два шума с большим шагом, они как раз будут отвечать за сушу и воду.
        Noise noise1 = new Noise(w, h, 32);
        Noise noise2 = new Noise(w, h, 32);

        int[] map = new int[w * h];

        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                int i = x + y * w;

//находим значение шумов с широким шагом
                double val = Math.abs(noise1.map[i] - noise2.map[i]) * 3 - 2;

//так же считаем побочные шумы для гор
                double mval = Math.abs(mnoise1.map[i] - mnoise2.map[i]);
                mval = Math.abs(mval - mnoise3.map[i]) * 3 - 2;

//ищем самый длинный скаляр в итерации
                double xd = x / (w - 1.0) * 2 - 1;
                double yd = y / (h - 1.0) * 2 - 1;
                if (xd < 0) xd = -xd;
                if (yd < 0) yd = -yd;
                double dist = xd >= yd ? xd : yd;
//уменьшаем его, это лишь наш коэффициент удаления от краев
                dist = dist * dist * dist * dist;
                dist = dist * dist * dist * dist;
//удаляясь от краев значение все больше
                val = val + 1 - dist * 20;

//и работаем с полученным значением как хотим.
                if (val < -0.5) {
                    map[i] = WATER;
//дальше определяем, если находимся выше моря, но ниже суммы горных шумов - строим горы
                } else if (val > 0.5 && mval < -1.5) {
                    map[i] = ROCK;
                } else {
                    map[i] = GRASS;
                }
            }
        }

//дальше хаотично находим место с травой и заселяем его плотненько песочком
        for (int i = 0; i < w * h / 2800; i++) {
            int xs = random.nextInt(w);
            int ys = random.nextInt(h);
            for (int k = 0; k < 10; k++) {
                int x = xs + random.nextInt(21) - 10;
                int y = ys + random.nextInt(21) - 10;
                for (int j = 0; j < 100; j++) {
                    int xo = x + random.nextInt(5) - random.nextInt(5);
                    int yo = y + random.nextInt(5) - random.nextInt(5);
                    for (int yy = yo - 1; yy <= yo + 1; yy++)
                        for (int xx = xo - 1; xx <= xo + 1; xx++)
                            if (xx >= 0 && yy >= 0 && xx < w && yy < h) {
                                if (map[xx + yy * w] == GRASS) {
                                    map[xx + yy * w] = SOIL;
                                }
                            }
                }
            }
        }

//так же находим траву и заселяем ее в радиусе random.nextInt(10) - random.nextInt(10) деревьям
        for (int i = 0; i < w * h / 400; i++) {
            int x = random.nextInt(w);
            int y = random.nextInt(h);
            for (int j = 0; j < 200; j++) {
                int xx = x + random.nextInt(10) - random.nextInt(10);
                int yy = y + random.nextInt(10) - random.nextInt(10);
                if (xx >= 0 && yy >= 0 && xx < w && yy < h) {
                    if (map[xx + yy * w] == GRASS) {
                        map[xx + yy * w] = TREE;
                    }
                }
            }
        }

        return map;
    }

может получится примерно такое:

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

Рассказать друзьям:
  • Добавить ВКонтакте заметку об этой странице
  • Мой Мир
  • Facebook
  • Twitter
  • Яндекс.Закладки
  • В Живую Ленту Google
  • Сто закладок
Нет комментариев - Генерация ландшафта на основе генерации шумов (noise)

Ответ

Ответить

Comments

Перед отправкой формы: