Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Virtual Tennis Tutorial #461

Draft
wants to merge 13 commits into
base: canon
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
'sphinx.ext.todo',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
'sphinx_tabs.tabs',
]

# Add any paths that contain templates here, relative to this directory.
Expand Down Expand Up @@ -186,6 +187,7 @@

intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'miniconda': ('https://docs.conda.io/en/latest/', None),
}

# -- Options for todo extension ----------------------------------------------
Expand Down
3 changes: 1 addition & 2 deletions docs/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ the `Python.org tutorial <https://docs.python.org/3/tutorial/index.html>`_ or

Additionally, you need to have Python 3.7 or later on your machine. You can
install this via `Python.org <https://www.python.org/downloads/>`_ or
`Anaconda <https://www.anaconda.com/python-3-7-package-build-out-miniconda-release/>`_
whichever is more comfortable for you.
:doc:`Anaconda <miniconda:miniconda>` whichever is more comfortable for you.


Installing ppb
Expand Down
4 changes: 4 additions & 0 deletions docs/tutorials/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ Tutorials live here, except for the basic Quick Start tutorial.

A tutorial is an complete project that takes you from an empty file to a
working game.


.. toctree::
virtual-tennis/index
258 changes: 258 additions & 0 deletions docs/tutorials/virtual-tennis/ball.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
============================================
A Ball That Bounces
============================================

Our next major step is to create a ball. The ball should move a little each
frame, bounce off the edges of the screen, and look like a ball. In order to do
that we'll need to create a :class:`~ppb.Sprite``, then put a copy of that
sprite into the game :class:`~ppb.Scene`.

Right now, we don't need to define our own scene, ppb provides one for us when
we call :func:`~ppb.run`. The run function does accept a function argument that
lets us initialize this first scene.

.. code-block::
:caption: main.py
:lineno-start: 3
:emphasize-lines: 4-7

RESOLUTION = (1200, 900)


def setup(scene):
scene.background_color = (0, 0, 0)


ppb.run(setup, resolution=RESOLUTION, title="Hello Window!")

In this step, we will set the background color of our scene object to pure
black. This better matches our example game, and demonstrates writing a
function.

Additionally, we've added the setup function as an argument to the call of the
ppb.run function. With that in place, let's explore :class:`~ppb.Sprite`.

Let's start by using the default sprite class directly:

.. code-block::
:caption: main.py
:lineno-start: 6
:emphasize-lines: 3

def setup(scene):
scene.background_color = (0, 0, 0)
scene.add(ppb.Sprite(image=ppb.Circle(255, 255, 255), size=1))


ppb.run(setup, resolution=RESOLUTION, title="Hellow Window!")

The :class:`~ppb.Sprite` does a lot of heavy lifting for us. Once added to a
running scene, it'll display its image at its location on the screen. If you run
your main script now, you should see a black background with a white circle in
the center. The size parameter allows us to adjust the size of the object on
screen. If you want the ball bigger (or smaller!) you can adjust this parameter.

.. todo:: Explain the __init__

So we now have a ball that looks like a ball, but it doesn't move. So we're
going to need to edit this code. First, let's define our first class.

.. code-block::
:caption: main.py
:lineno-start: 3
:emphasize-lines: 4-6, 11

RESOLUTION = (800, 600)


class Ball(ppb.Sprite):
image = ppb.Circle(255, 255, 255)
size = 1


def setup(scene):
scene.background_color = (0, 0, 0)
scene.add(Ball())

Run your program again and you'll note that nothing should have changed from
our last run. This version is functionally identical to our last step. Let's
talk about what's going on in this block now:

The `class` keyword tells Python we're creating a class, a type of blue print
for things we put in our game. The `Ball` is a name we give our class, you can
name yours something different, but letting yourself know the class is for the
tennis ball is good practice. Inside the parentheses we're telling python that
this class is based on the :class:`ppb.Sprite` class. This lets us share some
code and not write it ourselves.

Below that definition, you'll notice the indents, and we assigned some
variables. These variables are special and called class attributes. We can use
these as defaults for every object we make using this class. Combined with the
initialization code from ppb, it gives us a very powerful way to customize
objects.

