Sunday 2 September 2012

Tutorial: libgdx Orthographic Camera with Mouse Scroll

As part of my resolution to actually blog something useful and generally contribute to the world of programming, I've decided to reinstate this blog and make a relevant new post. So without further ado, this first outing will be about setting up a 2D orthographic camera with mouse scroll support.

(note: this article is based on an existing tutorial on the libgdx wiki that is classed as deprecated - please go there before reading on and download the "sc_map.png" image if you intend to run the code)

Setup 


While learning how to use libgdx I've seen multiple questions about viewports and stretching of textures with an orthographic camera. The forum contains many posts detailing how to do various solutions but for programmers like myself who just want an example, it's a little frustrating. Hopefully this article will clarify some points.

Firstly, in order to have sensible scrolling speed and a decoupling of world dimensions from rendering dimensions, you need a ratio of pixels to world unit. In this example we'll use a simple constant for this, along with a fixed world size.
public interface Constants
{
   float PIXELS_PER_METER = 32;
   float WORLD_WIDTH_METERS = 100;
   float WORLD_HEIGHT_METERS = 100;
}

You'll note that I'm using meters - this is what Box2D uses and it's a good enough world unit as any other.

Secondly we need to create the basic 'screen' that we'll be using to render. This is literally a Screen object and contains all the objects we need for rendering:
public class ScrollableScreen implements Screen, InputProcessor, Constants
{
   private final OrthographicCamera _camera;
   private final Vector3 _lastMouseWorldMovePos;
   private final Vector3 _lastMouseWorldDragPos;
   private final Vector3 _lastMouseWorldPressPos;
   private final Vector3 _lastMouseWorldReleasePos;
   private final Vector3 _lastMouseScreenPos;
   private final Texture _texture;
   private final SpriteBatch _batch;

   private int _mouseButtonDown = -1;

