Lab Exercise 11: Designing Interaction
This project completes the semester with a design task that requires a multi-stage interaction with the user combined with some type of simulation. This is the fourth and final part of a multi-part project, and the primary challenge is designing and then implementing a more complex user interface and interaction than prior projects. Alternatively, you can choose to simulate a phenomenon of your choice.
If you have not already done so, mount your personal space, create a new project11 folder and bring up TextWrangler and a Terminal. You will need the Zelle graphics package file graphicsPlus.py. You will also want to copy over your physics_objects.py, collision.py, and the file containing your rotating block class from project 10 (if it is in a separate file).
The Zelle documentation, is also available.
- Examine a Simple Ship Class
Go ahead and download a simple Ship class. The ship will act like a ball, with respect to collisions and motion, but it also has the capability to rotate around its center. Open up the file and go through the various parts to get an understanding of how it works.
- The __init__ method looks similar to the RotatingBlock class, except that the Ship uses two polygons (triangles). The first triangle is the body of the ship. The second triangle is the exhaust. They are defined in model coordinates in the __init__, with the ship body triangle centered on the origin.
- The next set of methods--draw, undraw, getAngle, setAngle, getRotVelocity, setRotVelocity, and rotate--are identical to the equivalent methods in the RotatingBlock class. A smart coder would create a parent rotator class an inherit these rather than having duplicate code in multiple classes.
The final four functions are unique to the Ship class, though the mathematics and process of rendering are almost identical to the RotatingBlock class, but slightly simpler because the anchor is at the center of the ship. In the render method, note that all of the points from both polygons are rotated, then the first three points make up the ship polygon and the second three points make up the exhaust polygon. Finally, the ship is colored.
In the update method, the only difference from the RotatingBlock is the code designed to make the exhaust flicker. This, combined with the setFlickerOn and setFlickerOff enabled the main loop to turn on flickering for a period of time after the user hits a key.
Test the ship-simple.py file by running it. It doesn't do anything except make a window, render the ship, and wait for a mouse click.
- Create a Main Loop
Remove the win.getMouse() from the main function. Then set up a main loop that runs until the user types 'q'. Since the program will be responding to many different keys, assign the result of win.checkKey() to a variable before testing the value returned.
Test your program to make sure it quits when you type q.
Before the main loop, create two variables, dt and frame, and assign them the values 0.01 and 0, respectively. Also, set the ship's rotational velocity to something slow, like 20 (deg/s).
At the end of the main loop, call the ship.update() method, passing in dt as the time step and thenincrement the frame variable by 1. Then, add an if statement specifying that if the frame variable modulo 10 is equal to zero, then call win.update() and call time.sleep with a value of dt*0.5. Recall that the modulo operator is % in Python.
# if frame modulo 10 is equal to zero # call the window's update method
Run your program. The ship should spin slowly and the program should quit when you hit the q key.
- Make The Ship Rotate On Command
The next step is to make the ship rotate when the user hits the right or left arrow keys. If you want to simulate an actual space vehicle, then firing attitude rockets will add or subtract from the current rotational velocity.
If the user hits the left arrow key, you want to add a certain amount of rotational velocity to the ship. If the user hits the right arrow key, you want to subtract a certain amount of rotational velocity from the ship. When the user hits the left arrow, the key variable will have the value 'Left'. When the user hits the right arrow, the key variable will have the value 'Right'.
Before the main loop, create another variable gamma and give it the value 10. Inside your main loop, implement the following code.
# if the user hits the 'Left' key # set the rotational velocity to the old rotational velocity plus gamma # call the ship's setFlickerOn method with no arguments # elif the user hits the 'Right' key # set the rotational velocity to the old rotational veloity minus gamma # call the ship's setFlickerOn method with no arguments
Run your program. You should be able to rotate the ship left and right. Don't hold the arrow keys down too long.
- Make the Ship Accelerate on Command
The next step is to make the ship move forward when the user hits the 'space' bar. This action depends on the current angle of the ship. As with the rotational velocity, each time the user hits the space bar the program should add something to the ship's current velocity.
Before the main loop, create another variable delta and give it the value 1. Inside the main loop, add a case for the 'space' key to the if/elif structure from the prior step.
# elif the user types 'space' # assign to a the ship's current angle (getAngle) # assign to theta the result of multiplying a by math.pi and dividing by 180 # assign to v the ship's current velocity (getVelocity) # set the ship's velocity to it's new values # The new X velocity is v_new_x = v_old_x + cos(theta) * delta # The new Y velocity is v_new_y = v_old_y + sin(theta) * delta # call the ship's setFlickerOn method with no arguments
Run your program. You should be able to rotate (arrow keys) and accelerate (space bar). How long can you keep the spaceship in the window?
- Make the World Wrap
The final task is to make the ship's world wrap from top to bottom and left to right. The basic idea is to figure out if the ship has gone out of bounds. If it has, add or subtract a value from its current position to make it look like the world wraps around the window.
First, create two more variables, winWidth and winHeight, prior to the loop that hold the window width and height. Note these are not the size you made the GraphWin. These need to be in world coordinates, which by default are 10x less than window coordinates. So if you made your GraphWin 500 x 500, then the height and width of the window in world coordinates is 50 x 50.
Next, implement the following in the main loop just before the call to ship.update()
# assign to moveit the value False # assign to p the ship's current position. You might want to cast it to a list. # if the x coordinate is less than 0 # add winWidth to the x coordinate # assign to moveit the value True # elif the x coordinate is greater than winWidth # subtract winWidth from the x coordinate # assign to moveit the value True # if the y coordinate is less than 0 # add winHeight to the y coordinate # assign to moveit the value True # elif the y coordinate is greater than winHeight # subtract winHeight from the y coordinate # assign to moveit the value True # if moveit: # set the ship's position to p
Run your program and test if the ship wraps.
When you are done with the lab exercises, you may begin the project.
If you are working on a mac, there is a simple way to play sound. The first step is to import a package called os.
The os package has a method called system, that lets you send a string to the terminal as though you typed a command and hit return.
Download a sound. Once you have downloaded it to your working directory, open a terminal and cd to that directory. Then type the following command.
afplay lasergun.wav &
It should play the sound. Now run the python shell by typing python3. Then import the os package. Finally, use the os.system function to execute the command you just typed into the terminal.
os.system( 'afplay lasergun.wav &' )
When you type and run the above line in the python interpreter shell, it should play the sound. You can use the same line of code when you want to play a sound in your python program. Just make sure the sound file is in the same directory as your code.