The next step is to get our ball moving. To do this, we're going to use vectors
and integration. (Don't worry, you won't need to know how these work, ppb
handles much of the work for us.)

We use vectors in ppb for the position of our :class:`~ppb.Sprite <Sprites>` and
in this case will also use it for a velocity vector. To do so, we'll set a
default velocity of `ppb.Vector(0, 0)` (this would be a ball that isn't moving,
like the one we already have) and then we'll use the velocity vector to move the
position of the ball each time we step through the simulation.

.. code-block::
:caption: main.py
:lineno-start: 6
:emphasize-lines: 4

class Ball(ppb.Sprite):
image = ppb.Circle(255, 255, 255)
size = 1
velocity = ppb.Vector(0, 0)


def setup(scene):

This is a small change, and just like last time, doesn't change the behavior.
To change that, we're going to want to respond to events.

.. note::
So vectors can be complicated if you've never used them before, but ppb
does a lot so you can ignore the specifics of the math. Just know that the
first number in the vector (called the x component) represents a change from
left-to-right or right-to-left. The second number (the y component)
represents up and down. Additionally, you can perform some useful
mathematical operations on them.

In ppb, events are what drive all the action. The most important event is the
:class:`~ppb.events.Update` event, which happens about sixty times per second
by default. To respond to any event, you need to write a method (a special kind
of function attached to a class) that looks like this (don't write this in
your file!):

.. code-block::

def on_update(self, event, signal):
self.do_the_thing()

All event handlers use this pattern. The name of these methods is important:
`ppb` always looks for a method named 'on' followed by an underscore and the
name of the event in snake case. In the case of `Update` this looks like
`on_update`, but for a longer name, like the `PreRender` event it would look
like `on_pre_render`.

So to get our ball moving, we'll write one of these handlers in our `Ball`
class.

.. code-block::
:caption: main.py
:lineno-start: 6
:emphasize-lines: 6, 7

class Ball(ppb.Sprite):
image = ppb.Circle(255, 255, 255)
size = 1
velocity = ppb.Vector(0, 0)

def on_update(self, event, signal):
self.position += self.velocity * event.time_delta


def setup(scene):

This function is called each time an update event happens, and the ball adds its
velocity (often a measurement of change in position per second) and multiplies
it by the update event's time_delta attribute so that we only apply as much
velocity as is relevant in that time period. If we just added velocity to
position our ball would move almost 60 times faster than intended.

If you run it again, you'll see we still haven't changed what the program does.

Let's finally make it happen:

.. code-block::
:caption: main.py
:lineno-start: 15
:emphasize-lines: 3

def setup(scene):
scene.background_color = (0, 0, 0)
scene.add(Ball(velocity=ppb.directions.Left))

And now our ball is moving on screen! This is very slow for now, that's
intentional, but this does demonstrate the ppb.directions module, which has a
bunch of length 1 vectors for you to use.

If you watch for a bit, the ball will wander right off the left hand side of
the screen. This is pong, though, so we're going to want to bounce off the
walls and ceiling. To do that, we're going to add a check to make sure the ball
is still inside the camera.

.. admonition:: Camera?

I know, the camera is a new concept, but all you need to know is the camera
helps ppb figure out what in your scene needs to get drawn to the screen
and it has sides we can use to measure where the ball is.

Before we move our ball each frame, we're going to check if any side of the
square around our ball (this is called an axis aligned bounding box and ppb
gives us this for free) is beyond the same wall of the camera's view. So
top-to-top, left-to-left and so on.

.. code-block::
:caption: main.py
:lineno-start: 11
:emphasize-lines: 2-18

def on_update(self, event, signal):
camera = event.scene.main_camera
reflect = ppb.Vector(0, 0)

if self.left < camera.left:
reflect += ppb.directions.Right

if self.right > camera.right:
reflect += ppb.directions.Left

if self.top > camera.top:
reflect += ppb.directions.Down

if self.bottom < camera.bottom:
reflect += ppb.directions.Up

if reflect:
self.velocity = self.velocity.reflect(reflect.normalize())
self.position += self.velocity * event.time_delta

