【问题标题】:Create 2D map on which objects can be placed and move in Java在 Java 中创建可以放置和移动对象的 2D 地图
【发布时间】:2016-02-14 02:12:40
【问题描述】:

我是 Java 新手,我正在尝试制作一个基于文本的 2D 游戏,该游戏内部包含一个世界地图,对象可以在该地图上进行交互和移动。但是,我一开始就很困惑。假设我有一张 3x3 地图。然后,每个图块都是多维数组中的一个值(例如坐标 [0][2] 上的图块)。但是,每个图块必须是对象数组(例如,图块对象(森林、地面、沙漠)或实体(人、岩石、鸭子))。它应该是一个数组,因为每个图块必须至少包含一个图块对象(森林、地面、沙漠),但理论上可以包含无限数量的实体。

我在这里尝试并结合了一些答案。当前的想法是制作一个只有坐标的超类实体。创建实体时(给定一些坐标),该对象将链接到该位置的世界地图(似乎不起作用)。世界地图是一个可以容纳实体的多维数组。

实体没有链接到世界地图,我不知道为什么。 Entity.java 和 World.java 文件肯定出错了,但我仍然不确定如何将位置设置为世界地图。 请注意,稍后我需要从地图中设置或更改实体的位置,或者将实体作为一个整体移除(包括其在地图上的位置)。

【问题讨论】:

  • 不要将你的类命名为 Map,因为已经有 Map 被定义为 java 集合的一部分
  • world.mapArray[0][0] = tile; 虽然我不建议将mapArray 直接暴露给外界
  • 什么时候将对象放入地图中? -- 在你的 for 循环中执行。
  • @redFIVE 谢谢,不知道。 Eclipse 没有给我警告——够奇怪的。帖子已更新。
  • @Michael 你的意思是你要把mapArray设为私有并使用setter?我仍然对为什么需要private 之类的东西感到困惑......但我必须仔细阅读。帖子已更新。

标签: java arrays multidimensional-array tile


【解决方案1】:

我认为你的图块不应该是一个实体。我认为瓷砖应该封装实体。例如。

public class Tile {
    final int xPos,yPos;
    private String type;
    List<Entity> entities = new ArrayList<>();
    public Tile(int xPosition, int yPosition) {
        xPos = xPosition;
        yPos = yPosition;
    }

    public void setType(String type){
        this.type = type;
    }
    public String getType() {
        return type;
    }
    public void addEntity(Entity e){
        entities.add(e);
    }
    public void removeEntity(Entity e){
        entities.remove(e);
    }
}

那么世界级将包含实体:

public class World {
    Tile[][] coordinates;
    List<Tile> tiles = new ArrayList<>();

    // Creates an instance of a map with a user-defined size
    public World(int width, int height) {
        coordinates = new Tile[width][height];
        for(int i = 0; i<width; i++){
            for(int j = 0; j<height; j++){
                coordinates[i][j] = new Tile(i,j);
                tiles.add(coordinates[i][j]);
            }
        }
    }

    public void addEntity(Entity e){
        int x = e.getX();
        int y = e.getY();
        coordinates[x][y].addEntity(e);
    }
    public void setTileType(int x, int y, String type){
        coordinates[x][y].setType(type);
    }
    public List<Tile> getTiles(){
        return new List<>(tiles);
    }

    public List<Entity> getEntities(){
       List<Entity> entities = new ArrayList<>();
       for(Tile[] row: coordinates){
           for(Tile tile: row){
               entities.addAll(tile.entities);
           }
       }
    }
}

然后你的 main 方法看起来像这样:

