kivy/doc/sources/tutorials/pong.rst

450 lines
16 KiB
ReStructuredText
Raw Normal View History

2012-01-11 03:53:01 +00:00
.. _pong:
.. highlight:: python
:linenothreshold: 3
Pong Game Tutorial
==================
2012-01-11 03:53:01 +00:00
Introduction
------------
2012-01-12 00:47:16 +00:00
.. container:: title
2012-01-11 03:53:01 +00:00
Welcome to the Pong tutorial
2012-01-12 00:47:16 +00:00
This tutorial will teach you how to write pong using Kivy. We'll start with
2012-01-11 03:53:01 +00:00
a basic application like the one described in the :ref:`quickstart` and make
it into a playable pong game describing each step along the way.
.. image:: pong.jpg
:align: center
:height: 392px
2012-03-23 08:57:08 +00:00
Here is a check list for things you should know before starting this tutorial:
2012-01-11 03:53:01 +00:00
- You have a working Kivy installation. See the :doc:`/installation/installation`
2012-01-12 00:47:16 +00:00
section for detailed descriptions
- You know how to run a basic Kivy application. See :doc:`/guide/quickstart`
2012-01-12 00:47:16 +00:00
if you don't.
2012-01-11 03:53:01 +00:00
If you have read the programming guide, and understand both basic Widget
concepts (:doc:`/guide/firstwidget`) and basic concepts of the kv language
(:doc:`/guide/kvlang`, :doc:`/guide/designwithkv`), you can probably skip the first 2
steps and go straight to step 3.
.. note::
You can find the entire source code, and source code files for each step in
the Kivy examples directory under `tutorials/pong/`
2012-01-12 00:47:16 +00:00
Ready? Sweet, let's get started!
2012-01-11 03:53:01 +00:00
Getting Started
---------------
2012-01-12 00:47:16 +00:00
.. container:: title
Getting Started
2012-01-12 00:47:16 +00:00
Let's start by getting a really simple Kivy app up and running. Create a
2012-01-11 03:53:01 +00:00
directory for the game and a file named *main.py*
.. include:: ../../../examples/tutorials/pong/steps/step1/main.py
:literal:
2012-01-12 00:47:16 +00:00
Go ahead and run the application. It should just show a black window at this
point. What we've done is create a very simple Application, which creates an
instance of our ``PongGame`` Widget class and returns it as the root element
for the applications UI. In the next step, we will draw the Pong background
and scores by defining how the ``PongGame widget`` looks.
2012-01-11 03:53:01 +00:00
Add simple graphics
-------------------
.. container:: title
2012-01-11 03:53:01 +00:00
Creation of pong.kv
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
We will use a .kv file to define the look and feel of the ``PongGame`` class.
2012-05-28 13:58:04 +00:00
Since our :class:`~kivy.app.App` class is called ``PongApp``, we can simply create a file
2012-01-12 00:47:16 +00:00
called ``pong.kv`` in the same directory that will be automatically loaded
when the application is run. So create a new file called ``*pong.kv*`` and add
the following contents.
2012-01-11 03:53:01 +00:00
2012-08-09 11:50:05 +00:00
.. literalinclude:: ../../../examples/tutorials/pong/steps/step2/pong.kv
:language: kv
:linenos:
2012-01-12 00:47:16 +00:00
If you run the app now, you should see a vertical bar in the middle, and two
zeros where the player scores will be displayed.
.. note::
Try to resize the application window and notice what happens. That's
right, the entire UI resizes automatically. The standard behaviour of the
Window is to resize the root element based on the elements `size_hint`. The
2012-03-23 08:57:08 +00:00
default Widget size_hint is (1,1), so it will be stretched to full size.
2012-01-12 00:47:16 +00:00
Since the pos and size of the Rectangle and score labels were defined with
references to the our PongGame class, these properties will automatically
update when the corresponding widget properties change. Using the Kv
2012-01-12 00:47:16 +00:00
language gives you automatic property binding. :)
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
Explaning Kv file syntax
~~~~~~~~~~~~~~~~~~~~~~~~
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
Before going on to the next step, you might want to take a closer look at
the contents of the kv file we just created and figure out what is going on.
2012-03-23 08:57:08 +00:00
If you understand what's happening, you can probably skip ahead to the next
2012-01-12 00:47:16 +00:00
Step.
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
On the very first line we have::
#:kivy 1.0.9
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
This first line is required in every kv file. It should start with ``#:kivy``
followed by a space and the Kivy version it is intended for (So Kivy can make
sure, you have at least the required version, or handle backwards compatibility
later on)
After that, we define one rule that is applied to any PongGame instance::
<PongGame>:
...
2012-01-11 03:53:01 +00:00
2012-05-28 14:12:12 +00:00
Like python, kv files use indentation to define nested blocks. A block defined
2012-01-12 00:47:16 +00:00
with a class name inside the ``<`` and ``>`` charachters is a
:class:`~kivy.uix.widget.Widget` rule, it will be applied to any instance of
the named class. If you replaced ``PongGame`` with Widget in our example, all
Widget instances would have the vertical line and the two Label widgets inside
them for instance.
2012-03-23 09:03:37 +00:00
Inside a Rule section, you can add various blocks to define the style and
2012-01-12 00:47:16 +00:00
contents of the widgets it will be applied to. You can set property values,
child widgets that will be automatically added, or a `canvas` section in
which you can add Graphics instructions that define how the widget itself is
rendered.
2012-08-09 11:50:05 +00:00
The first block inside the ``<PongGame>`` rule we have is a canvas block:
.. code-block:: kv
2012-01-12 00:47:16 +00:00
<PongGame>:
canvas:
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
So this canvas block says that the ``PongGame`` widget itself should draw some
graphics primitives. In this case, we add a Rectangle to the canvas. We set
the pos of the rectangle to be 5 pixels left of the horizontal center of
2012-01-12 00:47:16 +00:00
the widget itself, and 0 for y. The size of the rectangle is set to 10 pixels
in width, and the widgets height in height. The nice thing about defining the
graphics like this, is that the rendered rectangle will be automatically
updated when the properties of any widgets used in the value expression change.
2012-03-23 08:57:08 +00:00
The last two section we add, look pretty similar. Each of them adds a Label
2012-01-12 00:47:16 +00:00
widget as a childwidget to the ``PongGame`` widget itself. For now the text on
both of them is just set to *"0"*, we'll have to hook that up to the actual
score once we have the logic for that implemented. But the labels already
look good, since we set a bigger font_size, and positioned them relatively
to the root widget. The ``root`` keyword can be used inside child block to
refer back to the parent/root widget the rule applies to (``PongGame`` in this
2012-08-09 11:50:05 +00:00
case):
.. code-block:: kv
2012-01-12 00:47:16 +00:00
<PongGame>:
2012-08-09 11:50:05 +00:00
# ...
2012-01-12 00:47:16 +00:00
Label:
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: "0"
Label:
font_size: 70
center_x: root.width * 3 / 4
top: root.top - 50
text: "0"
2012-01-11 03:53:01 +00:00
Add the ball
------------
.. container:: title
Add the ball
2012-01-12 00:47:16 +00:00
Ok, so we have a basic pong arena to play in, but we still need the players and
a ball to pong around. Let's start with the ball. We'll add a new `PongBall`
2012-01-11 03:53:01 +00:00
class to create a widget that will be our ball and make it bounce around.
.. note::
2012-01-12 00:47:16 +00:00
2012-01-11 03:53:01 +00:00
We'll just look at the python class and kv rule for PongBall first.
2012-03-23 08:57:08 +00:00
To make it all usable, and add the ball to the arena, you'll also need to add
2012-01-11 03:53:01 +00:00
the proper imports and register the `PongBall` class with the widget factory
2012-03-23 08:57:08 +00:00
so you can add it as a childwidget in the `<PongGame>` rule. However, don't
2012-01-11 03:53:01 +00:00
worry, the entire code is listed at the end of this step.
2012-01-12 00:47:16 +00:00
PongBall class
~~~~~~~~~~~~~~
Here is the python code for the PongBall class::
class PongBall(Widget):
# velocity of the ball on x and y axis
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
# referencelist property so we can use ball.velocity as
# a shorthand..just like e.g. w.pos for w.x and w.y
velocity = ReferenceListProperty(velocity_x, velocity_y)
2012-03-23 08:57:08 +00:00
# ``move`` function will move the ball one step. This
# will be called in equal intervals to animate the ball
2012-01-12 00:47:16 +00:00
def move(self):
self.pos = Vector(*self.velocity) + self.pos
2012-01-11 03:53:01 +00:00
2012-08-09 11:50:05 +00:00
And here is the kv rule used to draw the ball as a white circle:
.. code-block:: kv
2012-01-12 00:47:16 +00:00
<PongBall>:
2012-01-11 03:53:01 +00:00
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
2012-01-12 00:47:16 +00:00
To make it all work, you also have to add the imports for the
:doc:`/api-kivy.properties` Property classes used, the
:class:`~kivy.vector.Vector`, and the :class:`~kivy.factory.Factory` singleton.
The factory is used to register your custom classes, so that Kivy knows what
class to instantiate when you use e.g. a custom classname inside a kv rule.
2012-01-12 00:47:16 +00:00
Once that's done, you can add a ``PongBall`` to the ``<PongGame>`` class, just
like we added the Labels before.
2012-01-11 03:53:01 +00:00
Here is the entire updated python code and kv file for this step:
2012-01-12 00:47:16 +00:00
2012-01-11 03:53:01 +00:00
main.py:
2012-01-12 00:47:16 +00:00
.. include:: ../../../examples/tutorials/pong/steps/step3/main.py
:literal:
2012-01-11 03:53:01 +00:00
pong.kv:
2012-08-09 11:50:05 +00:00
.. literalinclude:: ../../../examples/tutorials/pong/steps/step3/pong.kv
:language: kv
:linenos:
2012-01-11 03:53:01 +00:00
Adding ball animation
---------------------
.. container:: title
Making the ball move
2012-01-11 03:53:01 +00:00
2012-03-23 08:57:08 +00:00
Cool, so now we have a ball, and it even has a ``move`` function... but it's not
2012-01-12 00:47:16 +00:00
moving yet. Let's fix that.
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
Scheduling functions on the Clock
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2012-01-11 03:53:01 +00:00
2012-03-23 08:57:08 +00:00
We need the ``move`` method of our ball to be called regularly. Luckily Kivy
makes this pretty easy, by letting us schedule any function we want on the
:class:`~kivy.clock.Clock` and specify the interval::
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
Clock.schedule_interval(game.update, 1.0/60.0)
2012-01-11 03:53:01 +00:00
2012-03-23 08:57:08 +00:00
That line for example, would cause the ``update`` function of the game object to
2012-01-12 00:47:16 +00:00
be called once every 60th of a second (60 times per second).
Object Properties/References
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We have another problem though. We'd like to make sure the PongBall has its
2012-03-23 08:57:08 +00:00
``move`` function called regularly, but in our code we don't have any references
2012-01-12 00:47:16 +00:00
to the ball object, since we just added it as a child widget of our ``PongGame``
class inside the kv rule for the ``PongGame`` class. The only reference to our
game is the one we return in the Applications build method.
Since we're going to have to do more than just move the ball (e.g.
bounce it off the walls and later the players racket), we'll probably need
an ``update`` method for our ``PongGame`` class anyways. Furthermore given that
2012-03-23 08:57:08 +00:00
we have a reference to the game object already, we can easily schedule its new
``update`` method when the application gets build::
2012-01-12 00:47:16 +00:00
class PongGame(Widget):
def update(self):
# call ball.move and other stuff
pass
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
class PongApp(App):
def build(self):
game = PongGame()
Clock.schedule_interval(game.update, 1.0/60.0)
return game
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
2012-03-23 08:57:08 +00:00
However that still doesn't help the fact that we don't have a reference to the
``PongBall`` child widget created by the kv rule. To fix this, we can add an
2012-01-12 00:47:16 +00:00
ObjectProperty to the PongGame class, and hook it up to the widget created in
the kv rule. Once that's done, we can easily reference the ball property
2012-03-23 08:57:08 +00:00
inside the ``update`` method and even make it bounce of the edges::
2012-01-12 00:47:16 +00:00
class PongGame(Widget):
ball = ObjectProperty(None)
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
def update(self):
self.ball.move()
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
# bounce off top and bottom
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.velocity_y *= -1
# bounce off left and right
if (self.ball.x < 0) or (self.ball.right > self.width):
self.ball.velocity_x *= -1
Don't forget to hook it up in the kv file, by giving the child widget an id
2012-08-09 11:50:05 +00:00
and setting the games property to that id:
.. code-block:: kv
2012-01-12 00:47:16 +00:00
<PongGame>:
ball: pong_ball
# ... (canvas and Labels)
PongBall:
id: pong_ball
center: self.parent.center
2012-01-11 03:53:01 +00:00
.. note::
2012-01-12 00:47:16 +00:00
2012-01-11 03:53:01 +00:00
At this point everything is hooked up for the ball to bounce around. If
your coding along as we go, you might be wondering why the ball isn't
moving anywhere. The ball's velocity is set to 0 on both x and y.
2012-01-11 03:53:01 +00:00
In code listing below for the entire source a ``serve_ball`` method is
2012-03-23 08:57:08 +00:00
added to the ``PongGame`` class and called in the apps ``build`` method. It sets a
2012-01-12 00:47:16 +00:00
random x and y velocity for the ball, and also resets the position, so we
2012-01-11 03:53:01 +00:00
can use it later to reset the ball when a player has scored a point.
Here is the entire code for this step:
main.py:
.. include:: ../../../examples/tutorials/pong/steps/step4/main.py
:literal:
pong.kv:
2012-08-09 11:50:05 +00:00
.. literalinclude:: ../../../examples/tutorials/pong/steps/step4/pong.kv
:language: kv
:linenos:
2012-01-12 00:47:16 +00:00
Connect input event
-------------------
.. container:: title
Adding Players and reacting to touch input
2012-01-12 00:47:16 +00:00
2012-03-23 09:03:37 +00:00
Sweet, our ball is bouncing around. The only things missing now are, the movable
2012-01-12 00:47:16 +00:00
player rackets and keeping track of the score. We won't to go over all the
2012-01-16 11:34:44 +00:00
details of creating the class and kv rules again, since those concepts were
2012-01-11 03:53:01 +00:00
already covered in the previous steps. Instead lets focus on how to move the
2012-01-12 00:47:16 +00:00
Player widgets in response to user input. You can get the whole code and kv
rules for the ``PongPaddle`` class at the end of this section.
2012-01-11 03:53:01 +00:00
2012-01-12 00:47:16 +00:00
In Kivy, a widget can react to input by implemeting the ``on_touch_down``,
``on_touch_move`` and ``on_touch_up`` methods. By default, the Widget class
implements these methods by just calling the corresponding method on all it's
2012-01-11 03:53:01 +00:00
child widgets to pass on the event until one of the children returns True.
2012-01-12 00:47:16 +00:00
Pong is pretty simple, the rackets just need to move up and down. In fact it's
2012-01-11 03:53:01 +00:00
so simple, we don't even really need to have the player widgets handle the
events themselves. We'll just implement the ``on_touch_move`` function for the
2012-01-12 00:47:16 +00:00
``PongGame`` class and have it set the position of the left or right player based
2012-01-11 03:53:01 +00:00
on whether the touch occured on the left or right side of the screen.
2012-01-12 00:47:16 +00:00
Check the ``on_touch_move`` handler::
2012-01-11 03:53:01 +00:00
def on_touch_move(self, touch):
if touch.x < self.width/3:
self.player1.center_y = touch.y
if touch.x > self.width - self.width/3:
self.player2.center_y = touch.y
2012-01-12 00:47:16 +00:00
We'll keep the score for each player in a
:class:`~kivy.properties.NumericProperty`. The score labels of the ``PongGame``
are kept updated by changing the static string we had in the kv file before to
the score property of our new ``PongPaddle`` child widgets. When the ball
2012-01-12 00:47:16 +00:00
get's out of bounce on of the sides, we'll update the score and serve the ball
2012-03-23 08:57:08 +00:00
again by changing the ``update`` method in the ``PongGame`` class. The player
class also implements a ``bounce_ball method``, so that the ball bounces
2012-01-12 00:47:16 +00:00
differently based on where on the racket it hits. Here is the code for the
`PongPaddle` class::
2012-01-11 03:53:01 +00:00
class PongPaddle(Widget):
2012-01-12 00:47:16 +00:00
2012-01-11 03:53:01 +00:00
score = NumericProperty(0)
def bounce_ball(self, ball):
if self.collide_widget(ball):
speedup = 1.1
offset = 0.02 * Vector(0, ball.center_y-self.center_y)
ball.velocity = speedup * (offset - ball.velocity)
2012-01-12 00:47:16 +00:00
And here it is in context. Pretty much done:
2012-01-11 03:53:01 +00:00
main.py:
.. include:: ../../../examples/tutorials/pong/steps/step5/main.py
:literal:
pong.kv:
2012-08-09 11:50:05 +00:00
.. literalinclude:: ../../../examples/tutorials/pong/steps/step5/pong.kv
:language: kv
:linenos:
2012-01-11 03:53:01 +00:00
Where to go now?
----------------
.. container:: title
Have some fun
2012-01-11 03:53:01 +00:00
Well, the pong game is pretty much complete. If you understood all of the
things that are covered in this turoial, give yourself a pat on the back and
think about how you could improve the game. Here are a few ideas of things
2012-01-11 03:53:01 +00:00
you could do:
2012-01-12 00:47:16 +00:00
* Add some nicer graphics / images (hint check out the source property on
2012-03-23 08:57:08 +00:00
the graphics instructions like Circle or Rectangle, to set an image as the
2012-01-12 00:47:16 +00:00
texture for it)
* Make the game end after a certain score. Maybe once a player has 10
points, you can display a large "PLAYER 1 WINS" label and/or add a main menu
to start, pause and reset the game (hint: check out the 'Button' and 'Label'
2012-01-12 00:47:16 +00:00
classes and figure out how to use the `add_widget` & `remove_widget`
functions form the `Widget` class, to add or remove widgets dynamically.
* Make it a 4 player Pong Game. Most tablets have Multi-Touch support,
wouldn't it be cool to have a player on each side and play four people at
the same time?
2012-01-11 03:53:01 +00:00
.. note::
2012-01-12 00:47:16 +00:00
2012-01-11 03:53:01 +00:00
You can find the entire source code, and source code files for each step in
2012-01-12 00:47:16 +00:00
the Kivy examples directory under tutorials/pong/