3

我一直在使用artemis-odblibGDX 开发基于回合的瓷砖地图游戏。

我想要地图的不同地形类型,例如草、沙、水、山脉等,这些不同的地形类型中的每一种都有不同的移动成本和与游戏相关的各种附加属性。

我目前正在考虑几种不同的方法:

  1. 我可以使地图成为一个系统GameMapSystem,并让每种类型的地形由一个实体表示,并具有每种地形的相关组件(例如TerrainStats,偶尔会出现拼写效果组件Exploding)。我主要关心的是如何管理瓷砖到地形类型实体的映射。从概念上讲,这应该像使用int[][]与地形实体的 id 对应的值一样容易维护,但是在这种情况下,临时标记组件 ( Exploding) 将一次附加到所有给定的地形类型。这似乎不太理想。那么我需要为每个图块设置一个单独的实体吗?如果我这样做,我不会为实体框架创建额外的开销吗?

  2. 我还考虑过制作游戏地图和地形类型 POJOS,然后简单地使用标记组件创建标记实体以实现特殊效果。然而,这样做,看起来我会随意传递GameMap对象,以便让各种系统能够处理它(用于渲染、碰撞、路径等)。此外,我的游戏地图是否还需要在任何给定时间跟踪地图上的实体及其位置以执行我的路径逻辑?如果可能的话,我更愿意将实体的管理完全保留在实体框架的范围内,因为这意味着维护会稍微容易一些。

我很好奇是否有任何我尚未研究过的方法。否则我会倾向于方法#2,除非有某种方法可以修复我忽略的方法#1。

4

2 回答 2

1

我最终使用了两种方法中的一些东西。以下代码片段应该有助于说明我采用的方法:

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实体放入地图中的事件。在这种情况下,经理会矫枉过正,因为它会告诉我每个实体都被插入或删除,而我只关心与地图相关的实体(或者更具体地说是与地图相关的具有位置的实体)。然后,我有一些单独的寻路逻辑,它使用额外的(此处未显示)方法来引导 AI,只需从世界对象请求这个被动系统即可。

为了完整起见,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此实现的性能尚未经过严格的测试!

于 2014-04-23T00:13:31.987 回答
0

我正在弄清楚同样的事情。只是想分享一些来自 Artemis 开发人员关于这个案例的答案,这让我们实际上没有答案,但值得一提:

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

老实说,Artemis“仍然被认为是实验性的”。这是我想研究的一种新范式,它很有希望,但仍有一些问题我还没有真正找到答案,系统扮演的角色有多大,你没有将什么放入实体/组件等。当涉及到看似非实体的事物(如地形、背景音乐等)时,我的脑海中正在发生一些关于实体/组件扮演的角色有多大的争论。

他给出的另一个提示是区分:

  • 系统在实体上/与实体“可以做”。(“它可以”AcquireEnemyTarget(系统),“它可以”SpawnNewBaddies(系统))
  • 组件是相应组件表中的表行,包含特定功能/状态的数据。

所以看起来应该由我们来探索实际的解决方案,因为范式还不够成熟,不能有一个“正确”的答案

于 2014-05-02T00:30:23.057 回答