public static void main(String[] args) {
    // Creators
    World worldMap = new World(mapWidth, mapHeight);
    Character john = new Character("John", 0, 0);
    Character mary = new Character("Mary", 1, 4);
    worldMap.addEntity(john);
    worldMap.addEntity(mary);

    for (int x = 0; x < mapWidth; x++) {
        worldMap.setTileType(x, 5, "forest");
    }

    // Printing out info about character(s)
    //I think the world map should  have a list of characters.

    for (Character character : charList) {
        System.out.print(character+": " + character.getName() + "\n");
    }
    System.out.print("\n"+charList.size() + " characters in play\n\n");
    List<Tile> tileList = worldMap.getTiles(); 
    // Printing out info about tile(s)
    for (Tile tile : tileList) {
        System.out.print(tile + " type: " + tile.getType() + "\n");
    }
    System.out.print("\n"+tileList.size() + " tiles in play");
}

之所以有两个#addEntity(),是因为想如何回答下面的困境。

“我想创建一个角色 john 并将他添加到位置 x,y 的世界中。”

我们当然会有

Character john = new Character("john", x, y);

那么,我们如何将 john 添加到 World 中?我说,我们只是将他添加到世界中,让世界知道如何处理瓷砖等。

world.addEntity(john);

约翰有一个位置,世界可以找到瓷砖,并将约翰放在瓷砖上。相反,与您尝试过的更相似,是找到该图块并直接添加 john。

world.coordinates[john.getX()][john.getY()].addEntity(john);

正如您在示例中所展示的,这与索引越界异常存在一些问题,您应该处理这些问题。所以问题是,你需要知道很多关于瓦片的结构才能添加一个实体。相反,让世界添加实体,因为这样它就可以一次性处理所有警告并将实体放置在正确的图块上。

【讨论】:

  • 嗯,我喜欢这种方法。基本上你正在做的是创建一个特定大小的世界地图,然后用瓷砖填充它。稍后您将分配图块类型。然后您还可以将项目添加到特定坐标?不过,我很困惑,您是如何实现addEntity() 的。世界级有一个,瓷砖级有一个。我如何看待它,您将 .addEntity 调用到世界,然后将其传递给所需的图块。因此,从某种意义上说,您正在将实体添加到图块中。正确的?我认为令人困惑的是他们有相同的名字!
  • 我昨天晚上更新了我的代码,并添加了一个赏金来给你们一些动力 :) 不过我非常喜欢你的方法,所以我可能会摆弄一下,看看我能不能用你的方法解决我所有的问题。如果你愿意,你可以阅读我更新的帖子,看看你可以在你的答案中添加什么。在此先感谢您,并为您已有的想法 +1!
  • @BramVanroy Re addEntity。是的,这看起来有点令人困惑,这就是好的 API 的重要性。我会在答案的底部添加解释。
  • 感谢您的回复。我一直在查看您的代码,有些事情我不确定。正如我所说,我是 Java 新手,所以我在这里可能完全错了:在 World 的第 4 行,我想你在 new ArrayList&lt;() 中忘记了 &gt;Entities 类型也不存在。这条线似乎是多余的,因为您已经在 Tile 中定义了它,还是我错了?另外,感谢您的更新!
  • @BramVanroy 好眼光。注意 World 中的 List 跟踪世界中的所有实体。 tile 中的 List 仅跟踪 该 tile 中的实体。我同意 World 类中的 List 有点多余。您可以在需要时创建该列表。
【解决方案2】:

您只是将平铺地图误认为世界。这些不是相同的概念。瓦片地图描述了世界的基本外观,可能还有一些基本属性(如可步行、不可通过)。

然而,它并不是对世界的完整描述。任何不是瓦片的东西都需要另外建模。我能想到三个可行的概念:

a.) 有一个额外的数组用于占据一个平铺位置的东西。如果在任何时候只有一个“东西”可以占据特定位置,这可能会很好。

b.) 不要使用 tile 数组,而是使用 location 数组。不同之处在于您可以对位置进行建模以包含您需要的任何内容,例如一个图块加上该位置的居住者列表。那是a的OO变体。

c.) 拥有一个独立的数据结构来跟踪世界上存在的事物,例如人员名单。您需要将某物所在的位置建模为可以填充世界的项目状态的一部分,例如一个人现在有一个额外的 x,y 坐标来指示他们在世界上的位置。

