doom2d.org

Главная база плоских морпехов
It is currently 21 Apr 2024, 19:45

All times are UTC + 3 hours




Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: 11 Feb 2024, 18:32 
Offline
Site Admin
User avatar

Joined: 17 Oct 2009, 23:43
Posts: 7532
Location: \\HULK
Тут будет топик про автотайлинг.

Для автотайлинга нужны тайлсеты - наборы тайлов.
Тайлсет можно сделать отдельной сущностью.
Это будет картинка с шаблоном, вот такого типа. Здесь тайлсет 16х16, можно делать их 8х8, 24х24, 32х32 и так далее.
Тайлсеты можно выделить на отдельную вкладку, а можно просто добавлять в текстуры.

Image

Редактор должен будет сам резать шаблон на тайлы и рисовать ими с помощью специального инструмента - автотайла.
Это такой клеточный автомат, который подбирает нужный тайл на поле по определенным правилам.

Вот хорошая демка, начинается примерно с 12:00.

_________________
И неважно, что нет морей на Марсе, каждый морпех носит море в сердце.


Top
 Profile  
 
PostPosted: 11 Feb 2024, 18:44 
Offline
Принципиально неуничтожаем
User avatar

Joined: 18 Oct 2009, 04:01
Posts: 6679
Location: Владивосток
Дубликат? viewtopic.php?f=57&t=1916

_________________
Чёрный Думер, Чёрный Думер
С монстрами сражается.
Чёрный Думер, Чёрный Думер
Рокетланчер плавится.


Top
 Profile  
 
PostPosted: 11 Feb 2024, 19:02 
Offline
Site Admin
User avatar

Joined: 17 Oct 2009, 23:43
Posts: 7532
Location: \\HULK
В какой-то степени, ведь там обсуждение, а тут уже четкий концепт.

Можно, в принципе, и объединить.

_________________
И неважно, что нет морей на Марсе, каждый морпех носит море в сердце.


Top
 Profile  
 
PostPosted: 11 Feb 2024, 20:30 
Offline
Site Admin
User avatar

Joined: 17 Oct 2009, 23:43
Posts: 7532
Location: \\HULK
Обновил шаблон тайлсета

_________________
И неважно, что нет морей на Марсе, каждый морпех носит море в сердце.


Top
 Profile  
 
PostPosted: 11 Feb 2024, 22:09 
Offline
Приколист
User avatar

Joined: 17 Oct 2009, 19:57
Posts: 4016
Location: Киров
Тут ещё что-то подобное было, хотя речь и о другом. viewtopic.php?f=38&t=2929#p43429

_________________
Давай, картечью демонов
Размажем по стене.
Давай, берсерком выпустим
Весь ливер сатане!

Сделайте нормальный огнемёт! :evil:


Top
 Profile  
 
PostPosted: 11 Feb 2024, 22:53 
Offline
Site Admin
User avatar

Joined: 17 Oct 2009, 23:43
Posts: 7532
Location: \\HULK
ar888» Тут ещё что-то подобное было, хотя речь и о другом. viewtopic.php?f=38&t=2929#p43429
Там немного о другом. Там о новых наборах текстур типа SQR*. Как их называть.

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

Image


Тайлы выбираются по принципам клеточного автомата, типа "Жизни" Конвея.
Image
Сложность в том, что комбинаций довольно много - навскидку, сотни. Надо или перебрать их все, или вывести правила.
Интересная математическая задачка.

Юху, я пытаюсь придумать алгоритм, но нихрена не понимаю в программировании и не смогу его закодить ) Хотя в теории можно скоммуниздить из Годота.


_________________
И неважно, что нет морей на Марсе, каждый морпех носит море в сердце.


Top
 Profile  
 
PostPosted: 12 Feb 2024, 12:58 
Offline
Site Admin
User avatar

Joined: 17 Oct 2009, 23:43
Posts: 7532
Location: \\HULK
Вывел правила, они очень простые.

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

Image

В итоге этот тайлсет:
Image

