• Home
  • Tutorials
    • Game Development Tutorial>
      • Unit 1: Beginning Java>
        • Before you begin...
        • Day 1: Setting Up
        • Day 2: Java Basics
        • Day 3: More Basics
        • Day 4: Java Math
        • Day 5: More Math
        • Day 6: If... else...
        • Day 7: More Control Flow
        • Day 8: Looping
        • Day 9: More on Looping
        • Day 10: Inheritance, Interface
        • Day 11: Threads and Graphics
      • Unit 2: Creating a Game I>
        • Day 1: Foundations
        • Day 2: Basic Framework
        • Day 3: Taking User Input
        • Day 4: Enter the Robot
        • Day 5: Background and Sprites
        • Day 6: Adding Enemies
        • Day 7: Shooting Bullets
        • Day 8: Animations
        • Day 9: 2D-Arrays
        • Day 10: Painting the Tilemap
      • Unit 3: Creating a Game II>
        • Day 1: Level Creation - Part 1
        • Day 2: Level Creation - Part 2
        • Day 3: Level Creation - Part 3
        • Collision Detection Basics
        • Day 4: Collision Detection Part 1
        • Day 5: Collision Detection Part 2
        • Day 6: Collision Detection Part 3
        • Day 7: Health System & Death
        • Day 8: Basic AI & Final Touches
      • Unit 4: Android Game Development>
        • Day 1: Introduction to Android
        • Day 2: Setting up for Development
        • Day 3: Creating our First Android Application
        • Day 4: Parts of an Android Application
        • Day 5: The Android Game Framework: Part I
        • Day 6: The Android Game Framework: Part II
        • Create an Android Game From Scratch (or port your existing game)
        • Day 7: Creating an Android Game (From Start to Finish)
      • Reference Sheet
    • Zombie Bird Tutorial (Flappy Bird Remake)>
      • Introduction
      • Day 1: Flappy Bird - An In-depth Analysis
      • Day 2: Setting up libGDX
      • Day 3: Understanding the libGDX Framework
      • Day 4: GameWorld and GameRenderer and the Orthographic Camera
      • Day 5: The Flight of the Dead - Adding the Bird
      • Day 6: Adding Graphics - Welcome to the Necropolis
      • Day 7: The Grass, the Bird and the Skull Pipe
      • Day 8: Collision Detection and Sound Effects
      • Day 9: Finishing Gameplay and Basic UI
      • Day 10: GameStates and High Score
      • Day 11: Supporting iOS/Android + SplashScreen, Menus and Tweening
      • Day 12: Completed UI & Source Code
    • Android Application Development Tutorial>
      • Unit 1: Writing Basic Android Apps>
        • Before you begin...
        • Day 1: Android 101
        • Day 2: Getting to Know the Android Project
        • Day 3: The Development Machine
        • Day 4: Building a Music App - Part 1: Building Blocks
        • Day 5: Building a Music App - Part 2: Intents
        • Day 6: Building a Music App - Part 3: Activity Lifecycles
  • Forum
  • About Us
    • Contact Us
  • Our Games
    • TUMBL: FallDown
  • Donate
  • Facebook
  • Twitter

GAME DEVELOPMENT TUTORIAL: DAY 3-2: LEVEL CREATION PART II

11/30/2012

55 Comments

 
Picture
Continuing with the series, we have Part 2 of the Level Creation lessons. In this lesson, we will be adding a method that will read a .txt file's information and draw a map to the screen accordingly.

A typical map file may look like this:
Picture
A map file
In such a file, each space would represent a tile (or an empty tile), and we would be able to create corresponding structures on the screen in this way.
Picture
Corresponding map on screen
Using this method, we will be able to create multiple levels for our character to traverse through.
Let's begin.

Making Changes to the StartingClass

I. We will begin at the beginning by adding a few more types of tiles to the game.

1. Find the line (yay syntax highlighting):

    public static Image tiledirt, tileocean;
 
And change it to this one:
    public static Image tilegrassTop, tilegrassBot, tilegrassLeft, tilegrassRight, tiledirt;
 
2. Find this segment in your init() method:
        tiledirt = getImage(base, "data/tiledirt.png");
        tileocean = getImage(base, "data/tileocean.png");
 
