Saturday, October 25, 2014

Virtual Controller Angle Tutorial


This is part of an educational curriculum to teach 9th and 10th grade students how to build mobile games on Android. The student needs to be familiar with trigonometry. The primary target student is in 10th grade.
There are two examples. The main lesson is in main.py and focuses on the getting the angle from the virtual controller. The second lesson is in bullet.py and shows how to fire bullets from a moving player. There is sound generated each time a bullet is fired.
Here's a video snippet showing the 360 degree movement and independent firing.
Screen shot of virtual controller with bullets
Additional sounds can be downloaded from SoundBible.
Since the player is moving, I also implemented a rudimentary bounds detection so that the player doesn't move off the screen.

Create the Controller

In this lesson, the controller is a circle of radius 50. The main objective is to calculate the angle of the player's right thumb in relation to the center of the circle. Since we're running this on a desktop computer before loading it onto an Android phone, the mouse will represent the point the thumb touches the screen.
 pygame.draw.circle(SCREEN, RED, v_control.center, 50, 2)
 pygame.draw.circle(SCREEN, RED, v_control.center, 3)
There is a rectangle called, v_control that I'm using with colliderect.
 v_control = pygame.Rect(650, 450, 100, 100)
Before I calculate the angle, I make sure that the thumb is inside of the rectangle for the virtual controller.
if v_control.collidepoint(pos):
    rad = get_angle(pos, v_control.center)

Review Your Trigonometry

You'll need to use arc tangent to calculate the angle in radians. You'll also need to use sine and cosine. If your trigonometry is a rusty, review it now.
math.atanmath.sin, and math.cos are in the python math standard library. You'll need to addimport math at the top of your program.
Review of sine, cosine, and tangent
To use arc tangent you'll need to calculate the lengths of the opposite and adjacent sides of a right triangle. Since the formulas to calculate the lengths of the sides of a triangle are slightly different depending on where the thumb is on the controller, I've divided the controller into four quadrants, starting with quadrant one in the upper right and rotating counter-clockwise.
For each quadrant, you'll need to adjust the formula to calculate the opposite and adjacent sides of the triangle. For example, if the mouse is above the center of the controller, you'll need to subtract the mouse y position from the centery of the controller.

Define Each Quadrant

Diagram of characteristics of each quadrant
For the y-axis, the mouse point is either:
  1. above the center of the controller
  2. below the center of the controller
  3. at the same height of the center of the controller

Quandrants 1 and 2

Mouse Point Located Above Controller Center
If the mouse point is above the center of the controller, than check for one of three conditions:
  1. x is to the right of the controller
  2. x is to the left of the controller
  3. x is at the same point as the centerx of the controller

Quadrant 1

Mouse point is located above and to the right of controller
Diagram of Quadrant 1
Example code. Note that you need to convert to floating point.
center is a two number tuple (400,300), the center of the player. x, y is the mouse position.
    opposite = float(center[1] - y)
    if x > center[0]:
        adjacent = float(x - center[0])
        rad = math.atan(opposite/adjacent)
Here's what it looks like with the game running. Note that the angle of the mouse pointer relative to the center of the virtual controller is the same as the angle of the beam relative to the center of the player.
Screenshot of game with beam in quadrant 1


Quadrant 3

Mouse point is below and to the left of the controller center
If the mouse pointer is not in quadrant 1, add the appropriate radian value. For example, if the mouse pointer is in quadrant 3, then add pi (3.14) to the radian value.
calculation of quadrant 3

Using Radian Angle to Control Beam

Creating a beam is easier than a bullet. It is a line with the starting point at the center of the player and the end point 30 pixels out from the center.
Once you have the angle, use sine and cosine to calculate the length of opposite and adjacent sides of the triangle.
def beam(angle, center):
    """
    :param angle: radians calculated from the virtual controller
    :return: x,y coordinates of the end-point
    Start with the center of the player.  The end of the beam is 100 pixels
    away from the center.  To make a bullet instead of beam, create a class
    for bullet and have the hypoteneuse be an attribute that increases
    in size.  Remember to delete the bullet from the sprite group or list
    when it goes off the screen.
    """
    hypoteneuse = 30.0
    adjacent = math.cos(angle) * hypoteneuse
    x = adjacent + center[0]
    opposite = math.sin(angle) * hypoteneuse
    y = center[1] - opposite
    beam_end = (x, y)
    return beam_end

Shoot Bullets Instead of a Beam