Описывается такой картой:

000 000 000 000 000 000
011 111 110 011 111 110
011 111 110 010 010 010

011 111 110 010 010 010
011 111 110 011 111 110
011 111 110 010 010 010

011 111 110 010 010 010
011 111 110 011 111 110
000 000 000 000 000 000

110 011 111 011 111 111
111 111 111 111 111 111
010 010 010 011 110 011

010 010 110 010 110 011
111 111 111 111 111 111
110 011 110 111 111 111

000 011 110 000 000 100
010 011 110 111 111 000
010 010 010 110 011 001

010 010 010 110 011 001
010 011 110 111 111 000
010 011 110 000 000 100

010 000 000 000 000 000
010 011 111 110 010 000
000 000 000 000 000 000



_________________
И неважно, что нет морей на Марсе, каждый морпех носит море в сердце.


Top
 Profile  
 
PostPosted: 12 Feb 2024, 16:53 
Offline
Приколист
User avatar

Joined: 04 Feb 2010, 14:42
Posts: 902
Location: Equestria
Тут потенциально 512 комбинаций тайлов. Многовато.
Для раскладки на тайлсете (и быстрого поиска в редакторе) можно использовать ту же формулу как для рассчета адреса массива:
Code:
TILE: ARRAY 2, 2, 2, 2, 2, 2, 2, 2, 2 OF Image;
TILE[↖][↑][↗] (* порядок можно поменять для более удобного размещения на тайлсете *)
    [←][྿][→]
    [↙][↓][↘];
i := ↖*2*8 + ↑*2*7 + ↗*2*6
   + ←*2*5 + ྿*2*4 + →*2*3
   + ↙*2*2 + ↓*2*1 + ↘;
y := i DIV TilesPerWidth;
x := i MOD TilesPerWidth;


Top
 Profile  
 
PostPosted: 13 Feb 2024, 03:07 
Offline
Site Admin
User avatar

Joined: 17 Oct 2009, 23:43
Posts: 7532
Location: \\HULK
512 - это в теории. На практике их 48 с пустым. Проверено.
Остальные нам не нужны, и центр тайла можно не учитывать, он всегда 1 - т.е. конфиг тайла описывается одним байтом.
Вот на этой картинке я как только не извращался, а всё отображается правильно.

Image

Сделали с Ancevt (он не с нашего форума) демку автотайла, для его игры.
Я нарисовал несколько тайлсетов и рассчитал комбинации для тайлов, а он всё закодил.

Такая штука упрощает создание карт в разы - и используется во многих движках типа Годота.

Управление курсором на стрелках, пробел удалять, буква - соответствующий тайлсет.
Это демка, в редакторе должна поддерживаться и мышь, и клава, и выбор тайлсета надо будет реализовать как-то иначе, но уже очень удобно.
(Кстати, оказалось офигенно удобно рисовать с ноутбука)

Собственно, о тайлсетах.
Я очень тщательно рассчитал их размеры и расположение частей.
Создается такой тайлсет за несколько минут, просто на основе квадратика 8х8.
В ДФ это было бы 16х16, кроме размера, разницы нет.
Image

Чёрный Думер wrote:
Дубликат? viewtopic.php?f=57&t=1916

Я подумал и выделил все-таки в отдельную тему. Здесь фокус именно на автотайлинг и специфически устроенные тайлсеты для него.

_________________
И неважно, что нет морей на Марсе, каждый морпех носит море в сердце.


Top
 Profile  
 
PostPosted: 15 Feb 2024, 18:46 
Offline
Site Admin
User avatar

Joined: 17 Oct 2009, 23:43
Posts: 7532
Location: \\HULK
Примерный код этого безобразия (осторожно, Java!)

Code:
package com.ancevt.bcnl.core.world.object;

