diff --git a/docs/conf.py b/docs/conf.py index 53adfff3..b7791583 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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. @@ -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 ---------------------------------------------- diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 31448cc4..c95c5c32 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -15,8 +15,7 @@ the `Python.org tutorial `_ or Additionally, you need to have Python 3.7 or later on your machine. You can install this via `Python.org `_ or -`Anaconda `_ -whichever is more comfortable for you. +:doc:`Anaconda ` whichever is more comfortable for you. Installing ppb diff --git a/docs/tutorials/index.rst b/docs/tutorials/index.rst index f92452a1..8a1b4a72 100644 --- a/docs/tutorials/index.rst +++ b/docs/tutorials/index.rst @@ -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 \ No newline at end of file diff --git a/docs/tutorials/virtual-tennis/ball.rst b/docs/tutorials/virtual-tennis/ball.rst new file mode 100644 index 00000000..e976b090 --- /dev/null +++ b/docs/tutorials/virtual-tennis/ball.rst @@ -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 ` 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. diff --git a/docs/tutorials/virtual-tennis/index.rst b/docs/tutorials/virtual-tennis/index.rst new file mode 100644 index 00000000..d4506370 --- /dev/null +++ b/docs/tutorials/virtual-tennis/index.rst @@ -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 `_ 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. + +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 + best results. For more information, see the + `Django Girls tutorial `_. + 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 `_. +#. You'll also need to know the basics of Python. Again, Django Girls has a + great `tutorial `_ + 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. diff --git a/docs/tutorials/virtual-tennis/setup.rst b/docs/tutorials/virtual-tennis/setup.rst new file mode 100644 index 00000000..ecb7855b --- /dev/null +++ b/docs/tutorials/virtual-tennis/setup.rst @@ -0,0 +1,101 @@ +=============================== +Setup +=============================== + +For this project, we're going to want to have a project folder and a virtual +environment set up. Follow along and we'll get that set up. + +.. note:: Before continuing, if you're new to software development, you + should pick a directory or folder on your computer to save your project. A + common name for this directory is ``src``. It can live anywhere you like. You + should copy the path from your file explorer and hold on to it, we're going + to use it later. We reference this path as ``/path/to/src/``. + +So the first thing you need to do is open your terminal. + +.. tabs:: + + .. group-tab:: Windows + + On Windows, there are two terminals: cmd and powershell. If you're not + sure which to pick, choose cmd. Future directions are written with this + terminal in mind. + + .. group-tab:: MacOS + + The MacOS default terminal is just called Terminal. + + .. group-tab:: Ubuntu + + Your default terminal is likely called Terminal, but if you search + for your version of linux and terminal you will find a specific one. + +With your terminal open, you're going to want to navigate to the +``/path/to/src/``. After that, we'll set up a project directory, and then +navigate into it. In the commands below, replace ``/path/to/src/`` with your +specific path you saved earlier. The name ``virtual-tennis`` is a nice +descriptive name for your project folder, but you can change the name if you'd +like. + +.. warning:: Each of these steps has multiple commands. Make sure to enter them + one at a time and hit the enter or return key and wait until they stop + putting new text on the screen before the next command. + +On all systems: + +.. code-block:: + + cd /path/to/src/ + mkdir virtual-tennis + cd virtual-tennis + +Our next step is to set up a virtual environment. We're going to use the python +library ``venv`` for this. After creating it, we need to activate it, and +install ``ppb``. Below, we call our virtual environment ``.venv``, but this is +only one of many possible names. If you change the name, replace it in following +commands with your new name. The structure in a virtual environment doesn't +change based on its name. + +.. note:: A virtual environment is a way to isolate the requirements of your + project from other Python projects on your computer. This lets you have + projects with conflicting requirements, like two different versions of + ``ppb``. + +.. tabs:: + + .. group-tab:: Windows + + .. code-block:: + + py -3.8 -m venv .venv + .venv\Scripts\activate + python -m pip install ppb + + .. group-tab:: MacOS + + .. code-block:: + + python3.8 -m venv .venv + source .venv/bin/activate + python -m pip install ppb + + .. group-tab:: Ubuntu + + .. code-block:: + + python3.8 -m venv .venv + source .venv/bin/activate + python -m pip install ppb + +The last step will depend on the code editor you've picked. If you're using an +IDE (PyCharm, VSCode, or similar) you'll want to open your project in your IDE. + +If you're using a plain text editor (GEdit, Notepad++) open it, but don't create +any files yet. + +Keep your terminal open, you're going to use it later. If you close it, you +should navigate back to your project folder and activate the virtual environment +again. + +With all of this out of the way, we can move on to our first step: Creating a +window. diff --git a/docs/tutorials/virtual-tennis/window.rst b/docs/tutorials/virtual-tennis/window.rst new file mode 100644 index 00000000..b9bd90ef --- /dev/null +++ b/docs/tutorials/virtual-tennis/window.rst @@ -0,0 +1,87 @@ +=============================== +Opening a Window +=============================== + +So we've installed everything we need, but it's always a good idea to make +sure our environment is right before moving on. A good first step for any video +game is to make sure you can make an empty window. + +Inside your :doc:`project directory ` we need to create a file. Do so using your +code editor, and call it ``main.py``. Make sure to open it so we can add code +to it. + +.. note:: We're using ``main.py`` here, but like the name of your virtual + environment, this is just convention. If you change it, make sure to replace + the name ``main.py`` in any console commands shown later. We do suggest + keeping the name, though. + +If you haven't already, open up ``main.py`` in your code editor. Inside, add +the following code: + +.. code-block:: python + :caption: main.py + :linenos: + + import ppb + + ppb.run() + +Save this file and go back to your terminal. There, you should enter the +following command to run your game. + +.. code-block:: + :caption: Terminal + + python main.py + +You should have a window open that looks like this: + +(Add image.) + +To run any python script (not just our ``main.py``) you call ``python``, add a +space and then the name of the script you're wanting to run. There's lots of +other options, but this is all you need to know so far. + +Before we continue, we're going to do one more thing. The default resolution of +800x600 is great, but you might want a bigger (or smaller) window. We're going +to add a constant value and give that to ppb to tell it how big of a window we +want. We'll also add a title to our game, which shows up in the title bar of +the window we create. + +.. code-block:: + :caption: main.py + :linenos: + :emphasize-lines: 3,5 + + import ppb + + RESOLUTION = (1200, 900) + + ppb.run(resolution=RESOLUTION, title="Hello Window!") + +Save this and rerun it and the screen should be bigger this time and say +"Hello Window!" on the top. + +The reason ``RESOLUTION`` is spelled with all caps is because this is a what +programmers call a constant. As a community, Python developers use `special +capitalization rules`_ to tell the difference between different kinds of +variables. The value is what we call a tuple, which is collection of values +that can't be changed. These values are the width and height of the window in +screen pixels. + +Just defining this value isn't enough. The :class:`ppb.GameEngine` doesn't look +for values, you have to tell it what you want. In this case the keyword argument +`resolution` is how you inform ``ppb`` what you want. So in this case, +``resolution=RESOLUTION`` is giving our constant to the engine to change the +size of the window. + +You can change either the width or the height in ``RESOLUTION`` to find a +window size you like. The Atari 2600 had a maximum resolution of 160x192, but +this is exceptionally small on modern screens. It's better to pick bigger, we +can manipulate the size of the things on the screen later. When you change the +value, you should use ``python main.py`` again to see the result. Keep experimenting + +Once you've found a screen size and shape you like, we can move on to putting +something on screen. + +.. _special capitalization rules: https://www.python.org/dev/peps/pep-0008/ diff --git a/examples/virtual_tennis/part_1_screen/main.py b/examples/virtual_tennis/part_1_screen/main.py new file mode 100644 index 00000000..243e0efb --- /dev/null +++ b/examples/virtual_tennis/part_1_screen/main.py @@ -0,0 +1,5 @@ +import ppb + +RESOLUTION = (1200, 900) + +ppb.run(resolution=RESOLUTION, title="Hellow Window!") \ No newline at end of file diff --git a/examples/virtual_tennis/part_2_ball/main.py b/examples/virtual_tennis/part_2_ball/main.py new file mode 100644 index 00000000..1c1af394 --- /dev/null +++ b/examples/virtual_tennis/part_2_ball/main.py @@ -0,0 +1,37 @@ +import ppb + +RESOLUTION = (1200, 900) + + +class Ball(ppb.Sprite): + image = ppb.Circle(255, 255, 255) + size = 1 + velocity = ppb.Vector(0, 0) + + 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 + + +def setup(scene): + scene.background_color = (0, 0, 0) + scene.add(Ball(velocity=ppb.directions.UpAndLeft * 3)) + + +ppb.run(setup, resolution=RESOLUTION, title="Hellow Window!") \ No newline at end of file diff --git a/requirements-docs.txt b/requirements-docs.txt index 43f94e9b..dc0ab145 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,4 +1,5 @@ sphinx sphinx_rtd_theme +sphinx-tabs importlib_metadata; python_version < "3.8" doc-utils<0.18 # Fix for read the docs default sphinx version. If we update sphinx, test to see if we can remove this.