More 2d drawing

The last class was somewhat of a free-wheeling let's-see-what-raylib-can-do class. I'll go over a few of the questions and topics, and we'll expand on what we did.

If you recall in the class before this one, we learned how to create a raylib project, and draw some text on the screen. We also learned how to draw images to the screen in the homework, as well as exploring some other drawing tools (rectangles, circles, etc).

One question James asked is how we can react to button clicks on the mouse. We learned that the IsMouseButtonPressed(int button) function will return true when someone has clicked the mouse (pressed AND released the button). The button integer passed in is the mouse button (starting with 0 for the left mouse button). At first, I showed how you can use this to affect how the drawing happens. In my example program that shows the grid lines of the screen coordinates, I changed the colors to a random color when you clicked the screen.

Another question from James was how to make the background multiple colors. This is done simply by drawing more rectangles over the background. For example, if we wanted to make a background half white and half green, we would clear the background with the color white, and then draw a rectangle over half the screen to make that part green.

Finally, a question from Johann was how use a timeout to do things automatically. For this, raylib has some very crude timing mechanisms. My recommendation is to use a D feature called StopWatch. You can find this by adding import std.datetime.stopwatch; to the top of the file. Now you can declare a StopWatch and use it to determine how much time has elapsed since you started it. We can use some special code constructs to tell us how much time has elapsed since the stopwatch was started. Here is a short synopsis of how you can use stopwatch:

StopWatch sw = StopWatch(AutoStart.yes);
while(!WindowShouldClose())
{
   if(sw.peek() > 1.seconds) {
      doSomething();
      sw.reset(); // restart the timer
   }
}

A couple notes about this code. First, the code 1.seconds is a special kind of construct that D provides, where a function named seconds is called with a value of 1. In other words, this is the same as seconds(1). The value returned by this function is called a Duration. The Duration type is documented here. This is also the type that is returned by the StopWatch.peek() function. You can add or subtract and compare Duration types. The special functions that create Duration types from numbers are:

Using these constructs, you can create a large variety of Duration values. Just generate them using these functions, and add or subtract them together.

The code above allows us to do something every 1 second. But what if you need to time several things? Maybe one thing happens every 0.5 seconds, and one thing happens every second. How would we time something like this? What we can do is establish a future time at which to do something, and simply avoid resetting the stopwatch. For example:

StopWatch sw = StopWatch(AutoStart.yes);
Duration secondTimer = 1.seconds;
Duration halfSecondTimer = 500.msecs;
while(!WindowShouldClose())
{
   if(sw.peek() > secondTimer) {
      handleSecondTimer();
      secondTimer += 1.seconds; // go to the next second
   }
   if(sw.peek() > halfSecondTimer) {
      handleHalfSecondTimer();
      halfSecondTimer += 500.msecs; // go to the next half second
   }
}

So in this way, we can establish a time for things to happen. What if we need something to happen ONCE, after a certain timeout? Just add the current stopwatch value to the time you want it to take, and check the stopwatch against that final time to figure out when to do it (imagine this is in the main loop):

if(shouldStartMoving) {
   character.isMoving = true;
   stopMovingTime = sw.peek() + 1500.msecs; // stop moving after 1.5 seconds
}

if(character.isMoving)
   moveCharacter(character);

if(sw.peek() > stopMovingTime) {
   character.isMoving = false;
}

One more thing I want to bring up that I recently discovered: raylib will run the main loop as fast as possible unless you tell it to slow down. On my computer, this means it was running about 2500 to 3000 frames per second! We probably don't need that much frame rate (most monitors are only refreshing 60 to 120 Frames per second anyway), so there is a function to tell raylib to slow down. It's SetTargetFPS(int desiredFrames). Call this before starting your main loop to save the computer from churning too quickly. Note that even though the target is probably reachable, it's not going to be exact, due to the way raylib achieves this. When I set it to 60 on my system, it runs at 58FPS. You can check your frame rate by drawing it to the screen using the function DrawFPS(int x, int y).

Start of the Bricker game

In class, I wrote a simple ball simulation, where a ball rolled back and forth. When you clicked the button, it jumped into the air. It reminded us of a brick breaking game, so let's start writing that one! Unlike the ball bouncing game, we're going to need something to control other than a simple bouncing ball -- we need a paddle that can send the ball back up to break more bricks.

Let's write some code that draws a paddle to the screen, AND allows the mouse to control where the paddle is. First, we need to create a structure that holds all the information we need about the paddle:

struct Paddle
{
    int height = 50;
    int width = 150;
    int pos = 0;
}

By giving the members of the struct values, whenever we create a Paddle, it will start with those values for those fields. Now, we need to have code that draws the paddle:

void main()
{
    InitWindow(640, 720, "Bricker");

    Paddle paddle;

    SetTargetFPS(60);

    while(!WindowShouldClose())
    {
        BeginDrawing();
        ClearBackground(Colors.WHITE);
        DrawRectangle(paddle.pos, GetScreenHeight() - paddle.height - 30,
                      paddle.width, paddle.height, Colors.BLACK);
        EndDrawing();
    }

    CloseWindow();
}

In this case, I drew a white background and a black paddle. You can pick whatever colors you want. You can also play around with the paddle sizes to see what looks best for you.

Let me just dissect the code to draw the paddle, with the DrawRectangle call. paddle.pos is the x coordinate of the paddle. This is the left side of the paddle, and so we start there. The y coordinate is the top of the paddle, which I want to be paddle.height pixels above the bottom of the screen (GetScreenHeight() returns how tall the window is), along with a small gap of 30 pixels so we can have a place for the ball to go through when it gets past the paddle. The width and height of the paddle are defined by the struct. Why did I make these variables? Maybe you want to have powerups at some point that affect the width or height! Now, all we would have to do is change those fields and the drawing code stays the same.

This is a pretty boring game, if you run it, it draws a paddle on the left side of the screen, and it just sits there. So let's make the paddle follow the mouse cursor. The mouse position is retrieved using the functions GetMouseX() and GetMouseY(), which gets the x and y coordinates of the mouse in terms of the window. We really only need the x coordinate, because we are only going to move the paddle side to side. So let's just set the paddle position to the x coordinate:

    while(!WindowShouldClose())
    {
        // adjust paddle to correct x coordinate
        paddle.pos = GetMouseX();
        
        ... // existing drawing code
    }

Note that the comment for existing drawing code is where your existing code should stay, it's not something you type in.

Now if we run the application, we can see the paddle follow the mouse! Except... it lines up the left side of the paddle with the mouse cursor. And the paddle goes off the screen!

The Assignment

The homework (besides creating this applicaiton) is to fix the setting of the paddle.pos variable so the mouse is lined up with the middle of the paddle, not the left side. The second part is to stop the paddle from going off the screen. Use if statements to figure out if the paddle is off screen and set the pos accordingly.

In the next class, we'll start creating and animating the ball, and talk about how to process collisions of objects.

©2019-2020 Steven Schveighoffer