import com.ancevt.bcnl.core.util.RadialUtils;
import com.ancevt.bcnl.core.world.object.appearance.Appearance;
import com.ancevt.bcnl.core.world.object.appearance.AtlasManager;
import com.ancevt.d2d2.display.Sprite;
import com.ancevt.d2d2.display.texture.Texture;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * TiledBlock - объект игрового мира, представляющий собой плитку с текстурой.
 */
public class TiledBlock extends Sprite implements ITangible, IColored, IStaticOptimizedTangible {

    // Карта соответствия состояний с индексами в текстуре
    private static final Map<Integer, Integer> TILEMAP = createTileMap();

    // Список текстур для различных состояний блока
    private List<List<Texture>> textures;

    // Генератор случайных чисел для выбора текстуры
    private static final Random random = new Random();

    /**
     * Конструктор по умолчанию для создания объекта TiledBlock.
     * Устанавливает текстуру и внешний вид по умолчанию, а также прямоугольник коллизии.
     */
    public TiledBlock() {
        setTexture(AtlasManager.getInstance().mainAtlas().createTexture(152, 8, 8, 8));
        setAppearance(
            Appearance.builder()
                .idle("0,128,8,8")
                .put("tiledblock", "0,200,43,2")
                .variety(1)
                .build()
        );
        setCollisionRect(new Rect(0, 0, 8, 8));
    }

    /**
     * Метод для создания карты соответствия состояний блока и индексов текстур в текстурном атласе.
     * @return Карта соответствия.
     */
    private static Map<Integer, Integer> createTileMap() {
        // Создание пустой карты соответствия
        Map<Integer, Integer> map = new HashMap<>();

        /* смотреть картинку */
        /*
        000 000 000 000 000 000
        0 1 1 1 1 0 0 1 1 1 1 0
        011 111 110 010 010 010

        011 111 110 010 010 010
        0 1 1 1 1 0 0 1 1 1 1 0
        011 111 110 010 010 010

        011 111 110 010 010 010
        0 1 1 1 1 0 0 1 1 1 1 0
        000 000 000 000 000 000

        110 011 111 011 111 111
        1 1 1 1 1 1 1 1 1 1 1 1
        010 010 010 011 110 011

        010 010 110 010 110 011
        1 1 1 1 1 1 1 1 1 1 1 1
        110 011 110 111 111 111

        000 011 110 000 000 100
        0 0 0 1 1 0 1 1 1 1 0 0
        010 010 010 110 010 001

        010 010 010 110 011 001
        0 0 0 1 1 0 1 1 1 1 0 0
        010 011 110 000 000 100

        010 000 000 000 000 000
        0 0 0 1 1 1 1 0 0 0 0 0
        000 000 000 000 000 000

            */


        // Добавление записей в карту соответствия: состояние блока (в бинарной форме) и индекс текстуры
        map.put(0b00001011, 0);
        map.put(0b00011111, 1);
        map.put(0b00010110, 2);
        map.put(0b00001010, 3);
        map.put(0b00011010, 4);
        map.put(0b00010010, 5);

        map.put(0b01101011, 6);
        map.put(0b11111111, 7);
        map.put(0b11010110, 8);
        map.put(0b01001010, 9);
        map.put(0b01011010, 10);
        map.put(0b01010010, 11);

        map.put(0b01101000, 12);
        map.put(0b11111000, 13);
        map.put(0b11010000, 14);
        map.put(0b01001000, 15);
        map.put(0b01011000, 16);
        map.put(0b01010000, 17);

        map.put(0b11011010, 18);
        map.put(0b01111010, 19);
        map.put(0b11111010, 20);
        map.put(0b01111011, 21);
        map.put(0b11111110, 22);
        map.put(0b11111011, 23);

        map.put(0b01011110, 24);
        map.put(0b01011011, 25);
        map.put(0b11011110, 26);
        map.put(0b01011111, 27);
        map.put(0b11011111, 28);
        map.put(0b01111111, 29);

        map.put(0b00000010, 30);
        map.put(0b01101010, 31);
        map.put(0b11010010, 32);
        map.put(0b00011110, 33);
        map.put(0b00011011, 34);
        map.put(0b10000001, 35);

        map.put(0b01000010, 36);
        map.put(0b01001011, 37);
        map.put(0b01010110, 38);
        map.put(0b11011000, 39);
        map.put(0b01111000, 40);
        map.put(0b00100100, 41);

        map.put(0b01000000, 42);
        map.put(0b00001000, 43);
        map.put(0b00011000, 44);
        map.put(0b00010000, 45);
        map.put(0b00000000, 46);

        map.put(0b01111110, 35);
        map.put(0b11011011, 41);

        return map;
    }

