Introduction to Game Input and Events in Quorum

In this tutorial, we will learn about using player input and the game engine’s event system. With these tools, our games will be able to react to mouse and keyboard input, as well as to colliding game objects.

Input Polling

The first method we will discuss for getting player input is known as polling . Polling is a technique which involves regularly checking the current state of the mouse or keyboard to see if the player is giving us any input.

To do this in the Quorum Game Engine, we must include the InputMonitor library with the statement use Libraries.Game.InputMonitor. If we want to monitor keyboard input, we will also include the KeyboardEvent library with the statement use Libraries.Interface.Events.KeyboardEvent.

To see input polling in action, we will use a sample program which will play a sound when we press the spacebar on the keyboard or when we click inside the screen. To download a sample project that includes audio files, click here.

To begin with this sample, let’s start by looking at the objects we will use in our program.

InputMonitor monitor
KeyboardEvent keys
Audio keySound
Audio mouseSound

We put our InputMonitor in the Update action because we want to check (poll) the status of the inputs at every frame. This is where we test for input, and react accordingly.

   action Update(number seconds)
       // If the user is currently clicking the mouse, play a sound.
        if monitor:IsClicked()
            mouseSound:Play()
        end

       // If the user is pressing the spacebar, play a sound.
        if monitor:IsKeyPressed(keys:SPACE)
            keySound:Play()
        end
    end

The two if statements are where we check for input. In if monitor:IsClicked() we ask the InputMonitor to check if any of the mouse buttons are currently being held down. If any of them are, we will enter the code inside the if block.

Then we reach if monitor:IsKeyPressed(keys:SPACE) . The InputMonitor action IsKeyPressed will test if a particular key on the keyboard is being held down. Inside the action, we have to tell Quorum which key we want to test, so we give it the value of SPACE from the KeyboardEvent class. To see the names of all the keys refer to the KeyboardEvent documentation.

These two actions are enough to test for input from both mouse and keyboard, but the InputMonitor has other actions to poll for other kinds of information, such as the current location of the mouse. For full details on what an InputMonitor can do, see its documentation page here.

The full code for the polling example follows:

use Libraries.Game.Game
use Libraries.Game.InputMonitor
use Libraries.Interface.Events.KeyboardEvent
use Libraries.Sound.Audio

class Main is Game
   //create an InputMonitor object
   InputMonitor monitor

   //create a KeyboardEvent object
   KeyboardEvent keys
   Audio keySound
   Audio mouseSound

   action Main
       StartGame()
   end

   action CreateGame
       keySound:Load("AudioFiles/lightSound.wav")
       mouseSound:Load("AudioFiles/heavySound.wav")
   end

   action Update(number seconds)
       // If the user is currently clicking the mouse, play a sound.
       if monitor:IsClicked()
           mouseSound:Play()
       end

       // If the user is pressing the spacebar, play a sound.
       if monitor:IsKeyPressed(keys:SPACE)
           keySound:Play()
       end
   end

end

Handling Input with Events

The second method for getting player input is known as event handling. Event handling is a technique in which we designate code blocks that run whenever a specific event occurs, such as the mouse being clicked or a key being pressed. When we use event handling, we rely on the game engine to notify us when an event occurs by automatically calling certain predefined actions.

The Difference Between Polling and Event Handling

Most games, especially large games, use event handling instead of polling to detect input. To understand why, it’s helpful to think of an analogy for how the two approaches work.

Consider the situation where you want to get a book from the library, but it is currently checked out. One approach (polling) is to call the library once every hour and ask the librarian if the book has been returned yet. The other approach (event handling) is to register your request with the librarian and respond when you are notified that the book is available.

From this example, you can see that event handling is much more efficient than input polling, because it doesn’t waste time repeatedly checking. There are cases where it isn't noticeable which approach you use and others where it is necessary to poll, but the event handling approach should generally be preferred.

Using KeyboardEvents

