The following sequence of assignments reflects the J-term course given at Gustavus Adolphus College
in 2013. This content was originally presented with a mixture of
lecture and lab; most of this had verbal explanation. But it's posted
here to support future offerings of the course and for the curious out
there who may want to work through this on their own. It's a great way
to gain useful coding experience and learn the Python language.
First, some thanks directed at sites that sparked this one:
This is a progression starting from very basic Pygame ideas, through 1D and 2D (pure Python) physics engines, ending with an application of the Box2D physics engine.
There is a physics instructor behind this, so of course there will be references to things like air tracks and air tables.
Time-based physics calculations are separated from any rendering. The
engines calculate object position (meters in floating point) based on
time intervals in the game loop. Euler's integration method is used
(rate calculations). Both impulse and non-impulse collision
calculations are made. The hope here is that students do not need
formal training in physics (but probably do need some high school math).
is attention given to the issue of stickiness (object-penetration
correction). This uses an intuitive calculation to determine where the
objects would be after the collision had they not penetrated each other.
I wanted the incentive of a tournament behind the course, so there's multiplayer networking.
is all aimed at students of a January-term class. The bulk of this had
to be absorbed by the students in about 2.5 weeks (two lectures and a
lab each day). This leaves them about a week to do project work in a
Each section below has the following:
An assignment in PDF form that includes a problem
statement, reference material, algorithms, conceptual drawings, and
(sometimes obfuscated) code hints. The good stuff is in the PDFs.
Source code in a raw text file and also a colored HTML version.
A screen-capture video of the code running and rendering in a Pygame window.
Installation of the Working Environment: PDF / (No code)
that this download is a snapshot of all the installation files that
were used for the course. More recent versions of these files can be
found, but you can be certain this set will work. These files are
tested on Win7 and XP. The download link is in the PDF or here: zip file.
Python 2.7, with Pygame, pybox2d, pgu, and PodSixNet
Window's Command Window (cmd) and the Notepad++ Editor: PDF / (No code)
This is a small collection of useful Windows and Notepad++ tips for the beginner and/or those new to Windows.
first assignment illustrates the display screen and event-handling
features that are used from Pygame. Drawing, erasing, and screen
updating are viewed in the Pygame window. Keyboard and mouse events are
interpreted and used to control the drawing algorithms. A game loop
keeps repeating the process: erase, draw, update the screen. Holding
down the "e" key enables screen erasing every time through the game
loop. Holding down the "f" inhibits the flip operation that is use to
update the screen at the end of the game loop. The two mouse buttons
are used to change the color of the circle.
starts with the erase feature. With erasing enabled, the ball seems
animated (the drawing tail does not persist). Later the flip (screen
update) operation is inhibited (these are drawn in memory but not to
the screen). Finally a combination of erasing and flip inhibiting cause
the ball to lag behind and then catch up to the cursor.
1D framework introduces the relationship between the screen and the
physics world. It also brings in the first taste of Euler's method in
animating the motion. There are no car-wall or car-car collisions here;
no gravity. Cars just pass through each other (and walls) and no
acceleration from gravity. OOP classes are used here to organize the
code and prepare for the object nature of the games to follow.
video shows five short demos (press keyboard numbers 1 through 5). The
first two are two-car animations. The last three show stacks of cars
spreading out due to the differences in their velocities. The car in
the middle of the stack has zero velocity. At any point in time, the
relative velocity of any pair of cars is proportional to the separation
distance between them (kind of like Hubble's law).
method comes to life here. Velocities are changing under the
accelerating influence of gravity. There is a drawing in the PDF that
illustrates the idea of penetration/stickiness correction. (Note that
the drawings in the PDFs display with varying quality depending on the
browser; Chrome: great, IE: OK, Firefox: not so hot.) For now, we'll
just do the stickiness correction; later there will be several video
demos of this issue. A coefficient of restitution is used to model the
energy loss in the wall collisions. Notice the apparent settling of the
cars at the end of the video.
(Note: You can watch in
full-screen mode using the YouTube controls in the lower right corner
of each video. Esc to get back to this page.)
collision physics and car-car stickiness correction are added here.
This script uses the "c" and the "s" keys to toggle two algorithmic
features that help to illustrate the collisions and stickiness. The "c"
key toggles the "color-transfer" feature which, when enabled, causes
the colors of two colliding cars to swap. So when two cars start to
settle near a wall under the influence of gravity, they collide
frequently and the colors will swap quickly to show the cars are still
colliding. If this feature is off the cars will appear to settle, but
the cars really don't settle in our basic physic engine! (Later with
Box2D we will show true settling.) The "s" toggles the stickiness
correction on and off. So if you turn off the correction, the cars will
be pulled into each other as they settle. Hit the "s" key again and
they will unstick with a little pop. A combination of the "c" and "s"
key is used at the tail end of the video. Sorry, this one runs a little
long (kind of like watching paint dry).
user gets to interact with the objects on the screen. Cursor spring and
drag forces are calculated based on the separation distance between the
cursor and the selected car (and the velocity of the car). Cars are
selected by clicking on the car or holding down the mouse button and
letting the car run over the cursor. Cursors attach at the center of
the car. Each mouse button invokes a different cursor tether (with a
different spring constant and car drag coefficient). The left mouse
button is medium, the right mouse button is stiff, and the center
(roller) button has the softest spring. The video closes with me trying
to pull the car into the wall. The color transfer ("c") is turned on so
the frequent collisions are illustrated. Note you can again turn off
the stickiness correction here ("s") and pull the cars into the wall;
then toggle it off and watch them pop out (not so much "paint drying"
here since you can pull them in pretty quickly, especially with the
stiffer of the three cursor tethers).
here, not just sticky (ha ha). Controls have been added for stickiness
and color-transfer toggles as well as a gravity slider (for simulating
that bad cruise-liner experience) and a button to freeze the cars. If
gravity is set to zero, a freeze operation will stop the cars and they
will stay that way until...
mass is visualized here by hollowing out the lighter cars. The video
shows 10 of the 13 demos. The demo number is indicated in the window
title (upper part of the Pygame window frame). Demo #4 shows the
inelastic collisions between a set of cars where the total momentum of
the set is zero before and after the collision (a reverse explosion).
Most of the other demos make use of the color-transfer feature to
highlight the transfer of momentum through cars (like Newton's cradle).
A description of each demo is in the PDF.
video shows the server window and one client's game-pad window (also
running on the server's computer). Another client (running on a
networked laptop) is connected but not visible in the video. The state
(U:up or D:down) of the a-s-d-w keys of each client are also rendered
on the server screen. Please note that this client works only for this
The last 200 mouse locations (when mouse button is down) are drawn each game loop. This causes the dynamic tails effect.
Friday we ran this server on the computer that hosts the projector in
the lecture room. Many students connected with the client. For a while
we tried this with no verbal communication. It was interesting to see
how cooperative this became in spite of the silence.
video shows a vector sandbox that is based on the vector class. There
are seven demos that are run (start these using the number keys above
the keyboard). Each demo uses a set of vectors ranging in set size from
2 to 140. Vectors can be selected with the mouse (click and drag over
the arrow head). Components of selected vectors can be displayed ("c"
key toggles this on/off). Components include x, y, unit normal (red),
unit perpendicular (red), and the projection of the selected vector
onto a second vector. Vector rotation can be toggled on/off with the
"f" key. The "a" key toggles the display of the add vector (grand
total), which is shown in green. In "add" mode each vector in the sum
series is rendered head to tail. The "t" key toggles on/off the display
of a tail drawn from the head of the total vector. The tail is
represented with a 350 point FIFO list of the most recent points. The
tail can be shown as points or lines (toggle back and forth with the
"l" key). Zoom in/out with the "h" and "n" keys.
assignment merges the 1D engine with the 2D vector class and the
multiplayer module. The spring class is also introduced here. There are
drawings in the PDF that explain 2D collisions, stickiness corrections,
and the client-server relationship.
The video shows two
cursors, one from the local client and the other from a network client
on a laptop. Left and right hands are working the two cursors. It's
surprisingly natural (we've got two hands). One section of the video
shows the two cursors' tethers pulling two balls together and watching
the transition from stable to unstable as the cursors cross each other;
once unstable, the balls flip their positions to get back to stability.
Stickiness behavior is toggled on/off with the "p" key; the black
background turns grey when the stickiness corrections are off.
The client script that is used here works with all the following assignments (except the first two in the Box2D section).
raw tube and a jet tube are associated with a client-controlled puck.
The association causes the tubes to move with (are drawn relative to
the center of mass of) the pucks. A new three-point red polygon is used
to represent the jet's exhaust flame. The video shows the local client
and a network client controlling (rotating) tubes on the two pucks.
Python inheritance is introduced in this exercise: jet-tube is derived
from the tube base class.
thrust force is added to coincide with the jet's flame. This brings the
total to four forces that are being processed in the physics engine:
gravity, cursor-tether tension, spring-damper, and now jet force.
"w" key turns on the jet forces that are applied to the host puck along
the direction of the jet-tube axis. This jet-force vector is added to
the puck_forces_2d_N (net-force-on-puck) vector and processed in the
Euler's method calculations.
The video show a cursor
tether restraining a puck under continuously applied jet forces while
the jet tube is turned at a constant rate.
The physics of gun recoil
is modeled with an impulse force. Each bullet firing causes an opposing
impulse force to be delivered to the gun. The magnitude of this force
is equal to the change in momentum of the bullet divided by the
duration of the firing interval (one step in time by the physics
calculations in the game loop). Bullet clean-up is implemented by
assigning a birth time to each fired bullet. Bullet objects older than
3 seconds are deleted.
This assignment adds features to make a playable game. For example, a
hit counter (a hit by another player makes your puck flash red) is the
basis for establishing the health of each player. Health, or rather the
lack of it, is illustrated with an expanding red circle. Too many hits
and your puck pops (hence the name of the game: Puck Popper). Shields
prevent bullets from hitting your puck; when shields are up, you can't
fire. The "s" and "k" keys are used to flip the tube directions into an
orientation opposing the current motion of the puck; this can be useful
in breaking (stopping). The "f" and "g" keys can add an interesting
dimension to the game by the local user (user on the server computer);
jello simulation that's shown in some of the other videos has been
turned into a game here. You scramble the jello (this starts the timer)
for your opponent then press the "p" key to freeze the physics engine
and the corresponding timer on the screen. When your opponent is ready
to try and straighten out the jello, they press the "p" key again. That
resets and starts the timer and physics engine. When the jello is
straightened (no puck collisions), the timer will stop. A useful tool
to use in the straightening is repeated use of the "f" key. This
momentarily sets the velocity of each puck to zero. Low score (time)
wins. Players take turns.
The video shows me playing Puck
Popper solo with something heavy sitting on the "i" key of my laptop.
So the red player is pretty much a sitting duck in the corner. But you
get the idea of the shields, health circles, and the popping pucks.
I also do one round of Jello Madness and got a de-tangling time of
22.15 seconds (a big part of this is to really make a mess of it for
the other guy). Jello Madness requires an I5 processor or better; this
will crash (become unstable) on slower machines.
The perfect-kiss algorithm is a refinement to the overlap-correction calculations described in the 2D-Physics Engine Framework
assignment. This refinement offers true contact-normal calculations and
corresponding ideal modeling of 2D puck collisions. The video is
annotated to show three categories of puck collisions: (1) raw (no
overlap correction), (2) overlap correction using the
approximate-contact normal, and (3) overlap correction using the
idea-contact normal. The speed of the incoming puck is chosen to
produce large overlaps and its initial position is randomized to show a
variety of responses. The third category of runs shows a consistent
collision prediction that is independent of the amount of overlap at
the collision detection point. To illustrate the correction process two
intermediate steps are drawn with special colors: both overlapping
pucks are drawn in red; both kissing pucks are drawn in cyan; the final
corrected position is drawn in normal puck colors. There are cases
where some of the intermediate puck images are not visible because they
lie directly underneath a subsequent drawing.
script is based on the test_BodyTypes.py file in the examples directory
of the pybox2d distribution. This depends on the Pygame framework in
that distribution so all the framework files must be in the same
directory as this file. (This script works best if you run it in the
"...box2d_source_files\box2d_jdm" folder of the zip distribution
provided above. I fixed a few things in their framework files and those
fixes are only available in the box2d_jdm folder.)
file has been modified to support bullet shooting. (I looked through
their examples for one that had some hinged object that would work for
aiming the projectile stream.) The main adders here are: (1) bullet
aiming and firing, (2) masking of objects (bullets pass through the gun
base without a collision), (3) age limits on the bullets so they clean
up after themselves (the simulation would labor if the collection of
bullets kept growing), (4) the cursor gun, (5) something interesting to
shoot at (target generation): pyramids, circles, and squares.
script is based on the simple_01.py example in the pybox2d example
directory. Unlike the example above, this one does not depend on the
framework files, so it will run anywhere. I've extracted mouse-joint
and zooming features as well as some basic polygon rendering
facilitated by Pygame. This demonstrates the body-transform overload
operation on a vertex of a polygon (to get its physics-world
coordinates). There are also two force points defined on one of the
cars (keyboard controlled).
The video shows an air-track
type environment, except of course this is 2D and the cars are allowed
to behave very badly and can end up off the track. Cars can be dragged
with the mouse. Zoom the view with the mouse wheel; pan the view with
the right mouse button. Control the two force points with the "f" and
"g" keys. When a force is applied at a force point, the point lights up
green. (View in full-screen mode at 480p to see the force points.)
here is an integration of the box2d engine into our own 2D framework.
This is like taking the engine out of our Honda Civic and replacing it
with a 426 Hemi. This opens the door to non-spherical object
collisions, object rotation, torque (and rotational drag), surface
friction, and true settling behaviors.
The basic idea
here is that we define an interface between our air-table framework and
the pybox2d engine using force points. So we keep all the force
generating objects such as springs and cursor-tethers and communicate
their forces to the Box2D world. Then we let Box2D take care of
collisions and object motion and let it inform our rendering
functionality as to the state/position of the objects.
few notes on the interface: gun and jet controls are the same as
before. The "t" key will torque a selected object. Shift-t will torque
it the other way. The "f" key still freezes the translational motion,
but now there is also the "r" key which will stop the rotation of all
the objects in the world. Cursor tethers attach, by default, to the
center of mass of the object, but with the shift key down, you can
attach cursor tether anywhere on the object. The "h" and "n" keys zoom
the view in and out; if the control key is down, mouse movement acts to
pan the view. Various demos are initiated as usual with the keyboard
number keys (i.e. above the letters). In this video I have one network
client, a laptop, connected (red cursor); the regular cursor is
controlled by the server.