Jump to content
  • Advertisement

AWT GUI Facade (5): Load a level

Sign in to follow this  
phylyp

691 views

In this article, I propose to use the Visitor Pattern to easily load a level. The GUI facade in the previous post is used to display the level. This pattern allows (among other things) to easily browse a data structure to extract information. In our case, it is an XML file created by “Tiled Map Editor” which is analyzed to load a level in memory.

This post is part of the AWT GUI Facade series

I start by creating a level in XML with Tiled:

adw_tiled_map1_en.png

You have to choose in the left panel Tile layer format as “XML”. This produces an XML file that looks like this:

<map version="1.0" orientation="orthogonal" renderorder="left-up" width="16" height="16" tilewidth="16" tileheight="16" nextobjectid="1">
 <tileset firstgid="1" name="advancewars-tileset1" tilewidth="16" tileheight="16" tilecount="256">
  <image source="advancewars-tileset1.png" width="256" height="256"/>
 </tileset>
 <tileset firstgid="257" name="advancewars-tileset2" tilewidth="16" tileheight="16" tilecount="256">
  <image source="advancewars-tileset2.png" width="256" height="256"/>
 </tileset>
 <layer name="Ground" width="16" height="16">
  <data>
   <tile gid="41"/>
   <tile gid="41"/>
   <tile gid="41"/>
   <tile gid="41"/>
   <tile gid="41"/>
   <tile gid="41"/>
   <tile gid="41"/>
   <tile gid="41"/>
   ...
   <tile gid="41"/>
  </data>
 </layer>
 <layer name="Objects" width="16" height="16">
  <data>
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
   ...
   <tile gid="0"/>
   <tile gid="0"/>
   <tile gid="0"/>
  </data>
 </layer>
</map>

We first have a <main> tag with global properties, then two <tileset> tags that define the tile sets used (one for the background, the other for the objects), and finally two <layer> tags that define the tiles for two layers (one for the background, the other for the objects).

To decode this file, I propose to use the XML parser included in the standard Java library. It is based on the Visitor pattern, and allows you to easily work on tags without having to worry about character decoding issues.

Visitor Pattern

The Visitor pattern can be presented as follows:

visitor01-1024x999.png

The IElement interface represents the elements of the structure. In this example, there are two possible types of objects: ElementA and ElementB. The interface requires a method like accept() which takes as argument a visitor who implements the IVisitor interface. There can be several ways to visit the structure, and so many other methods similar to accept(). Parameters can also be used to influence the traversal.

The IVisitor interface is implemented by the user who wants to traverse the structured data. In general, the interface methods correspond to the different types of elements that compose it: in this example, they are the ElementA and ElementB classes. It is quite possible to imagine other cases that may interest a user, such as being at the beginning or at the end of an element. In all cases, the accept() method calls the methods of the IVisitor interface according to the cases encountered during its course. Within these methods, the user is free to view and modify the data.

Load a level

To read and store the level information, I define a Level class that contains this information in attributes:

public class Level
{
    private ArrayList<String> tilesetImages = new ArrayList();
    private int tileWidth;
    private int tileHeight;

    private int width;
    private int height;
    private int[][][] level;
    
    private int tilesetWidth;
    private int tilesetHeight;
    private int x;
    private int y;
    ...
  • The tilesetImages attribute contains the file names of texture images;
  • The tileWidth and tileHeight attributes contain the width and height of a tile (16 x 16 pixels in our example);
  • The width and height attributes contain the width and height of the level (16 x 16 cells in our example);
  • The level attribute contains for each cell coordinate: the (x, y) tile coordinates of the tile to draw, and the tile set to use.
  • The following attributes are only used to decode the information in the file.

To decode the file, I define a LevelLoader class that implements org.xml.sax.helpers.DefaultHandler. The LevelLoader class is the equivalent of the Visitor class in the diagram above, and DefaultHandler is the equivalent of IVisitor. I’m only interested in the startElement() method, equivalent to the visitElementA() and visitElementB() methods in the diagram above. This method is called whenever the parser encounters a new opening tag:

public class LevelLoader extends DefaultHandler {