To see how events can be used for keyboard input, we’ll use another sample program. This program will play a sound when the space bar is pressed, and a different sound when it is released. We will use the same audio files in the previous sample, or you can use your own.

Our code for this sample uses two classes, which are each in a separate file. Let’s start by looking at the Soundboard class.

use Libraries.Interface.Events.KeyboardListener
use Libraries.Interface.Events.KeyboardEvent
use Libraries.Sound.Audio

class Soundboard is KeyboardListener
   Audio pressedSound
   Audio releasedSound

   on create
       // We load our sounds when this object is created.
       pressedSound:Load("AudioFiles/lightSound.wav")
       releasedSound:Load("AudioFiles/heavySound.wav")
   end

   // This action will be called when any keyboard key is pressed.
   action PressedKey(KeyboardEvent event)
   if event:keyCode = event:SPACE
       pressedSound:Play()
   end
end
   // This action will be called when any keyboard key is released.
   action ReleasedKey(KeyboardEvent event)
       if event:keyCode = event:SPACE
           releasedSound:Play()
       end
   end

end

An important thing to note about this code is in the first line (the class definition) where we specify that the class is a KeyboardListener ( class Soundboard is KeyboardListener ). By inheriting from the KeyboardListener class, our class gets special actions that can "listen" for KeyboardEvents. This gives us certain action definitions that the engine will automatically call when a keyboard event occurs. If we choose to listen and respond to an event we simply put our actions inside these actions.

In any class that inherits from KeyboardListener, the PressedKey action will be called automatically by the engine when the user presses a key on the keyboard. The PressedKey action must take a KeyboardEvent as a parameter which is passed by the engine. This KeyboardEvent contains a field keyCode that represents what key was pressed. A KeyboardEvent also contains values representing all possible supported keys on a keyboard, which we use in our example to make sure that the key that was pressed is the space bar. We can choose to listen to whichever events we want to and respond to whichever keys we specify. If a key is pressed that we don't respond to, the event is ignored by our code.

action PressedKey(KeyboardEvent event)
   if event:keyCode = event:SPACE
      pressedSound:Play()
   end
end

In any class that inherits from KeyboardListener, the ReleasedKey action will be called automatically by the engine when the user releases a key on the keyboard. Let’s start by looking at the Soundboard class.Like the PressedKey action, this action must take a KeyboardEvent as a parameter which will provide the information on which key was released.

action ReleasedKey(KeyboardEvent event)
   if event:keyCode = event:SPACE
      releasedSound:Play()
   end
end

With this class ready to receive KeyboardEvents, we can set up our Main class as follows:

use Libraries.Game.Game

class Main is Game
   // We create a object using our Soundboard listener class.
   Soundboard board

   action Main
       StartGame()
   end

   action CreateGame
       // We add the listener to our game.  This has the effect of letting the engine know 
       //that the Soundboard object should be notified of keyboard events.
       AddKeyboardListener(board)
   end

   action Update(number seconds)
   end
end

The most important addition to the Main class is AddKeyboardListener(board) . This action tells the game engine that our Soundboard object is ready to start receiving KeyboardEvents. Now our program will "get"events from the keyboard (meaning that the engine will call the actions we specified in the Soundboard class when events occur).

Using MouseEvents

We can also use events for mouse input. There are three possible listeners for MouseEvents.

Using MouseListeners

In this next sample program, we will use a MouseListener to make a square change color while we’re clicking on it, and return it to its previous color when we release the mouse button.

Our code is split between two files again. We will start in the ColorSquare class.

//Notice that we are using mouse event libraries now instead of keyboard event libraries.
use Libraries.Interface.Events.MouseListener
use Libraries.Interface.Events.MouseEvent
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color

