Pygrr - rendering and framerate (part 2)
Welcome back to "rendering and framerate"! In this conclusion, I will talk about how the stuff we learnt from last entry is applied to Pygrr, and go over some interesting quirks and things. Without further ado, here we go...
For Pygrr's rendering, I have opted to use the standard library of tkinter (click to read about it!). This is a lightweight library that comes pre-installed with Python itself. This means that the users of Pygrr do not have to install any further software after the library itself, and it also means that Pygrr will be able to run on all systems (Linux, Mac & Windows), as they all share tkinter!
Tkinter has a 'window' (also referred to as a 'root') that must be initialised, and also a 'canvas', which can be used to bind gadgets (shapes, buttons, etc) and draw them onto the window. There are 3 functions that the canvas can call which can draw an "oval", a "polygon" and a "line" on it. I will go over this after explaining how the window and canvas is initialised in Pygrr!
There are 3 arguments that the window takes in. This is width, height, and background (colour). These can be called when the user creates the window, by calling the function:
create_window(width=500, height=400, background='white')
As you can see, if the width is missing from that argument, it will be defaulted to 500, the height 400, and the background "white". Some colours are hard-coded with tkinter, so if you input them as a string, the program will translate them into a hex code, which you can learn about in the last post in this entry!
All the supported colours are - image from dftwiki (smith.edu):
I originally forgot these default string colours existed, so hard coded a bunch myself... Oops! Both the window and canvas are created and cached automatically by Pygrr when you call the function. You can also change the window / canvas parameters during runtime by calling the functions:
set_width(new_width)
set_height(new_height)
set_background(new_color)
There's also get versions of those, where you can call get_width() for example, to return the width of the canvas. For simplicity and elegance, the end-user cannot resize the window themselves, by stretching the window on the screen!
You can also call destroy_window() to... Well, destroy the window! This allows for a fresh wipe of the window, and, thus, new 'scenes' in the game.
As we learnt in the last entry, these functions can only display a still screen, because there is no frame system. Well, there's also get and set_framerate(new_framerate), where it is measured in frames per second (FPS). Framerate is defaulted at 30. These work with the user's game loop, in their code, they must set up a loop (infinite or finite), and at the end of the loop, call next_frame(). This will invoke a special timing protocol I coded to pause execution for the correct length of time, and render new stuff.
Creating objects? Tkinter allows for creation of three primitive objects on a canvas, an oval (of given width and height), a polygon (of given array of points), and a line (of given pair of points). Unfortunately, tkinter wasn't going to let me get away that easily with rendering of ovals, such as circles... On every operating system apart from windows, they work flawlessly - however, on windows, upon the oval moving, it leaves a trail of ugly pixels... I couldn't find a way around this, so, I did what every programmer does, and code another program to fix it! This came in the form of a 'circle estimator' that I programmed, which turns a circle into a polygon (array of points), with a given accuracy. Here's some videos of two different accuracies:
Now - if the framerate ever fluctuates (dips or gets bigger) due to computer speed being limited, for example, background tasks can take up different amounts of power at a time, the movement of an object will be messed up. What? Let me show you: if an object, let's call it "player" moves by 1gu (graphical unit) every frame, if the framerate is 50 per second, it will move 50gu a second, whereas, with half the framerate, it will move twice as slowly. This can be combated by inputting a simple multiplication inside the move.
while True:
player.move_x(10) # this will move at different speeds in different framerates
player.move_x(10 * pygrr.deltatime) # this will not
How does that work? Deltatime is expressed as the length of time between the last frame and now. This means, if the framerate fluctuates, so will the length of time, and, thus, the distance moved will fluctuate (being multiplied by deltatime). If that sounds confusing, don't worry, I'll show you an example!
Moving 25 * deltatime, at 50 frames per second:
Isaac, over and out...