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.