Skip to content

TUTORIAL User Input

Javidx9 edited this page Sep 12, 2022 · 2 revisions

So far, we've used some simple pixel drawing routines to draw a basic environment for our BreakOut clone. Players need to interact with their games somehow, and olc::PixelGameEngine provides input support via the keyboard and mouse. There are also extensions which add gamepad and joystick support - though that is not covered in this tutorial.

Input either via mouse or keyboard is handled in the same way. Per frame, you interrogate the state of the particular input you want to respond too. Both mouse and keyboard buttons are defined as olc::HWButton, and this simple structure provides three flags.

bPressed - Is true only for the frame the button is pressed, all other frames it is false.

bHeld - Is true for all frames the button is held down in, including the first frame along with bPressed, All other frames it is false.

bReleased - Is true only for the frame the button is released, all other frames it is false.

In our game, I want to move the bat left or right, depending on whether or not the left or right arrow keys are held down. I'll modify the OnUserUpdate() function:

bool OnUserUpdate(float fElapsedTime) override
{
	// Handle User Input
	if (GetKey(olc::Key::LEFT).bHeld) fBatPos -= 1.0f;
	if (GetKey(olc::Key::RIGHT).bHeld) fBatPos += 1.0f;

	if (fBatPos < 11.0f) fBatPos = 11.0f;
	if (fBatPos + fBatWidth > float(ScreenWidth()) - 10.0f) fBatPos = float(ScreenWidth()) - 10.0f - fBatWidth;

Here, I call the GetKey() function, passing along a specific olc::Key identifier, and if the returned olc::HWButton structure's bHeld flag is true, I'll increment or decrement the bat's position accordingly. After that I constrain the bats position to the width of the playing area, so the bat does not leave the arena.

Well it works, kind of... It's very fast. And this is to be expected! The application is running at about 4000 frames per second, and each frame we change the bat's position by one whole pixel. Since the arena is about 500 pixels wide, it only takes 0.125 seconds to move across the whole area. This is why I declared the bat's position as a float. We need to move things in "sub-pixels". I'll add in another variable "fBatSpeed" and adjust the input handling code.

private:
	float fBatPos = 20.0f;
	float fBatWidth = 40.0f;

	olc::vf2d vBall = { 200.0f, 200.0f };
	float fBatSpeed = 0.1f;

public:
	bool OnUserCreate() override
	{
		return true;
	}

	bool OnUserUpdate(float fElapsedTime) override
	{
		// Handle User Input
		if (GetKey(olc::Key::LEFT).bHeld) fBatPos -= fBatSpeed;
		if (GetKey(olc::Key::RIGHT).bHeld) fBatPos += fBatSpeed;

And this is important! Our objects in the game world can quite happily have floating point position values, when we draw them, they will be automatically aligned to the nearest valid pixel. The olc::PixelGameEngine won't do any fancy anti-aliasing or blurring if trying to draw at locations in between pixels.

However! This way of moving things is crude, and prone to a problem we will look at in the next tutorial. For now it's good enough though, the bat moves (somewhat) consistently along the bottom of the arena.

In the BreakOut clone, I have no real need for mouse input, but it may be worth looking at anyway since we're here. The mouse buttons behave exactly the same way as keyboard buttons. Instead of GetKey() we use the GetMouse() function and pass in a numeric identifier of which button we want to use. Typically, 0 = left button, 1 = right button and 2 = middle button. We can use the GetMouseX() and GetMouseY() functions to get the mouse's coordinate on the screen. I'm going to add some code that allows us to move the ball with the mouse if the left button is held down.

bool OnUserUpdate(float fElapsedTime) override
{
    // Handle User Input
    if (GetKey(olc::Key::LEFT).bHeld) fBatPos -= fBatSpeed;
    if (GetKey(olc::Key::RIGHT).bHeld) fBatPos += fBatSpeed;
    if (fBatPos < 11.0f) fBatPos = 11.0f;
    if (fBatPos + fBatWidth > float(ScreenWidth()) - 10.0f) fBatPos = float(ScreenWidth()) - 10.0f - fBatWidth;

    // Cheat! Moving The Ball With Mouse
    if (GetMouse(0).bHeld)
    {
	vBall = { float(GetMouseX()), float(GetMouseY()) };
    }

It is also possible to respond to the mouse scroll wheel. To demonstrate this, I'll adjust the ball's radius depending on whether or not the mouse wheel is scrolled up or down. The GetMouseWheel() function returns positive or negative values depending on how much it was scrolled that frame. I've added a variable "fBallRadius" which is adjusted depending on the scroll wheel being moved.

Here is the complete code so far:

#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"

class BreakOut : public olc::PixelGameEngine
{
public:
	BreakOut()
	{
		sAppName = "TUTORIAL - BreakOut Clone";
	}

private:
	float fBatPos = 20.0f;
	float fBatWidth = 40.0f;

	olc::vf2d vBall = { 200.0f, 200.0f };
	float fBatSpeed = 0.1f;
	float fBallRadius = 5.0f;

public:
	bool OnUserCreate() override
	{
		return true;
	}

	bool OnUserUpdate(float fElapsedTime) override
	{
		// Handle User Input
		if (GetKey(olc::Key::LEFT).bHeld) fBatPos -= fBatSpeed;
		if (GetKey(olc::Key::RIGHT).bHeld) fBatPos += fBatSpeed;

		if (fBatPos < 11.0f) fBatPos = 11.0f;
		if (fBatPos + fBatWidth > float(ScreenWidth()) - 10.0f) fBatPos = float(ScreenWidth()) - 10.0f - fBatWidth;

		// Cheat! Moving The Ball With Mouse
		if (GetMouse(0).bHeld)
		{
			vBall = { float(GetMouseX()), float(GetMouseY()) };
		}

		if (GetMouseWheel() > 0) fBallRadius += 1.0f;
		if (GetMouseWheel() < 0) fBallRadius -= 1.0f;
		if (fBallRadius < 5.0f) fBallRadius = 5.0f;

		// Erase previous frame
		Clear(olc::DARK_BLUE);

		// Draw Boundary
		DrawLine(10, 10, ScreenWidth() - 10, 10, olc::YELLOW);
		DrawLine(10, 10, 10, ScreenHeight() - 10, olc::YELLOW);
		DrawLine(ScreenWidth() - 10, 10, ScreenWidth() - 10, ScreenHeight() - 10, olc::YELLOW);

		// Draw Bat
		FillRect(int(fBatPos), ScreenHeight() - 20, int(fBatWidth), 10, olc::GREEN);

		// Draw Ball
		FillCircle(vBall, int(fBallRadius), olc::CYAN);
		return true;
	}
};

int main()
{
	BreakOut demo;
	if (demo.Construct(512, 480, 2, 2))
		demo.Start();
	return 0;
}

In the next tutorial, we'll add ball movement and examine how you should handle "time" in your games.

Clone this wiki locally