Wednesday, April 1, 2020

Tutorial 1.6: Smile, you’re (not) on candid camera - Real world units + Camera class


So far we have been using pixel coordinates to move or set the location of the sprite. That works, but pixels don’t translate very well to “real units”. The Orb is now 100 pixels big… But what is that? 100 nanometers? 100 Kangaroo jumps? And what if you buy a new phone with  a resolution twice as big… does that mean the orb appears twice as small?

And how about that movement speed of 0.2 “things per cycle”? It’s like your teacher asking how fast a car can drive, and you answer with “3 knots per fart”. It doesn’t mean much for one thing, and yet another problem is that you can’t always predict how many cycles the phone does per second. If the game runs “ASAP” - As Fast As Possible. It could be anything from 10 to 10000 cycles per second. The speed might be reasonable on your current phone, but a new phone may blast away that orb at hyper-speed, as it is capable of doing much more cycles per second (having a stronger CPU/GPU). If you ever wondered why some of those old (DOS) games seem fast-forwarded when trying to replay them. Well, that’s why. The programmers were lazy and thought computers couldn’t become much faster than 16 MegaHertz. Or maybe, these games just weren't meant to be played in the future, who knows.


Fuck pixels, can’t rely on them. Before writing piles of code, first get the units to your likes. What is a “pixel” anyway? Sure, a dot on the screen, but it doesn’t mean much in a (game)world context. I want to work with real sizes, real velocities. William Wallace was 10 feet tall and could shit lighting bolts from his arse, at a velocity of 100 miles per hour, using 320 Kilojoules. You know what, fuck the imperial system too. Metric units. Meters, meters per second. From now on, we talk in meters.

So, let’s say that Orb-sprite should have an initial diameter of 4 meters (remember it will grow as foes enter the sphere). Ok… And how big is that on your screen then? That, of course, depends on the Camera Zoom.



The Camera
In every game, you “spectate” the battlefield through a virtualcamera. That camera could be a first person view, thus inside your hero’s (invisible) head. It could also be a side-view, or a “God” view from above. Or a POV, when looking at a - never mind. A camera can zoom in and out (or in this case, fly up and down). But it can also move. In the code above we draw the orb at the center of the screen. But, where is that? The center of the universe? It’s all relative. The orb should be in the center of my view, if the camera is exactly above it.

So, let’s start doing some actual ENGINE programming: our Camera class. But before going too fast, let’s see what LibGDX itself has to offer. And well, yes it has a “Camera” class already! Nonetheless, I will make my own camera class as a wrapper in between though. Just so we can add our little magic and twerks to it, if ever needed. So let’s make a new file. In these tutorials, you will see a lot of Wrapper classes that extend the functionality of basic LibGDX elements, such as the Sprite, Camera, (level)Map, and so on.



Added a “engine” folder, and some (empty) sub-folders within. I’ll get into detail on this in the next chapter. For now, just do it. Anyway, the Camera class. What can it do? Let’s keep it simple for now: it can move (in X/Y direction, over the map, topview), an zoom in/out. We move in meters, and {0,0} is considered to be the centre of the map. Ok? Deal.


In addition, I made a “moveToPos( posX, posY, maxSpeedMtrPerSec )” function. When calling this, it will overrule manual controls, and flies to a certain point of the map. For example, if the Orb explodes, you may want to move the camera quickly to that point, just before the “Game Over” rolls by. Again, note that the Engine doesn’t decide how & when the camera is controlled. The Game code does that part. We just offer code that makes things happen.

Finally, it has an “update( deltaSecs )” function, which is called every cycle so it can update its movements. More about that later. The constructor, update and move functions of this Camera look like this, so far:
public GxCamera()
{
    this.screenWidth    = Gdx.graphics.getWidth();
    this.screenHeight   = Gdx.graphics.getHeight();

    this.camera = new OrthographicCamera( 1.f, (float)this.screenHeight / (float)this.screenWidth );
    this.camera.zoom = 30; // Zoom out a bit
} // create


    public void update( float deltaSecs )
    {
        this.camera.update();
    } // update

    public void move( float deltaMetersX, float deltaMetersY )
    {
        this.camera.translate( deltaMetersX, deltaMetersY );
    } // move

(make sure you convert to floats when doing the division in the Constructor. Wasted 30 minutes figuring out why I couldn’t see shit. Camera ended up with a rounded 0 height for a view, which isn’t much).


 

Internally, it uses a LibGDX Orthographic camera. Orthographic basically means “without perspective” – stuff doesn’t get smaller or disappears as the distance grows. Since we’re not making 3D graphics, a simple view does it. Pay attention to the width / height arguments we are giving. Since we want to get rid of pixels, we say the camera sees “one” (meter) unit wide, and a bit more height, as the phone display is typically not exactly square. In other words, if the camera is not zoomed out (thus at zoom level 0), it can only see 1 meter wide.

There is a Camera class now, but engines don't start themselves. You will have to utilize it somehow in your game-code, so that it will actually CREATE a camera and APPLY it every render cycle:
 @Override
   public void create () {
      camera = new GxCamera();  // Your new class!
      batch  = new SpriteBatch();
      img    = new Texture("Images/Orb/Orb01.png");

      spr       = new Sprite( img );
      spr.setOrigin( 0,0 );
      spr.setSize( 4,4 );
   } // create


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

      camera.update(); // Update the camera sub-routines
      batch.setProjectionMatrix( camera.combined ); // Apply view

      spr.setPosition( -2, -2 );

      batch.begin();
      spr.draw( batch );
      batch.end();
   } // render
Now run the program… You may wonder where the hell the Orb went, or why the entire screen is pink/purple in all of a sudden... Ah right,I forgot to adjust the sprite position & size. It was still sized to 100 pixels - which is now 100 meters (entire screen pink). And also the position was far, far away.

Probably you will need a moment screwing around with the position, size and zoom numbers. I always have a hard time setting up the camera initially, in a near empty world, since you don't have any reference point. Imagine waking up in a complete ugly-green world, like our camera and orb did above.

In the next tiny chapter, I'll explain a few things about the Camera MOVE function, and how to work with proper velocity units, like "meters per second".





No comments:

Post a Comment