Monday, March 30, 2020

Tutorial 1.4: Our first own Sprite


Just learning LibGDX here, and still having only the auto-generated code, let's see what we get if we compile / run this son of a BEAST.


Ok. It is… something. Just the idea that we can actually connect a real phone, and make this “app” running on it, is pretty exciting for me (I wrote dozens of PC / Linux / Machine apps, but never phone stuff really). Oh, and bare in mind Android Studio has a neat emulator as well. My past experience was that it was REALLY SLOW, but it seems they fixed things: it works pretty smooth nowadays.

But let’s see what we can do with that “render” (draw) code. The first 2 lines will clear the background with a red ( 1 red, 0 green, 0 blue, 1 alpha) colour. Its like starting a new canvas, with a certain background colour. Not shown here, but you can also clear depth buffers, in case you are making a 3D-ish game, that uses depth-testing in order to make things appear in the right order. Here however, we won’t do that.

@Override
public void render () {
   Gdx.gl.glClearColor(1, 0, 0, 1);
   Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
   batch.begin();
   batch.draw(img, 0, 0);
   batch.end();
}
The other 3 lines will draw “img” at pixel coordinate {0,0}, which seems to be the bottom-left corner of the screen (in landscape mode at least, remember you can rotate phones). Img is instance of class “Image”, which, as the name implies, loads a picture from disc (or well, not exactly in case of a phone), into the video memory so it’s ready for drawing.

“batch” is an instance of “SpriteBatch”. A class used for drawing images. Or sprites, as we typically call them in games. I’d like to talk about coordinates, movement and size in the next lesson. But first, let’s replace that “Badlogic” smiley logo with a picture of a puppet. Just, for fun.

@Override
public void create () {
   batch = new SpriteBatch();
   img = new Texture("badlogic.jpg");   put something else here
} // create

As explained a little bit in 1.2, most games begin with loading resources. Compare with a toddler unpacking a box of Mickey Mouse stamps. During creation you unpack the box, and “load” the stamp on the toddlers workbench. The auto-generated code offers the “create” function to do that.

 

Stay away from drugs and JPEG kids
In my case, “badlogic.jpg” was placed in “..\JunkThrower2\android\assets\”, so for now lets put all our images and other resource files into this \assets\ folder – or a subfolder there. I’m not sure what texture format is favoured by LibGDX, normally I use either DDS or TGA. But never a JPG! For the simple reason that JPG is lossy.


For a background image, this might be ok. But when dealing with transparent images like your foes, explosions, items or text-fonts, JPG will cause flaky edges and artefacts. The good news however, is the small size. Unlike your PC, the average phone doesn’t have zillion megabytes of storage space (yet), so compression can be welcome. But in that case, I’d rather use PNG, which can compress fairly well without loss. And which is a widely popular format anyway.
That guy doesn't look very healthy


Either case, keep in mind that compressed images are still decompressed once your app loads them into video memory. So that tiny JPG/PNG can still become a little monster in terms of GPU memory usage. Therefore, keep the image resolutions relative low. I must say that also phone and tablet hardware is becoming more powerful every generation, which makes you less caring about the bits and bytes. But still, don’t use more than necessary. Try to live as a sober monk.

To , a 128x128 pixel image would do fine for a small stuff (like the Orb below) on your screen. A 4096 x 4096 image (compressed or not) on the other hand may still consume up to 64 MB (!) video memory. How to compute the usage? Pretty simple: MB := ( width x height x 4 ) / 1024 / 1024. That is, if your image is loaded as a RGBA image with 8 bits per channel - which is pretty standard. 

Anyway,

img = new Texture("Images/Orb/Orb01.png");

So, I just changed the image path. And hey, notice how the orb image is partially transparent? With that I mean, you can see a sphere. But 2D images always come as a square or rectangular shape. Every pixel in this image has, besides a RGB color, also an alpha channel. A value of 0 means that pixel is completely transparent, whereas 255 would mean fully opaque. A value somewhere halfway could make the pixel semi-transparent, though it depends on the type of blending mode that is being activate at the time of rendering.

Anyhow, in case you didn’t learn this yet, check out how alpha channels (or “masks”) work in Photoshop / GIMP / Paint.NET / Paint Shop Pro / …. Unfortunately I can’t really tell you, as I’m still using an ancient version of good old Paint Shop Pro, but I guess the idea is the same. Besides making the image itself, you will make a greyscale mask like this:

Note how the edges are fading out smoothly. Otherwise the edges would appear more “jaggy”, pixelated. Be aware how your edge/background color looks though, as it will blend-in. If you look carefully at the orb on top of the murky-green background, there is a pink glow - on purpose.



Changing the path of that image wasn’t too difficult. Be prepared though, this is only the beginning. Likely you will have dozens, hundreds, maybe even thousands of images to deal with later on. Many different objects, but also animations come as multiple images, or 1 big image with multiple frames. Pretty soon, we will be making our own systems to manage and categorize all these resources.


Before leaving, let’s change one more thing in the code. Right now we use this for drawing:

batch.draw(img, 0, 0);

But we’re going to change that a little bit, using the LibGDX sprite class:

SpriteBatch batch;
Texture img;
Sprite spr;

@Override
public void create () {
   batch  = new SpriteBatch();
   img    = new Texture("Images/Orb/Orb01.png");

   spr       = new Sprite( img );
   spr.setPosition( 0,0 );
   spr.setSize( 100,100 );
} // create

   @Override
   public void render () {
      Gdx.gl.glClearColor(0.05f, 0.21f, 0.2f, 1.0f);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

      batch.begin();
      spr.draw( batch );
      batch.end();
   } // render
 
See the little difference? We made a sprite object (using that same image), set its location to {x:0, y:0} and changed its size to 100x100 pixels. Then in the render call, we draw it, using the batch. Don’t ask why Daniel San. Not yet. We'll get to that.