class ColorSquare is Drawable, MouseListener
   Color clickColor
   Color releaseColor

   on create
       // Set the colors that this program will use.
       clickColor:SetColor(0, 0.9, 0, 1)
       releaseColor:SetColor(0.9, 0, 0, 1)
       SetColor(releaseColor)

       // Add this object as a listener so that it receives mouse events when it is clicked.
       AddMouseListener(me)
   end

   // This action will be called when the ColorSquare receives a MouseEvent from a mouse button being clicked.
   action ClickedMouse(MouseEvent event)
       SetColor(clickColor)
   end

   // This action will be called when the ColorSquare receives a MouseEvent from a mouse button being released.
   action ReleasedMouse(MouseEvent event)
       SetColor(releaseColor)
   end
end

Notice that when we create our class using the line class ColorSquare is Drawable, MouseListener , we inherit from MouseListener, similar to how we inherit from KeyboardListener to listen for KeyboardEvents. We also need to call AddMouseListener to notify the engine like we did before.

// Ensure this object will receive mouse events when it is clicked.
AddMouseListener(me)

In this case, instead of adding the listener directly to the Game class, we show an alternative approach where we add the listener directly to an Item. Functionally these act in the same manner in that the engine calls the predefined actions of the class (if they are specified), however in this case, the listener only applies to the specific Item. If we wanted different behavior for different objects, we could define different listeners for different objects and add them where we want them. Since this class is a Drawable and that inherits from Item, our class is an Item and has the AddMouseListener action.

Once our MouseListener is receiving events, we can write the code we want to be called when we receive events.

In any class that inherits from MouseListener, the ClickedMouse action will automatically be called when the user clicks the mouse. This action must take a MouseEvent as a parameter. The MouseEvent can contain lots of information, including what mouse button was clicked or the X,Y coordinates of the mouse when the event occurred. To see everything that a MouseEvent can contain, see its full documentation here.
action ClickedMouse(MouseEvent event)
   SetColor(clickColor)
end

Similarly, a class that inherits from MouseListener can use the ReleasedMouse action to automatically call code when the user releases a mouse button. This action must also take a MouseEvent as a parameter.

action ReleasedMouse(MouseEvent event)
   SetColor(releaseColor)
end

This finishes our ColorSquare class. We can now write the Main class.

use Libraries.Game.Game
use Libraries.Game.Graphics.Color

class Main is Game
   ColorSquare square

   action Main
       StartGame()
   end

   action CreateGame
       // This code draws our square on the screen.
       Color white
       white = white:White()
       square:LoadFilledRectangle(200, 200, white)
       square:SetPosition(300, 200)
       Add(square)
   end

   action Update(number seconds)
   end
end

The image on the left is when the game starts and the image on the right is when the image is clicked on

This is an image of a red squareThis is an image of a green square

The code in Main is used to position and draw our square on the screen. All of the code to make our square react to MouseEvents is already self-contained inside of ColorSquare, so we don’t need to do anything else here. Our program is now ready for use.

Using MouseMovementListeners

MouseEvents can also be used with MouseMovementListeners to detect when the mouse is moved or dragged (which means that the mouse was moved while a mouse button was held down). In this next sample, we will make a square follow the mouse when the mouse is moved, and make the square mirror the mouse's movements in reverse when the mouse is dragged.

Our code is found in two different files again. We will start with the MouseFollower class.

use Libraries.Game.Graphics.Drawable
use Libraries.Interface.Events.MouseMovementListener
use Libraries.Interface.Events.MouseEvent

class MouseFollower is Drawable, MouseMovementListener
   // This action will be called when the MouseFollower receives a MouseEvent because the mouse was moved with no mouse buttons being held down.
   action MovedMouse(MouseEvent event)
       // This code sets the center of the box directly under the mouse.
       number x = event:GetX()
       number y = event:GetY()
       SetCenter(x, y)
   end
   // This action will be called when the MouseFollower receives a MouseEvent because the mouse was moved while a mouse button was being held down.
   action DraggedMouse(MouseEvent event)
       // This code makes the box move in the opposite direction of the mouse.
       number x = event:GetMovementX()
       number y = event:GetMovementY()
       Translate(-x, -y)
   end
end

