-
-
Notifications
You must be signed in to change notification settings - Fork 98
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
pathunstrom
wants to merge
13
commits into
ppb:canon
Choose a base branch
from
pathunstrom:tutorial-pong
base: canon
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
ea43463
Adds an outline for new tutorial.
pathunstrom 14951ab
Adds preamble and setup instruction to virtual-tennis tutorial.
pathunstrom 111fa55
Adds instructions for opening and manipulating the window.
pathunstrom f0476b1
Merge branch 'canon' into tutorial-pong
pathunstrom 97594fc
In progress draft of section 2: Making a ball.
pathunstrom c2f70be
Merge canon
pathunstrom 9987220
Fix bad link to Anaconda blog with intersphinx.
pathunstrom 397d476
Add Ubuntu instructions to setup.
pathunstrom 92458f6
Minor touchups to window section of virtual tennis
pathunstrom fa54f22
Add example for part 1 of virtual tennis tutorial
pathunstrom 63282b7
Finish the bouncing ball page.
pathunstrom a31b662
Fix highlighting in example 1 of ball
pathunstrom 8fd6133
Update docs/tutorials/virtual-tennis/index.rst
pathunstrom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.