Update like so:

        tiledirt = getImage(base, "data/tiledirt.png");
        tilegrassTop = getImage(base, "data/tilegrasstop.png");
        tilegrassBot = getImage(base, "data/tilegrassbot.png");
        tilegrassLeft = getImage(base, "data/tilegrassleft.png");
        tilegrassRight = getImage(base, "data/tilegrassright.png");
 
II. You will want to download these (overwriting tiledirt.png and deleting tileocean.png).
They go inside your data folder.
Picture
tiledirt.png
File Size: 3 kb
File Type: png
Download File

Picture
tilegrasstop.png
File Size: 3 kb
File Type: png
Download File

Picture
tilegrassleft.png
File Size: 3 kb
File Type: png
Download File

Picture
tilegrassright.png
File Size: 3 kb
File Type: png
Download File

Picture
tilegrassbot.png
File Size: 3 kb
File Type: png
Download File



While you're at it, download the map file also (place it also in the data folder):
Picture
map1.txt
File Size: 1 kb
File Type: txt
Download File

III. Now that we got our assets ready, navigate to your start() method.
In here, we have a double for loop that creates our Tile map. We will now be encapsulating this inside a method. Find the following section:

        // Initialize Tiles

        for (int i = 0; i < 200; i++) {
            for (int j = 0; j < 12; j++) {

                if (j == 11) {
                    Tile t = new Tile(i, j, 2);
                    tilearray.add(t);

                } if (j == 10) {
                    Tile t = new Tile(i, j, 1);
                    tilearray.add(t);

                }

            }
        }