Just like the other listeners, when we create our class with the line class MouseFollower is Drawable, MouseMovementListener, we need to inherit from the listener we are using, which in this case is the MouseMovementListener. This lets us create our two special actions that will be called automatically for us. This lets us create our two special actions that will be called automatically for us.

The first of these two actions is MovedMouse. This action will be called anytime the mouse is moved and there are no buttons held down on the mouse. In our example, we use event:GetX() and event:GetY() to get the x, y coordinates of the mouse after it has moved.

// This action will be called when the MouseFollower receives a MouseEvent
   // because the mouse was moved with no mouse buttons being held down
   action MovedMouse(MouseEvent event)
       // This code sets the center of the box directly under the mouse.
       number x = event:GetX()
       number y = event:GetY()
       SetCenter(x, y)
   end

The other action that will be automatically called by inheriting from MouseMovementListener is the DraggedMouse action. This action is called when the mouse is moved while any of the mouse buttons are held down. In our example, we use event:GetMovementX() and event:GetMovementY() to get the distance traveled by the mouse since the last time the mouse was moved or dragged.

// This action will be called when the MouseFollower receives a MouseEvent because the mouse was moved while a mouse button was being held down.
   action DraggedMouse(MouseEvent event)
       // This code makes the box move in the opposite direction of the mouse.
       number x = event:GetMovementX()
       number y = event:GetMovementY()
       Translate(-x, -y)
   end

Now we've finished our MouseFollower class. Our program still needs the Main class, though.

use Libraries.Game.Game
use Libraries.Game.Graphics.Drawable

class Main is Game
   MouseFollower box

   action Main
       StartGame()
   end

   action CreateGame
       // This code sets the box's image and adds it to the game screen.
       box:LoadFilledRectangle(50, 50)
       Add(box)

       // This line makes the box listen for mouse events from mouse movement.
       AddMouseMovementListener(box)
   end

   action Update(number seconds)
   end
end

The most important thing in our Main class is the line AddMouseMovementListener(box). This line tells the game engine to notify our box object every time the mouse is moved or dragged, and to call the appropriate action. Our program is now finished.

Using MouseWheelListeners

The last type of listener for MouseEvents is the MouseWheelListener. In this sample, we will use a MouseWheelListener to rotate a triangle on the screen when the user scrolls the mouse wheel.

Our program will be use two classes, RotatingItem and Main. We will start by looking at RotatingItem.

use Libraries.Game.Graphics.Drawable
use Libraries.Interface.Events.MouseEvent
use Libraries.Interface.Events.MouseWheelListener

class RotatingItem is Drawable, MouseWheelListener
   //This action will be called when the mouse wheel is scrolled.
   action ScrolledMouse(MouseEvent event)
       integer amount = event:scrollAmount
       Rotate(amount/10)
   end
end

Inside our class there is only a single action, action ScrolledMouse(MouseEvent event) . This is the only action that will be called automatically when using a MouseWheelListener. Inside this action we use event:scrollAmount . (Note that it is lower-case without parenthesis - we are directly using a variable inside event, instead of calling an action.) The scrollAmount value represents how far the mouse wheel has been scrolled. If the wheel was scrolled up, it will be positive, and if it was scrolled down, it will be negative. The faster the wheel was scrolled, the larger the number will be.

Now that we have made our ScrolledMouse action, this class is ready, and we can create our Main class.

use Libraries.Game.Game
use Libraries.Game.Graphics.Drawable

class Main is Game
   RotatingItem triangle

   action Main
       StartGame()
   end

   action CreateGame
       // This code sets the triangle's image and location, and adds it to the game screen.
       triangle:LoadFilledTriangle(0, 0, 150, 150, 150, 0)
       triangle:SetPosition(325, 225)
       Add(triangle)

       // This line makes the triangle listen for mouse events from the mouse wheel.
       AddMouseWheelListener(triangle)
   end

   action Update(number seconds)
   end
end