    /**
     * Перестроить блок с учетом состояния соседних блоков.
     * Если состояние блока с учетом соседей присутствует в карте TILEMAP,
     * выбирает случайную текстуру из соответствующего списка и устанавливает ее как текстуру блока.
     * В противном случае устанавливает текстуру по умолчанию.
     */
    public void retile() {
        // Получение состояния соседних блоков
        int neighbourState = getNeighbourState();
        // Получение битового представления состояния соседей
        boolean[] bits = getBits(neighbourState);

        // Условия для сброса определенных битов в зависимости от состояния других битов
        if (!bits[1]) {
            bits[0] = bits[2] = false;
        }

        if (!bits[3]) {
            bits[0] = bits[5] = false;
        }

        if (!bits[4]) {
            bits[2] = bits[7] = false;
        }

        if (!bits[6]) {
            bits[5] = bits[7] = false;
        }

        // Пересоздание состояния с учетом измененных битов
        neighbourState = createState(bits);

        // Проверка наличия состояния в карте TILEMAP
        if (TILEMAP.containsKey(neighbourState)) {
            // Получение списка текстур для определенного состояния блока
            List<Texture> varTextures = textures.get(TILEMAP.get(neighbourState));
            // Выбор случайной текстуры из списка
            Texture texture = varTextures.get(random.nextInt(varTextures.size()));
            // Установка текстуры блока, если она отличается от текущей
            if (!texture.equals(getTexture())) {
                setTexture(texture);
            }
        } else {
            // Установка текстуры по умолчанию, если состояние отсутствует в карте TILEMAP
            setTexture(AtlasManager.getInstance().mainAtlas().createTexture(152, 8, 8, 8));
        }
    }


    /**
     * Получить состояние блока с учетом соседних блоков.
     *
     * Биты:
     * [0] [1] [2]
     * [3]     [4]
     * [5] [6] [7]
     *
     * @return Целочисленное представление состояния блока на основе состояний соседей.
     */
    public int getNeighbourState() {
        // Получение списка соседних блоков
        List<TiledBlock> tiledBlocks = getWorld().getTiledBlocks();
        // Инициализация переменной для хранения битового представления состояния
        int result = 0;
        // Получение координат текущего блока в сетке
        int thisX = (int) getX() / 8;
        int thisY = (int) getY() / 8;

        // Перебор соседних блоков для определения их воздействия на текущий блок
        for (TiledBlock tiledBlock : tiledBlocks) {
            // Проверка, что текущий блок не равен соседнему и расстояние между ними менее 16 единиц
            // и у них совпадают цвет, внешний вид и класс
            if (this != tiledBlock &&
                RadialUtils.distance(this, tiledBlock) < 16 &&
                getColor().equals(tiledBlock.getColor()) &&
                getAppearance().getIdleTexture().x() == tiledBlock.getAppearance().getIdleTexture().x() &&
                getAppearance().getIdleTexture().y() == tiledBlock.getAppearance().getIdleTexture().y() &&
                getClass() == tiledBlock.getClass()) {

                // Получение координат соседнего блока в сетке
                int thatX = (int) tiledBlock.getX() / 8;
                int thatY = (int) tiledBlock.getY() / 8;

                // Проставление битов в зависимости от положения соседнего блока относительно текущего
                if (thatX > thisX && thatY > thisY) {
                    result = setBit(result, 0, true);
                }

                if (thatX == thisX && thatY > thisY) {
                    result = setBit(result, 1, true);
                }

                if (thatX < thisX && thatY > thisY) {
                    result = setBit(result, 2, true);
                }

                if (thatX > thisX && thatY == thisY) {
                    result = setBit(result, 3, true);
                }

                if (thatX < thisX && thatY == thisY) {
                    result = setBit(result, 4, true);
                }

                if (thatX > thisX && thatY < thisY) {
                    result = setBit(result, 5, true);
                }

                if (thatX == thisX && thatY < thisY) {
                    result = setBit(result, 6, true);
                }

                if (thatX < thisX && thatY < thisY) {
                    result = setBit(result, 7, true);
                }
            }
        }

        // Возвращение результата - целочисленного представления состояния блока
        return result;
    }

