Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
15. października 2013

 

In this part we are going to look at how you handle mouse and keyboard input in LibGDX.  There are two ways to go about handling input, by polling for it ( as in… “Has anything happened yet? No, ok…  What about now? No, ok… Now?  Yes!  Handle it” ) or by handling events ( “Hey, you, I’v got this event for you!” ).  Which you go with generally depends on the way you structure your code.  Polling tends to be a bit more resource intensive but at the end of the day that is mostly a non-factor.

 

Polling the keyboard for input

 

Let’s jump right in and look at how you poll the keyboard for input.  Here is the code:

package input.gamefromscratch.com;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class InputDemo implements ApplicationListener {
    private SpriteBatch batch;
    private Texture texture;
    private Sprite sprite;
    
    @Override
    public void create() {        
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();
        batch = new SpriteBatch();
        
        texture = new Texture(Gdx.files.internal("data/0001.png"));
        sprite = new Sprite(texture);
        sprite.setPosition(w/2 -sprite.getWidth()/2, h/2 - sprite.getHeight()/2);
    }

    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        if(Gdx.input.isKeyPressed(Input.Keys.LEFT)){
            if(Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT))
                sprite.translateX(-1f);
            else
                sprite.translateX(-10.0f);
        }
        if(Gdx.input.isKeyPressed(Input.Keys.RIGHT)){
            if(Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT))
                sprite.translateX(1f);
            else
                sprite.translateX(10.0f);
        }
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

 

Other than the highlighted bit and the translateX method, everything here we have seen before.  Basically we draw a simple sprite centered to the screen and each frame we check to see if the user has pressed the LEFT or RIGHT arrow.  If they have, we check if they also have the left control key held.  If so, we move slowly to the left or right.  If they don’t have Control pressed, we move instead by 10 pixels.

 

Here is the app, you need to click it first to give it keyboard focus:

 

If it doesn’t work in an frame, click here.

Just use the left and right arrows to move the jet. Hold down control to move slowly.  There is no clipping so the sprite can fly way off screen.

 

In terms of what the new code is doing, the Sprite.translateX method is pretty self explanatory.  It moves the sprite by a certain amount of pixels along the X axis.  There is a translateY method as well, as well as a more general translate method.  The key method in this example is isKeyPressed() member function of the input instance of the global Gdx object.  We used a similar instance member when we accessed Gdx.files.  These are public static references to the various sub-systems GDX depends on, you can read more here.  isKeyPressed is passed a Key value defined in the Keys object and returns true if that key is currently pressed.  As you can see when we later tested if the Control key is also pressed, multiple keys can be pressed at the same time.

 

Polling the Mouse for input

 

Now let’s take a look at how you poll the mouse for input.  To save space, this code is identical to the last example, with only the render() method replaced.

public void render() {        
    Gdx.gl.glClearColor(1, 1, 1, 1);
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    
    if(Gdx.input.isButtonPressed(Input.Buttons.LEFT)){
        sprite.setPosition(Gdx.input.getX() - sprite.getWidth()/2,
                Gdx.graphics.getHeight() - Gdx.input.getY() - sprite.getHeight()/2);
    }
    if(Gdx.input.isButtonPressed(Input.Buttons.RIGHT)){
        sprite.setPosition(Gdx.graphics.getWidth()/2 -sprite.getWidth()/2, 
                Gdx.graphics.getHeight()/2 - sprite.getHeight()/2);
    }
    batch.begin();
    sprite.draw(batch);
    batch.end();
}

 

Here we instead are checking if a mouse button has been pressed using isButtonPressed passing in a button value defined in the Buttons object.  If the left button is pressed, get then poll the mouse position using Gdx.input.getX() and Gdx.input.getY() and set the sprites location to that position.  The math may look a bit overly complicated, why didn’t we simply set the location to the values returned by getX/Y?  There are two reasons.  First, our sprites coordinate is relative to it’s bottom left corner. so if we want to center the sprite, we need to take half the sprites width and height into consideration.  The next complication comes from the fact that LibGDX sets the origin at the bottom left corner, but mouse positions are relative to the top left corner.  Simply subtracting the position from the screen height gives you the location of the mouse in screen coordinates.  We also check to see if the user as hit the right mouse button, and if they have we reposition the jet sprite at the center of the window.

 