If you want to shoot bullets, I'm using sprites. Don't be intimidated by sprites even though the code looks a bit funky. The bullet moves forward by increasing the length of the hypotenuse by 5 pixels.
class Bullet(pygame.sprite.Sprite):
    def __init__(self, angle, p_pos):
        YELLOW = (250, 223, 65)
        RED = (200, 10, 10)
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((6,6))
        pygame.draw.circle(self.image, YELLOW, (3, 3), 3)
        pygame.draw.circle(self.image, RED, (3,3), 1)
        self.rect = self.image.get_rect()
        self.hypotenuse = 30.0
        self.angle = angle
        self.cent = p_pos

    def update(self):
        adjacent = math.cos(self.angle) * self.hypotenuse
        x = adjacent + self.cent[0]
        opposite = math.sin(self.angle) * self.hypotenuse
        y = self.cent[1] - opposite
        self.rect.center = (x, y)
        self.hypotenuse += 5

Move The Player

In the second example, I'm using almost the same code to move the player around the screen.
The most likely scenario is to start with Swarm and build from there.
My son is planning to try something with the Android accelerometer.

Saturday, October 11, 2014

Using Android Accelerometer with Pygame

My son recently started to use the accelerometer functions in his Android phone to develop games with Python. He's building a variation of Swarm, a 2D tile map game that he wrote in middle school.

The accelerometer is easy to get working with pgs4a.

To pull the x,y,z axis, use this:


You will get a floating point number for each of the three axises.

Here's a simple way to set four directions, up, down, left, right:


In the example above, I'm holding the phone sideways, in landscape mode.  The left-right movement is controlled by the 2nd value in the list and the up-down movement is controlled by the 1st item in the list.

It would easy to set up 8 direction movement or even a greater range of movement.  Unlike Swarm, which used only 8 directions for bullets, we're using sin and cos to give the bullets a greater range of movement.

In order to test the application on my desktop, I've also created a virtual controller to simulate the accelerometer.

Here's the application running on my desktop with the virtual controller.
video


After you install the application on your Android phone, it is a bit more difficult to debug and test the accelerometer.   You can print out the value of the accelerometer and then tune your game so that the player moves with the sensitivity that works for your game. If you print out the accel_reading, you will see a three number tuple, with the numbers all in floating point.


I/python  (21058): Initialize Python for Android
I/python  (21058): ['/data/data/org.pychildren.accel/files/lib/python2.7/site-packages', '/data/data/org.pychildren.accel/files/lib/site-python']
I/python  (21058): Opening APK '/data/app/org.pychildren.accel-1.apk'
I/python  (21058): (-3.8019924163818359, -0.31603461503982544, 8.7819318771362305)
I/python  (21058): (-3.8019924163818359, -0.31603461503982544, 8.7819318771362305)
I/python  (21058): (-3.8019924163818359, -0.31603461503982544, 8.7819318771362305)

If you don't know how to see the output from your app when it is running on your phone, read my   post explaining how you can see the output of your print statements with adb.

Here's a demo of the character running on an old Samsung phone without the flock.   The controller in the lower right is for 360 degree bullet firing.

video

Organizing Pygame Programs Into Separate Files - Getting Started With Basics

Everyone starts off with one long block of code in a main.py file. At some point, we break the program into separate files. In python, the separate file is called a module. Within each module, you can put functions and classes. As soon as you start to break up the program into separate files, everyone wonders how to get the variables from the main while loop in pygame to the module that is drawing something to the screen.  There are many ways to do this.  The easiest is to pass the main screen to your module as a global variable.

In this example, I call my main program main.py and I call my module draw.py.

craig@ubuntu-desktop:~/Development/dad/screens$ lsdraw.py  draw.pyc  main.py 
Ignore the file, draw.pyc.  It is created automatically when you run the program.

From main.py, you can access draw.py using import draw



My draw module just puts two graphics to the screen, one with a function and with with a class.

To access the module from the program, you can either call the function directly with draw.dot(SCREEN) or instantiate the class.
Here's the full code listing:



Image of program showing how main.py calls up the draw function from draw.py.


Sunday, September 21, 2014

Pygame on Android - What to Do When Your Android App Dies Soon After Startup

You can use adb to get console error messages and print statement output from your pygame app on Android phones.  One of the most frustrating thing for beginners to pygame on Android is when the app runs fine on their desktop, but dies soon after startup on their Android phone.  Usually, you click on the app, the splash screen comes up, then the Android app silently dies.  Unless you're using adb, corrently you could be stuck.

With the usb cable connected between your phone and desktop, run 

