Структура данных для тайловой карты для использования с Artemis

Я работал над пошаговой игрой, основанной на тайловой карте, используя artemis-odb и libGDX.

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

В настоящее время я рассматриваю несколько разных подходов:

  1. Я мог бы сделать карту системой GameMapSystem и представить каждый тип местности сущностью с соответствующими компонентами для каждого типа местности (например, TerrainStats, а иногда и компонентами эффектов заклинаний Exploding). Меня больше всего беспокоит, как управлять сопоставлением плиток с объектами типа ландшафта. Концептуально это должно быть так же просто, как поддерживать int[][] со значениями, соответствующими идентификатору объекта местности, однако в этом случае компоненты временного маркера (Exploding) будут прикреплены ко всем данным типам местности одновременно. Это кажется менее чем оптимальным. Тогда мне нужно будет иметь отдельный объект для каждой плитки? Разве я не создаю дополнительные накладные расходы для структуры сущностей, если я сделаю это тогда?

  2. Я также подумал о том, чтобы сделать игровую карту и типы ландшафта POJOS, а затем просто создать объекты маркеров с компонентами маркеров для специальных эффектов. Однако, делая это таким образом, похоже, что я буду передавать объект GameMap волей-неволей, чтобы различные системы могли его обрабатывать (для рендеринга, коллизий, путей и т. д.). Кроме того, не должна ли моя игровая карта также отслеживать объекты, которые находятся НА карте в любой момент времени, с их позициями, чтобы выполнить мою логику пути? Я бы предпочел, чтобы управление сущностями было полностью в рамках структуры сущностей, если это возможно, поскольку это означает немного более легкое обслуживание.

Мне любопытно, есть ли какие-либо подходы, которые я еще не изучил. В противном случае я склоняюсь к методу № 2, если только нет способа исправить метод № 1, который я упустил из виду.


person MLevy    schedule 22.04.2014    source источник


Ответы (2)


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

class TerrainType {
    public String displayName;
    public String regionName;
    public int movementCost;
    /* additional properties omitted */
    /* constructors omitted */
}

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

class GameMapSystem extends EntityProcessingSystem {
    @Mapper private ComponentMapper<MapPosition> pm;
    @Mapper private ComponentMapper<SolidObject> som;

    private ListMultimap<MapPosition, Entity> entityByLocation;

    private int[][] map;
    private int width, height;
    private Array<TerrainType> terrainTypes;

    /**
     * Accepts an Array of TerrainType objects and an 2d integer array with
     * values corresponding to indices into the array for the correct type.
     * 
     * In my case, these values are gleaned by reading a level description
     * file, but any source should be fine.
     */
    public GameMapSystem(Array<TerrainType> terrainTypes, int[][] map) {
        super(Aspect.getForAll(MapPosition.class));
        this.terrainTypes = terrainTypes;
        this.map = map;
        this.width = map.length;
        this.height = map[0].length;
        this.entityByLocation = ArrayListMultimap.create();
    }

    public boolean isOccupied(int x, int y) {
        List<Entity> entities = entityByLocation(new MapPosition(x, y));
        for(Entity e : entities) {
            if(som.has(e)) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected void inserted(Entity e) {
        this.entityByLocation.put(pm.get(e), e);
    }

    @Override
    protected void removed(Entity e) {
        this.entityByLocation.remove(pm.get(e), e);
    }

    /* additional EntityProcessingSystem overrides omitted */
}

Затем этот EntityProcessingSystem подключается к моему миру в пассивном режиме. Не должно быть никакой реальной причины выполнять какую-либо обработку моего мира в этой системе, я действительно хотел иметь возможность прослушивать события inserted и removed для размещения объектов на карте. Менеджер был бы излишним в этой ситуации только потому, что он сообщил бы мне о КАЖДОМ объекте, который вставляется или удаляется, и меня заботили только те, которые связаны с картой (или, точнее, те, которые связаны с картой с позицией). Затем у меня есть отдельная логика поиска пути, которая использует дополнительные (не показанные здесь) методы для направления ИИ, просто запрашивая эту пассивную систему у объекта мира.

Для полноты ниже также показан класс MapPosition. Важно включение equals() и hashcode(), чтобы облегчить использование MapPosition в качестве ключа в коллекции.

public class MapPosition extends Component
{
    public int x, y;

    public MapPosition(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public boolean equals(Object other) {
        if(!(other instanceof MapPosition)) {
            return false;
        }

        MapPosition pos = (MapPosition)other;

        return (pos.x == this.x && pos.y == this.y);
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 59 * hash + this.x;
        hash = 59 * hash + this.y;
        return hash;
    }
}

Я, вероятно, попытаюсь найти более удобную структуру данных, чем использование guava Multimap, в конечном итоге, но она работает прямо сейчас, и я чувствую себя комфортно, переходя к конкретизации остальной части общедоступного API для этих классов. Если этот ответ поможет кому-то еще, имейте в виду, что производительность ArrayListMultimap для этой реализации не подвергалась тщательному тестированию!

person MLevy    schedule 23.04.2014

Я в процессе выяснения того же самого. Просто хотел поделиться некоторыми ответами от разработчика Artemis об этом случае, который на самом деле оставляет нас без ответа, но стоит упомянуть:

http://slick.ninjacave.com/forum/viewtopic.php?p=20125#p20125 http://slick.ninjacave.com/forum/viewtopic.php?p=20136#p20136

Честно говоря, Artemis «до сих пор считается экспериментальной». Это новая парадигма, на которую я хотел взглянуть, она многообещающая, но все еще есть вопросы, на которые я не нашел ответов, насколько большую роль играют системы, что вы не вкладываете в сущности/компоненты и т. д. В моей голове происходит некоторая битва о том, насколько большую роль играют сущности/компоненты, когда дело доходит до того, что кажется несущественным, например, ландшафт, фоновая музыка и т. д.

Также еще один намек, данный им, состоит в том, чтобы различать как:

  • Системы, которые «это может делать» с сущностями. ("может" AcquireEnemyTarget(Система), "может" SpawnNewBaddies(Система))
  • Компоненты представляют собой строки таблицы в соответствующей таблице компонентов и содержат данные для определенных функций/состояний.

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

person Sergey Yakovlev    schedule 02.05.2014
comment
Добро пожаловать в StackOverflow! Обязательно пройдите тур и просмотрите справочный центр для получения такой информации, как Как написать хороший ответ. В этом случае вы, возможно, могли бы улучшить свой ответ, по крайней мере, резюмируя вопросы, обсуждаемые в ваших ссылках, а не просто вставляя сами ссылки. - person dg99; 02.05.2014