This was a lot of code in one shot, but don't worry, I'll explain.
First, we get the scene's camera. Inside of an event handler, you can
always access the current scene with `event.scene`. All scenes get a
camera, which you can get with `scene.main_camera`. Here, we store that camera
in a variable called camera. This makes it easier to type the rest of this
routine.

Next, we set up a reflect vector. What we're trying to create is something
called a surface normal. You can think of it as an arrow pointing straight
away from an object. For example a tabletop has a surface normal that points
straight up.

In our case, there are four surfaces we care about: the four walls of the
camera. (They're not actually walls, and you saw previously!) When the
camera reaches any edge, measured when the side of the ball goes past the value
for that wall, the surface normal is pointing the opposite way.

We add all of the relevant normals together, and then check if they're greater
than 0. If they are, we set our velocity to our velocity vector reflected
across our reflect vector normalized.

.. note::
A vector with no length (specifically ``ppb.Vector(0, 0)``) is called the
zero vector. We can use this property to our advantage and only do a
reflection when we have a vector to work with.

With this in place, you have a bouncing ball, the first major component of our
virtual tennis game! Before moving on, go ahead and try changing the initial
velocity vector (inside the setup function) by using different directions and
multiplying it by different values. You can also experiment with changing the
size of the ball and changing the colors.
78 changes: 78 additions & 0 deletions docs/tutorials/virtual-tennis/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
===============================
Virtual Tennis
===============================

.. toctree::
:hidden:
:maxdepth: 0
:titlesonly:

setup
window
ball

In this tutorial, we're going to build a game virtual tennis game in the vein
of Pong_. We're going to go through the whole process: planning our approach,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
of Pong_. We're going to go through the whole process: planning our approach,
of `Pong <https://en.wikipedia.org/wiki/Pong>`. We're going to go through the whole process: planning our approach,

breaking out individual tasks, setting up our environment, and coding.

Before you start writing code on a project, it's best to think about the game
we're trying to make. For research, go try `this implementation of
Pong <https://archive.org/details/PONGV2.11996DJTAction>`_ on the internet
archive.

Let's think about what's going on in this version:

#. It opens on a menu with sample play happening in the background.
#. The menu explains all the controls.
#. When you start a one player game, you receive control of one of the paddles.
#. The ball launches, and you need to move up and down to deflect the ball.
#. The ball bounces off the top and bottom walls.
#. If either of you miss the ball, the other player's score goes up.
#. If a player reaches 15 points, the game ends with fanfare and the word
"Winner!" printed repeatedly on that side of the screen.
#. Then it goes back to the menu.

Now that we've looked at an example of our project, let's identify the
important parts:

* The core game play is a ball and two paddles, each controllable by a player.
* The ball needs to be able to bounce off the top and bottom wall and either
paddle.
* The paddles need to be able to move.
* We need to be able to track the score.
* We need to be able to end the game.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you also need an AI paddle?


This tutorial will break each of these requirements into smaller pieces so we
can test features as we go.

Before we get started, you'll need a few things done first:

#. Install python 3.8 on your system. We suggest installing from python.org for
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#. Install python 3.8 on your system. We suggest installing from python.org for
#. Install Python 3.8 on your system. We suggest installing from `python.org <https://www.python.org/downloads/>` for

best results. For more information, see the
`Django Girls tutorial <https://tutorial.djangogirls.org/en/installation/#python>`_.
for more specific instructions on installation.
#. Install a code editor. We suggest PyCharm, Sublime Text, GEdit or VSCode.
Extra information and suggestions can be found at
`Django Girls <https://tutorial.djangogirls.org/en/installation/#code-editor>`_.
#. You'll also need to know the basics of Python. Again, Django Girls has a
great `tutorial <https://tutorial.djangogirls.org/en/python_introduction/>`_
for this.

Once you have those things done, you can move on to our first step: setting up
our project.


( The following will be deleted before final publication.)

2. A ball that bounces on the edges of the screen.
3. A player paddle that can be moved.
4. Collision between player paddle and ball.
5. A score board tracking how many times the player hits the far side of the
screen.
6. Removing the ball from play when it hits the far wall.
7. Launching the ball with a key press.
8. Removing the ball if it hits the player's wall.
9. Adding a second player paddle.
10. Adding a new score board for second player.
11. End the game.
12. Ideas for making the game your own.
Loading