无论您选择哪种,它们都有其优点和缺点,您也可以使用混合,例如有一个位置图一个项目列表。这结合了两种模型的优势(通过位置图快速简单地检查某个位置的周围环境,以及通过列表简单地访问整个世界人口)。选择适合您需要的任何内容。

【讨论】:

    【解决方案3】:

    我尽量不告诉他们按照我的方式思考来帮助他们。去你的直觉引导你的地方通常会更好。话虽如此,我仍然会说你应该重构你的 Map 类,因为你并不是真的想要一个 java Map,并给它一个不同的名字,在这里我建议 World 作为你的类来包含你的图块。此外,您的 Main 类应该是带有 main 方法的 Game。我之所以建议这些更改,只是因为 Map 和 main 在 java 中有自己的含义。

    您还需要分离您的类,以便每个类都在自己的 .java 文件中。所以你开始的内容,加上上面提到的重构留下了三个文件:

        Entity.java
    package stackoverflow;
    
    public class Entity {
        String name;
        int xPosition, yPosition;
    
        // Creates an entity with a name, and an x/y-position on a maps
        public Entity(String name, int xPosition, int yPosition) {
            this.name = name;
            this.xPosition = xPosition;
            this.yPosition = yPosition;
        }
    }
    
    World.java
    package stackoverflow;
    
    public class World {
    
        Entity[][] mapArray;
    
        // Creates an istance of a map with a user-defined size
        public World(int width, int height) {
            mapArray = new Entity[width][height];
        }
    }
    
    Game.java
    package stackoverflow;
    
    public class Game {
        static int mapWidth = 8;
        static int mapHeight = 8;
    
        public static void main(String[] args) {
            World world = new World(mapWidth, mapHeight);
            Entity john = new Entity("John", 0, 0);
            for (int x = 0; x < mapWidth; x++) {
                for (int y = 0; y < mapHeight; y++) {
                    Entity tile = new Entity("Tile", x, y);
                }
            }
        }
    }
    

    多维数组可以工作,但有一些缺点。你也可以让你的世界成为一个实体列表,你可以添加、删除、遍历等,可能比数组更容易。但是你可以用你现在知道的东西弄湿你的脚。对于测试,我建议您将 JUnit 添加到您的项目中。我也喜欢宣传测试驱动开发,但这可能是一个完全不同的问题。

    要使您的实体移动,请向您的实体对象添加一个 move(x, y) 方法或 moveTo(x, y) 方法,并将移动距离(或新坐标)作为参数。

    【讨论】:

      【解决方案4】:


      我不是 100% 确定您想要实现什么,但让我回答您的直接问题,然后可能会给您一些提示。 因此,如果您想将实体 John 分配到左上角,您可以通过以下方式完成:world.mapArray[0][0] = john; 无论如何,为您的Java beans/POJOs 设置访问器和修改器方法是一个好习惯。话虽如此,我建议您将所有字段设为私有然后。在这种情况下,您的代码将如下所示:

      public class Map {
            private Entity[][] mapArray;
      
            // Creates an istance of a map with a user-defined size
            public Map(int width, int height) {
               mapArray = new Entity[width][height];
            }
      
            public void setEntityAt(Entity entity, int x, int y) {
               if (x < 0 || x > mapArray.length || y < 0 || y > mapArray[x].length) {
                     throw new IllegalArgumentException("Incorrect X or Y parameters");
               }
               mapArray[x][y] = entity;
            }
         }
      

      我建议您不要将实体的位置作为其上下文的一部分,否则每次移动它时都必须更新它。这样你就可以得到类似的东西:

      Map world = new Map(mapWidth, mapHeight);
      Entity john = new Entity("John");
      world.setEntityAt(john, 0, 0);
      

      最后但同样重要的是,如果您尝试编译代码,您必须知道 Java 文件中不能有超过 1 个公共类。 附言您可以使用JUnit 进行测试。

      【讨论】:

      • 如果您仍然感兴趣:我添加了赏金和大量附加信息。
      猜你喜欢
      • 2019-11-02
      • 2011-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-25
      • 1970-01-01
      相关资源
      最近更新 更多