Heya! The next lesson adresses the project structure and introduces asset packing. The video turned out to be a bit confusing, but the extended transcript is below. Let me know if you have any questions!
What is a game and how we build it
If we think about it, what is a game? Let’s check out the main file, DodgingHero. If we really dumb it down, the game is a cycle of displaying info and getting user’s feedback. The game screen actually renews many times per second (FPS, frames per second, actually indicate this exact value). The render() function in Libgdx is doing exactly this. It cleans the screen (glClear command), then it draws our own things.
The create() function initializes our game. Those are the operations that need to be done once. Mainly, it’s resource allocation. We need to load the textures/sprites only once (the line img = new Texture(“badlogic.jpg”); does exactly that). We also initialize SpriteBatch (batch = new SpriteBatch();), essentially this is the structure that sends the commands to our graphic card.
On the opposite side, we have dispose() function, that is called after our game is done running. We need to free up resources. And this is exactly happens in this particular case: both batch and img are being disposed.
Now that we have a very general idea what’s going on, I’m going to tell you how I usually structure my projects. A good project structure ensures that project maintenance and code updates will go much smoother. As they say, “hours of planning can save you weeks of programming.”
Preparing our art
First thing first, let’s find some art for our prototype. I usually use opengameart if I want to build something fast, and this case will be no exception. After some search, I found http://opengameart.org/content/tiny-16-basic – tileset with some monsters and humans which we can use for our game prototype. I’m going to pick the tiles they have, pick two of them and will use them to show our initial background, repeated the tiles for the background. Essentially I’m doing some extra work here (because the tilesets on the link are already very neatly organized), but I need to show you what’s going on by an example.
In our project root folder (the same folder where we have “android”, “core”, “html” … folders) , let’s make a folder named “assets.” Inside that folder, make another folder, named “unpacked.” From the tileset, cut out one floor and one wall tile (I usually use paint.net, http://www.getpaint.net/index.html for that purpose, but the simple ‘paint’ will do for now).Each image should be 16×16 pixels in size, save them as ground.png and wall.png accordingly. Now, we have two tile sprites, but what do we do with them? For a better performance, all sprites should be put into spritesheets. (It takes time for the graphic card to switch between textures/sprites). It’s really not a problem for modern computers most of the time (for a small game), but I’d rather teach you to do things the ‘proper’ way first. In our Android Studio, go to desktop package and open DesktopLauncher. For Desktop version, we’re going to add texture packing. (Whenever we run desktop version, the sprites are going to be packed into one file. We’ll be able to use this this pre-generated file in the other platforms, like android). The main reason I’m doing it this way is because TexturePacker is not supported by some of our platforms (at least HTML), so I’d rather execute it on Desktop only.
In DesktopLauncher class, add the following private function:
static void Pack() { TexturePacker.Settings settings = new TexturePacker.Settings(); settings.maxHeight = 2048; settings.maxWidth = 2048; settings.pot = true; TexturePacker.process(settings, "../../assets/unpacked", "packed", "game"); }
Then, right at the start of main() function, add the call to Pack() function, it will look like this now:
public static void main (String[] arg) { Pack(); LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); new LwjglApplication(new DodgingHero(), config); }
Loading the art
Select the DesktopLauncher configuration and run our program on it (first launch might take some time). Now go and check the android\assets\ folder. There should be a new directory, “packed” there. Inside that directory, there should be two files: game.png and game.atlas. First one is the picture of two tiles put together. The other one is actually a text file (you can check it out with any text editor, I use Notepad++). It is, like extensions says, an atlas, that describes different sprites in it and coordinates of those sprites in our new big picture.
Now we’ll have to load this atlas. The next thing we do, make a resource class, which will hold the graphical data. Make it in the same package as your main game class. In my case, I right click on “com.coldwild.dodginghero”, choose “New” and pick “Java Class.”
Name it “Resources.” This one will be responsible for loading and storing the assets for our game. Go to your new file, and inside Resources class declare the public constructor and TextureAtlas variable gameSprites.
public class Resources { TextureAtlas gameSprites; public TextureRegion ground; public TextureRegion wall; public Resources() { gameSprites = new TextureAtlas(Gdx.files.internal("packed/game.atlas")); ground = gameSprites.findRegion("ground"); wall = gameSprites.findRegion("wall"); } public void dispose() { gameSprites.dispose(); } }
Before constructor declaration, we declare TextureAtlas named “gameSprites”– this is the thing that is going to store our spritesheet with game characters and background.
dispose() function will be called after the end of our program, to unload all resources that have been used.
We put the initialization of the gameSprites into our constructor with the following line:
gameSprites = new TextureAtlas(Gdx.files.internal("packed/game.atlas"));
This will take the generated atlas from ourproject/android/assets/packed/ folder. After that, declare two TextureRegion variables, ground and wall right after gameSprites atlas.
TextureAtlas gameSprites;
public TextureRegion ground;
public TextureRegion wall;
Great, now let’s assign some values in our constructor. It’s not hard: just add the following lines:
ground = gameSprites.findRegion("ground"); wall = gameSprites.findRegion("wall");
The final result should look like this:
public class Resources { TextureAtlas gameSprites; public TextureRegion ground; public TextureRegion wall; public Resources() { gameSprites = new TextureAtlas(Gdx.files.internal("packed/game.atlas")); ground = gameSprites.findRegion("ground"); wall = gameSprites.findRegion("wall"); } public void dispose() { gameSprites.dispose(); } }
Testing what we have
Now ground and wall point to the specific tiles and we’re be able to draw them! Now go to your main class file (in my case it’s DodgingHero.java) in core folder and add new public variable, Resources right at the start of the file. You should initialize it at the start of create() function. Remove the “img” variable and all code related to it from the file. You won’t need it anymore. Let’s just test if we can draw our simple tiles. In dispose function, add res.dispose(); Final result should look like this:
public class DodgingHero extends ApplicationAdapter { public Resources res; SpriteBatch batch; @Override public void create () { res = new Resources(); batch = new SpriteBatch(); } @Override public void render () { Gdx.gl.glClearColor(1, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.end(); } @Override public void dispose () { batch.dispose(); } }
Now, for the sake of testing our tiles, let’s add simple drawing between batch.begin() and batch.end() inside our render() function:
@Override public void render () { Gdx.gl.glClearColor(1, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(res.ground, 0, 0); batch.draw(res.wall, 0, 16); batch.end(); }
This way we should draw the wall above the ground. The first parameter is the sprite that we want to draw, the next is the coordinates(x, y). Unless you change something, the x coordinates are going from left side of the screen to right side (0->width) and y is going from bottom to top (0->height). 0 means the lowest point of the window. Run the program and you should see both small tiles drawn at the left side of the screen:
Adjusting Code Structure
So, Vladimir, are we ready to build the game right here? Hell no. It might seem like a good idea to write all code here, but it will quickly become bloated if nothing is done: imagine that we have to program all the menus and screens in one file. The way I usually do it is to split every separate screen into a separate file and do the rendering/control checking there. The good news is that libgdx allows you to do this quite easily.
In our core/java/com.yourname.gamename package, create a new package called “screens.” There, we’ll add the DefaultScreen parent class, which will store the link to our game object (and will be able to access our resources from there), from which we’ll inherit the next screens.
Right click on the “screens”, package select “new” -> “Java Class.” Name it DefaultScreen, make it implement Screen (public class DefaultScreen implements Screen), add the necessary import from Libgdx by placing map cursor over “Screen” and pressing alt+enter. Press alt-enter again to automatically implement the missing methods. Don’t touch them. (for now). Now, we’ll do two things:
- Declare a variable of our main class (it will point out to game)
- Create a constructor for DefaultScreen
That should not take much time:
public class DefaultScreen implements Screen { public DodgingHero game; public DefaultScreen(DodgingHero _game) { game = _game; }
Very good, our DefaultScreen class is ready.
Now we should implement the actual game screen. Right click on screens package, add new Java Class, let’s name it GameScreen. GameScreen should extend the Default Screen. (public class GameScreen extends DefaultScreen). Press alt+enter to implement the default constructor. Your class should look like this:
public class GameScreen extends DefaultScreen { public GameScreen(DodgingHero _game) { super(_game); } }
Now go to your main class (in my case it’s DodgingHero), and blatantly cut-paste render() function from there to GameScreen. Change the input parameters to accept float delta (public void render (float delta)). We now have this:
public class GameScreen extends DefaultScreen { public GameScreen(DodgingHero _game) { super(_game); } @Override public void render (float delta) { Gdx.gl.glClearColor(1, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(res.ground, 0, 0); batch.draw(res.wall, 0, 16); batch.end(); } }
batch is unresolvable. We need to move it from main class here. Main class should be very small now:
public class DodgingHero extends ApplicationAdapter { public Resources res; @Override public void create () { res = new Resources(); } @Override public void dispose () { res.dispose(); } }
The last thing to do is to change batch.draw calls. We don’t have res variable here, but we can access it via our game variable. Change batch.draw(res.ground, 0, 0); to batch.draw(game.res.ground, 0, 0); Do the same change with wall. The final GameScreen class should look like this:
public class GameScreen extends DefaultScreen { SpriteBatch batch; public GameScreen(DodgingHero _game) { super(_game); batch = new SpriteBatch(); } @Override public void render (float delta) { Gdx.gl.glClearColor(1, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(game.res.ground, 0, 0); batch.draw(game.res.wall, 0, 16); batch.end(); } @Override public void dispose() { batch.dispose(); } }
Run the game now. Oh no! What do we have in front of us? It’s a black screen! Something went wrong. No worries, the issue is that we moved the code to the separate screen, but did not instantiate it in any way. We need to tell our game to explicitly switch to it. Go back to our main class, and do two things:
- Change class declaration: public class DodgingHero extends ApplicationAdapter { should change to public class DodgingHero extends Game {
- At the end of create() function add the following line: setScreen(new GameScreen(this)); Press alt+enter to resolve and auto-import GameScreen class
Run the game now. You should see the red screen with two tiles at the bottom. It might not look as much, but our project has changed a lot. After this, we’ll be able to independently work on specific screens, and adding new screens (like main menu, credits, etc) won’t be a problem.
Git Commit: https://github.com/vladimirslav/dodginghero/commit/d02228f6ecb39fdbf98bb4ad54e57b5fb8f29b73
I’ve taken assets from: http://opengameart.org/content/tiny-16-basic, they are made by Lanea Zimmerman. I’ve cut separate tiles so it would be easier to follow (see my git repo).
You might want to check https://libgdx.badlogicgames.com/documentation.html for more details and in-depth descriptions.