Change it to the following. Ignore errors.
// Initialize Tiles
        try {
            loadMap("data/map1.txt");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
This is a pretty straightforward segment of code. We call the loadMap() method (which we will now create) and pass in the parameter map1.txt which you should have downloaded above.

Since we are dealing with files, Java requires us to create a try/catch statement in case an exception occurs.

IV. Now we will create the loadMap() method.
The easiest way to do so is to put your mouse over the error shown in the try/catch statement like so:
Picture
Click "Create method 'loadMap(String)'"

Eclipse will automatically create the loadMap for you. You have to make a few changes.

1. You want to change the name of the parameter (string) to filename.
2. In addition, as we are dealing with files in this method, we want to handle the exceptions. Rather than use try/catch statements, we have an alternative. Declare "throws IOException" after the parameters are stated in the method declaration.

If you do so, the method will look like this.

    private void loadMap(String filename) throws IOException{
        // TODO Auto-generated method stub
       
    }
 
Let's now talk in detail about what this loadMap() method will do.

LoadMap() Method

The functions of this method are as follows:

1. To create an ArrayList and fill it with the lines parsed from the .txt file.
2. To utilize a double for loop to create tiles (we are basically rewriting the tile initializing for loops in the init() method in this method).
3. Assigning a type (recall in the last unit, we used integers to represent whether the tile was an ocean tile or a grass tile) by reading the text file at the (x , y) index of the tile.

The completed method looks like this:

    private void loadMap(String filename) throws IOException {
        ArrayList lines = new ArrayList();
        int width = 0;
        int height = 0;

        BufferedReader reader = new BufferedReader(new FileReader(filename));
        while (true) {
            String line = reader.readLine();
            // no more lines to read
            if (line == null) {
                reader.close();
                break;
            }

            if (!line.startsWith("!")) {
                lines.add(line);
                width = Math.max(width, line.length());

            }
        }
        height = lines.size();

        for (int j = 0; j < 12; j++) {
            String line = (String) lines.get(j);
            for (int i = 0; i < width; i++) {
                System.out.println(i + "is i ");

                if (i < line.length()) {
                    char ch = line.charAt(i);
                    Tile t = new Tile(i, j, Character.getNumericValue(ch));
                    tilearray.add(t);
                }

            }
        }

    }
 
Discussion:
We initialize some variables in the first section (including the ArrayList) and the width and height (which represent the number of lines and characters per line in the text we read).

We use a while loop to fill the ArrayList lines with the lines read from the text file. If the line begins with an "!", we ignore it (I used ! to begin comments in the map file).

We give width and height the appropriate values to serve their function. 


The familiar double for loop creates tiles at the (i, j) index, i representing the x coordinate and j representing the y coordinate. 



Now, we check what the character at the index i of the current line is (which has index j). We then create a new Tile with the parameters x position, y position, and type. Since the characters we read from the text file are characters, not integers (there's a distinction between '1' and 1 much like there's a distinction between "a" and a), we use a built-in method: Character.getNumericValue(ch) to convert it to a number.

The purpose of the if(i < line.length()){... statement is there to ensure that our i index never searches for a character that does not exist. If this is not there, we can have all kinds of problems with our map.txt so this is very important!

Finishing Touches

Now all we have to do is handle the tile types inside the Tile class. Make these changes to the if statements within the constructor, and we will be good to go!

    public Tile(int x, int y, int typeInt) {
        tileX = x * 40;
        tileY = y * 40;

        type = typeInt;

        if (type == 5) {
            tileImage = StartingClass.tiledirt;
        } else if (type == 8) {
            tileImage = StartingClass.tilegrassTop;
        } else if (type == 4) {
            tileImage = StartingClass.tilegrassLeft;

        } else if (type == 6) {
            tileImage = StartingClass.tilegrassRight;

        } else if (type == 2) {
            tileImage = StartingClass.tilegrassBot;
        }

    }
 
Finally, let me explain my reasoning for choosing the seemingly arbitrary typeInt values.

If you look at a typical numpad:
Picture
There are arrows below the numbers 8, 4, 6, and 2. I used these numbers to represent dirt tiles with grass on the side that the arrows point towards. 5, of course, has no arrows and represents a dirt tile with no grass on any side.

Examine the map1.txt file to see how I applied this technique to create a simple level.

At this time, you can run the code and it will work!

In the next lesson, we will implement some basic collision detection to these tiles, and we will move on from there! (Please don't send me emails saying "I can't jump on the platforms!" because we haven't coded anything to make them platforms yet. They are as of now just images).

Like Kilobolt Studios on Facebook and I will keep you posted.
Picture
unit3day2.zip
File Size: 302 kb
File Type: zip
Download File

Go to Unit 3: Day 1
Go to Unit 3: Day 3
 


Comments

Nick Morozov
12/01/2012 10:11am

Hi, first things first: awesome series, absolutely love reading it.

Now, some random thoughts: you should mention "Save Actions" feature, it can save a lot of time and some unneeded keystrokes. Also, how about adding your project to some svn/git repo? Again, it can save time, plus you can easily play with the code without fear of breaking it or making it incompatible with the next lesson.

Anyway, thanks for your hard work!

Reply
Mark
12/06/2012 3:41pm

Great tutorial. I'm looking forward to the collision detection tutorial as well!

Reply
Jackie link
12/08/2012 8:53am

First off thanks...

I deviated a bit and instead of doing tile type the way you did I created an Enum....

public enum TileType{
OCEAN(StartingClass.tileocean,1),
GROUND(StartingClass.tiledirt,5),
GRASS_RIGHT(StartingClass.tilegrassRight,6),
GRASS_LEFT(StartingClass.tilegrassLeft,4),
GRASS_TOP(StartingClass.tilegrassTop,8),
GRASS_BOTTOM(StartingClass.tilegrassBot,2);
private Image image;
private int ordinal;
private TileType(Image image,int ordinal){
this.image = image;
this.ordinal = ordinal;
}
public Image getImage(){
return image;
}
public int getOrdinal(){
return ordinal;
}
public static TileType getByOrdinal(int ordinal){
for(TileType t : TileType.values()){
if(t.getOrdinal() == ordinal){
return t;
}
}
//Return default
return null;
}
}

Which in my mind makes the code a lot more straight forward....

public Tile(int x, int y, TileType type) {
tileX = x * 40;
tileY = y * 40;

this.type = type;
if(type != null){
tileImage = type.getImage();
}
}

public void update() {
// TODO Auto-generated method stub
switch(this.type){
case OCEAN:
if (bg.getSpeedX() == 0){
speedX = 0;
}else{
speedX = -2;
}
break;
case GROUND:
speedX = bg.getSpeedX()*5;
}
tileX += speedX;
}

TileType type = TileType.getByOrdinal(Character.getNumericValue(ch));
if(type != null){
Tile t = new Tile(i, j, type);
tilearray.add(t);
}

Reply
AnthonyB
02/12/2013 4:47am

Hi,

Enum is a good idea, but you should use it to optimize the tile choice too.

Instead of looping on the enum each time you have to get a tile type, you could save all of them in a static map (HashMap for instance), indexed by the ordinal (as an Integer or Character).

Fill the static in a static block (you loop on the enum only here) and when you need a type, you have just to call the "get" method on the map.

This will increase performances of your code.

Reply
eastbullet link
01/19/2013 11:53pm

hi, may i know why my tiles map have no spacing.. for example:

5 5

so, the must be spacing right? but when i run my code the map become like this :

5555555

thank you for this great tutorial and your answer..

Reply
eastbullet link
01/19/2013 11:54pm

actually the spacing between the first two 5 are lot bigger.. but it has been reformat in comment..

Reply
Stefan Doychev link
02/05/2013 6:58am

Thanks for this series of tutorial, really great and well-explained! Awesome work!
I just noticed a small error in the files uploaded in today's lesson - the normal tiledirt.png /https://kilobolt.com/uploads/1/2/5/7/12571940/tiledirt.png/ is the same as the tilegrasstop.png - probably mistake during the upload.

Cheers,
Thanks once again for all the work!
Stefan

Reply
AnthonyB
02/12/2013 4:42am

Hi,

To begin with, this is a very good tutorial that I'm reading with a real great pleasure! Thank you so much for making it :)

I've just noticed that in the loadMap method that you have never used the "height" variable locally, except when you get its value.

I think this is the location where it should be used:
"for (int j = 0; j < 12; j++) {" /* 12 should be "height" */

Kind regards,

Reply
MK
05/16/2013 9:31pm

I agree here - I was thinking the 12 should be height.

P.S. I also agree with everyone else, these tutorials are fantastically written and very useful for beginners like me

Reply
Steve
02/17/2013 1:51pm

Great Tutorial!

Like Stefan Doychev, i noticed that the download link of tiledirt.png is wrong.

Thanks for your work, keep it up!

Reply
Tom Bull
02/21/2013 2:15pm

Absolutely brilliant tutorial, thank you!

a couple of questions...
why is it that when i run it on my mac (macbook 3.1 running 10.7.5) i don't get movement or shooting, but when i run it in windows it all works fine?

is there something i can add to the key pressed method to make to work on both? or do i just have to keep the developing to windows?

Reply
James
02/22/2013 6:16am

Tom, I wish I had a Mac to diagnose this for you, but I do not :(

If you ever find a solution, do let me know!

Reply
Cheeno
12/03/2013 3:48pm

virtual box maybe?

Reply
Simon
02/23/2013 5:45am

Where exactly is the data folder? i put my map1.txt into my assets folder but its not found there

Reply
Simon
02/23/2013 5:50am

I'm on android btw, should mention that :P

Reply
Alasdair
03/08/2013 4:48am

We created it in /src/ back in lesson 2-12. It's where all the png files are stored.

Reply
JP
03/02/2013 8:14am

Hi. I have 1 small question. Where does it say at what position does the tile map begin to work? What i mean is when you launch the game, you have to move past the helicopter enemies and a bit more to the right for you to see the tiles that are constructed according to the map1.txt. How did we say that it should start from that position on the map?

Reply
Harrison
03/08/2013 3:34am

Hi, great tutorial it has helped with my programming a lot. My question is, on the txt example we use less than 9 different tiles thus only use single digit numbers. Can you use multiple digits in the txt tile maps? Or should I just use a secondary element to sort between different sets of scenery?

Reply
James C.
03/12/2013 12:06pm

I recommend using single digits for simplicity. If you use multiple digits, your code will have to handle them specifically.

Reply
Christopher
03/09/2013 2:38am

Hey. Just wanted to say that the "tiledirt.png" link opens the old "tiledirt.png" image, the one who is now "tilegrassTop.png".

Easy to solve with paint, but I spent a few minutes trying to look through the code for errors when the result was not as expected. :)

Reply
Ritish
04/09/2013 7:27am

I just downloaded the image directly and renamed it tiledirt.png
No need to involve innocent paint into this :P

Reply
Cheesemonkey
05/02/2013 7:07pm

+1 on this, did the same thing, still linked to wrong image. Right click image and save it as tiledirt.png to fix your level.

Reply
Reece
03/15/2013 4:15pm

Another great tutorial!

Thanks so much!!!!

Reply
Anthony B
03/18/2013 6:54am

Very well done tutorials. I enjoy following them. You should look into putting these onto youtube and voice/video record yourself implementing everything you are teaching us. I'm will to bet that the number of viewers you will have over double if this series was on youtube. Honestly, this series is better than any on youtube that i have found yet. People are lazy nowadays and dont like to read, especially long tutorials like this. Just a thought to help u guys out, id like to see u get bigger and create more tutorials because u are very helpful. But yea thanks again, keep up the awesome job !

Reply
Jonathan W.
03/27/2013 7:06am

I have been following this tutorial for the past few days I just think this is probably the best thing for any beginner to learn. But I did run into a problem.

For some reason when I run it the grass for the ground doesn't show up. It only seems to show up when mound is created. I copied and pasted your codes for the StartingClass and Tile class but it didn't change. Any ideas?

Reply
bill siegel
04/12/2013 5:34am

The download link for the tiledirt.png is the same graphics as tilegrasstop.png I thought my code was wrong on the "5" tiledirt reading from the text file, but opening the link and the png shows it looks the same as tilegrasstop this could cause confusion.

Reply
Nick
04/29/2013 11:28am

So I just have a quick question...
I don't know whether its possible to even suggest this, but how preformance-optimized is the text method as opposed to, say, an XML file or even using middle ware such as the Tiled map editor?

Reply
Louis N
06/10/2013 3:26pm

Why is there a line at the bottom of map.txt with a bunch of "#" followed by 9 spaces? The program ran just fine after I deleted it.

Reply
Peter
08/17/2013 8:36am

ty a lot for the tut but the tiledirtp.png file is the wrong download. download the image its over him and rename it.

Reply
SThiongane
08/27/2013 5:26am

another great tuto ! thks

Reply
Abdul malik badshah
10/04/2013 7:27am

i just don't understand .. when we call loadMap() method there is infinite loop while(true){}. So how does control come to the code below the while loop.. while when we create a new class and use the same while(true) loop in a method and write any thing below this while loop it gives error UNREACHEABLE.. whats the matter here.
can any body clear me this..

Reply
Jay Ryan
10/10/2013 2:22pm

In that while statement is the if (line == null) clause. If a line is black or does not exist, the reader is closed and a break is sent. That stops the while statement. It's kind of like saying when the end of the file is reached, break. That's why the // no more lines to read comment is there.

Reply
Jay Ryan
10/10/2013 2:28pm

Why didn't you use a switch/case in the Tile class? Not only is it cleaner looking but it allows for adding new tiles, later, easily.

switch (typeInt) {
case 2:
tileImage = StartingClass.tilegrassBot;
break;
case 4:
tileImage = StartingClass.tilegrassLeft;
break;
case 5:
tileImage = StartingClass.tiledirt;
break;
case 6:
tileImage = StartingClass.tilegrassRight;
break;
case 8:
tileImage = StartingClass.tilegrassTop;
break;
}

Reply
Martin
10/26/2013 6:21am

Thanks for great tutorials! Question: if I am planning to make a game with a map of a city (pretty simple, only streets, few types of buildings and stuff, for android) do I have to draw it with notepad (because it would be A LOT OF numbers) or are there other ways (programs that help to do that, by using my own tiles)

Thank you!
Martin

Reply
Topher Donovan link
10/28/2013 3:17pm

To get the system to read the map file, I had to change this line:

BufferedReader reader = new BufferedReader(new FileReader(filename));

to

BufferedInputStream in = (BufferedInputStream) getClass.getResourceAsStream(filename);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

Reply
Twiggy Stardust
01/05/2014 6:16pm

I'm using netbeans, so I don't know if that matters because almost everything up to this point worked (as far as I can tell netbeans does not have the create getters/setters option, but what I was going to point out that to make this all work, on the line

loadMap("data/map1.txt");

I had to add src to the beginning like

loadMap("src/data/map1");

or it would give me the error "The system cannot find the path specified" the applet would still start up, but without the tilemap drawn. Just curious if anyone can tell me if this is netbeans or otherwise why it would do that on my system?

by the way, as almost everyone has said, good tutorial, I've been a c++ programmer for a year and a (super) quick scan of you java tut made everything clear and after about 10 minutes skimming that, I moved on to the game tut and I've not had one question about anything! good job!

Reply
Twiggy Stardust
01/05/2014 6:18pm

opps the new line for loadMap is actually

loadMap("src/data/map1.txt");

Reply
Twiggy Stardust
01/05/2014 6:25pm

ok found the getter and setter generator

Reply
Alex
01/27/2014 6:37pm

Hello, first off I would like to say to you James that i am absolutely loving this tutorial and have learned so much from it. I just seem to have one problem with my program; only one 'Heliboy' appears in my game. No matter how far right I go there is only one 'Heliboy'. I am pretty sure that i have followed the tutorial and your code perfectly up until this point so i do not understand why only one is showing. Is this normal for the point I am in the tutorial, or am i missing something? Immediate help would be appreciated!

Reply
grizeldi
01/30/2014 11:44am

Umm I love those tutorials, but I'm using Intellij Idea. When i run the program it gives me this error:
java.io.FileNotFoundException: ..\src\data\map1.txt (The system cannot find the path specified)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:97)
at java.io.FileReader.<init>(FileReader.java:58)
at game.startClass.loadMap(startClass.java:217)
at game.startClass.start(startClass.java:97)
at sun.applet.AppletPanel.run(AppletPanel.java:474)
at java.lang.Thread.run(Thread.java:722)
and doesn't load any tiles. I had problems with accessing files at images, but I solved it like this:
background = getImage(this.getClass() .getResource("/data/background.png"));
Anyone got clue how to load that tilemap?

Reply
Sven
02/14/2014 1:19am

I got what the loadMap() method does but i can't help seeing how it works


String line = (String) lines.get(j);

Plz somebody explain this statement in detail?

Reply
Sven
02/14/2014 3:12am

I'm a bit confused of the meaning of (string) within this statement?

Reply
Sven
02/14/2014 7:35am

think i figured it out by comparing to statements from U 3 D 1

Omama Moin
02/14/2014 2:43am

Hi,

Nice tutorial. I have a question. How have you generated map.txt file. Have you used any automated tool?

Reply
Sven
02/14/2014 7:30am

does int in the for loop set variable int i =0?

is it possible that i and j have been mixed up within the loadMap() method comparing the 2 for loops from unit 3 day 1?

and is the statement String line = (String) lines.get(...); correct?

i think it should be look like String line = (String) line.get(...);

Reply
Sven
02/14/2014 7:33am

and what is int height = 0 for? it seems it it isn't used within the loadMap() method

Reply
Sven
02/15/2014 7:32am

i'm wrong XD

Reply
Sven
02/14/2014 7:38am

can i also say int height = lines.length insted of lines.size()?

Reply
Sven
02/15/2014 7:34am

.size() is correct XD

Reply
Sven
02/14/2014 7:43am

doesn't BufferedReader reader = new BufferedReader() not a reader class it referes to in order to be build like importing i.g. Color?

Reply
Max
04/05/2014 6:59pm

What is the purpose of the code:

width= Math.max(width,line.length() ;why not just
width=line.length();?

Thanks

Reply
marquel2k
04/17/2014 12:05pm

can't get the map to load at all....and where int height = 0 is height is underlined yellow and says the value of the local variable height is not used

Reply
SoLReePzz
04/17/2014 5:51pm

I'm having the same issue. The problem is in the update method for Tile. I'm looking into now. Will post code when I've figured it out.

Not sure what the "height" is for yet. We will probably use it in collisions or bounds checking.

Reply
SoLReePzz
04/17/2014 5:55pm

If you peek ahead to Day 3, you'll see that you update Tile's update method. You'll be able to see the entire map then.

Reply
marquel2k
04/17/2014 10:07pm

Thank you




Leave a Reply

    Author

    James Cho is the lead developer at Kilobolt Studios. He is a college student at Duke University and loves soccer, music, and sharing knowledge.


© 2014 Kilobolt, LLC. All rights reserved.