Once again, to make the game inform our listener of events, we need to add it. You can see this in our example code on the line AddMouseWheelListener(triangle) . Now our program is finished and ready.

Detecting Collisions with Events

In addition to testing for input using events, we can also use events to detect when two Items have collided in our game. In this last sample program, a black rectangle will be at the center of the screen. A white circle will be on the left side of the screen, which will pass over the rectangle as it moves off the right edge of the screen, and reappears on the left side. Every time the circle and the rectangle begin overlapping, a sound will play, the rectangle will turn blue, and the circle will turn red. Every time the circle and the rectangle begin overlapping, a sound will play, the rectangle will turn blue, and the circle will turn red. The complete sample, including audio, can be downloaded by clicking here .

Our code is in a single file.

use Libraries.Game.Game
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color
use Libraries.Interface.Events.CollisionListener2D
use Libraries.Sound.Audio
use Libraries.Interface.Events.CollisionEvent2D
use Libraries.Interface.Item2D

class Main is Game, CollisionListener2D

   Drawable block
   Drawable circle

   Audio beginSound
   Audio finishSound
   action Main
       StartGame()
   end

   action CreateGame
       // These two lines load our sound files
       beginSound:Load("AudioFiles/lightSound.wav")
       finishSound:Load("AudioFiles/heavySound.wav")

       // These lines are used to load and draw the circle and square.
       block:LoadFilledRectangle(300, 200)
       Color white
       white = white:White()
       circle:LoadFilledCircle(60, white)
       block:SetPosition(250, 200)
       circle:SetPosition(0, 240)
       Add(block)
       Add(circle)

       // These two lines give our Drawables a name so that we can identify
       // each object and do things to them when they begin or finish colliding
       block:SetName("block")
       circle:SetName("circle")

       // These two lines are used to make the two items detect collisions.
       block:SetCollidable(true)
       circle:SetCollidable(true)

       // This line is used to make our Game class listen for and handle collisions
       AddCollisionListener(me)
   end

   action Update(number seconds)
       // The Update action is used in this program to make the circle move.
       circle:SetX(circle:GetX() + 200 * seconds)
       if circle:GetX() > GetScreenWidth()
           circle:SetX(0 - circle:GetWidth())
       end
   end

   /*
   This is the action that gets called automatically by the game engine when
   a collision begins between two collidable items. We are passed an item
   that represents the event of the collision. From that item we can get the
   items that are colliding.
   */
   action BeginCollision(CollisionEvent2D event)
       // First things first, play the begin collision sound
       beginSound:Play()

       // Next, we want to get our items out of the collision event
       Drawable itemA = cast(Drawable, event:GetItemA())
       Drawable itemB = cast(Drawable, event:GetItemB())

       // the block will turn blue when they start colliding
       Color blue
       blue = blue:Blue()

       // the circle will turn red when they start colliding
       Color red
       red = red:Red()
       if itemA:GetName() = "block"
           // if itemA is the block, then itemA gets the color blue and itemB
           // bets the color red.
           itemA:SetColor(blue)
           itemB:SetColor(red)
       else
           // otherwise, the reverse is true
           itemA:SetColor(red)
           itemB:SetColor(blue)
       end
   end

   /*
   This is the action that gets called automatically by the game engine when
   a collision finishes between two collidable items. We are passed an item
   that represents the event of the collision. From that item we can get the
   items that are colliding.
   */
   action FinishCollision(CollisionEvent2D event)
       // First things first, play the finish collision sound
       finishSound:Play()

       // Next, we want to get our items out of the collision event
       Drawable itemA = cast(Drawable, event:GetItemA())
       Drawable itemB = cast(Drawable, event:GetItemB())

       // the block will turn black when they finish colliding
       Color black
       black = black:Black()

       // the circle will turn white when they finish colliding
       Color white
       white = white:White()

       if itemA:GetName() = "block"
           // if itemA is the block, then itemA gets the color black and itemB
           // bets the color white.
           itemA:SetColor(black)
           itemB:SetColor(white)
       else
           // otherwise, the reverse is true
           itemA:SetColor(white)
           itemB:SetColor(black)
       end
   end