   public ScrollableScreen()
   {
      //background texture
      _texture = new Texture(Gdx.files.internal("sc_map_centered.png"));
      _camera = new OrthographicCamera();
      //batching
      _batch = new SpriteBatch();
      //controls
      _lastMouseWorldMovePos = new Vector3();
      _lastMouseWorldDragPos = new Vector3();
      _lastMouseWorldPressPos = new Vector3();
      _lastMouseWorldReleasePos = new Vector3();
      _lastMouseScreenPos = new Vector3();
   }

[...]

The bundle of Vector3 objects to do with mouse positioning is simply to record all the individual mouse operations that occur.

Rendering


The first method we'll implement as part of the Screen interface is render, which contains the following code:
@Overridepublic void render(float delta)
{
   _camera.update();
   Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
   _batch.setProjectionMatrix(_camera.combined);
   _batch.begin();
   //100mx100m texture in world coordinates
   _batch.draw(_texture, 
               -WORLD_WIDTH_METERS/2f, 
               -WORLD_HEIGHT_METERS/2f, 
               WORLD_WIDTH_METERS, 
               WORLD_HEIGHT_METERS);
   _batch.end();
}

The important part here is that our background image, _texture, is being drawn right in the middle of the world using the world dimensions. In this specific case the draw call is equivalent to:
_batch.draw(_texture, -50, -50, 100, 100);

Thanks to libgdx camera transforms the pixel position of the image is not something we need to worry about. What does this mean when we're using Box2D? It means we can simply pass the world position to the rendering call without worrying about any messy calculations. Awesome!

The last critical thing we need to implement is the resize method content. This is remarkably simple and, incidentally, is called before render, allowing us to dump all the dimension calculations in this method. This is what resize does:
@Override
public void resize(int width, int height)
{
   Vector3 oldpos = new Vector3(_camera.position);
   _camera.setToOrtho(false,
                      width/PIXELS_PER_METER,
                      height/PIXELS_PER_METER);
   _camera.translate(oldpos.x-_camera.position.x,oldpos.y-_camera.position.y);
}

Line by line:
  1. The current camera position is recorded in a new Vector3 object.
  2. The camera is set to orthographic mode for a viewport size matching that of the scaled world size. Remember that libgdx's camera handles the rendering transform for us, but in order to maintain this we must specify the camera in world coordinates as well. Given that we have a fixed pixel per meter ratio and we know the new width of the screen, it's simply the matter of calculating the number of meters the screen can now display and telling OpenGL to give us a window sized appropriately.
  3. The camera is moved back to where it was before the resize. This is necessary as setToOrtho will reset the camera's position to WIDTH/2 and HEIGHT/2, which isn't ideal. We recorded the old position of the camera on line 1 so the translation is the difference between this and the new position.
Now what we get when resizing the OpenGL window is more of the world being shown, rather than the same amount of the world being shown but stretched to the window resolution. The camera will remain focused on the same position as before to prevent disorientation of the player.

Scrolling

 

Now that rendering is resolution agnostic we can implement scrolling. This requires filling in the methods touchDown, touchUp, touchDragged and touchMoved:
@Override
public boolean touchDown(int x, int y, int pointer, int button)
{
   _mouseButtonDown = button;
   _lastMouseWorldPressPos.set(x, y, 0);
   _camera.unproject(_lastMouseWorldPressPos);
   return false;
}

@Override
public boolean touchUp(int x, int y, int pointer, int button)
{
   _mouseButtonDown = -1;
   _lastMouseWorldReleasePos.set(x, y, 0);
   _camera.unproject(_lastMouseWorldReleasePos);
   return false;
}

@Override
public boolean touchDragged(int x, int y, int pointer)
{
   if (_mouseButtonDown == Input.Buttons.RIGHT)
   {
      _camera.translate((x-_lastMouseScreenPos.x)/PIXELS_PER_METER,
                        (y-_lastMouseScreenPos.y)/-PIXELS_PER_METER);
   }
   _lastMouseWorldDragPos.set(x, y, 0);
   _camera.unproject(_lastMouseWorldDragPos);
   _lastMouseWorldMovePos.set(x,y,0);
   _camera.unproject(_lastMouseWorldMovePos);
   _lastMouseScreenPos.set(x,y,0);
   return false;
}
 @Override
public boolean touchMoved(int x, int y)
{
   _lastMouseWorldMovePos.set(x, y, 0);
   _camera.unproject(_lastMouseWorldMovePos);
   _lastMouseScreenPos.set(x,y,0);
   return false;
}

Amongst the code that sets various Vector3 objects with mouse down, up, move and drag locations is the code in touchDragged that instructs the camera to translate. There's no need to unproject for translation as we already have a suitable translation distance: the difference between the current and last mouse position and the scale of the world to the screen. By dividing the distance the mouse has dragged by the actual distance this represents in the world we can provide a sensible screen distance to move the camera.
Note that the Y parameter to the translate call uses the negative PIXELS_PER_METER value, as Y is up in OpenGL.

Notes

 

This probably isn't the most efficient code, nor the best way of providing this feature, but it's simple and easy to expand upon. I'll probably be using this as a base for further articles.

Source


The full source as it currently appears in my project. Feel free to use however you wish.
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector3;
import com.gingermess.Constants;

public class ScrollableScreen2 implements Screen, InputProcessor, Constants
{
   private final OrthographicCamera _camera;
   private final Vector3 _lastMouseWorldMovePos;
   private final Vector3 _lastMouseWorldDragPos;
   private final Vector3 _lastMouseWorldPressPos;
   private final Vector3 _lastMouseWorldReleasePos;
   private final Vector3 _lastMouseScreenPos;

   private SpriteBatch _batch;

   private int _mouseButtonDown;
   private Texture _texture;
   private float _zoomingQuantity = 0f;

   public ScrollableScreen2()
   {
      //test texture that is 100m by 100m
      _texture = new Texture(Gdx.files.internal("sc_map_centered.png"));
      _camera = new OrthographicCamera();
      //batching
      _batch = new SpriteBatch();

      //controls
      _lastMouseWorldMovePos = new Vector3();
      _lastMouseWorldDragPos = new Vector3();
      _lastMouseWorldPressPos = new Vector3();
      _lastMouseWorldReleasePos = new Vector3();
      _lastMouseScreenPos = new Vector3();
   }

   @Override
   public void render(float delta)
   {
      _camera.zoom+=_zoomingQuantity;
      _camera.update();
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      _batch.setProjectionMatrix(_camera.combined);
      _batch.begin();
      //100mx100m texture in world coordinates
      _batch.draw(_texture, -WORLD_WIDTH_METERS/2f, -WORLD_HEIGHT_METERS/2f,
WORLD_WIDTH_METERS, WORLD_HEIGHT_METERS);
      _batch.end();
   }

