New to programming? Follow along one step at a time with the step by step version, where every step is on its own page.
I wanted a tiny first game that someone who has never programmed can build in an afternoon, and that actually looks like a game when it runs. This one is called Bobgame. Bob is a round head seen from above, you move him with the W A S D keys, and an arrow on him always points at your mouse. That’s it, but it’s the exact base that real top-down shooters start from.
The finished code is open source, so you can always compare against mine: github.com/carlpaton/bobgame. The whole game is only about 90 lines.
This post is written so an 11 year old can follow it. If something doesn’t make sense, run the code anyway, change a number, and see what happens. That is how most programmers learn.
This is what you’ll have at the end. Bob is the round head, and the red arrow points at the mouse.

1. Install Python
Python is the language we write the game in. Your computer needs to understand it first.
- Go to python.org/downloads and download the latest Python 3 (this game needs 3.10 or newer).
- Run the installer. On the very first screen, tick the box that says “Add python.exe to PATH” before you click Install. If you miss this, the commands below won’t work.
Open a terminal (press the Windows key, type cmd, press Enter) and check it worked
1 | py --version |
You should see something like Python 3.14.0.
On Ubuntu, Python is usually already installed, so use python3 --version instead, and if you’re missing the venv tool in step 3 run sudo apt install python3-venv.
Docs: Python downloads • Using Python on Windows
2. Install VS Code and the Python extension
VS Code is the program we type our code into (an editor). It’s free.
- Download and install it from code.visualstudio.com.
- Open VS Code, click the Extensions icon on the left (it looks like four squares) or press
Ctrl+Shift+X. - Search for Python and install the one published by Microsoft. This is the only extension we need. It colours your code so it’s easier to read, points out mistakes, and lets you run your game with one click.
Docs: Python in Visual Studio Code
3. Make the project folder and a virtual environment
Make a folder called bobgame somewhere easy to find, then in VS Code choose File > Open Folder and open it. Open the built-in terminal with Ctrl+` (that’s the key above Tab).
Now create a virtual environment. Think of it as a private toolbox just for this game, so the things we install for Bobgame don’t get mixed up with other projects.
1 | py -3 -m venv .venv |
This makes a hidden folder called .venv. VS Code will usually pop up a message asking if you want to use it, click Yes. If it doesn’t, press Ctrl+Shift+P, type Python: Select Interpreter, and pick the one that has .venv in its path.
On Ubuntu this is python3 -m venv .venv.
Docs: venv - Creation of virtual environments
4. Install pygame-ce
pygame-ce is a library, which is code somebody else wrote and shared so we don’t have to start from nothing. It gives us a window, drawing shapes, the keyboard and the mouse. Even though we install pygame-ce, in our code we still write import pygame.
In VS Code create a new file in the bobgame folder called requirements.txt with this single line in it
1 | pygame-ce |
A requirements.txt is just a shopping list of libraries the project needs. Install everything on the list
1 | .venv\Scripts\pip install -r requirements.txt |
On Ubuntu use .venv/bin/pip install -r requirements.txt.
You’ll see a lot of text scroll by. When it stops without a red error you’re done.
Docs: pygame-ce documentation
5. Plan the files
A game gets messy fast if all the code is in one big file. We’ll use four small files, each with one job. Create these now (empty is fine). The bobgame folder inside the bobgame folder is on purpose, it’s called a package.
1 | bobgame/ |
In VS Code right-click in the left panel and choose New Folder / New File.
6. Write settings.py
Open bobgame/settings.py and type this in
1 | WIDTH = 800 |
Each line is a variable: a name with a value stored in it, like a labelled box. We write the names in CAPITAL LETTERS because they are settings that never change while the game runs.
WIDTHandHEIGHTare the size of the window in pixels (the tiny dots your screen is made of).FPSis frames per second. A game is like a flipbook, it draws a new picture many times a second. 60 looks smooth.- Colours are three numbers: how much red, green and blue (0 to 255).
(0, 0, 0)is black,(255, 255, 255)is white, and(30, 32, 38)is a dark grey-blue. BOB_SPEEDis how many pixels Bob moves each second. The#starts a comment, a note for humans that Python ignores.
Try changing a colour later and see what happens. Pick colours with any “RGB colour picker” on the web.
7. Write game.py and open a window
Now the most important file. Open bobgame/game.py
1 | import pygame |
That’s a lot, so here it is piece by piece.
import pygamebrings in the library. The linefrom .settings import ...brings in our own numbers fromsettings.py. The dot means “from the same folder”.class Gameis a class, a blueprint. Imagine a blueprint for a house: it isn’t a house, but you can build a house from it. Our blueprint describes a game.__init__runs once, the moment the game is built. It starts pygame, makes the window withset_mode, gives it a title, and creates aClock, which keeps time so the game runs at the same speed on every computer.selfjust means “this game”.self.running = Trueis a switch that says the game is on.runis the game loop, the heart of every game.while self.running:means “keep repeating this for as long as running is True”. Each lap of the loop is one frame, which is one picture of the flipbook. When the switch becomesFalse, the loop ends andpygame.quit()closes everything.self.clock.tick(FPS)waits so we don’t go faster than 60 laps a second, and tells us how many milliseconds that lap took. Dividing by 1000 turns it into seconds. We call that number dt (short for “delta time”, or “the time since the last frame”). We’ll use it soon.stepis what happens on each lap. First it checks the events, things that happened like a key press or clicking the window’s close button. If you close the window (QUIT) or pressEscape, we flip the switch toFalse. Then it paints the whole screen with the background colour, anddisplay.flip()shows the finished picture.
The -> None and : float bits are called type hints. They are notes saying “this gives back nothing” and “this is a decimal number”. Python doesn’t need them, but they help you and VS Code understand the code.
8. Write main.py and run it
Open main.py, in the top folder (not inside the inner bobgame folder), and type
1 | from bobgame.game import Game |
This builds a Game from the blueprint and calls run() on it. The if __name__ == "__main__": line just means “only do this when I run this file directly”.
Run it in the VS Code terminal
1 | .venv\Scripts\python main.py |
On Ubuntu use .venv/bin/python main.py. You can also click the play button in the top right of VS Code while main.py is open.
A dark 800 by 600 window should appear. Nothing is in it yet, but you’ve made the window and the game loop. Press Escape to close it.
If you get ModuleNotFoundError: No module named 'pygame' then you probably didn’t run it with the .venv python. Use the full command above.
9. Write player.py and draw Bob
Time to make Bob. Open bobgame/player.py
1 | import pygame |
Bob is another class, another blueprint.
self.posis Bob’s position. AVector2is a pair of numbers,x(how far across) andy(how far down). In games,(0, 0)is the top-left corner andygets bigger as you go down, which is the opposite of the maths you do at school. We start Bob in the middle of the window,WIDTH / 2across andHEIGHT / 2down.self.facingis the direction Bob looks. It is also aVector2, but this time think of it as an arrow with a length of 1 that points somewhere.(0, -1)means 0 across and 1 up (remember, up is negative), so he starts looking at the top of the screen.drawpaints Bob onto asurface(the canvas, in our case the window).pygame.draw.circletakes a canvas, a colour, a centre and a size.- First we draw a skin coloured circle. Then we draw a hair coloured circle with a
4on the end, which means “only draw the outline, 4 pixels thick”. That gives a ring of hair. Lastly we draw a smaller hair circle slightly towards Bob’s back. We find that spot by going from his centre in the opposite direction (the minus sign) of where he faces. From above, it looks like he has hair on the back of his head. round(...)turns a decimal like400.4into a whole number, because pixels can’t be cut in half.
Now we need the game to make a Bob and draw him. Change game.py in three places
1 | from .player import Bob |
add that near the top, below import pygame. Then in __init__ add one line
1 | self.bob = Bob() |
below self.clock = .... And in step draw him after the background is painted
1 | self.screen.fill(BG_COLOR) |
The order matters. If we drew Bob first and then painted the background, we’d cover him up!
Run it again. You should see a round head with hair in the middle of the window. He’s not moving yet, so let’s fix that.
10. Move Bob with W A S D
Add this update method to the Bob class in player.py, between __init__ and draw
1 | def update(self, dt: float, keys, mouse_pos) -> None: |
keys is a list of every key on the keyboard, and each one is True if it’s being held down or False if not. Python treats True as 1 and False as 0, so we can do maths with them.
- Across:
DminusA. If you holdDthat is1 - 0 = 1(right). HoldAand it’s0 - 1 = -1(left). Hold both and they cancel out to0. - Down:
SminusW, the same trick.Wis-1because up is negative. - So
directionends up as a little arrow telling us which way you want to go. HoldDandStogether and it’s(1, 1), down and to the right. - There’s a catch.
(1, 1)is a longer arrow than(1, 0), so going diagonally would be faster than going straight. That’s a sneaky bug lots of games have.normalize_ip()fixes it by squashing any arrow to a length of exactly 1, while keeping the direction. We only do thatifthe arrow isn’t zero length, because you can’t squash nothing. self.pos += direction * BOB_SPEED * dtis the actual move. Direction (which way) times speed (250 pixels per second) timesdt(how many seconds this frame took). If a frame takes 1/60 of a second, Bob moves about 4 pixels. Usingdtmeans he moves at the same speed on a fast computer or a slow one.- The last two lines are a fence.
minpicks the smaller of two numbers andmaxpicks the bigger, so together they trapxandybetween the edges of the window. We leave a gap ofBOB_RADIUSso the whole head stays in view.
The mouse_pos isn’t used yet, we’ll use it in the next step. In game.py we need to call update on every frame. Add this to step, right before the line that paints the background
1 | self.bob.update(dt, pygame.key.get_pressed(), pygame.mouse.get_pos()) |
pygame.key.get_pressed() hands over the list of keys and pygame.mouse.get_pos() gives the mouse position. Run the game and use W A S D. Bob should glide around and stop at the edges.
11. Make the arrow follow the mouse
Add the rest of the update method, at the bottom of it, so Bob works out where to look
1 | to_mouse = pygame.Vector2(mouse_pos) - self.pos |
Subtracting Bob’s position from the mouse position gives an arrow that goes from Bob to the mouse. We squash it to length 1 with normalize() and store it as facing. The if stops us trying to squash an arrow of length 0, which happens if the mouse is exactly on Bob’s centre.
Now draw the arrow. Add this to the bottom of the draw method
1 | # Arrow: line from the head edge plus a triangle tip. |
It’s built from three points along the line Bob is facing:
startis a bit inside Bob’s head.tipis the sharp point, 18 pixels past the edge of his head.baseis 10 pixels back from the tip, where the triangle begins.sideis a small arrow pointing sideways to the facing direction. Swapping the two numbers and flipping one sign turns an arrow by a quarter turn, which is a neat trick. We use it to find the two corners of the triangle either side ofbase.draw.linedraws the shaft (3 pixels thick) anddraw.polygonfills in the triangle from the three corners: the tip, and the two sides.
Run it and wiggle the mouse around. The arrow should spin to follow it while you walk around with the keyboard.
12. Check it against the finished code
Here is the finished game.py so you can check yours
1 | import pygame |
Every frame the game loop does the same three things: handle input, update the world, then draw it. Almost every game ever made is built on that idea, from this one to the biggest ones you play.
If something isn’t working, compare your files with the ones in the bobgame repo. Common culprits are:
- Indentation. Python cares about the spaces at the start of a line. Everything inside a class or a
defis indented by 4 spaces. - Typos in names.
self.posandself.Posare different things. - A missing colon at the end of a line starting with
def,class,iforwhile. - Wrong folder.
main.pymust be in the topbobgamefolder, with the innerbobgamefolder beside it.
The error message in the terminal tells you the file and the line number. Read it, it’s usually trying to help.
Docs: pygame.draw • pygame.math.Vector2 • pygame.time.Clock
Challenges
Now it’s yours. Change one thing at a time, run it, and see what happens.
- Change
BG_COLORto a different colour. Can you make the background white? - Make Bob twice as fast by changing
BOB_SPEED. Then make him very slow. - Make Bob bigger or smaller with
BOB_RADIUS. What happens to the arrow? - Change
WIDTHandHEIGHTto make a tall, thin window. - Give Bob blonde hair, then green skin.
- Make the arrow longer by changing the
18intip. - Make the game use the arrow keys instead of
WASD. Hint: look forpygame.K_UP. - Change the
FPSto 10. Does Bob still move at the same speed? Why?
Have fun with it, and send me what you build.