end

Just like other events, we can declare one or more of our game objects as a listener for that event. In this case, our Main class also listens for collisions between two 2D items via the line class Main is Game, CollisionListener2D . Similarly to other events, the game engine automatically passes an object representing the event to our actions for handling that event; in this case, we are passed a CollisionEvent2D object representing a collision between two 2D items in our BeginCollision and FinishCollision actions.

If we want the game engine to tell us when two items are colliding, we need to tell the game engine that those items are collidable. We do this by calling the SetCollidable action on the items with the parameter true . A handy place to do this is in our CreateGame action.

// These two lines are used to make the two items detect collisions.
block:SetCollidable(true)
circle:SetCollidable(true)

It is important to note that collidable items can only collide with other items that have been declared collidable. So, if two items are overlapping in our game, but one of them has not been set to be collidable, we will not receive a collision event for those two items. On the other hand, if two items are overlapping and both of them have been declared collidable, we will receive a collision event for these two items.

Additionally, if we want to be notified about collision events, we need to tell our game about our collision listener object. We do so by calling the AddCollisionListener action and passing it the object that should be listening for collisions. Again, a great place to do this is in our CreateGame action.

// This line is used to make our Game class listen for and handle collisions
AddCollisionListener(me)

All objects declared as collision listeners should implement an action for handling when items start to collide, called BeginCollision. This action is passed a CollisionEvent2D or CollisionEvent3D object (depending on whether the listener is listening for 2D or 3D collisions) that represents the collision.

action BeginCollision(CollisionEvent2D event)

end

Similarly, objects declared as collision listeners should implement an action called FinishCollision for handling when two items stop colliding. This action also takes as a parameter an event object representing either the 2D or 3D collision event.

action FinishCollision(CollisionEvent2D event)

end

In the above mentioned BeginCollision and FinishCollision actions, we should perform the actions that we want to occur when items either begin or finish colliding. Both of the items that are colliding are in the event object passed to the BeginCollision and FinishCollision actions. We can get the colliding items from the event object by calling the GetItemA() and GetItemB() actions on the event object. From there, we can do the things that we want to happen when items are overlapping such as playing a sound or changing the color of the items.

action BeginCollision(CollisionEvent2D event)
   // First things first, play the begin collision sound
   beginSound:Play()

   // Next, we want to get our items out of the collision event
   Drawable itemA = cast(Drawable, event:GetItemA())
   Drawable itemB = cast(Drawable, event:GetItemB())

   // the block will turn blue when they start colliding
   Color blue
   blue = blue:Blue()

   // the circle will turn red when they start colliding
   Color red
   red = red:Red()

   if itemA:GetName() = "block
       // if itemA is the block, then itemA gets the color blue and itemB
       // bets the color red.
       itemA:SetColor(blue)
       itemB:SetColor(red)
   else
       // otherwise, the reverse is true
       itemA:SetColor(red)
       itemB:SetColor(blue)
   end
end

With our Main class filled, our program is ready. The Game should consist of a circle moving from left to right, making a sound when it begins overlapping the rectangle and changing its color to red and the rectangle's color to white. When it stops overlapping with the rectangle, a different sound is played, it changes color to white, and the rectangle changes color to black.

The Image on the left is when the red circle make a sound that it has collided with the blue rectangle and the picture on the right is when the white circle leaves the black rectangle making a sound.

This is an image of a red circle enter in a blue rectangle This is an image of a white circle leave in a black rectangle

Resources

The"lightSound.wav"sound file is used under the Creative Commons Attribution 3.0 license: Sword Swing. The"heavySound.wav"sound file is used under the Creative Commons Attribution 3.0 license: Battle Axe Sound

Next Tutorial

In the next tutorial, we will discuss 3D Drawing, which describes how to draw in 3D.