   @Override
   public void resize(int width, int height)
   {
      /*
      This resize code will ensure that the window is filled with world. The camera position will be maintained during
      the resize so that whatever it was looking at isn't suddenly displaced or off screen altogether.
      Zoom is conveniently handled by the camera internals so doesn't need to be taken into account here.
       */
      Vector3 pos = new Vector3(_camera.position);
      //enforce a fixed number of pixels per meter otherwise aspect ratio will skew in one dimension
      _camera.setToOrtho(false,width/PIXELS_PER_METER,height/PIXELS_PER_METER);
      //move the camera back to where it was - zoom hasn't changed so this is ok to do in screen coords.
      _camera.translate(pos.x-_camera.position.x,pos.y-_camera.position.y);
   }

   @Override
   public void dispose()
   {
      _texture.dispose();
   }

   @Override
   public boolean keyDown(int keycode)
   {
      if (Input.Keys.A == keycode)
         _zoomingQuantity+=0.02;
      if (Input.Keys.Q == keycode)
         _zoomingQuantity-=0.02;
      return false;
   }

   @Override
   public boolean keyUp(int keycode)
   {
      if (Input.Keys.A == keycode)
         _zoomingQuantity-=0.02;
      if (Input.Keys.Q == keycode)
         _zoomingQuantity+=0.02;
      return false;
   }

   @Override
   public boolean keyTyped(char character)
   {
      return false;
   }

   @Override
   public boolean touchDown(int x, int y, int pointer, int button)
   {
      _mouseButtonDown = button;
      _lastMouseWorldPressPos.set(x, y, 0);
      _camera.unproject(_lastMouseWorldPressPos);
      System.out.println("Mouse down at world: "+ _lastMouseWorldPressPos);
      return false;
   }

   @Override
   public boolean touchUp(int x, int y, int pointer, int button)
   {
      _mouseButtonDown = Constants.NONE;
      _lastMouseWorldReleasePos.set(x, y, 0);
      _camera.unproject(_lastMouseWorldReleasePos);
      System.out.println("Mouse up at world: "+_lastMouseWorldReleasePos);
      return false;
   }

   @Override
   public boolean touchDragged(int x, int y, int pointer)
   {
      if (_mouseButtonDown == Input.Buttons.RIGHT)
      {
         _camera.translate((x-_lastMouseScreenPos.x)/PIXELS_PER_METER,
                           (y-_lastMouseScreenPos.y)/-PIXELS_PER_METER);
      }
      _lastMouseWorldDragPos.set(x, y, 0);
      _camera.unproject(_lastMouseWorldDragPos);
      _lastMouseWorldMovePos.set(x,y,0);
      _camera.unproject(_lastMouseWorldMovePos);
      _lastMouseScreenPos.set(x,y,0);
      return false;
   }

   @Override
   public boolean touchMoved(int x, int y)
   {
      _lastMouseWorldMovePos.set(x, y, 0);
      _camera.unproject(_lastMouseWorldMovePos);
      _lastMouseScreenPos.set(x,y,0);
      return false;
   }

   @Override
   public boolean scrolled(int amount)
   {
      return false;
   }


   @Override
   public void show()
   {
   }

   @Override
   public void hide()
   {
   }

   @Override
   public void pause()
   {
   }