If it doesn’t work in an frame, click here.

Once again, you need to click within above before it will start receiving mouse events ( depending on your browser ).  Left click and the sprite should move to the location you clicked.  Right click to return to default ( in theory… ), right click behaviour is a bit random in web browsers.

 

Event driven keyboard and mouse handling

 

Now we will look at handling the functionality of both of the above examples ( as a single example ), but this time using an event driven approach.

package input.gamefromscratch.com;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class InputDemo implements ApplicationListener, InputProcessor {
    private SpriteBatch batch;
    private Texture texture;
    private Sprite sprite;
    private float posX, posY;
    
    @Override
    public void create() {        
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();
        batch = new SpriteBatch();
        
        texture = new Texture(Gdx.files.internal("data/0001.png"));
        sprite = new Sprite(texture);
        posX = w/2 - sprite.getWidth()/2;
        posY = h/2 - sprite.getHeight()/2;
        sprite.setPosition(posX,posY);
        
        Gdx.input.setInputProcessor(this);
    }

    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        sprite.setPosition(posX,posY);
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public boolean keyDown(int keycode) {
        float moveAmount = 1.0f;
        if(Gdx.input.isKeyPressed(Keys.CONTROL_LEFT))
            moveAmount = 10.0f;
        
        if(keycode == Keys.LEFT)
            posX-=moveAmount;
        if(keycode == Keys.RIGHT)
            posX+=moveAmount;
        return true;
    }

    @Override
    public boolean keyUp(int keycode) {
        return false;
    }

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

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        if(button == Buttons.LEFT){
            posX = screenX - sprite.getWidth()/2;
            posY = Gdx.graphics.getHeight() - screenY - sprite.getHeight()/2;
        }
        if(button == Buttons.RIGHT){
            posX = Gdx.graphics.getWidth()/2 - sprite.getWidth()/2;
            posY = Gdx.graphics.getHeight()/2 - sprite.getHeight()/2;
        }
        return false;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        return false;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        return false;
    }

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

 

And here it is running:

If it doesn’t work in an frame, click here.

 

The code is structured quite a bit differently from when we polled for input.  The most immediate thing to be aware of is our class declaration:

public class InputDemo implements ApplicationListener, InputProcessor {

 

We are implementing another interface, InputProcessor, which as you can see adds a number of overrides to our code.  The most important ones we are dealing with here are keyDown and touchDown.  Touch you say?  Yeah, LibGDX treats the mouse and touch input as the same thing.  We will look at this in a bit more detail later on.  In addition to implementing the various methods of our interface, we also need to register our InputProcessor with the global input instance, this is done here:

Gdx.input.setInputProcessor(this);

 

At this point, our various event handlers will now be called whenever an event occurs.  keyDown will be fired when a key is pressed ( while keyUp is fired when it is released, and keyTyped is fired after it has been fired and released ).  The parameter is the value of the key pressed.  Once again, these values are available in the Keys object.  One thing you may have noticed is that we still poll to see if the Control key is pressed.  The alternative would be to set a flag when the control key is pressed and clear it when it is released.  It is important to realize that a keyDown event will be fired for each individual key fired, so if you want to handle multiple simutanious key presses, this may not be the best way to approach the subject.  Another thing you might notice is that you have to hit the key multiple times to move.  This is because a key press generates only a single event ( as does it’s release ).  If you want to have the old behavior that holding down the key will move the character continously, you will need to implement the logic yourself.  Once again, this can simply be done by setting a flag in your code on keyDown and toggle it when the keyUp event is called.

 

The touchDown event on the other hand is much more straight forward.  It can be a bit confusing handling “mouse” events called “touches”, but it makes sense.  Generally the logic you handle for both would be exactly the same, so no sense treating them differently.  The parameters passed in to touchDown are the x and y coordinates of the touch/click location, the pointer and button clicked.  On a mobile device the Button value will always be Buttons.LEFT.  Once again, screen coordinates and image coordiantes arent the same, so we need to deal with that in our positioning.  Notice how I glossed over just what exactly pointer is?  Well, pointer is a bit oddly named in my opinion.  TouchIndex would probably have made more sense, especially with pointer having a pair of very well defined meanings already.  The pointer value is value between 0 and n ( defined as 20 in LibGDX, in reality much lower ) that represents the ORDER in which the touch event occurred in the event of multiple simultaneous touches.  Therefore if you have multiple fingers touching, a pointer value of 0 would indicate that this touch event represents the first finger to touch the screen, while a value of 3 would be the fourth finger to touch the screen.  Dont worry, we will talk about this later when we deal specifically with touch.

Programming , ,

blog comments powered by Disqus

Month List

Popular Comments

Creating a game sprite: Simple keyframe animation
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

Home > Art >

13. September 2013

 

In this section we look at how to do key framed animation.  What exactly is keyframe animation?  In simple terms, you define a few (*cough* key *cough*) frames of animation ( by posing or positioning your model ) and then the computer fills in the missing animation.  It’s exceedingly handy as it greatly simplifies the animation process ( it would really suck if you had to update every single movement on every single frame! ) and it also reduces the amount of data you need to store.  Fortunately, except for a few gotchas, keyframe animation is pretty simple in Blender.

 

Let’s jump right in.

 

Welcome to the Timeline

 

First thing we want to do is bring up a Timeline window:

image

 

The timeline presents the animation in your scene over time.

image

 

As you add keyframes, they will show up on the graph in Yellow.  The currently selected frame is shown in green:

image

 

Notice how the legend or lower axis ( numbers across the bottom representing frames ) is much smaller in the second shot?  That is because you can affect the granularity using the scroll wheel on your mouse.  This is handy if you are working a lot less than the default 200 frames, or if you are creating an animation that is substantially longer.  You can also can pan left and right holding the middle mouse button and dragging.

 

Let’s start by creating about the simplest animation possible.  We want our sprite to rotate 360 degrees around the Y axis, representing a banked roll.  To do this, we will create a total of four keyframes, one every 5th frames, one at the first frame and one at the last.  We want our animation to take a total of 20 frames, so start by setting the end frame to 20.

 

Click in the end frame and enter 20.  This is a dual function button, in the you can use the left or right arrows to increment the value or click in the center to enter a value.

image

 

You will notice the light gray portion of the timeline has shrunk to only include the active animation duration from frame 1 to frame 20.

 

Setting Keyframes

 

 

Now let’s set our first frame.  We actually want to create a key at frame 1 of the jet in the position it is currently in. 

 

First we want to make sure we are at Frame 1 in the timeline.  You can accomplish this by either clicking at the 1 position in the timeline, or by typing 1 in the currently selected frame box ( beside the End frame entry in the menu bar ). 

 

Once we’ve moved to position 1 on the timeline, we want to create a keyframe for our jet model.  To do so, go into 3D View, in Object mode, and select the Jet model.  Now hit i.  This will bring up the Insert Keyframe Menu:

image

 

You need to tell it what information you want to keyframe on.  In this case, it is only the rotation that we are changing, so we select Rotation.  If you were instead translating the model you would select Location.  Of course, if you were translating, rotating and scaling as part of your animation you would select LocRotScale.  Again, in this case we just want Rotation.

 

Now advance to frame 5 in the timeline.  You will notice in the 3D view, as you advance frames in the timeline, it will be represented in the 3D View in a label at the bottom left corner of the window:

image

 

… yeah, I really should have renamed it to something other than Cube at this point eh?

 

Anyways, once you are at Frame 5, in the 3D view bring up the properties panel, and set the Y rotation to 90 degrees:

image

 

When performing rotations for animation, it is very important you set them here, as if you rotate by hand it will often set it to –90 when you in fact meant 270!  This is only applicable for rotations, for translation and scaling, feel free to use whatever method you want.  Oddly enough, this behaviour varies from version to version.  On my Mac if I perform 3x 90 degree rotations, the Y value gets set to 270 ( what we want ), while on my Windows install, it gets set to –90 ( which we certainly dont want ).  So, better safe than sorry!

 

Now that we have updated the position, Press i and set a rotation key.  Keep in mind, you need to perform the rotation BEFORE you set the key.  Also, if you make a change later, you need to set another key.  So if you say… changed Rotation to 89, the change would not take effect unless you set a new rotation key!  Dont worry, keys will simply overwrite the old one.

 

Notice how the rotation field background is now yellow?

image

 

This shows you that this value has a keyframe controlling it.  If you go to any frame in between ( such as frame 3 ) you will see that it is shown in green:

image

 

This indicates that it is an interpolated value… or in other words, the computer calculated it based on the keyframe before and after it.

 

Now repeat this process.  Go to frame 10 in the timeline, set Y Rotation to 180, press i and select rotate.  Now advance to frame 15, rotate to 270, set a keyframe, then finally advance to frame 20, rotate to 360 then set another key.  Congratulations, your very first animation is complete!  Press the Play icon on the timeline and you should see a smooth ( if somewhat fast ) animation of your jet rotating.

 

RotateAnimation

 

Of course, there is the possibility you could make a mistake and want to get rid of a keyframe.  If that is the case, go to the frame you want to remove the keyframe from in the timeline.  Then in 3D view with the object selected chose the Object menu –> Animation->Delete Keyframes…  Then when prompted click Delete Keyframe.

image

 

If you instead want to get rid of all keyframes for the selected object, select Clear Keyframes… and once again, click the confirmation prompt.

 

Controlling animation playback rate

 

Now, the playback may be wayyyyy higher than you want it to be when you click the Play button in timeline.  This is because by default Blender is set up to play and render animations at 24 frames per second.  You can alter the playback speed, but it may not be where you expect it to be.

 

To change the playback speed, in the Properties panel select Render:

image

 

Now scroll down and locate Frame Rate and select Custom:

image

 

A new option will be made available allowing you to set the framerate to whatever you want.  Here for example it is set to 12FPS

image

 

Now when I play in timeline ( or if I rendered to a movie ), it would play at half the previous speed.

 

 

A little more control

 

Sometimes you might find that you want a bit more control over how the frames are calculated between keyframes.  Fortunately, exactly that functionality is available.  You can take fine control of your animations using the Graph Editor

image

 

The user interface is initially pretty daunting, but isn’t all that bad actually.

image

 

Basically the axis on the left shows the value ( in this case, rotation in degrees ), while the axis across the bottom illustrates the frames in the animation.  You may notice the circle on each end of the axis, they can be used to increase or decrease the range of values displayed.  Simply click and drag the circle on either end to expand or contract the axis range.  The green line represents the amount of rotation over time. 

 

Using the control handles for each point on the curve you can manipulate how the rotation will occur between key frames.  Simply right click a keyframe ( black dots ) to select it.  Then right click a handle and move the mouse to alter the curve.  Once you've positioned it like you want, left click to commit.  Right clicking again will instead cancel the change.

 

For example, the following change:

image

 

Would change the rotation, so that for the first couple frames there would be almost no rotation at all, then it would quickly rotate up to a total of 90 degrees by the next keyframe at position 5.  You simply use F-Curves to fine tune the results if required.

 

If you take a look at the Key menu, you can see you can control almost all aspects of keyframing in the Graph Editor including creating and deleting them.

image

 

Also remember, right now we are only doing a single action ( rotation ) on a single axis ( Y ).  In a more advanced animation, there would be many more lines, all colour coded with the legend on the left hand panel.

 

In video form

 

I have a feeling that was a bit difficult to follow in text only form, so I’ve recorded a video going through the exact process I just described.  There is no voice over or description beyond what was already mentioned above.  So, if you had trouble following what I just explained, watch the entire process in action.  Otherwise there is nothing else new in the video.

 

 

Next we move on to using the camera.


Click here for the Next Part

 

Art ,

blog comments powered by Disqus

Month List

Popular Comments