adb logcat |grep python

Here's the output I got today.

I/python  (  580): 
Opening APK '/data/app/org.pychildren.surfsc-2.apk'I/python  (  580): 
Traceback (most recent call last):I/python  (  580):   
File "main.py", line 323, in <module>I/python  (  580):     
main()I/python  (  580):   
File "main.py", line 296, in mainI/python  (  580):     
pprint.pprint(weather.w_dict)I/python  (  580): 
NameError: global name 'pprint' is not definedI/python  (  580): 
Python for android ended.I/ActivityManager( 7163): Process org.pychildren.surfsc:python (pid 580) has died.

It is clear that there's a problem with the pprint.pprint statement that I was using to display the Python dictionary of weather data.  It even gives me the line number in my source code.  I simply commented out the line and the app started working again on my Android phone.



I've added basic weather information to my version.  My son's version has a cleaner interface and better colors.


Tuesday, September 16, 2014

Sphinx Documentation System

I've started to encourage my son to focus on code that is easier to read by humans.  There are three parts to this:

  1. choose variable, class, and function names that are easy to understand;
  2. break the code into functions and classes instead of one long block and use local variables;
  3. comment the code
For the last part, I started off using pydoc -w filename to extract the docstrings into a document.  This didn't give me as nice a document as I wanted.  I ended up using Sphinx with the autodoc extension

$ sphinx-quickstart

I took all the defaults except for autodoc.
In conf.py, I edited the sys.path

# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('.'))
I just uncommented the line above.

I then edited index.rst
.. automodule:: main   :members:
I then edited the docstrings in my python main.py file.




Monday, September 15, 2014

Overview of Teaching Python to Graph Output from Cloud API - Spitcast Surf


I've been working with my son to build a mobile app that displays graphs of tide charts and tables of surf forecasts for the Santa Cruz region.  The app runs on his Android Motorola Moto-G phone and my Samsung Galaxy Note 2.  He grabs the data using the Spitcast API and displays the output using Pygame.


The tide has a huge influence on the quality of surf in Eastside Santa Cruz.  The optimal tide conditions at my favorite spot is a tide that is rising from 2 feet to 3.5 feet.  The swell height and direction also plays a big role.  In the future, we'll use the API from OpenWeatherMap to pull the sunrise, sunset, and wind.

The steps are:
  1. Pull data from Spitcast using urllib2
  2. Read JSON data from API into program and then convert to a list of Python dictionaries using the json library


The lesson gets off to an exciting start. The student will realize the potential of accessing cloud-based APIs. Assuming the student has a basic understanding of Python data structures lists and dictionaries, they'll also understand how easy it is to parse the data. The simple idea to convert the tide values of feet into pixels is just to multiply feet by a constant number. In this case, I multiply the height of the wave in feet by 50 to get a pixel value. For example, a wave height of 5 feet will correspond to a screen height of 250 pixels. To generate the y coordinate for the Pygame screen, I subtract the pixel height from 550. Remember that a y value of 0 is the top of the screen. I set my screen height on the phone to 500 pixels. A wave height of 1 foot will equal screen height of 50 pixels. Subtracting this from 550 will yield a y position of 500.
  1. Build start and end points for a series of 23 vertical lines spaced evenly apart to create the x axis grid. 
  2.  Create a point list that will be used by pygame.draw.lines in a future section.



At this point, the student will have a list of points that they can then draw with pygame.draw.lines for the tide graph or pygame.draw.line x, y axis grids.
Here's the basic algorithms with a bit more bells and whistles.

In the main while loop, I have this code.
This is the android-presplash.jpg screen of the application.


In the future, we'll merge the features from my son's Weather App project into Santa Cruz Surf.





Below is a shot of the app developer benefiting from the application.




Installing POSIX Man Pages on Ubuntu

I was fairly surprised to find that $ man jobs didn't work on Ubuntu 14.04.  When I looked at the installed man pages, I realized that the sections 1, 6, and 8 of the UNIX manual pages were missing.  These correspond to the user commands, games, and system admin commands.  I was able to install the man pages with:

 $ sudo apt-get install manpages-posix

This made me wonder if the current group of Linux users were using basic UNIX commands like fg, bg, jobs.  I then realized that the full set of document was available with info.  Many years ago, the man pages were in better shape then the info pages.  I now see that there's more documentation in info.  Despite being a heavy user of Emacs, I've never really warmed to the navigation commands in info and prefer the interface for less and man.  I'll give info another try over the next few months.