    public void startElement (String uri, String localName,
                          String qName, Attributes attributes)
       throws SAXException {
        if (qName.equals("tileset")) {
            if (tilesetImages.isEmpty()) {
                tileWidth = Integer.parseInt(attributes.getValue("tilewidth"));
                tileHeight = Integer.parseInt(attributes.getValue("tileheight"));
            }
        }
        else if (qName.equals("image")) {
            if (tilesetImages.isEmpty()) {
                tilesetWidth = Integer.parseInt(attributes.getValue("width")) / tileWidth;
                tilesetHeight = Integer.parseInt(attributes.getValue("height")) / tileHeight;
            }
            tilesetImages.add(attributes.getValue("source"));                
        }
        else if (qName.equals("layer")) {
            if (level == null) {
                width = Integer.parseInt(attributes.getValue("width"));
                height = Integer.parseInt(attributes.getValue("height"));
                level = new int[height][width][3];                    
            }
            x = 0;
            y = 0;
        }
        else if (qName.equals("tile")) {
            int id = Integer.parseInt(attributes.getValue("gid"));
            if (id != 0) {
                if (id >= 257) {
                    level[x][y][2] = 1;
                    id -= 256;
                    level[x][y][0] = id % tilesetWidth - 1;
                    level[x][y][1] = id / tilesetWidth - 1;
                }
                else {
                    level[x][y][0] = id % tilesetWidth - 1;
                    level[x][y][1] = id / tilesetWidth;
                }
            }
            x ++;
            if (x >= width) {
                x = 0;
                y ++;
                if (y > height) {
                    throw new SAXException("Erreur dans le fichier");
                }
            }
        }
    }
}

The method is a long discussion based on the tag encountered, whose name is placed in the qName argument. For the “tileset” and “image” cases the information relating to a set of tiles is decoded. For the “layer” case, the level is initialized, and for “tile”, the information relating to a tile is stored. The x and y attributes are used to store the coordinates of the next tile to be decoded.

Finally, I define a load() method in the Level class that uses the LevelLoader class to load the level:

public boolean load(String fileName) {
    try {
        SAXParserFactory spf = SAXParserFactory.newInstance();
        SAXParser saxParser = spf.newSAXParser();
        XMLReader xmlReader = saxParser.getXMLReader();
        xmlReader.setContentHandler(new LevelLoader());
        URL fileURL = this.getClass().getClassLoader().getResource(fileName);
        xmlReader.parse(fileURL.toString());
        return true;
    }
    catch(Exception ex) {
        return false;
    }
}

The Level class also has accessors/mutators (getters/setters) not listed here.

Display the level

To display the level, I use the GUI facade from the previous post:

Level level = new Level();
if (!level.load("advancewars-map1.tmx")) {
    JOptionPane.showMessageDialog(null, "Error when loading advancewars-map1.tmx", "Error", JOptionPane.ERROR_MESSAGE);
    return;
}

int scale = 2;

Layer backgroundLayer = gui.createLayer();
backgroundLayer.setTileSize(level.getTileWidth(),level.getTileHeight());
backgroundLayer.setTexture(level.getTilesetImage(0));
backgroundLayer.setSpriteCount(level.getWidth()*level.getHeight());
for(int y=0;y<level.getHeight();y++) {
    for(int x=0;x<level.getWidth();x++) {
        int index = x + y * level.getWidth();
        backgroundLayer.setSpriteLocation(index, new Rectangle(scale*x*level.getTileWidth(), scale*y*level.getTileHeight(), scale*level.getTileWidth(), scale*level.getTileHeight()));
        if (level.getTileset(x, y) == 0) {
            Rectangle tile = new Rectangle(level.getTile(x, y), new Dimension(1,1));
            backgroundLayer.setSpriteTexture(index, tile);
        }
    }
}

Layer groundLayer = gui.createLayer();
groundLayer.setTileSize(level.getTileWidth(),level.getTileHeight());
groundLayer.setTexture(level.getTilesetImage(1));
groundLayer.setSpriteCount(level.getWidth()*level.getHeight());
for(int y=0;y<level.getHeight();y++) {
    for(int x=0;x<level.getWidth();x++) {
        int index = x + y * level.getWidth();
        groundLayer.setSpriteLocation(index, new Rectangle(scale*x*level.getTileWidth(), scale*(y-1)*level.getTileHeight(), scale*level.getTileWidth(), scale*2*level.getTileHeight()));
        if (level.getTileset(x, y) == 1) {
            Rectangle tile = new Rectangle(level.getTile(x, y), new Dimension(1,2));
            groundLayer.setSpriteTexture(index, tile);
        }
    }
}

gui.createWindow("Load a level with the Visitor Pattern",
    scale*level.getTileWidth()*level.getWidth(),
    scale*level.getTileHeight()*level.getHeight());

The code of this post can be downloaded here:

To compile: javac com/learngameprog/awtfacade05/Main.java
To run: java com.learngameprog.awtfacade05.Main

 

View the full article

 

Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • Blog Entries

  • Similar Content

