Sometimes I like to go back to how it all started for me. Playing around with code, making things happen on the screen. Chasing down new discoveries as I learned them and letting these discoveries lead me down whatever rabbit hole they would.
I learned coding for fun. I wasn’t driven by any specific objectives. I had no particular goals in mind. I just enjoyed the creative process of it, making things happen, expanding my knowledge and therefore my control over the things I could make my computer do.
12 years old, I was. When my parents gave me my first computer for Christmas. A Commodore 64. And let me tell you, it was THE BOMB back in the day :D
It wasn’t until about a year or so later, I started hanging with all the “wrong” sorts of people. Other computer enthusiasts (yes you can say it… geeks) like myself, at our local computer club. Imagine that - a computer space where people would actually meet up. In person. And exchange ideas and experiences (and erm… software). And a lot of showing off. “Look what I can make my computer do!”.
This set me off, I guess. I started picking up and picking apart what the others had been coding - most of them years older than me. Over the years that followed, I learned assembly language (6510 ASM) by mostly looking at other people’s code, tweaking and modifying it, evolving it and “making it mine”. Nothing but the trusted C-64 Reference Guide to support me - there was no such thing as Internet Tutorials, Websites, or anything of the sort back then.
Anyway. This post isn’t supposed to be a trip down memory lane. I have a point I’m getting to. I found that; when learning something entirely new - the easiest thing for me to do, is STILL to follow the above approach. Start messing about with it, no particular goal in mind. Adapt what other people have already done (standing on the shoulders of giants, and so on), take it apart, reassemble it, make it my own.
So when I recently decided; “hey let’s find out what this Python thing is all about”… well this is what I did.
And I’m now sharing this story with you.
Why not? :D
Python is also generally recognised as the primary language of choice for AI development; there are a large collection of libraries dealing with machine learning and data science readily available.
And it runs everywhere. On anything. It’s very easy to learn.
And it’s just for fun. Learning new languages, any language really, will broaden your perspective and horizon. Even if you’re not going to be coding Python in your day-to-day job.
Back to the roots. I was originally driven by making things happen on screen. And I don’t mean buttons and dropdowns and sliders; I get more than enough of that in my daily life. No, I’m talking about colours, movement, interactions, effects. Fun stuff. And for this reason it was natural for me to start my Python adventures by looking into Pygame.
Pygame is a cross-platform set of Python modules designed for writing video games. It includes computer graphics and sound libraries designed to be used with the Python programming language.
Graphics. Sound. Here we go. Come on, follow me along. Fire up Terminal or Powershell and get going with these simple steps ;-)
winget install python
Then restart your Terminal. Slightly different approach for MacOS (but everything in this post should work just the same). See: https://www.dataquest.io/blog/installing-python-on-mac/ or here for a Homebrew approach: https://docs.python-guide.org/starting/install3/osx/
Now let’s add a few modules we’re going to need. For this we use ‘PIP’. PIP is the package manager of choice for Python; so like NPM for NodeJS and Nuget for dotnet.
pip install pygame pip install numpy pip install moderngl pip install pymunk
Test that everything is working
Which will give you a prompt. You are now coding Python directly in your console window. Type in the following:
Python will respond, unsurprisingly, with “hello world”. Excellent. You are now a Python developer. Press
CTRL-Z (EOF) and ENTER to exit your interactive Python session.
Now let’s get going with our first pygame.
VSCode should prompt you to install the extensions for Python. I highly recommend you do. And now for some code:
# Basic 101 of setting up an empty pygame project import pygame as pg RESOLUTION = W, H = 1280, 1024 FPS = 60 pg.init() surface = pg.display.set_mode(RESOLUTION) clock = pg.time.Clock() pg.display.set_caption('Hello World!')
Not a whole lot going on, I imagine the code should read fairly easily if you’ve ever developed in any computer language in the past. Pygame is imported and given the shorthand
pg for ease of use, a variable tuple RESOLUTION is defined and also split out into the variables W and H. FPS is set to 60, we won’t be dealing with more details around that for now.
Then pygame is initialised, the pygame “surface” is set to the resolution we defined (so basically, the pygame window). We set the title of our pygame window. And we’re off to the races.
And now we just need our main loop; keeping pygame running until we close our window.
never_gonna_give_you_up = True while never_gonna_give_you_up: # Draw whatever surface.fill(pg.Color('black')) # Listen for events (keyb, mouse, etc) for i in pg.event.get(): if i.type == pg.QUIT: never_gonna_give_you_up = False # flip the buffers. We're always drawing to the off-screen buffer. pg.display.flip() clock.tick(FPS)
Again, I don’t imagine there being too many surprises here. We fill our pygame surface with the colour black. Then we listen for incoming events, specifically the
pg.QUIT event which tells us, it’s time to go.
And that’s it. The “Hello World” of pygame. Not much to look at, is it? :D
Find the complete source code at pygame-hello.py
But we’re not quite done. For this Sunday, I wanted to mess around with pymunk as well.
Pymunk is a easy-to-use pythonic 2d physics library that can be used whenever you need 2d rigid body physics from Python.
Fortunately, pymunk is very easy to introduce. First, we need to import the library.
import pygame as pg import pymunk.pygame_util
Pymunk works with a lot of different Python libraries, so we’re specifying here that we want the pygame integration of pymunk.
Then we need to tell it, what’s up and what’s down. Yes I’m not kidding :D Perhaps a little explainer is in order.
In school when you learned maths, you no doubt worked at some point with graphs and curves. You drew these in a coordinate system. Something like this:
Y | | | | | |--------------------X
Where 0,0 would be the bottom left.
Pygame is different. It follows “screen space” where 0,0 is address 0 of the screen memory and therefore in the top left corner. As Y increases, we move downward. This is fine, we just need to tell pymunk about it.
pymunk.pygame_util.positive_y_is_up = False
And then after we’ve defined our pygame surface area, we need to let pymunk know about it.
draw_options = pymunk.pygame_util.DrawOptions(surface)
And with this; pygame and pymunk are now integrated. Now all we need to do, is set up pymunk’s simulation space.
space = pymunk.Space() space.gravity = 0, 2000
Our new initialisation code, including pymunk, now looks like this:
import pygame as pg import pymunk.pygame_util pymunk.pygame_util.positive_y_is_up = False RESOLUTION = W, H = 1280, 1024 FPS = 60 pg.init() surface = pg.display.set_mode(RESOLUTION) clock = pg.time.Clock() pg.display.set_caption('Hello World with pymunk physics!') draw_options = pymunk.pygame_util.DrawOptions(surface) space = pymunk.Space() space.gravity = 0, 2000
As for the main loop itself, we just need to add a little bit of code. To make sure the pymunk engine ticks along nicely with us.
We “step” the engine forward relative to our frame update speed. And then for now, dump the output to our draw surface that we linked above.
Main Loop now looks like this:
never_gonna_give_you_up = True while never_gonna_give_you_up: surface.fill(pg.Color('black')) for i in pg.event.get(): if i.type == pg.QUIT: never_gonna_give_you_up = False space.step(1/FPS) space.debug_draw(draw_options) pg.display.flip() clock.tick(FPS)
Find the complete source code at pymunk-hello.py
What good is a graphics library and a physics engine, if all we can see is a black screen? I completely agree. Let’s do something with it.
After initialising my pymunk space, I’m going to drop in a couple of objects to it. A ball, and a floor for the ball to land on. Very simple stuff.
I’ll add a ball to the space. Strictly speaking it’s a circle, since we’re operating entirely in 2D space here.
ball_mass, ball_radius = 1, 60 ball_momentum = pymunk.moment_for_circle(ball_mass, 0, ball_radius) ball_body = pymunk.Body(ball_mass, ball_momentum) ball_body.position = W // 2, 0 # Place ball at the center/top of our space ball_shape = pymunk.Circle(ball_body, ball_radius) space.add(ball_body, ball_shape) # Now add it to our physics space
You can run the code at this point. You’ll see the ball appearing at the top center of the surface and then quickly and rather disappointingly drop through the bottom.
So let’s add a floor as well.
segment_shape = pymunk.Segment(space.static_body, (0, H), (W, H), 20) # Shape is a line (segment) starting at 0, H (0,1024) ending at W, H (1280, 1024) # with a thickness of 20 space.add(segment_shape)
Much better. Our ball now stays with us. You might be expecting the ball to bounce off the floor. Not at all unreasonable. For this, all we need to do is tell pymunk a little bit about the elasticity of our objects.
segment_shape.elasticity = 0.8 ball_shape.elasticity = 0.8
And we’re off to the races again. Things are working.
Having gone through all the “hard” work of setting up pygame and pymunk, it’s time to see what we can do with it. I’m gonna start with something relatively simple, but I highly encourage you to mess around with this example and come up with something of your own. Go crazy. Share it.
Expanding upon everything we’ve done so far, let’s make just a few more changes. First; let’s extract the code for generating and adding our ball to our space into a method of its own. Then we can add lots and lots of them. We’re also going to need some random numbers, so up top add the following:
from random import randrange
def create_ball(space, pos, radius=60): ball_mass, ball_radius = radius/5, radius ball_momentum = pymunk.moment_for_circle(ball_mass, 0, ball_radius) ball_body = pymunk.Body(ball_mass, ball_momentum) ball_body.position = pos ball_shape = pymunk.Circle(ball_body, ball_radius) ball_shape.elasticity = 0.8 ball_shape.friction = 0.5 ball_shape.color = (randrange(0,255), randrange(0,255), randrange(0,255), 255) space.add(ball_body, ball_shape)
The only new concepts introduced here would be the
friction (surface friction of our ball, how it influences other objects it interacts with) and spawning our balls with random colours.
Now let’s add some spawning functionality. For this, let’s listen to the mouse events and use them to spawn a new ball of a random size and colour whenever the user presses the left mouse button. Add the following to our event loop:
# When user clicks left mouse button, a ball of random size and color is spawned on the location if i.type == pg.MOUSEBUTTONDOWN: if i.button == 1: # button 1? (left) create_ball(space, i.pos, randrange(10, 80))
Whee. Off we go.
Find the complete source code at fun-with-pymunk.py
And that’s it.
The shoulders of giants I stood on, for my very initial introduction to these concepts can be found here. https://www.youtube.com/watch?v=-q_Vje2a6eY
As this series moves along, I’ll be adding my examples to this GitHub repo. You can find it here: https://github.com/cassidydotdk/py-sics
If you want to show off your awesome creations and variations of the examples we’ve gone through here today, feel free to submit a PR. Place your variations here: https://github.com/cassidydotdk/py-sics/tree/main/hello-world/community
Have a great day! :-)