How can a roguelike have so large maps?

I'm wondering how a roguelike can store HUGE maps (think Dwarf Fortress) without the speed of the program being affected much? Generating a world of, say, 1000x1000 tiles is incredibly tolling on my programs.

Answers

  • Only draw what you can see.

    1k square is only a million, nothing for a modern computer.

    I should try this, see how hard it is. I am sure these are mostly solved problems.

  • Please post anything you do!

    I understand the concept of drawing what you see, but how would I pre-create a gigantic world that you can explore? Obviously, you can't generate a world according to the travels of the player, or else the world will be mangled and disjointed...

  • I once made a map where you move a player (a red dot)

    the map is much bigger than the screen and it scrolls

    http://www.openprocessing.org/sketch/149191

  • Well, that is slightly closer to what I'm getting at...

    I guess it's more a question of procedural generation, and how to store large maps that have been generated in a way that won't bog down the program.

  • edited June 2014 Answer ✓

    yes, when you use byte or short as data type for the 2D array it can be a huge map

    number 1 could mean grass, 2 forest, 3 path etc.

    doesn't take much memory for computer

    for the number 1/2/3 you just show another image in that cell (tile)

    so you only need 3 images that are repeated

    ;-)

  • Answer ✓

    I made an example of navigating a large unit map. At any given point you can only see a 12x12 section of the map, but the rest of the map exists.

    More importantly, only the 12x12 section is drawn, the rest is ignored. Likewise I would imagine that you will have other entities / other characters / bad guys later, you should make similar code where only other entities close enough to where you are on the map are given actions. Say you are at location (10, 30), what some other entity might be doing at location (600, 800) really doesn't matter. This will be what makes it run at a reasonable speed despite being large.

    int locX, locY;
    int unit = 50;
    boolean[][] terrain = new boolean[1000][1000];
    
    void setup() {
      size(600, 600);
    
      // Random terrain, false is water and true is ground
      for (int i = 0; i < 1000; i++) {
        for (int j = 0; j < 1000; j++) {
          terrain[i][j] = random(1.0) > 0.5;
        }
      }
    
      // Random starting position
      locX = int(random(1000));
      locY = int(random(1000));
    
      println("Starting position = ("+locX+", "+locY+")");
    }
    
    void draw() {
      for (int i = 0; i < 12; i++) {
        for (int j = 0; j < 12; j++) {
          int offsetX = locX+i-6;
          int offsetY = locY+j-6;
    
          // Out of bounds
          if (offsetX < 0 || offsetX > 999 || offsetY < 0 || offsetY > 999) fill(0xff808080);
    
          // Draw based on terrain
          else {
            if (terrain[offsetX][offsetY]) fill(0xff008000);
            else fill(0xff0000c0);
          }
    
          rect(i*unit, j*unit, unit, unit);
        }
      }
    }
    
    void keyPressed() {
      if (key == CODED) {
        if (keyCode == UP) locY--;
        else if (keyCode == RIGHT) locX++;
        else if (keyCode == DOWN) locY++;
        else if (keyCode == LEFT) locX--;
        println("Current position = ("+locX+", "+locY+")");
      }
    }
    
  • Thank you for the help!

    Note: @asimes The window size shows up as about 100x100, and that seems to happen whenever a large array is made in Processing. I fixed it using frame.setResizable(true);. Also, I would love to use what I know to polish up your code for you!

  • Here is a combination of what both of you suggested, based off of the example:

    int locX, locY;
    
    final int unit = 50;
    byte[][] terrain = new byte[1000][1000];
    
    void setup() {
      size(600, 600);
      frame.setResizable(true);
    
      // Random terrain, false is water and true is ground
      for (int i = 0; i < 1000; i++) {
        for (int j = 0; j < 1000; j++) {
          terrain[i][j] = byte(ceil(noise(i*0.05,j*0.05)*60));
        }
      }
    
      // Random starting position
      locX = int(random(6,994));
      locY = int(random(6,994));
    
      println("Starting position = ("+locX+", "+locY+")");
    }
    
    void draw() {
      for (int i = 0; i < 12; i++) {
        for (int j = 0; j < 12; j++) {
          int offsetX = locX+i-6;
          int offsetY = locY+j-6;
    
          // Out of bounds
          if (offsetX < 0 || offsetX > 999 || offsetY < 0 || offsetY > 999) fill(0xff808080);
    
          // Draw based on terrain
          else {
            if (terrain[offsetX][offsetY]>34){
              fill(0xff005000);
            }
            else if (terrain[offsetX][offsetY]>29){
              fill(0xff008000);
            }
            else if (terrain[offsetX][offsetY]>26){
              fill(0xffc0c000);
            }
            else {
              fill(0xff0000c0);
            }
          }
    
          rect(i*unit, j*unit, unit, unit);
        }
      }
      fill(255,0,0);
      ellipse(300,300,20,20);
    }
    
    void keyReleased() {
      if (key == CODED) {
        if (keyCode == UP && locY>6) locY--;
        else if (keyCode == RIGHT && locX<994) locX++;
        else if (keyCode == DOWN && locY<994) locY++;
        else if (keyCode == LEFT && locX>6) locX--;
        println("Current position = ("+locX+", "+locY+")");
      }
    }
    
  • Glad it made sense and I am kind of surprised noise() makes reasonable looking terrain

  • I think my version is a little better since the scrolling of the map stops on the borders but the player moves on and walks to the end of the map (on the screen border)

    when he walks back to the center, the map scrolling only starts again when he reaches the center of the map again

    do you have that?

  • edited June 2014

    Couldn't resist and made a online remix version too: =:)
    http://studio.processingtogether.com/sp/pad/export/ro.9Ql2E8JBC54LJ/latest

    P.S.: Hit SPACE or ENTER in order to switch turbo mode on/off! ;)

    /**
     * Random Tiled Terrain Gen (v3.01)
     * by  Asimes (2014/Jun)
     * mod Matyze & GoToLoop
     *
     * forum.processing.org/two/discussion/5985/
     * how-can-a-roguelike-have-so-large-maps
     *
     * studio.processingtogether.com/sp/pad/export/ro.9Ql2E8JBC54LJ/latest
     */
    
    static final byte OUTSIDE = 0, GRASS = 1, FOREST = 2;
    static final byte LAND = 3, WATER = 4;
    
    static final color[] colors = {
      #808080, #005000, #008000, #C0C000, #0000C0
    };
    
    static final int GRID = 05000, UNIT = 060;
    static final int GW = 5*UNIT << 2, GH = 4*UNIT << 2;
    static final int UW = GW/UNIT, UH = GH/UNIT;
    final byte[][] tiles = new byte[GRID][GRID];
    
    static final int DIAM  = 020, FPS = 25, SMOOTH = 4;
    static final color INK = #FF0000;
    static final float NOISE = .05, WEIGHT = 1.5;
    
    int px, py, ix, iy;
    int north, south, west, east, turbo;
    
    void setup() {
      size(GW, GH, JAVA2D);
      frameRate(FPS);
      smooth(SMOOTH);
    
      //noStroke();
      strokeWeight(WEIGHT);
    
      rectMode(CORNER);
      ellipseMode(CENTER);
    
      terrainGenerator(tiles, NOISE);
    
      px = (width>>1)  + (UNIT>>1);
      py = (height>>1) + (UNIT>>1);
    
      ix = (int) random(GRID);
      iy = (int) random(GRID);
    }
    
    void draw() {
      moveHero();
      renderTerrain(tiles);
    
      fill(INK);
      ellipse(px, py, DIAM, DIAM);
    
      info();
    }
    
    void info() {
      if (!online)  frame.setTitle("Grid: " + GRID + " x " + GRID
        + "\t\t(" + nf(ix, 4) + ", " + nf(iy, 4) + ")\t\t"
        + (turbo > 0? "TURBO MODE" : ""));
    }
    
    void drawTile(int x, int y, int diam, color c) {
      fill(c);
      rect(x, y, diam, diam);
    }
    
    void renderTerrain(byte[][] terrain) {
      for (int row = UH; row-- != 0;) {
        int offsetY = iy+row - (UH>>1);
    
        if (offsetY < 0 | offsetY >= GRID)
          for (int col = UW; col-- != 0;
            drawTile(col*UNIT, row*UNIT, UNIT, colors[OUTSIDE]));
    
        else {
          byte[] columns = terrain[offsetY];
    
          for (int col = UW; col-- != 0;) {
            int offsetX = ix+col - (UW>>1);
    
            color c = offsetX < 0 | offsetX >= GRID
              ? colors[OUTSIDE] : colors[columns[offsetX]];
    
            drawTile(col*UNIT, row*UNIT, UNIT, c);
          }
        }
      }
    }
    
    void terrainGenerator(byte[][] terrain, float noiseScale) {
      for (int row = terrain.length; row-- != 0;) {
        byte[] columns = terrain[row];
    
        for (int col = columns.length; col-- != 0;) {
          float val = noise(row*noiseScale, col*noiseScale);
          byte type;
    
          if      (val > .55)  type = GRASS;
          else if (val > .45)  type = FOREST;
          else if (val > .35)  type = LAND;
          else                 type = WATER;
    
          columns[col] = type;
        }
      }
    }
    
    void keyPressed() {
      int k = keyCode;
    
      if (k == ' ' | k == ENTER | k == RETURN)  turbo ^= 2;
      else  setDirection(k, 1);
    }
    
    void keyReleased() {
      setDirection(keyCode, 0);
    }
    
    void setDirection(int k, int spd) {
      if      (k == UP    | k == 'W')  north = spd;
      else if (k == DOWN  | k == 'S')  south = spd;
      else if (k == LEFT  | k == 'A')  west  = spd;
      else if (k == RIGHT | k == 'D')  east  = spd;
    }
    
    void moveHero() {
      ix += east  - west  << turbo;
      iy += south - north << turbo;
    }
    
  • @Chrisir My program does allow for not being able to magically walk off the map, but it won't allow walking to the edge.

  • I now made it more roguelike:

    int locX, locY;
    
    final int unit = 25;
    byte[][] terrain = new byte[1000][1000];
    
    void setup() {
      size(550, 550);
      textSize(unit);
      frame.setResizable(true);
    
      // Random terrain, false is water and true is ground
      for (int i = 0; i < 1000; i++) {
        for (int j = 0; j < 1000; j++) {
          terrain[i][j] = byte(ceil(noise(i*0.05,j*0.05)*60));
        }
      }
    
      // Random starting position
      locX = int(random(6,994));
      locY = int(random(6,994));
    
      println("Starting position = ("+locX+", "+locY+")");
    }
    
    void draw() {
      background(0);
      for (int i = 0; i < 22; i++) {
        for (int j = 0; j < 22; j++) {
          int offsetX = locX+i-10;
          int offsetY = locY+j-10;
          char c = ' ';
    
          if(offsetX == locX && offsetY == locY){
            continue;
          }
    
          // Draw based on terrain
    
            if (terrain[offsetX][offsetY]>43){
              fill(0xff803000);
              c='^';
            }
            else if (terrain[offsetX][offsetY]>34){
              fill(0xff005000);
              c='T';
            }
            else if (terrain[offsetX][offsetY]>29){
              fill(0xff008000);
              c='#';
            }
            else if (terrain[offsetX][offsetY]>26){
              fill(0xffc0c000);
              c='%';
            }
            else {
              fill(0xff0000c0);
              c='~';
            }
    
    
          text(c,i*unit, j*unit+unit);
        }
      }
      fill(255,250,250);
      text("@",245,250+unit);
    }
    
    void keyReleased() {
      if (key == CODED) {
        if (keyCode == UP && locY>11 && terrain[locX][locY-1]>26 && terrain[locX][locY-1]<44) locY--;
        else if (keyCode == RIGHT && locX<989 && terrain[locX+1][locY]>26 && terrain[locX+1][locY]<44) locX++;
        else if (keyCode == DOWN && locY<989 && terrain[locX][locY+1]>26 && terrain[locX][locY+1]<44) locY++;
        else if (keyCode == LEFT && locX>11 && terrain[locX-1][locY]>26 && terrain[locX-1][locY]<44) locX--;
        println("Current position = ("+locX+", "+locY+")");
      }
    }
    
  • edited May 2016

    I've updated my remix version to be more rogue-like too: :bz
    http://studio.ProcessingTogether.com/sp/pad/export/ro.9Ql2E8JBC54LJ

    /**
     * Random Tiled Terrain Gen (v4.11)
     * by  Asimes (2014/Jun)
     * mod Matyze & GoToLoop
     *
     * forum.Processing.org/two/discussion/5985/
     * how-can-a-roguelike-have-so-large-maps#Item_16
     *
     * studio.ProcessingTogether.com/sp/pad/export/ro.9Ql2E8JBC54LJ
     */
    
    static final byte VOID = ' ', HERO = '@', MOUNT = '^';
    static final byte TREE = 'T', GRASS = '#';
    static final byte LAND = '%', WATER = '~';
    
    static final int COLORS = 0200;
    static final color[] colors = new color[COLORS];
    static  // comment this line out for JS Mode!
    {
      colors[VOID]   = #808080;
      colors[HERO]   = #E0E0E0;
      colors[MOUNT]  = #803000;
      colors[GRASS]  = #008000;
      colors[TREE]   = #005000;
      colors[LAND]   = #C0C000;
      colors[WATER]  = #0000C0;
    };
    
    void terrainGenerator(byte[][] terrain, float noiseScale) {
      noiseSeed((long) random(MIN_INT, MAX_INT));
    
      for (int row = terrain.length; row-- != 0; ) {
        byte[] columns = terrain[row];
    
        for (int col = columns.length; col-- != 0; ) {
          float val = noise(row*noiseScale, col*noiseScale);
          byte type;
    
          if      (val > .65)  type = MOUNT;
          else if (val > .55)  type = GRASS;
          else if (val > .45)  type = TREE;
          else if (val > .35)  type = LAND;
          else                 type = WATER;
    
          columns[col] = type;
        }
      }
    }
    
    static final int GRID = 05000, UNIT = 030;
    static final int GW = 8*UNIT << 2, GH = 6*UNIT << 2;
    static final int UW = GW/UNIT, UH = GH/UNIT;
    final byte[][] tiles = new byte[GRID][GRID];
    
    static final byte[] FPS = { 25, 40, 60, 80, 100 };
    int fps;
    
    static final int DIAM  = UNIT>>1, SMOOTH = 4;
    static final color INK = #FF0000, BG = 0;
    static final float NOISE = .05, WEIGHT = 1.5;
    
    static final boolean ONLINE = 1/2 == 1/2.;
    static final boolean OUTLINE = true;
    
    static final String RENDER = ONLINE? JAVA2D : FX2D; // P3+
    //static final String RENDER = JAVA2D; // P2
    //static final String RENDER = P2D; // P1
    
    int px, py, ix, iy;
    int north, south, west, east;
    boolean rogue = true;
    
    void settings() {
      size(GW, GH, RENDER);
      smooth(SMOOTH); // P2+
      //smooth(); // P1
    }
    
    void setup() {
      if (width != GW)  settings();
      frameRate(FPS[fps]);
    
      if (OUTLINE)  stroke(BG);
      else          noStroke();
      strokeWeight(WEIGHT);
    
      textSize(UNIT);
      textAlign(LEFT, TOP);
    
      rectMode(CORNER);
      ellipseMode(CENTER);
    
      px = (width>>1)  + DIAM;
      py = (height>>1) + DIAM;
    
      reset();
    }
    
    void draw() {
      if (rogue)  background(BG);
    
      moveHero();
      renderTerrain(tiles);
    
      if (rogue)  drawHeroChar();
      else        drawHeroTile();
    
      if (!ONLINE)  info();
    }
    
    void info() {
      frame.setTitle("Grid: " + GRID + " x " + GRID
        + "    (" + nf(ix, 4) + ", " + nf(iy, 4)
        + ")    FPS: " + nf(round(frameRate), 3));
    }
    
    void reset() {
      terrainGenerator(tiles, NOISE);
      teleport();
    }
    
    void drawHeroTile() {
      fill(INK);
      ellipse(px, py, DIAM, DIAM);
    }
    
    void drawHeroChar() {
      fill(colors[HERO]);
      text((char) HERO, px - DIAM, py - DIAM);
    }
    
    void drawTile(int x, int y, int diam, color c) {
      fill(c);
      rect(x, y, diam, diam);
    }
    
    void drawChar(int x, int y, char ch, color c) {
      fill(c);
      text(ch, x + (UNIT>>2), y);
    }
    
    void renderTerrain(byte[][] terrain) {
      for (int row = UH; row-- != 0; ) {
        int oy = iy+row - (UH>>1);
    
        if (oy < 0 | oy >= GRID)  for (int col = UW; col-- != 0; )
          if (rogue)  drawChar(col*UNIT, row*UNIT, (char) VOID, colors[VOID]);
          else        drawTile(col*UNIT, row*UNIT, UNIT, colors[VOID]);
    
        else {
          byte[] columns = terrain[oy];
    
          for (int col = UW; col-- != 0; ) {
            int ox  = ix+col - (UW>>1);
            char ch;
            color c;
    
            if (ox >= 0 & ox < GRID) {
              ch = (char) columns[ox];
              c  = colors[ch];
            } else {
              ch = VOID;
              c  = colors[VOID];
            }
    
            if (rogue)  drawChar(col*UNIT, row*UNIT, ch, c);
            else        drawTile(col*UNIT, row*UNIT, UNIT, c);
          }
        }
      }
    }
    
    void keyPressed() {
      int k = keyCode;
    
      if (k == ENTER | k == RETURN)  frameRate(FPS[fps = (fps+1)%FPS.length]);
      else if (k == CONTROL | k == 'T')  teleport();
      else if (k == DELETE  | k == ALT)  reset();
      else if (k == SHIFT   | k == ' ')  rogue ^= true;
      else setDirection(k, 1);
    }
    
    void keyReleased() {
      setDirection(keyCode, 0);
    }
    
    void setDirection(int k, int spd) {
      if      (k == UP    | k == 'W')  north = spd;
      else if (k == DOWN  | k == 'S')  south = spd;
      else if (k == LEFT  | k == 'A')  west  = spd;
      else if (k == RIGHT | k == 'D')  east  = spd;
    }
    
    void moveHero() {
      int ox = ix + east  - west;
      int oy = iy + south - north;
    
      if (ox < 0 | ox >= GRID | oy < 0 | oy >= GRID)  return;
    
      byte ch = tiles[oy][ox];
      if (ch != WATER & ch != MOUNT) {
        ix = ox;
        iy = oy;
      }
    }
    
    void teleport() {
      byte ch;
    
      do {
        ix = (int) random(GRID);
        iy = (int) random(GRID);
    
        ch = tiles[iy][ix];
      } while (ch == WATER | ch == MOUNT);
    }
    
  • awesome! Now we need to do city generation... :P

    I'm kinda lost when it comes to that

  • I was rather thinking about enemies & treasures! O:-)

  • lol, that would be easier...

    • 1st I recommend to check if there is are lib

    • 2nd you need an graphical editor to be able to edit the map, save it (So longt text pieces with entries like 786576 to decode your city / map content) load it. This map will later be used in the game. Maybe the lib has it. Should have it. Because when you have different levels, for each Level / town you need it.

    • 3rd You need a graphical editor (windows paint or so) to paint the single tiles (cells of the map) such as street, small house with grass in front, big house...

    when it comes to city generation, I don't think you can work with random, because next to a street leading right, on the right side must come a tile that starts with a street on the left (and can go straight or go left or right or be a dead end)

    When you don't have random generation, you need some kind of editor, see above. You could type it in in a text editor (45451546) but that'd be quite a pain...

    ;-)

  • edited June 2014

    You should be familiar with # 1 to 6 of the tutorials at last

    http://www.processing.org/tutorials/

    Look into

    www.openprocessing.org please

    e.g. in the collections you'll find games of course.

    http://www.openprocessing.org/collection/25

    You'll see a lot of games with code there.

    Also for games please see

    Link

    KevinWorkman wrote some basic tutorials on game development in Processing that might help you out: http://www.staticvoidgames.com/tutorials/

  • i went a different route with this, more SNES zelda than rogue, using graphic tiles but allowing sub-tile movement - tiles are 32 pixels wide but the player can move a pixel at a time.

    i've also used a png to define the map so i could just draw it using a graphics package, but which is terribly inefficient as it'll use 24 bits per pixel for the image which it then doesn't directly use. it moves all the non-empty pixels into a hashmap using position as key. this is probably slower than an array, but might be smaller if map is mostly empty - i was experimenting.

    it's more processor intensive than i'd like, but using opengl helps. i turned off the grass tiles because they looked too busy and took too much cpu.

    you'll need to supply the tiles yourself - grass.png, tree.png, lava.png, water.png, wall.png. they need to be 32x32 pixels. but i've uploaded the (crappy) map so the program will pick it up when it runs.

    < and > to rotate (actually , and .) collision detection isn't great, is probably half a tile out. it works ok for something i cobbled together in an hour.

    // megamap acd 2014
    import processing.opengl.*; // not absolutely necessary
    
    private static final int TILE_SIZE = 32;
    
    private static final int GRASS     = 0; 
    private static final int TREE      = 1; 
    private static final int WATER     = 2; 
    private static final int LAVA      = 3; 
    private static final int WALL      = 4; 
    private static final int NUM_TILES = 5; 
    
    private static final int GRASS_COL     = #ffffff; 
    private static final int TREE_COL      = #008000; 
    private static final int WATER_COL     = #4e00ff; 
    private static final int LAVA_COL      = #ff0000; 
    private static final int WALL_COL      = #000000;
    
    PImage mapImg;
    float playerX, playerY;
    float playerA;
    int XTILES, YTILES;  // number of tiles on screen
    int HXTILES, HYTILES;  // half the above
    int HWIDTH, HHEIGHT;  // half screen dimensions
    
    Tile[] tiles = new Tile[NUM_TILES];
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    
    void setup() {
      size(800, 600, OPENGL); // doesn't need opengl but is faster
      tiles[GRASS] = new Tile(GRASS, "grass.png", #ffffff, true, false);
      tiles[TREE]  = new Tile(TREE, "tree.png", #008000, false, false);
      tiles[WATER] = new Tile(WATER, "water.png", #4e00ff, true, true);
      tiles[LAVA]  = new Tile(LAVA, "lava.png", #ff0000, true, true);
      tiles[WALL]  = new Tile(WALL, "wall.png", #000000, false, false);
      println("loading map...");
      mapImg = loadImage("http://" + "www.koogy.clara.co.uk/map.png");
      for (int y = 0 ; y < mapImg.height ; y++) {
        //println("Line: " + y);
        for (int x = 0 ; x < mapImg.width ; x++) {
          switch (mapImg.get(x, y)) {
            case GRASS_COL:
              // default, don't add to map
              break;
            case TREE_COL:
              map.put(x + ":" + y, TREE);
              break;
            case WATER_COL:
              map.put(x + ":" + y, WATER);
              break;
            case LAVA_COL:
              map.put(x + ":" + y, LAVA);
              break;
            case WALL_COL:
              map.put(x + ":" + y, WALL);
              break;
            default:
              println("Not found[" + x + "][" + y + "]: " + mapImg.get(x, y));
          }
        }
      }
      playerX = 200 * 32;
      playerY = 75 * 32;
      playerA = 0;  // angle
      XTILES = width / TILE_SIZE;  // number of tiles across
      YTILES = height / TILE_SIZE;
      HXTILES = XTILES / 2;  // number of tiles across
      HYTILES = YTILES / 2;
      HWIDTH = width / 2;
      HHEIGHT = height / 2;
    //  frameRate(15);
    }
    
    void draw() {
      background(#009000); // grass
      // draw window full of tiles
      int tileX = (int)(playerX / TILE_SIZE);
      int tileY = (int)(playerY / TILE_SIZE);
      //println("TileXY:" + tileX + ":" + tileY);
      int yoff = -(int)playerY % TILE_SIZE;
      for (int y = tileY - HYTILES ; y < tileY + HYTILES + 2 ; y++) {
        int xoff = -(int)playerX % TILE_SIZE;
        for (int x = tileX - HXTILES ; x < tileX + HXTILES + 2 ; x++) {
          // get type for this square (default to GRASS)
          Integer tileType = map.get(x + ":" + y);
    //      if (tileType == null) {
    //        tileType = GRASS;
    //      }
          //println(x + ":" + y + ":" + tileType);
          // draw relevant image
          if (tileType != null) {
            image(tiles[tileType].img, xoff, yoff);
          }
          xoff += TILE_SIZE;
        }
        yoff += TILE_SIZE;
      }
      // can we go forward?
      float dx = 10 * cos(playerA);
      float dy = 10 * sin(playerA);
      float nextX = playerX + dx;
      float nextY = playerY + dy;
      Integer nextTile = map.get(((int)(nextX / TILE_SIZE)) + ":" + ((int)(nextY / TILE_SIZE)));
      // println(((int)(nextX / TILE_SIZE)) + ":" + ((int)(nextY / TILE_SIZE)) + "=" + ((nextTile == null) ? "null" : tiles[nextTile].traversable));
      // null = grass = traversable
      if (nextTile == null || tiles[nextTile].traversable) {
        playerX = nextX;
        playerY = nextY;
      }
      stroke(255, 255, 0);
      rectMode(CENTER);
      rect(HWIDTH, HHEIGHT, 5, 5);
      strokeWeight(4);
      line(HWIDTH, HHEIGHT, HWIDTH + 20 * cos(playerA), HHEIGHT + 20 * sin(playerA));
    }
    
    // this is a tile, there's one for each possible type
    class Tile {
      int type;
      PImage img;           // image for this tile
      int colour;           // pixel colour in map
      boolean traversable;  // can i walk over this?
      boolean fatal;        // will this kill me? (NB not implemented)
    
      public Tile(int index, String filename, int colour, boolean traversable, boolean fatal) {
        type = index;
        this.colour = colour;
        img = loadImage(filename);
        this.traversable = traversable;
        this.fatal = fatal;
      }
    }
    
    static final float ROT_SPEED = .05;
    void keyPressed() {
      if (key == '.') {
        playerA += ROT_SPEED;
      } else if (key == ',') {
        playerA -= ROT_SPEED;
      }
    }
    
  • edited June 2014

    It moves all the non-empty pixels into a HashMap using position as key.

    Hey @koogs! So you're using a String as some kinda tuple for coordinates, is it?
    I've had an idea here to make it faster: Rather than <String, Integer> pair, how about <Integer, Byte>?

    Graphics coordinates generally don't go any further than 16-bit value range.
    Therefore we can store x & y together as a 32-bit value! *-:)

    Check it out my experiment. Perhaps its implementation can help your game be less CPU intensive. =:)

    // forum.processing.org/two/discussion/5985/
    // how-can-a-roguelike-have-so-large-maps
    
    static final byte GRASS      = 0;
    static final byte TREE       = 1;
    static final byte WATER      = 2;
    static final byte LAVA       = 3;
    static final byte WALL       = 4;
    
    static final color GRASS_COL = -1;
    static final color TREE_COL  = #008000;
    static final color WATER_COL = #4E00FF;
    static final color LAVA_COL  = #FF0000;
    static final color WALL_COL  = 0;
    
    static final String HOST = "http://" + "www.koogy.clara.co.uk/";
    static final String FILE = "map", EXT = ".png";
    
    import java.util.Map;
    final Map<Integer, Byte> map = new HashMap();
    
    void setup() {
      size(800, 600);
      smooth(4);
    
      //buildMap(map, loadImage(HOST + FILE + EXT));
      buildMap(map, loadImage(FILE + EXT));
    
      exit();
    }
    
    static final void buildMap(Map<Integer, Byte> map, PImage img) {
      byte tile;
      for (int y = 0; y != img.height; y++)
        for (int shift = y << 020, x = 0; x != img.width; x++)
          if ((tile = matchColor(img.get(x, y))) >= 0)  map.put(shift | x, tile);
    }
    
    static final byte matchColor(final color c) {
      switch (c) {
      case GRASS_COL:
        return GRASS;
      case TREE_COL:
        return TREE;
      case WATER_COL:
        return WATER;
      case LAVA_COL:
        return LAVA;
      case WALL_COL:
        return WALL;
      default:
        return -1;
      }
    }
    
  • Now, what if we wanted to make a large world (500x500) with every tile conaining a map (200x200). That way, you can travel on the large map, and explore inside of the tile's map.

  • HashMap<Integer, Byte> would help, i think, but i think that's still going to be negligible compared with shifting the image data - something borne out by how much less cpu it uses when i switch to opengl.

    and an array will always be more efficient, speed and space-wise, except for the very sparsest of maps. and looking at zelda (below), it's pretty much all non-grass.

    maytze, i don't think that'll work. a tile is the smallest indivisible object. it holds all the attributes for that possible player position. having a 500x500 tile would be very limited. but there's nothing to stop you having multiple maps, each 500x500 tiles. that said, playing around with 1000x1000 tiles it was obviously TOO big.

    http://mikesrpgcenter.com/zelda3/maps.html ^ probably 128x128 x 16x16 tiles (maybe 256x256 x 8x8 tiles) 2048x2048px total

    but rogue-likes are different. there is no sub-tile positioning (i'm not convinced there is any in zelda either, except when the player is moving from one tile to the next - he can't stop halfway between tiles)

  • Sub-tiles are possible, except a 4D array would be needed.

  • please see this free tile editor

    http://www.mapeditor.org

  • see also

    http://forum.processing.org/two/discussion/comment/21445#Comment_21445

    which features a jump and run map (from the side)

    and not a look from above map

  • I actually have tiled :D

    I need help with exporting and using the maps I make.

  • Update:

    I am making a roguelike that features a 80x60 map with 40x30 subtiles.

  • Hello, I've been working on similat project and I can't make zoom working with your example. Do you have any idea how to handle that?

  • look at scale in the reference

    ;-)

    there is also a new forum now

  • @TheJeff This might help https://forum.processing.org/two/discussion/20853/scale-translate-ab-comparission#latest

    Please let's continue in the new forum. Don't forget to cross your future post with this one.

    Kf

Sign In or Register to comment.