    • By Blink4Blink
      I'm working on my first Android game and I have a few questions.
      I need to scale the graphics for different screen sizes/resolutions. I'm working in 16:9 and plan to use letter boxing to maintain this aspect ratio.
      Everything is fine for standard screen resolutions. Going from 320:180 to 640:360, one pixel just becomes four. I'm a little confused though as to what happens when you letterbox on a screen with a unusual resolution.
      Say, just for exsample, my original graphics are 160:90. Then to fit the devise I stretch everything by 1.1 and end up with a final resolution of 176:99. Its still 16:9 but now everything is a mess.
      If I had a sprite that used to be at x-33 y-33 it's new location would now be at x-36.3 y-36.3. Would I just drop the 0.3 of a pixel, round down and accept that it's no longer in its exact position? 
      Secondly what exactly happens when you stretch images by an amount like 1.1? How dose it decide what pixels to add to the image to make it bigger? 
    • By Aymen.Khelifi
      I have been fascinated by programming before 8 years from now. The journey took me from someone who loves to be software engineer to be a networks engineer. I work as full time IP networks engineer.
      Well,  I am a great fun of indie games developers. I have been following Dev-Logs for several indie game developers for a while. In the previous years many ideas of games have been floating in my mind and finally I took the decision to start my own small game project.
      I have been planning for the last 2 weeks for my game. I have decided to write the whole game and engine myself. My estimations are : 
      Because I am doing this as side project the whole idea ( a 2D platformer ) will take me between 6 months to 1 year. The Game Engine will be developed in parallel with the game I am probably going to Use Java LWJGL or any other OpenGl library  I should find a way to target my audience ( probably Youtube channel and dev-logs)  Perfectioning the game might have a longer duration than the development of it. I will publish it when it meets the 95% of my expectations If There is any piece of advice of how to start the journey, It will be very helpful. If you have any thoughts about my plan please share it with me. If you have any guidance about how to use this platform I would be more than happy to hear from you.
      I am just a man who lost his way when making his career related decisions and he is doing a lot of things for fun. 
      Thank you.
    • By MasqCrew
      Hey guys,
      I'm putting together a little thing in Python, a city builder of sorts. Nothing fancy, to be sure. The user will click on buttons to purchase items. In-game currency will be collected via taxes. I'd like to include population growth and pretty much anything that the big city builders keep track of. 
      One of the things I'm not sure how to do is simulate population growth. Each house will have a capacity, so it would be easy to say that a population couldn't grow beyond a certain point (to keep things simple), or if the population grew beyond residential capacity, there would be a homeless population to keep track of (maybe later). What factors would affect population? I'm thinking several things but not sure how to include them in formulas. Building a school could encourage more people to move in. Building other service buildings would help as well. This may not be a game design question (rather something for another forum), but how would you factor in multiple things to raise or lower the chances the population grows?
      And money won't be the only metric. Farms will produce food, which might be needed in order to build restaurants. 
      The pic I've attached shows the very basic and early GUI I'm playing with. Since there won't be any other graphics beyond the buttons (and maybe graphs later), there will be lots of buttons and pages to produce a lot of interactions and gameplay methods.
      I'd love any ideas you have to offer.

    • By Tony Vilgotsky
      Hello, dear colleagues!
      Recently I had a long evening of reflections on the topic, what makes a video game an indie game? Of course, indie projects are the whole niche in this days, but it seems sometimes that when some people are talking about “indie games”, they just don’t know what they are talking about. For some reason many people think that being indie means to be a low-qualified person who is obligated to produce tons of clones of classic games which were hits decades ago. And when they come to game making, they even don’t consider an opportunity to create something more original and interesting. They deliberately produce all these clones. Moreover, with deliberately downgraded graphics, sound and mechanics, which makes sense when it comes to retro scene, but… retro scene is a separate scene and all the indie games don’t have to fit its standards. And then they complain: why nobody is buying this? I think that the answer is obvious: modern gamers prefer modern graphics. So why shoot your own leg, filling your game with pixels, which aren’t even “an art”?
      I’m even silent about the gameplay clones. I think that there should be fewer of them, because indie development is just that thing that gives you freedom to be more original and implement bolder decisions. The world is playing Tetris for 200 years already (correct me if I’m wrong) - and nobody wants to play Tetris with crocodiles instead of bricks.
      It’s sometimes even getting funny: recently I’ve joined a new team (as a junior writer), making a 3D action/adventure game Between Realms with Unreal engine. This is an indie team and indie game, but the goal is to produce a major hit. The game incorporates modern 3D graphics, cutscenes and professional voice overs. And when we posted some stuff from it on the Internet, some people just didn’t believe that we are actually an indie team! I think it’s because modern indie projects almost always belong to the pixel scene or notably simplified from the visual side. So, when we show our materials, some people don’t believe that we belong to indie niche.
      Does it mean that one has to produce simple games with poor 2D pixel graphics to be considered an indie developer?
      From my (and my colleagues’) point of view, indie development must be not a compromise between production speed, expense and quality, but a freedom to create a conceptual product which is not limited by obligatory to sell millions of copies. Those who are tied by this obligatory, are forced to produce standardized, so to say, “tentpole” projects. But with modern technologies the developer is able to create nice, commercially promising, but still an independent project. As developers, we want to raise the bar of indie standard and wish all the rest to reach the same with the games which would be not only original in terms of gameplay but also attractive visually.
      What are you thoughts on this matter? Can a project with budget over $100 be called an indie project? What are the main features of typical indie game? What really makes a game an “indie” game?
    • By vvoodenboy
      Hi!
      My name is... nobodycaresbecauseitsnotimportant.
      I'm working on my own card game. I'm just in a stage of connecting some 'dots' of game's mechanics.
      I'm looking for a concept / 2D artist - because its time to give it some shape - I've a concept of a dark fantasy world where I want to place the plot but there is a lot of space for creativity.
      If you are interested please shout here or on discord (vvoodenboy#3207) or vvoodenboy@gmail.com.
      Have a nice day!
      V
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!