    /**
     * Установить внешний вид блока, основываясь на заданном внешнем виде.
     * @param appearance Внешний вид блока.
     */
    @Override
    public void setAppearance(Appearance appearance) {
        List<Texture> tex = appearance.get("tiledblock");

        appearance.put("idle", List.of(tex.get(0)));

        if (tex != null) {
            for (Texture texture : tex) {
                textures = new ArrayList<>();

                int x = 0;
                int y = 0;
                for (int i = 0; i < 48; i++) {
                    List<Texture> list = new ArrayList<>();

                    for (int j = 0; j < appearance.getVariety(); j++) {
                        Texture subtexture = texture.getSubtexture((j * 48) + x * 8, y * 8, 8, 8);
                        list.add(subtexture);
                    }

                    textures.add(list);

                    x++;
                    if (x > 5) {
                        y++;
                        x = 0;
                    }
                }
            }
        }

        IStaticOptimizedTangible.super.setAppearance(appearance);
    }

    /**
     * Установить бит в заданное состояние.
     * @param source Исходное состояние.
     * @param bitIndex Индекс бита.
     * @param value Значение бита.
     * @return Новое состояние с установленным битом.
     */
    public static int setBit(int source, int bitIndex, boolean value) {
        if (bitIndex < 0 || bitIndex > 7) {
            throw new IllegalArgumentException("Bit index should be in the range of 0 to 7");
        }

        if (value) {
            return source | (1 << bitIndex);
        } else {
            return source & ~(1 << bitIndex);
        }
    }

    /**
     * Получить значение бита по индексу.
     * @param source Исходное состояние.
     * @param bitIndex Индекс бита.
     * @return Значение бита.
     */
    public static boolean getBit(int source, int bitIndex) {
        if (bitIndex < 0 || bitIndex > 7) {
            throw new IllegalArgumentException("Bit index should be in the range of 0 to 7");
        }

        return (source & (1 << bitIndex)) != 0;
    }

    /**
     * Получить массив битов из целочисленного состояния.
     * @param source Исходное состояние.
     * @return Массив битов.
     */
    public static boolean[] getBits(int source) {
        boolean[] result = new boolean[8];
        for (int i = 0; i < 8; i++) {
            result[i] = getBit(source, i);
        }
        return result;
    }

    /**
     * Создать целочисленное состояние на основе массива битов.
     * @param bits Массив битов.
     * @return Целочисленное состояние.
     */
    public static int createState(boolean[] bits) {
        int result = 0;
        for (int i = 0; i < 8; i++) {
            result = setBit(result, i, bits[i]);
        }
        return result;
    }

    /**
     * Метод, вызываемый при инициализации игрового объекта.
     * Не используется в данной реализации.
     */
    @Override
    public void onGameObjectInit() {
        // not in use
    }

    /**
     * Метод, выполняемый в каждом кадре игрового процесса.
     * Не используется в данной реализации.
     */
    @Override
    public void process() {
        // not in use
    }
}



Пикчи:

Тестовый тайлсет 8x8
Image

Тестовый тайлсет 16x16
Image

Схема с номерами тайлов
Image


_________________
И неважно, что нет морей на Марсе, каждый морпех носит море в сердце.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC + 3 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
doom2d.org, since 2005