   @Override
   public void resume()
   {
   }
}

Wednesday 21 July 2010

Passion in Motion

As you may have guessed I'm quite passionate about games. Ever since my Atari I've been interested in playing, analysing, changing, tweaking and discussing games, and this wonderful thing called the Internet has brought all of these aspects together for everyone, and for the better I feel. We get information straight from the developers and designers now, and it's one of these statements that I'd like to talk about in this update.



Earlier this month GamerZines quoted Stephen Toulouse, Microsoft's Director of Policy and Enforcement for Xbox LIVE, regarding 'hardcore' gamers. I won't quote the entire article as it's a very quick read, so hop on over and read what he had to say.



I'm not going to start a diatribe about the softcore/hardcore gamer divide (if such a thing exists in general), but I am going to express my concern about what this statement might actually be saying.



Firstly, the hardcore gamer is a bit of a misnomer, having technically qualified for the label myself twice. The first time was during my nine to ten year stint playing QuakeWorld as a member of various clans (teams), participating in leagues and cups, running a few of my own competitions, hosting LAN parties, that sort of thing. The second time involved my lengthy enjoyment of World of Warcraft, which I still load up now and again. At neither point would I consider myself a 'hardcore' gamer, but rather, a passionate gamer. There's a difference.



The impression perceived when you suggest to someone who doesn't really play games that you might be a hardcore gamer, is one of superior skill or ability, or perhaps even sponsorship. If you were to suggest you're a passionate gamer, the impression would be that you care a lot about games, and desire to get the best out of them. I propose they're one and the same thing; a hardcore gamer is a person that cares much more about a game (or games) than the average person, and demonstrates this in not only playing the game, but also contributing to the community, helping other players into the game, building modifications to the game, perhaps extra levels, maybe even converting the game completely. You wouldn't consider most of these symptoms as qualifying someone for the 'hardcore' label, but the chances are these people know more about the game than someone who solely plays it. He or she may not be as good at the game, but then skill is only one measure of dedication. Thus, the hardcore gamer is in fact the passionate gamer, and is arguably one of the best fans of a game you can have.



Swinging back to Mr. Toulouse's statement, we see that hardcore gamers have in fact been wrong for the last ten years, and have been mostly wrong about market trends over the same period as well. I don't think this is a correct interpretation of what's happened, and I'd wager Mr. Toulouse is trying to highlight that the market Kinect is aimed at is not going to be as 'hardcore' as previous console milestones. That's Microsoft's choice of course, and they're perfectly entitled to make that decision. Given that the Wii has carved a huge market for itself in the last few years it's no wonder than Microsoft want to use Kinect (and Sony with Move) to barge into this market and reap the benefits. I don't think anyone saw the Wii coming, nor expected it to utterly dominate the video gaming market for a year or two, and it doesn't necessarily indicate the way the market is going in general. The Wii may simply have just created and immediately filled a niche, which if anything demonstrates perfect product design and marketing.



No, I'm going to go with the fact that Microsoft are risking a large investment in an unproven technology (the E3 videos are less than inspiring) in a very well serviced market. If this couples with their apparent attitude toward hardcore gamers and I'm correct about their complete misunderstanding regarding what that actually means, then the Kinect will suffer. You simply can't discard the opinions and desires of the hardcore crowd, as they're the most passionate about what they involve themselves in. They are the ones that spread the word of mouth recommendations, they're the ones that provide ongoing community interaction, and they're the ones that are ultimately returning customers. If you don't give them something to be passionate about then they'll look elsewhere, and Sony's E3 demonstrations at least hand a hint that perhaps, just perhaps, the more dedicated amongst us will get a system that makes our effort worthwhile. Sony may take advantage of this and steer their marketing towards the more advanced, deeper interaction with motion control. If they jump on the chance to reflect the "passionate gamer" angle into a few of their press releases I'm sure they'll gain more than Microsoft. Either way I just wish both companies would ditch the hardcore label and instead acknowledge that many gamers consider their games as more than just home entertainment.



Luckily, we won't have to wait long to find out how things go. Kinect hits the shelves in November with fifteen launch titles, and Sony's Move arrives much earlier in September. I eagerly look forward to seeing whether either product is deserving of the benefits the more dedicated gamer crowd can bestow, given half a chance.

Thursday 15 July 2010

Aesthetics

I'd just like to say a quick thanks to Martin Robbins who gave me a shameless plug on his Twitter account, despite the slight dig at ginger hair. Rest assured Mr. Robbins, the world domination plan will not be swayed by simple insults.

Regarding the title for this quick update, I'm still looking for a bit of imagery here and there for the background of the blog, something not typically slapped on every other gaming blog. Any suggestions welcome, bonus points for comedy.

Next update will be posted soon!

Wednesday 14 July 2010

The Wii (with family)

The inaugural post of Pixel Imperfect! If you found your way here by accident then welcome; pull up a chair and make yourself at home. I'll be player one, and over the course of this blog's lifetime I'll be leading us past many bad, confusing or just plain odd decisions to emerge from the video game world. I'll also throw us head-long into the good ones to keep things interesting, as how can one measure the lows without the highs?

So, to the first topic at hand on this blog: the Wii (with family). Yes, that white box by your TV that you bought because Nintendo, for one brief moment, appeared to have redefined family video gaming. Instead of aiming their latest incarnation of hardware at the typical video games audience, Nintendo leapt the fence and started preaching to everyone, parents and children alike. What resulted was nothing short of impressive: Wiis sold by the truckload, pre-orders were available on EBay for well beyond their retail price, and every parent that loved their child went to every effort to get one in time for Christmas. Even our ageing geriatric relatives that perpetually occupy the comfiest seat in the room now had the option of flailing wildly with a purpose, a Wiimote so tenuously held in their grasp. Retirement homes would never be the same.

There were problems with the Wii initially, as with all milestone hardware releases. Microsoft had the red ring fiasco, Sony had the wallet-melting cost and Nintendo had shattered living room furniture and broken televisions. The fixes were quick and everything seems more or less settled now, but that's not what this update is about. I'm more concerned with the software the Wii has available for it rather than the Wii itself, as ultimately this defines whether a console's lifetime is remembered as a classic era of gaming or forgotten in favour of more enjoyable alternatives, such as Pogs.

As you may or may not be aware, the Wii above all other consoles has fed the popularity of what could loosely be referred to as "family" games. The definition the family label is somewhat vague given that the primary requirements are to be multi-player and to not feature forced head-torso separation. Typically what you get is a loose collection of mini-games that are glued together by some sort of framework - the Olympic Games, arbitrary point scoring - with few exceptions. Those that do break the mould (Super Mario Brothers Wii being the only example to spring to mind) often have a very shallow plot winding through the game, or simply don't feature one at all.
Why is this a problem, you ask? Given the Wii's fantastically fast domination of this generation of consoles, it could be argued that a developer creating a game for the platform has a chance to invigorate a genre, or perhaps forge a new one. After all, the Wii is aimed at players of all ages so what more could you ask for in a potential audience?

From what I've seen so far this hasn't happened, at least not to any notable degree. Games aimed at the family are either lacking in depth for the older players, way too complex for the younger players to grasp, or simply too boring for anyone to care about. Nintendo missed the boat on the latest revision of their major intellectual properties (Mario, Zelda, and Mario Kart) and instead of redesigning them to take advantage of the new market, they just updated the graphics and stamped out version Wii. To say I was disappointed would be a dramatic understatement.
What would have been more impressive and (I think) perhaps more lucrative, is for Nintendo to have added a new level of interactivity to the games based on multiple player profiles. Zelda springs to mind as the obvious target for this, as the depth element is already present. Take a game like Four Swords Adventures - a great idea and well executed for the most part - but 'upgrade' it to the Wii level. Add in different types of character that suit different family members, provide different abilities and moves depending on how complex you want the interactivity to be, and you're already on the way to getting the whole family involved in rescuing the princess. I for one would find it satisfying to introduce a game to a friend that his kids, his wife and he could play at the same time, with everyone enjoying an experience tailored to their age.

It's not an easy idea to realise though, I acknowledge that. Games such as Mario Kart Wii, Super Mario Brothers Wii and Wii Sports do attempt to unite the family in gaming bliss, but only one of these (Super Mario Brothers Wii) actually achieves anything remotely original and captivating. And that's really the key, for the most part. Grabbing everyone's attention is easy, but keeping them playing the same game and continually challenging each person, while simultaneously developing the story, is not a design that can be conjured out of thin air. My family became bored with Wii Sports quite rapidly due to the fact that there wasn't much depth to it, and I personally tired of it because I realised the game doesn't really use accurate motion control at all, thus shattering the illusion. I've been told other reasons from friends, such as lack of variety and the simplistic presentation that contributed to their abandonment of the game. Why then did Wii Sports do so well? The answer is simple: it demonstrated the technology in the easiest, most colourful way. It was never designed to be a deep, fulfilling, immersive experience as Nintendo wanted to ship the Wii as soon as possible, yet Wii Sports appears to be the pinnacle of family gaming if the sales figures are considered.

Ultimately what I'm getting at is that a family doesn't have to be presented with, or if I'm honest subjected to, blatant rehashes of the same material. There's no reason to design a game with the kids as the lowest common denominator for the entire experience. There's certainly no justification for manufacturing shovelware in an effort to saturate the market with poor clones of Flash games, other than profit margins. There is however every reason to create a game that allows all levels of participation, from child to adult, without dumbing down or complicating the content for anyone. Adventure games, puzzle games, sports games, platform games, shooting games, they're all valid for change of this sort. Wouldn't it be cool to explore Hyrule with your wife and kids, you holding back the monsters with your vast array of abilities while they solve a puzzle, or keep each other from falling into pits? I can't be the only one in finding that concept worth investigating.

Perhaps in the future we'll be seeing games that are flexible not just in avatar customisation, but in experience customisation as well. Not everyone can meet the swordplay requirements for defeating the monsters, and not everyone wants to solve puzzles aimed at 10 year olds. Offering both at once and allowing everyone to enjoy the adventure is, I hope, a plan on someone's drawing board.

Thanks for reading, and please leave a comment if you agree or disagree!